starting a slider widget, using in easy places where the range is usize

This commit is contained in:
Dustin Carlino 2019-05-28 10:46:48 -07:00
parent 83f62631e9
commit 28b2734d59
6 changed files with 282 additions and 101 deletions

View File

@ -2,13 +2,13 @@ use crate::helpers::ID;
use crate::render::calculate_corners;
use crate::ui::UI;
use abstutil::Timer;
use ezgui::{EventCtx, GfxCtx, Key, ModalMenu, Text};
use ezgui::{EventCtx, GfxCtx, Key, ModalMenu, Slider, Text};
use geom::{Polygon, Pt2D, Triangle};
pub struct PolygonDebugger {
menu: ModalMenu,
items: Vec<Item>,
current: usize,
slider: Slider,
center: Option<Pt2D>,
}
@ -44,57 +44,57 @@ impl PolygonDebugger {
return Some(PolygonDebugger {
menu,
items: pts.iter().map(|pt| Item::Point(*pt)).collect(),
current: 0,
slider: Slider::new(0, pts.len() - 1),
center: Some(Pt2D::center(&pts_without_last)),
});
} else if ctx
.input
.contextual_action(Key::F2, "debug sidewalk corners")
{
let items: Vec<Item> =
calculate_corners(i, &ui.primary.map, &mut Timer::new("calculate corners"))
.into_iter()
.map(Item::Polygon)
.collect();
return Some(PolygonDebugger {
menu,
items: calculate_corners(
i,
&ui.primary.map,
&mut Timer::new("calculate corners"),
)
.into_iter()
.map(Item::Polygon)
.collect(),
current: 0,
slider: Slider::new(0, items.len() - 1),
items,
center: None,
});
}
}
Some(ID::Lane(id)) => {
if ctx.input.contextual_action(Key::X, "debug lane geometry") {
let items: Vec<Item> = ui
.primary
.map
.get_l(id)
.lane_center_pts
.points()
.iter()
.map(|pt| Item::Point(*pt))
.collect();
return Some(PolygonDebugger {
menu,
items: ui
.primary
.map
.get_l(id)
.lane_center_pts
.points()
.iter()
.map(|pt| Item::Point(*pt))
.collect(),
current: 0,
slider: Slider::new(0, items.len() - 1),
items,
center: None,
});
} else if ctx.input.contextual_action(Key::F2, "debug lane triangles") {
let items: Vec<Item> = ui
.primary
.draw_map
.get_l(id)
.polygon
.triangles()
.into_iter()
.map(Item::Triangle)
.collect();
return Some(PolygonDebugger {
menu,
items: ui
.primary
.draw_map
.get_l(id)
.polygon
.triangles()
.into_iter()
.map(Item::Triangle)
.collect(),
current: 0,
slider: Slider::new(0, items.len() - 1),
items,
center: None,
});
}
@ -112,22 +112,23 @@ impl PolygonDebugger {
return Some(PolygonDebugger {
menu,
items: pts.iter().map(|pt| Item::Point(*pt)).collect(),
current: 0,
slider: Slider::new(0, pts.len() - 1),
center: Some(center),
});
} else if ctx.input.contextual_action(Key::F2, "debug area triangles") {
let items: Vec<Item> = ui
.primary
.map
.get_a(id)
.polygon
.triangles()
.into_iter()
.map(Item::Triangle)
.collect();
return Some(PolygonDebugger {
menu,
items: ui
.primary
.map
.get_a(id)
.polygon
.triangles()
.into_iter()
.map(Item::Triangle)
.collect(),
current: 0,
slider: Slider::new(0, items.len() - 1),
items,
center: None,
});
}
@ -139,44 +140,52 @@ impl PolygonDebugger {
// True when done
pub fn event(&mut self, ctx: &mut EventCtx) -> bool {
let current = self.slider.get_value();
let mut txt = Text::prompt("Polygon Debugger");
txt.add_line(format!("Item {}/{}", self.current + 1, self.items.len()));
txt.add_line(format!("Item {}/{}", current + 1, self.items.len()));
self.menu.handle_event(ctx, Some(txt));
ctx.canvas.handle_event(ctx.input);
if self.menu.action("quit") {
return true;
} else if self.current != self.items.len() - 1 && self.menu.action("next item") {
self.current += 1;
} else if self.current != self.items.len() - 1 && self.menu.action("last item") {
self.current = self.items.len() - 1;
} else if self.current != 0 && self.menu.action("prev item") {
self.current -= 1;
} else if self.current != 0 && self.menu.action("first item") {
self.current = 0;
} else if current != self.items.len() - 1 && self.menu.action("next item") {
self.slider.set_value(ctx, current + 1);
} else if current != self.items.len() - 1 && self.menu.action("last item") {
self.slider.set_value(ctx, self.items.len() - 1);
} else if current != 0 && self.menu.action("prev item") {
self.slider.set_value(ctx, current - 1);
} else if current != 0 && self.menu.action("first item") {
self.slider.set_value(ctx, 0);
}
self.slider.event(ctx);
false
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
match self.items[self.current] {
let current = self.slider.get_value();
match self.items[current] {
Item::Point(pt) => {
g.draw_text_at(&Text::from_line(format!("{}", self.current)), pt);
g.draw_text_at(&Text::from_line(format!("{}", current)), pt);
}
Item::Triangle(ref tri) => {
for pt in &[tri.pt1, tri.pt2, tri.pt3] {
g.draw_text_at(&Text::from_line(format!("{}", self.current)), *pt);
g.draw_text_at(&Text::from_line(format!("{}", current)), *pt);
}
g.draw_polygon(ui.cs.get("selected"), &Polygon::from_triangle(tri));
}
Item::Polygon(ref poly) => {
g.draw_polygon(ui.cs.get("selected"), poly);
g.draw_text_at(&Text::from_line(format!("{}", self.current)), poly.center());
g.draw_text_at(&Text::from_line(format!("{}", current)), poly.center());
}
}
if let Some(pt) = self.center {
g.draw_text_at(&Text::from_line("c".to_string()), pt);
}
self.menu.draw(g);
self.slider.draw(g);
}
}

View File

@ -2,14 +2,14 @@ use crate::common::CommonState;
use crate::mission::{clip_trips, Trip};
use crate::ui::{ShowEverything, UI};
use abstutil::{prettyprint_usize, Timer};
use ezgui::{Color, EventCtx, GfxCtx, Key, ModalMenu, Text};
use ezgui::{Color, EventCtx, GfxCtx, Key, ModalMenu, Slider, Text};
use geom::{Circle, Distance, Speed};
use popdat::PopDat;
pub struct TripsVisualizer {
menu: ModalMenu,
trips: Vec<Trip>,
current: usize,
slider: Slider,
}
impl TripsVisualizer {
@ -17,7 +17,8 @@ impl TripsVisualizer {
let mut timer = Timer::new("initialize popdat");
let popdat: PopDat = abstutil::read_binary("../data/shapes/popdat", &mut timer)
.expect("Couldn't load popdat");
// TODO We'll break if there are no matching trips
let trips = clip_trips(&popdat, ui, 10_000, &mut timer);
TripsVisualizer {
menu: ModalMenu::new(
"Trips Visualizer",
@ -30,21 +31,22 @@ impl TripsVisualizer {
],
ctx,
),
trips: clip_trips(&popdat, ui, 10_000, &mut timer),
// TODO We'll break if there are no matching trips
current: 0,
slider: Slider::new(0, trips.len() - 1),
trips,
}
}
// Returns true if the we're done
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> bool {
let current = self.slider.get_value();
let mut txt = Text::prompt("Trips Visualizer");
txt.add_line(format!(
"Trip {}/{}",
prettyprint_usize(self.current),
prettyprint_usize(current + 1),
prettyprint_usize(self.trips.len())
));
let trip = &self.trips[self.current];
let trip = &self.trips[current];
txt.add_line(format!("Leave at {}", trip.depart_at));
txt.add_line(format!(
"Purpose: {:?} -> {:?}",
@ -65,21 +67,23 @@ impl TripsVisualizer {
if self.menu.action("quit") {
return true;
} else if self.current != self.trips.len() - 1 && self.menu.action("next trip") {
self.current += 1;
} else if self.current != self.trips.len() - 1 && self.menu.action("last trip") {
self.current = self.trips.len() - 1;
} else if self.current != 0 && self.menu.action("prev trip") {
self.current -= 1;
} else if self.current != 0 && self.menu.action("first trip") {
self.current = 0;
} else if current != self.trips.len() - 1 && self.menu.action("next trip") {
self.slider.set_value(ctx, current + 1);
} else if current != self.trips.len() - 1 && self.menu.action("last trip") {
self.slider.set_value(ctx, self.trips.len() - 1);
} else if current != 0 && self.menu.action("prev trip") {
self.slider.set_value(ctx, current - 1);
} else if current != 0 && self.menu.action("first trip") {
self.slider.set_value(ctx, 0);
}
self.slider.event(ctx);
false
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
let trip = &self.trips[self.current];
let trip = &self.trips[self.slider.get_value()];
let from = ui.primary.map.get_b(trip.from);
let to = ui.primary.map.get_b(trip.to);
@ -97,6 +101,7 @@ impl TripsVisualizer {
);
self.menu.draw(g);
self.slider.draw(g);
CommonState::draw_osd(g, ui, ui.primary.current_selection);
}
}

View File

@ -2,7 +2,7 @@ use crate::common::Warper;
use crate::helpers::ID;
use crate::render::DrawTurn;
use crate::ui::UI;
use ezgui::{Color, EventCtx, EventLoopMode, GfxCtx, Key, ModalMenu, Text};
use ezgui::{Color, EventCtx, EventLoopMode, GfxCtx, Key, ModalMenu, Slider, Text};
use geom::{Distance, Polygon};
use map_model::{Traversable, LANE_THICKNESS};
use sim::AgentID;
@ -12,8 +12,8 @@ pub struct RouteExplorer {
agent: AgentID,
steps: Vec<Traversable>,
entire_trace: Option<Polygon>,
current: usize,
warper: Option<Warper>,
slider: Slider,
}
impl RouteExplorer {
@ -62,7 +62,6 @@ impl RouteExplorer {
ctx,
),
agent,
current: 0,
warper: Some(Warper::new(
ctx,
steps[0]
@ -73,6 +72,7 @@ impl RouteExplorer {
Traversable::Turn(t) => ID::Turn(t),
},
)),
slider: Slider::new(0, steps.len() - 1),
steps,
entire_trace,
})
@ -80,47 +80,54 @@ impl RouteExplorer {
// Done when None
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Option<EventLoopMode> {
if let Some(ref warper) = self.warper {
// Don't block while we're warping
let ev_mode = if let Some(ref warper) = self.warper {
if let Some(mode) = warper.event(ctx, ui) {
return Some(mode);
mode
} else {
self.warper = None;
EventLoopMode::InputOnly
}
self.warper = None;
}
} else {
EventLoopMode::InputOnly
};
let current = self.slider.get_value();
let mut txt = Text::prompt(&format!("Route Explorer for {:?}", self.agent));
txt.add_line(format!("Step {}/{}", self.current + 1, self.steps.len()));
txt.add_line(format!("{:?}", self.steps[self.current]));
txt.add_line(format!("Step {}/{}", current + 1, self.steps.len()));
txt.add_line(format!("{:?}", self.steps[current]));
self.menu.handle_event(ctx, Some(txt));
ctx.canvas.handle_event(ctx.input);
if self.menu.action("quit") {
return None;
} else if self.current != self.steps.len() - 1 && self.menu.action("next step") {
self.current += 1;
} else if self.current != self.steps.len() - 1 && self.menu.action("last step") {
self.current = self.steps.len() - 1;
} else if self.current != 0 && self.menu.action("prev step") {
self.current -= 1;
} else if self.current != 0 && self.menu.action("first step") {
self.current = 0;
} else if current != self.steps.len() - 1 && self.menu.action("next step") {
self.slider.set_value(ctx, current + 1);
} else if current != self.steps.len() - 1 && self.menu.action("last step") {
self.slider.set_value(ctx, self.steps.len() - 1);
} else if current != 0 && self.menu.action("prev step") {
self.slider.set_value(ctx, current - 1);
} else if current != 0 && self.menu.action("first step") {
self.slider.set_value(ctx, 0);
} else if self.slider.event(ctx) {
// Cool, the value changed, so fall-through
} else {
return Some(EventLoopMode::InputOnly);
return Some(ev_mode);
}
let step = self.steps[self.slider.get_value()];
self.warper = Some(Warper::new(
ctx,
self.steps[self.current]
.dist_along(
self.steps[self.current].length(&ui.primary.map) / 2.0,
&ui.primary.map,
)
step.dist_along(step.length(&ui.primary.map) / 2.0, &ui.primary.map)
.0,
match self.steps[self.current] {
match step {
Traversable::Lane(l) => ID::Lane(l),
Traversable::Turn(t) => ID::Turn(t),
},
));
Some(EventLoopMode::InputOnly)
// We just created a new warper, so...
Some(EventLoopMode::Animation)
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
@ -129,7 +136,7 @@ impl RouteExplorer {
}
let color = ui.cs.get_def("current step", Color::RED);
match self.steps[self.current] {
match self.steps[self.slider.get_value()] {
Traversable::Lane(l) => {
g.draw_polygon(color, &ui.primary.draw_map.get_l(l).polygon);
}
@ -138,5 +145,6 @@ impl RouteExplorer {
}
}
self.menu.draw(g);
self.slider.draw(g);
}
}

View File

@ -19,7 +19,7 @@ pub use crate::runner::{run, EventLoopMode, GUI};
pub use crate::screen_geom::ScreenPt;
pub use crate::text::{Text, HOTKEY_COLOR};
pub use crate::widgets::{
Autocomplete, LogScroller, ModalMenu, ScrollingMenu, TextBox, Wizard, WrappedWizard,
Autocomplete, LogScroller, ModalMenu, ScrollingMenu, Slider, TextBox, Wizard, WrappedWizard,
};
pub enum InputResult<T: Clone> {

View File

@ -4,6 +4,7 @@ mod menu;
mod modal_menu;
mod screenshot;
mod scrolling_menu;
mod slider;
mod text_box;
mod wizard;
@ -13,5 +14,6 @@ pub use self::menu::{Menu, Position};
pub use self::modal_menu::ModalMenu;
pub(crate) use self::screenshot::{screenshot_current, screenshot_everything};
pub use self::scrolling_menu::ScrollingMenu;
pub use self::slider::Slider;
pub use self::text_box::TextBox;
pub use self::wizard::{Wizard, WrappedWizard};

157
ezgui/src/widgets/slider.rs Normal file
View File

@ -0,0 +1,157 @@
use crate::screen_geom::ScreenRectangle;
use crate::{Color, EventCtx, GfxCtx};
use geom::{Distance, Polygon, Pt2D};
// Pixels
const BAR_WIDTH: f64 = 300.0;
const BAR_HEIGHT: f64 = 100.0;
const SLIDER_WIDTH: f64 = 50.0;
const SLIDER_HEIGHT: f64 = 120.0;
const HORIZ_PADDING: f64 = 60.0;
const VERT_PADDING: f64 = 20.0;
pub struct Slider {
min: usize,
max: usize,
current_percent: f64,
mouse_on_slider: bool,
dragging: bool,
}
impl Slider {
pub fn new(min: usize, max: usize) -> Slider {
Slider {
min,
max,
current_percent: 0.0,
mouse_on_slider: false,
dragging: false,
}
}
pub fn get_value(&self) -> usize {
self.min + (self.current_percent * (self.max - self.min) as f64) as usize
}
pub fn set_value(&mut self, ctx: &mut EventCtx, value: usize) {
self.current_percent = (value - self.min) as f64 / (self.max - self.min) as f64;
// Just reset dragging, to prevent chaos
self.dragging = false;
let pt = ctx.canvas.get_cursor_in_screen_space();
self.mouse_on_slider = self.slider_geom().contains_pt(Pt2D::new(pt.x, pt.y));
}
// Returns true if the value changed.
pub fn event(&mut self, ctx: &mut EventCtx) -> bool {
if self.dragging {
if ctx.input.get_moved_mouse().is_some() {
let percent =
(ctx.canvas.get_cursor_in_screen_space().x - HORIZ_PADDING) / BAR_WIDTH;
let old_value = self.get_value();
self.current_percent = percent.min(1.0).max(0.0);
if self.get_value() != old_value {
return true;
}
}
if ctx.input.left_mouse_button_released() {
self.dragging = false;
}
} else {
if !ctx.canvas.is_dragging() && ctx.input.get_moved_mouse().is_some() {
let pt = ctx.canvas.get_cursor_in_screen_space();
self.mouse_on_slider = self.slider_geom().contains_pt(Pt2D::new(pt.x, pt.y));
}
if ctx.input.left_mouse_button_pressed() {
if self.mouse_on_slider {
self.dragging = true;
} else {
// Did we click somewhere else on the bar?
let pt = ctx.canvas.get_cursor_in_screen_space();
if Polygon::rectangle_topleft(
Pt2D::new(HORIZ_PADDING, VERT_PADDING),
Distance::meters(BAR_WIDTH),
Distance::meters(BAR_HEIGHT),
)
.contains_pt(Pt2D::new(pt.x, pt.y))
{
// TODO Argh, some code duplication
let percent = (pt.x - HORIZ_PADDING) / BAR_WIDTH;
let old_value = self.get_value();
self.current_percent = percent.min(1.0).max(0.0);
self.mouse_on_slider = true;
self.dragging = true;
if self.get_value() != old_value {
return true;
}
}
}
}
}
false
}
pub fn draw(&self, g: &mut GfxCtx) {
g.fork_screenspace();
// A nice background for the entire thing
g.draw_polygon(
Color::grey(0.3),
&Polygon::rectangle_topleft(
Pt2D::new(0.0, 0.0),
Distance::meters(BAR_WIDTH + 2.0 * HORIZ_PADDING),
Distance::meters(BAR_HEIGHT + 2.0 * VERT_PADDING),
),
);
g.canvas.mark_covered_area(ScreenRectangle {
x1: 0.0,
y1: 0.0,
x2: BAR_WIDTH + 2.0 * HORIZ_PADDING,
y2: BAR_HEIGHT + 2.0 * VERT_PADDING,
});
// The bar
g.draw_polygon(
Color::WHITE,
&Polygon::rectangle_topleft(
Pt2D::new(HORIZ_PADDING, VERT_PADDING),
Distance::meters(BAR_WIDTH),
Distance::meters(BAR_HEIGHT),
),
);
// Show the progress
if self.current_percent != 0.0 {
g.draw_polygon(
Color::GREEN,
&Polygon::rectangle_topleft(
Pt2D::new(HORIZ_PADDING, VERT_PADDING),
Distance::meters(self.current_percent * BAR_WIDTH),
Distance::meters(BAR_HEIGHT),
),
);
}
// The actual slider
g.draw_polygon(
if self.mouse_on_slider {
Color::YELLOW
} else {
Color::grey(0.7)
},
&self.slider_geom(),
);
}
fn slider_geom(&self) -> Polygon {
Polygon::rectangle_topleft(
Pt2D::new(
HORIZ_PADDING + self.current_percent * BAR_WIDTH - (SLIDER_WIDTH / 2.0),
VERT_PADDING - (SLIDER_HEIGHT - BAR_HEIGHT) / 2.0,
),
Distance::meters(SLIDER_WIDTH),
Distance::meters(SLIDER_HEIGHT),
)
}
}