diff --git a/ezgui/Cargo.toml b/ezgui/Cargo.toml index 01bad22b4e..64c4d002f8 100644 --- a/ezgui/Cargo.toml +++ b/ezgui/Cargo.toml @@ -15,6 +15,7 @@ profiler = ["cpuprofiler"] abstutil = { path = "../abstutil" } # backtrace = "0.3.40" cpuprofiler = { version = "0.0.3", optional = true } +downcast-rs = "1.1.1" geom = { path = "../geom" } glium = { version = "0.26.0", optional = true } glow = { version = "0.4.0", optional = true, default-features=false } diff --git a/ezgui/src/lib.rs b/ezgui/src/lib.rs index 50116b2091..80b9a2d768 100644 --- a/ezgui/src/lib.rs +++ b/ezgui/src/lib.rs @@ -47,7 +47,7 @@ pub(crate) use crate::widgets::popup_menu::PopupMenu; pub use crate::widgets::slider::{ItemSlider, Slider, WarpingItemSlider}; pub(crate) use crate::widgets::text_box::TextBox; pub use crate::widgets::wizard::{Choice, Wizard, WrappedWizard}; -pub(crate) use crate::widgets::WidgetImpl; +pub use crate::widgets::WidgetImpl; pub enum InputResult { Canceled, diff --git a/ezgui/src/managed.rs b/ezgui/src/managed.rs index 65e6780479..9f47124a97 100644 --- a/ezgui/src/managed.rs +++ b/ezgui/src/managed.rs @@ -1,10 +1,10 @@ +use crate::widgets::plot::Yvalue; use crate::{ Btn, Button, Checkbox, Choice, Color, Drawable, Dropdown, EventCtx, Filler, GeomBatch, GfxCtx, Histogram, HorizontalAlignment, JustDraw, MultiKey, Plot, PopupMenu, RewriteColor, ScreenDims, ScreenPt, ScreenRectangle, Slider, Text, TextBox, VerticalAlignment, WidgetImpl, }; -use abstutil::Cloneable; -use geom::{Distance, Duration, Polygon}; +use geom::{Distance, Polygon}; use std::collections::HashSet; use stretch::geometry::{Rect, Size}; use stretch::node::{Node, Stretch}; @@ -18,27 +18,17 @@ pub struct Widget { style: LayoutStyle, rect: ScreenRectangle, bg: Option, - // TODO Only use this, not the other things id: Option, } enum WidgetType { - Draw(JustDraw), Btn(Button), - Checkbox(Checkbox), - TextBox(TextBox), Dropdown(Dropdown), Slider(Slider), - Menu(PopupMenu>), - Filler(Filler), - // TODO Sadness. Can't have some kind of wildcard generic here? I think this goes away when - // WidgetType becomes a trait. - DurationPlot(Plot), - UsizePlot(Plot), - Histogram(Histogram), Row(Vec), Column(Vec), Nothing, + Generic(Box), } struct LayoutStyle { @@ -242,7 +232,7 @@ impl Widget { } pub(crate) fn just_draw(j: JustDraw) -> Widget { - Widget::new(WidgetType::Draw(j)) + Widget::new(WidgetType::Generic(Box::new(j))) } pub(crate) fn draw_text(ctx: &EventCtx, txt: Text) -> Widget { @@ -266,12 +256,12 @@ impl Widget { Widget::new(WidgetType::Slider(slider)) } - pub fn menu(menu: PopupMenu>) -> Widget { - Widget::new(WidgetType::Menu(menu)) + pub fn menu(menu: PopupMenu) -> Widget { + Widget::new(WidgetType::Generic(Box::new(menu))) } pub fn filler(filler: Filler) -> Widget { - Widget::new(WidgetType::Filler(filler)) + Widget::new(WidgetType::Generic(Box::new(filler))) } pub fn checkbox( @@ -290,21 +280,21 @@ impl Widget { } // TODO Not typesafe! Gotta pass a button. pub fn custom_checkbox(enabled: bool, false_btn: Widget, true_btn: Widget) -> Widget { - Widget::new(WidgetType::Checkbox(Checkbox::new( + Widget::new(WidgetType::Generic(Box::new(Checkbox::new( enabled, false_btn.take_btn(), true_btn.take_btn(), - ))) + )))) } pub fn text_entry(ctx: &EventCtx, prefilled: String, exclusive_focus: bool) -> Widget { // TODO Hardcoded style, max chars - Widget::new(WidgetType::TextBox(TextBox::new( + Widget::new(WidgetType::Generic(Box::new(TextBox::new( ctx, 50, prefilled, exclusive_focus, - ))) + )))) } pub fn dropdown( @@ -323,16 +313,12 @@ impl Widget { .outline(2.0, Color::WHITE) } - pub(crate) fn duration_plot(plot: Plot) -> Widget { - Widget::new(WidgetType::DurationPlot(plot)) - } - - pub(crate) fn usize_plot(plot: Plot) -> Widget { - Widget::new(WidgetType::UsizePlot(plot)) + pub(crate) fn plot>(plot: Plot) -> Widget { + Widget::new(WidgetType::Generic(Box::new(plot))) } pub(crate) fn histogram(histogram: Histogram) -> Widget { - Widget::new(WidgetType::Histogram(histogram)) + Widget::new(WidgetType::Generic(Box::new(histogram))) } pub fn row(widgets: Vec) -> Widget { @@ -368,21 +354,12 @@ impl Widget { impl Widget { fn event(&mut self, ctx: &mut EventCtx, redo_layout: &mut bool) -> Option { match self.widget { - WidgetType::Draw(_) => {} WidgetType::Btn(ref mut btn) => { btn.event(ctx); if btn.clicked() { return Some(Outcome::Clicked(btn.action.clone())); } } - WidgetType::Checkbox(ref mut checkbox) => { - if checkbox.event(ctx) { - *redo_layout = true; - } - } - WidgetType::TextBox(ref mut textbox) => { - textbox.event(ctx); - } WidgetType::Dropdown(ref mut dropdown) => { if dropdown.event(ctx, &self.rect) { *redo_layout = true; @@ -391,13 +368,6 @@ impl Widget { WidgetType::Slider(ref mut slider) => { slider.event(ctx); } - WidgetType::Menu(ref mut menu) => { - menu.event(ctx); - } - WidgetType::Filler(_) - | WidgetType::DurationPlot(_) - | WidgetType::UsizePlot(_) - | WidgetType::Histogram(_) => {} WidgetType::Row(ref mut widgets) | WidgetType::Column(ref mut widgets) => { for w in widgets { if let Some(o) = w.event(ctx, redo_layout) { @@ -406,6 +376,11 @@ impl Widget { } } WidgetType::Nothing => unreachable!(), + WidgetType::Generic(ref mut w) => { + if let Some(o) = w.event(ctx, &self.rect, redo_layout) { + return Some(o); + } + } } None } @@ -416,10 +391,7 @@ impl Widget { } match self.widget { - WidgetType::Draw(ref j) => j.draw(g), WidgetType::Btn(ref btn) => btn.draw(g), - WidgetType::Checkbox(ref checkbox) => checkbox.draw(g), - WidgetType::TextBox(ref textbox) => textbox.draw(g), WidgetType::Dropdown(ref dropdown) => dropdown.draw(g), WidgetType::Slider(ref slider) => { if self.id != Some("horiz scrollbar".to_string()) @@ -428,35 +400,22 @@ impl Widget { slider.draw(g); } } - WidgetType::Menu(ref menu) => menu.draw(g), - WidgetType::Filler(_) => {} - WidgetType::DurationPlot(ref plot) => plot.draw(g), - WidgetType::UsizePlot(ref plot) => plot.draw(g), - WidgetType::Histogram(ref hgram) => hgram.draw(g), WidgetType::Row(ref widgets) | WidgetType::Column(ref widgets) => { for w in widgets { w.draw(g); } } WidgetType::Nothing => unreachable!(), + WidgetType::Generic(ref w) => w.draw(g), } } // Populate a flattened list of Nodes, matching the traversal order fn get_flexbox(&self, parent: Node, stretch: &mut Stretch, nodes: &mut Vec) { - // TODO Can I use | in the match and "cast" to Widget? - let widget: &dyn WidgetImpl = match self.widget { - WidgetType::Draw(ref widget) => widget, - WidgetType::Btn(ref widget) => widget, - WidgetType::Checkbox(ref widget) => widget, - WidgetType::TextBox(ref widget) => widget, - WidgetType::Dropdown(ref widget) => widget, - WidgetType::Slider(ref widget) => widget, - WidgetType::Menu(ref widget) => widget, - WidgetType::Filler(ref widget) => widget, - WidgetType::DurationPlot(ref widget) => widget, - WidgetType::UsizePlot(ref widget) => widget, - WidgetType::Histogram(ref widget) => widget, + let dims = match self.widget { + WidgetType::Btn(ref widget) => widget.get_dims(), + WidgetType::Dropdown(ref widget) => widget.get_dims(), + WidgetType::Slider(ref widget) => widget.get_dims(), WidgetType::Row(ref widgets) => { let mut style = Style { flex_direction: FlexDirection::Row, @@ -486,8 +445,8 @@ impl Widget { return; } WidgetType::Nothing => unreachable!(), + WidgetType::Generic(ref w) => w.get_dims(), }; - let dims = widget.get_dims(); let mut style = Style { size: Size { width: Dimension::Points(dims.width as f32), @@ -516,18 +475,13 @@ impl Widget { let y: f64 = result.location.y.into(); let width: f64 = result.size.width.into(); let height: f64 = result.size.height.into(); - let top_left = match self.widget { - WidgetType::Slider(_) => { - // Don't scroll the scrollbars - if self.id == Some("horiz scrollbar".to_string()) - || self.id == Some("vert scrollbar".to_string()) - { - ScreenPt::new(x, y) - } else { - ScreenPt::new(x + dx - scroll_offset.0, y + dy - scroll_offset.1) - } - } - _ => ScreenPt::new(x + dx - scroll_offset.0, y + dy - scroll_offset.1), + // Don't scroll the scrollbars + let top_left = if self.id == Some("horiz scrollbar".to_string()) + || self.id == Some("vert scrollbar".to_string()) + { + ScreenPt::new(x, y) + } else { + ScreenPt::new(x + dx - scroll_offset.0, y + dy - scroll_offset.1) }; self.rect = ScreenRectangle::top_left(top_left, ScreenDims::new(width, height)); @@ -550,39 +504,15 @@ impl Widget { } match self.widget { - WidgetType::Draw(ref mut widget) => { - widget.set_pos(top_left); - } WidgetType::Btn(ref mut widget) => { widget.set_pos(top_left); } - WidgetType::Checkbox(ref mut widget) => { - widget.set_pos(top_left); - } - WidgetType::TextBox(ref mut widget) => { - widget.set_pos(top_left); - } WidgetType::Dropdown(ref mut widget) => { widget.set_pos(top_left); } WidgetType::Slider(ref mut widget) => { widget.set_pos(top_left); } - WidgetType::Menu(ref mut widget) => { - widget.set_pos(top_left); - } - WidgetType::Filler(ref mut widget) => { - widget.set_pos(top_left); - } - WidgetType::DurationPlot(ref mut widget) => { - widget.set_pos(top_left); - } - WidgetType::UsizePlot(ref mut widget) => { - widget.set_pos(top_left); - } - WidgetType::Histogram(ref mut widget) => { - widget.set_pos(top_left); - } WidgetType::Row(ref mut widgets) => { // layout() doesn't return absolute position; it's relative to the container. for widget in widgets { @@ -611,21 +541,15 @@ impl Widget { } } WidgetType::Nothing => unreachable!(), + WidgetType::Generic(ref mut w) => { + w.set_pos(top_left); + } } } fn get_all_click_actions(&self, actions: &mut HashSet) { match self.widget { - WidgetType::Draw(_) - | WidgetType::Slider(_) - | WidgetType::Menu(_) - | WidgetType::Filler(_) - | WidgetType::Checkbox(_) - | WidgetType::TextBox(_) - | WidgetType::Dropdown(_) - | WidgetType::DurationPlot(_) - | WidgetType::UsizePlot(_) => {} - WidgetType::Histogram(_) => {} + WidgetType::Slider(_) | WidgetType::Dropdown(_) => {} WidgetType::Btn(ref btn) => { if actions.contains(&btn.action) { panic!( @@ -641,6 +565,8 @@ impl Widget { } } WidgetType::Nothing => unreachable!(), + // TODO Will need something + WidgetType::Generic(_) => {} } } @@ -950,23 +876,41 @@ impl Composite { } } - pub fn menu(&self, name: &str) -> &PopupMenu> { + pub fn menu(&self, name: &str) -> &PopupMenu { match self.find(name).widget { - WidgetType::Menu(ref menu) => menu, + WidgetType::Generic(ref w) => { + if let Some(menu) = w.downcast_ref::>() { + menu + } else { + panic!("{} isn't a menu", name); + } + } _ => panic!("{} isn't a menu", name), } } pub fn is_checked(&self, name: &str) -> bool { match self.find(name).widget { - WidgetType::Checkbox(ref checkbox) => checkbox.enabled, + WidgetType::Generic(ref w) => { + if let Some(checkbox) = w.downcast_ref::() { + checkbox.enabled + } else { + panic!("{} isn't a checkbox", name); + } + } _ => panic!("{} isn't a checkbox", name), } } pub fn text_box(&self, name: &str) -> String { match self.find(name).widget { - WidgetType::TextBox(ref textbox) => textbox.get_entry(), + WidgetType::Generic(ref w) => { + if let Some(tb) = w.downcast_ref::() { + tb.get_line() + } else { + panic!("{} isn't a textbox", name); + } + } _ => panic!("{} isn't a textbox", name), } } @@ -986,8 +930,15 @@ impl Composite { } pub fn filler_rect(&self, name: &str) -> ScreenRectangle { - match self.find(name).widget { - WidgetType::Filler(ref f) => ScreenRectangle::top_left(f.top_left, f.dims), + let widget = self.find(name); + match widget.widget { + WidgetType::Generic(ref w) => { + if let Some(_) = w.downcast_ref::() { + widget.rect.clone() + } else { + panic!("{} isn't a filler", name); + } + } _ => panic!("{} isn't a filler", name), } } diff --git a/ezgui/src/widgets/checkbox.rs b/ezgui/src/widgets/checkbox.rs index 89d1d4012b..ead98a65e8 100644 --- a/ezgui/src/widgets/checkbox.rs +++ b/ezgui/src/widgets/checkbox.rs @@ -1,4 +1,4 @@ -use crate::{Button, EventCtx, GfxCtx, ScreenDims, ScreenPt, WidgetImpl}; +use crate::{Button, EventCtx, GfxCtx, Outcome, ScreenDims, ScreenPt, ScreenRectangle, WidgetImpl}; pub struct Checkbox { pub(crate) enabled: bool, @@ -22,23 +22,6 @@ impl Checkbox { } } } - - // If true, widgets should be recomputed. - pub(crate) fn event(&mut self, ctx: &mut EventCtx) -> bool { - self.btn.event(ctx); - if self.btn.clicked() { - std::mem::swap(&mut self.btn, &mut self.other_btn); - self.btn.set_pos(self.other_btn.top_left); - self.enabled = !self.enabled; - true - } else { - false - } - } - - pub(crate) fn draw(&self, g: &mut GfxCtx) { - self.btn.draw(g); - } } impl WidgetImpl for Checkbox { @@ -49,4 +32,25 @@ impl WidgetImpl for Checkbox { fn set_pos(&mut self, top_left: ScreenPt) { self.btn.set_pos(top_left); } + + fn event( + &mut self, + ctx: &mut EventCtx, + _rect: &ScreenRectangle, + redo_layout: &mut bool, + ) -> Option { + self.btn.event(ctx); + if self.btn.clicked() { + std::mem::swap(&mut self.btn, &mut self.other_btn); + self.btn.set_pos(self.other_btn.top_left); + self.enabled = !self.enabled; + *redo_layout = true; + } + + None + } + + fn draw(&self, g: &mut GfxCtx) { + self.btn.draw(g); + } } diff --git a/ezgui/src/widgets/dropdown.rs b/ezgui/src/widgets/dropdown.rs index 8f31d58a76..6f0a0f3053 100644 --- a/ezgui/src/widgets/dropdown.rs +++ b/ezgui/src/widgets/dropdown.rs @@ -52,7 +52,9 @@ impl Dropdown { // If true, widgets should be recomputed. pub fn event(&mut self, ctx: &mut EventCtx, our_rect: &ScreenRectangle) -> bool { if let Some(ref mut m) = self.menu { - m.event(ctx); + // TODO wraaaaaaaaaaaawng + let mut redo_layout = false; + m.event(ctx, our_rect, &mut redo_layout); match m.state { InputResult::StillActive => {} InputResult::Canceled => { diff --git a/ezgui/src/widgets/filler.rs b/ezgui/src/widgets/filler.rs index e99edde507..907baeb519 100644 --- a/ezgui/src/widgets/filler.rs +++ b/ezgui/src/widgets/filler.rs @@ -1,10 +1,10 @@ -use crate::{ScreenDims, ScreenPt, WidgetImpl}; +use crate::{EventCtx, GfxCtx, Outcome, ScreenDims, ScreenPt, ScreenRectangle, WidgetImpl}; // Doesn't do anything by itself, just used for widgetsing. Something else reaches in, asks for the // ScreenRectangle to use. pub struct Filler { - pub(crate) top_left: ScreenPt, - pub(crate) dims: ScreenDims, + top_left: ScreenPt, + dims: ScreenDims, } impl Filler { @@ -24,4 +24,14 @@ impl WidgetImpl for Filler { fn set_pos(&mut self, top_left: ScreenPt) { self.top_left = top_left; } + + fn event( + &mut self, + _ctx: &mut EventCtx, + _rect: &ScreenRectangle, + _redo_layout: &mut bool, + ) -> Option { + None + } + fn draw(&self, _g: &mut GfxCtx) {} } diff --git a/ezgui/src/widgets/histogram.rs b/ezgui/src/widgets/histogram.rs index dc7a6a70ab..30bf354f3f 100644 --- a/ezgui/src/widgets/histogram.rs +++ b/ezgui/src/widgets/histogram.rs @@ -1,6 +1,6 @@ use crate::{ - Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, ScreenDims, ScreenPt, Text, TextExt, - Widget, WidgetImpl, + Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, Outcome, ScreenDims, ScreenPt, + ScreenRectangle, Text, TextExt, Widget, WidgetImpl, }; use abstutil::prettyprint_usize; use geom::{Distance, Duration, Polygon, Pt2D}; @@ -99,8 +99,26 @@ impl Histogram { x_axis.evenly_spaced(), ])]) } +} - pub(crate) fn draw(&self, g: &mut GfxCtx) { +impl WidgetImpl for Histogram { + fn get_dims(&self) -> ScreenDims { + self.dims + } + + fn set_pos(&mut self, top_left: ScreenPt) { + self.top_left = top_left; + } + + fn event( + &mut self, + _ctx: &mut EventCtx, + _rect: &ScreenRectangle, + _redo_layout: &mut bool, + ) -> Option { + None + } + fn draw(&self, g: &mut GfxCtx) { g.redraw_at(self.top_left, &self.draw); if let Some(cursor) = g.canvas.get_cursor_in_screen_space() { @@ -115,16 +133,6 @@ impl Histogram { } } -impl WidgetImpl for Histogram { - fn get_dims(&self) -> ScreenDims { - self.dims - } - - fn set_pos(&mut self, top_left: ScreenPt) { - self.top_left = top_left; - } -} - // min, max, bars // TODO This has bugs. Perfect surface area to unit test. fn bucketize( diff --git a/ezgui/src/widgets/mod.rs b/ezgui/src/widgets/mod.rs index 361e50d445..361b412176 100644 --- a/ezgui/src/widgets/mod.rs +++ b/ezgui/src/widgets/mod.rs @@ -12,14 +12,27 @@ pub mod slider; pub mod text_box; pub mod wizard; -use crate::{EventCtx, ScreenDims, ScreenPt}; +use crate::{EventCtx, GfxCtx, Outcome, ScreenDims, ScreenPt, ScreenRectangle}; use ordered_float::NotNan; -pub trait WidgetImpl { +pub trait WidgetImpl: downcast_rs::Downcast { fn get_dims(&self) -> ScreenDims; fn set_pos(&mut self, top_left: ScreenPt); + + // TODO Require everyone to implement it + fn event( + &mut self, + _ctx: &mut EventCtx, + _rect: &ScreenRectangle, + _redo_layout: &mut bool, + ) -> Option { + None + } + fn draw(&self, _g: &mut GfxCtx) {} } +downcast_rs::impl_downcast!(WidgetImpl); + #[derive(Clone, Copy)] pub enum ContainerOrientation { TopLeft, diff --git a/ezgui/src/widgets/no_op.rs b/ezgui/src/widgets/no_op.rs index 5d6069be15..ce08711272 100644 --- a/ezgui/src/widgets/no_op.rs +++ b/ezgui/src/widgets/no_op.rs @@ -1,6 +1,6 @@ use crate::{ - svg, Drawable, EventCtx, GeomBatch, GfxCtx, RewriteColor, ScreenDims, ScreenPt, Text, Widget, - WidgetImpl, + svg, Drawable, EventCtx, GeomBatch, GfxCtx, Outcome, RewriteColor, ScreenDims, ScreenPt, + ScreenRectangle, Text, Widget, WidgetImpl, }; // Just draw something. A widget just so widgetsing works. @@ -43,10 +43,6 @@ impl JustDraw { pub fn text(ctx: &EventCtx, text: Text) -> Widget { JustDraw::wrap(ctx, text.render_ctx(ctx)) } - - pub(crate) fn draw(&self, g: &mut GfxCtx) { - g.redraw_at(self.top_left, &self.draw); - } } impl WidgetImpl for JustDraw { @@ -57,4 +53,17 @@ impl WidgetImpl for JustDraw { fn set_pos(&mut self, top_left: ScreenPt) { self.top_left = top_left; } + + fn event( + &mut self, + _ctx: &mut EventCtx, + _rect: &ScreenRectangle, + _redo_layout: &mut bool, + ) -> Option { + None + } + + fn draw(&self, g: &mut GfxCtx) { + g.redraw_at(self.top_left, &self.draw); + } } diff --git a/ezgui/src/widgets/plot.rs b/ezgui/src/widgets/plot.rs index c658717868..ab0a2af660 100644 --- a/ezgui/src/widgets/plot.rs +++ b/ezgui/src/widgets/plot.rs @@ -1,6 +1,6 @@ use crate::{ - Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, ScreenDims, ScreenPt, ScreenRectangle, - Text, TextExt, Widget, WidgetImpl, + Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, Outcome, ScreenDims, ScreenPt, + ScreenRectangle, Text, TextExt, Widget, WidgetImpl, }; use abstutil::prettyprint_usize; use geom::{Angle, Bounds, Circle, Distance, Duration, FindClosest, PolyLine, Pt2D, Time}; @@ -198,8 +198,56 @@ impl> Plot (plot, legend, x_axis, y_axis) } +} - pub(crate) fn draw(&self, g: &mut GfxCtx) { +// TODO Do we still need these two? :\ +impl Plot { + pub fn new_usize(ctx: &EventCtx, series: Vec>, opts: PlotOptions) -> Widget { + let (plot, legend, x_axis, y_axis) = Plot::new(ctx, series, 0, opts); + // Don't let the x-axis fill the parent container + Widget::row(vec![Widget::col(vec![ + legend, + Widget::row(vec![y_axis.evenly_spaced(), Widget::plot(plot)]), + x_axis.evenly_spaced(), + ])]) + } +} + +impl Plot { + pub fn new_duration( + ctx: &EventCtx, + series: Vec>, + opts: PlotOptions, + ) -> Widget { + let (plot, legend, x_axis, y_axis) = Plot::new(ctx, series, Duration::ZERO, opts); + // Don't let the x-axis fill the parent container + Widget::row(vec![Widget::col(vec![ + legend, + Widget::row(vec![y_axis.evenly_spaced(), Widget::plot(plot)]), + x_axis.evenly_spaced(), + ])]) + } +} + +impl> WidgetImpl for Plot { + fn get_dims(&self) -> ScreenDims { + self.dims + } + + fn set_pos(&mut self, top_left: ScreenPt) { + self.top_left = top_left; + } + + fn event( + &mut self, + _ctx: &mut EventCtx, + _rect: &ScreenRectangle, + _redo_layout: &mut bool, + ) -> Option { + None + } + + fn draw(&self, g: &mut GfxCtx) { g.redraw_at(self.top_left, &self.draw); if let Some(cursor) = g.canvas.get_cursor_in_screen_space() { @@ -233,44 +281,6 @@ impl> Plot } } -impl Plot { - pub fn new_usize(ctx: &EventCtx, series: Vec>, opts: PlotOptions) -> Widget { - let (plot, legend, x_axis, y_axis) = Plot::new(ctx, series, 0, opts); - // Don't let the x-axis fill the parent container - Widget::row(vec![Widget::col(vec![ - legend, - Widget::row(vec![y_axis.evenly_spaced(), Widget::usize_plot(plot)]), - x_axis.evenly_spaced(), - ])]) - } -} - -impl Plot { - pub fn new_duration( - ctx: &EventCtx, - series: Vec>, - opts: PlotOptions, - ) -> Widget { - let (plot, legend, x_axis, y_axis) = Plot::new(ctx, series, Duration::ZERO, opts); - // Don't let the x-axis fill the parent container - Widget::row(vec![Widget::col(vec![ - legend, - Widget::row(vec![y_axis.evenly_spaced(), Widget::duration_plot(plot)]), - x_axis.evenly_spaced(), - ])]) - } -} - -impl WidgetImpl for Plot { - fn get_dims(&self) -> ScreenDims { - self.dims - } - - fn set_pos(&mut self, top_left: ScreenPt) { - self.top_left = top_left; - } -} - pub trait Yvalue { // percent is [0.0, 1.0] fn from_percent(&self, percent: f64) -> T; diff --git a/ezgui/src/widgets/popup_menu.rs b/ezgui/src/widgets/popup_menu.rs index 5e9e98c272..c88b02aa10 100644 --- a/ezgui/src/widgets/popup_menu.rs +++ b/ezgui/src/widgets/popup_menu.rs @@ -1,5 +1,5 @@ use crate::{ - hotkey, text, Choice, EventCtx, GfxCtx, InputResult, Key, Line, ScreenDims, ScreenPt, + hotkey, text, Choice, EventCtx, GfxCtx, InputResult, Key, Line, Outcome, ScreenDims, ScreenPt, ScreenRectangle, Text, WidgetImpl, }; use geom::Pt2D; @@ -29,118 +29,6 @@ impl PopupMenu { m } - pub fn event(&mut self, ctx: &mut EventCtx) { - match self.state { - InputResult::StillActive => {} - _ => unreachable!(), - } - - // Handle the mouse - if ctx.redo_mouseover() { - if let Some(cursor) = ctx.canvas.get_cursor_in_screen_space() { - let mut top_left = self.top_left; - for idx in 0..self.choices.len() { - let rect = ScreenRectangle { - x1: top_left.x, - y1: top_left.y, - x2: top_left.x + self.dims.width, - y2: top_left.y + ctx.default_line_height(), - }; - if rect.contains(cursor) { - self.current_idx = idx; - break; - } - top_left.y += ctx.default_line_height(); - } - } - } - { - let choice = &self.choices[self.current_idx]; - if ctx.normal_left_click() { - // Did we actually click the entry? - let mut top_left = self.top_left; - top_left.y += ctx.default_line_height() * (self.current_idx as f64); - let rect = ScreenRectangle { - x1: top_left.x, - y1: top_left.y, - x2: top_left.x + self.dims.width, - y2: top_left.y + ctx.default_line_height(), - }; - if let Some(pt) = ctx.canvas.get_cursor_in_screen_space() { - if rect.contains(pt) && choice.active { - self.state = InputResult::Done(choice.label.clone(), choice.data.clone()); - return; - } - // Unconsume the click, it was in screen space, but not on us. - ctx.input.unconsume_event(); - } else { - // Clicked on the map? Cancel out - self.state = InputResult::Canceled; - return; - } - } - } - - // Handle hotkeys - for choice in &self.choices { - if !choice.active { - continue; - } - if let Some(ref hotkey) = choice.hotkey { - if ctx.input.new_was_pressed(hotkey) { - self.state = InputResult::Done(choice.label.clone(), choice.data.clone()); - return; - } - } - } - - // Handle nav keys - if ctx.input.new_was_pressed(&hotkey(Key::Enter).unwrap()) { - let choice = &self.choices[self.current_idx]; - if choice.active { - self.state = InputResult::Done(choice.label.clone(), choice.data.clone()); - return; - } else { - return; - } - } else if ctx.input.new_was_pressed(&hotkey(Key::UpArrow).unwrap()) { - if self.current_idx > 0 { - self.current_idx -= 1; - } - } else if ctx.input.new_was_pressed(&hotkey(Key::DownArrow).unwrap()) { - if self.current_idx < self.choices.len() - 1 { - self.current_idx += 1; - } - } - } - - pub fn draw(&self, g: &mut GfxCtx) { - let draw = g.upload(self.calculate_txt().render_g(g)); - // In between tooltip and normal screenspace - g.fork(Pt2D::new(0.0, 0.0), self.top_left, 1.0, Some(0.1)); - g.redraw(&draw); - g.unfork(); - - if let Some(ref info) = self.choices[self.current_idx].tooltip { - // Hold on, are we actually hovering on that entry right now? - let mut top_left = self.top_left; - top_left.y += g.default_line_height() * (self.current_idx as f64); - let rect = ScreenRectangle { - x1: top_left.x, - y1: top_left.y, - x2: top_left.x + self.dims.width, - y2: top_left.y + g.default_line_height(), - }; - if let Some(pt) = g.canvas.get_cursor_in_screen_space() { - if rect.contains(pt) { - let mut txt = Text::new(); - txt.add_wrapped(info.to_string(), 0.5 * g.canvas.window_width); - g.draw_mouse_tooltip(txt); - } - } - } - } - pub fn current_choice(&self) -> &T { &self.choices[self.current_idx].data } @@ -182,7 +70,7 @@ impl PopupMenu { } } -impl WidgetImpl for PopupMenu { +impl WidgetImpl for PopupMenu { fn get_dims(&self) -> ScreenDims { self.dims } @@ -190,4 +78,123 @@ impl WidgetImpl for PopupMenu { fn set_pos(&mut self, top_left: ScreenPt) { self.top_left = top_left; } + + fn event( + &mut self, + ctx: &mut EventCtx, + _rect: &ScreenRectangle, + _redo_layout: &mut bool, + ) -> Option { + match self.state { + InputResult::StillActive => {} + _ => unreachable!(), + } + + // Handle the mouse + if ctx.redo_mouseover() { + if let Some(cursor) = ctx.canvas.get_cursor_in_screen_space() { + let mut top_left = self.top_left; + for idx in 0..self.choices.len() { + let rect = ScreenRectangle { + x1: top_left.x, + y1: top_left.y, + x2: top_left.x + self.dims.width, + y2: top_left.y + ctx.default_line_height(), + }; + if rect.contains(cursor) { + self.current_idx = idx; + break; + } + top_left.y += ctx.default_line_height(); + } + } + } + { + let choice = &self.choices[self.current_idx]; + if ctx.normal_left_click() { + // Did we actually click the entry? + let mut top_left = self.top_left; + top_left.y += ctx.default_line_height() * (self.current_idx as f64); + let rect = ScreenRectangle { + x1: top_left.x, + y1: top_left.y, + x2: top_left.x + self.dims.width, + y2: top_left.y + ctx.default_line_height(), + }; + if let Some(pt) = ctx.canvas.get_cursor_in_screen_space() { + if rect.contains(pt) && choice.active { + self.state = InputResult::Done(choice.label.clone(), choice.data.clone()); + return None; + } + // Unconsume the click, it was in screen space, but not on us. + ctx.input.unconsume_event(); + } else { + // Clicked on the map? Cancel out + self.state = InputResult::Canceled; + return None; + } + } + } + + // Handle hotkeys + for choice in &self.choices { + if !choice.active { + continue; + } + if let Some(ref hotkey) = choice.hotkey { + if ctx.input.new_was_pressed(hotkey) { + self.state = InputResult::Done(choice.label.clone(), choice.data.clone()); + return None; + } + } + } + + // Handle nav keys + if ctx.input.new_was_pressed(&hotkey(Key::Enter).unwrap()) { + let choice = &self.choices[self.current_idx]; + if choice.active { + self.state = InputResult::Done(choice.label.clone(), choice.data.clone()); + return None; + } else { + return None; + } + } else if ctx.input.new_was_pressed(&hotkey(Key::UpArrow).unwrap()) { + if self.current_idx > 0 { + self.current_idx -= 1; + } + } else if ctx.input.new_was_pressed(&hotkey(Key::DownArrow).unwrap()) { + if self.current_idx < self.choices.len() - 1 { + self.current_idx += 1; + } + } + + None + } + + fn draw(&self, g: &mut GfxCtx) { + let draw = g.upload(self.calculate_txt().render_g(g)); + // In between tooltip and normal screenspace + g.fork(Pt2D::new(0.0, 0.0), self.top_left, 1.0, Some(0.1)); + g.redraw(&draw); + g.unfork(); + + if let Some(ref info) = self.choices[self.current_idx].tooltip { + // Hold on, are we actually hovering on that entry right now? + let mut top_left = self.top_left; + top_left.y += g.default_line_height() * (self.current_idx as f64); + let rect = ScreenRectangle { + x1: top_left.x, + y1: top_left.y, + x2: top_left.x + self.dims.width, + y2: top_left.y + g.default_line_height(), + }; + if let Some(pt) = g.canvas.get_cursor_in_screen_space() { + if rect.contains(pt) { + let mut txt = Text::new(); + txt.add_wrapped(info.to_string(), 0.5 * g.canvas.window_width); + g.draw_mouse_tooltip(txt); + } + } + } + } } diff --git a/ezgui/src/widgets/text_box.rs b/ezgui/src/widgets/text_box.rs index 0f38648095..5f8b936de2 100644 --- a/ezgui/src/widgets/text_box.rs +++ b/ezgui/src/widgets/text_box.rs @@ -1,6 +1,6 @@ use crate::{ - text, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, ScreenDims, ScreenPt, ScreenRectangle, - Text, WidgetImpl, + text, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, ScreenDims, ScreenPt, + ScreenRectangle, Text, WidgetImpl, }; use geom::Polygon; @@ -34,7 +34,41 @@ impl TextBox { } } - pub fn event(&mut self, ctx: &mut EventCtx) { + fn calculate_text(&self) -> Text { + let mut txt = Text::from(Line(&self.line[0..self.cursor_x])); + if self.cursor_x < self.line.len() { + // TODO This "cursor" looks awful! + txt.append_all(vec![ + Line("|").fg(text::SELECTED_COLOR), + Line(&self.line[self.cursor_x..=self.cursor_x]), + Line(&self.line[self.cursor_x + 1..]), + ]); + } else { + txt.append(Line("|").fg(text::SELECTED_COLOR)); + } + txt + } + + pub fn get_line(&self) -> String { + self.line.clone() + } +} + +impl WidgetImpl for TextBox { + fn get_dims(&self) -> ScreenDims { + self.dims + } + + fn set_pos(&mut self, top_left: ScreenPt) { + self.top_left = top_left; + } + + fn event( + &mut self, + ctx: &mut EventCtx, + _rect: &ScreenRectangle, + _redo_layout: &mut bool, + ) -> Option { if ctx.redo_mouseover() { if let Some(pt) = ctx.canvas.get_cursor_in_screen_space() { self.hovering = ScreenRectangle::top_left(self.top_left, self.dims).contains(pt); @@ -51,7 +85,7 @@ impl TextBox { } if !self.has_focus && !self.autofocus { - return; + return None; } if let Some(key) = ctx.input.any_key_pressed() { match key { @@ -79,9 +113,11 @@ impl TextBox { } }; } + + None } - pub fn draw(&self, g: &mut GfxCtx) { + fn draw(&self, g: &mut GfxCtx) { let mut batch = GeomBatch::from(vec![( if self.has_focus || self.autofocus { Color::ORANGE @@ -96,33 +132,4 @@ impl TextBox { let draw = g.upload(batch); g.redraw_at(self.top_left, &draw); } - - pub fn get_entry(&self) -> String { - self.line.clone() - } - - fn calculate_text(&self) -> Text { - let mut txt = Text::from(Line(&self.line[0..self.cursor_x])); - if self.cursor_x < self.line.len() { - // TODO This "cursor" looks awful! - txt.append_all(vec![ - Line("|").fg(text::SELECTED_COLOR), - Line(&self.line[self.cursor_x..=self.cursor_x]), - Line(&self.line[self.cursor_x + 1..]), - ]); - } else { - txt.append(Line("|").fg(text::SELECTED_COLOR)); - } - txt - } -} - -impl WidgetImpl for TextBox { - fn get_dims(&self) -> ScreenDims { - self.dims - } - - fn set_pos(&mut self, top_left: ScreenPt) { - self.top_left = top_left; - } } diff --git a/ezgui/src/widgets/wizard.rs b/ezgui/src/widgets/wizard.rs index af1e763dc8..108a99ebec 100644 --- a/ezgui/src/widgets/wizard.rs +++ b/ezgui/src/widgets/wizard.rs @@ -57,7 +57,7 @@ impl Wizard { pub fn current_menu_choice(&self) -> Option<&R> { if let Some(ref comp) = self.menu_comp { let item: &R = comp - .menu("menu") + .menu::>("menu") .current_choice() .as_any() .downcast_ref::()?; @@ -295,7 +295,14 @@ impl<'a, 'b> WrappedWizard<'a, 'b> { _ => {} } - let (result, destroy) = match self.wizard.menu_comp.as_ref().unwrap().menu("menu").state { + let (result, destroy) = match self + .wizard + .menu_comp + .as_ref() + .unwrap() + .menu::>("menu") + .state + { InputResult::Canceled => { self.wizard.alive = false; (None, true) diff --git a/game/Cargo.toml b/game/Cargo.toml index 24d4b01ada..22cfd747fa 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -15,7 +15,7 @@ aabb-quadtree = "0.1.0" abstutil = { path = "../abstutil" } built = { version = "0.4.0", optional = true, features=["chrono"] } chrono = "0.4.10" -downcast-rs = "1.0.4" +downcast-rs = "1.1.1" ezgui = { path = "../ezgui", default-features=false } geom = { path = "../geom" } instant = "0.1.2"