mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 03:35:51 +03:00
express many ezgui widgets as a generic trait.
- prepares the API for anyone to implement widgets - cleans up boilerplate code - gets rid of hacks with Plot<T>
This commit is contained in:
parent
2712ea8c74
commit
2b2b30a6bb
@ -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 }
|
||||
|
@ -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<T: Clone> {
|
||||
Canceled,
|
||||
|
@ -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<Drawable>,
|
||||
// TODO Only use this, not the other things
|
||||
id: Option<String>,
|
||||
}
|
||||
|
||||
enum WidgetType {
|
||||
Draw(JustDraw),
|
||||
Btn(Button),
|
||||
Checkbox(Checkbox),
|
||||
TextBox(TextBox),
|
||||
Dropdown(Dropdown),
|
||||
Slider(Slider),
|
||||
Menu(PopupMenu<Box<dyn Cloneable>>),
|
||||
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<Duration>),
|
||||
UsizePlot(Plot<usize>),
|
||||
Histogram(Histogram),
|
||||
Row(Vec<Widget>),
|
||||
Column(Vec<Widget>),
|
||||
Nothing,
|
||||
Generic(Box<dyn WidgetImpl>),
|
||||
}
|
||||
|
||||
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<Box<dyn Cloneable>>) -> Widget {
|
||||
Widget::new(WidgetType::Menu(menu))
|
||||
pub fn menu<T: 'static + Clone>(menu: PopupMenu<T>) -> 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<T: 'static + PartialEq>(
|
||||
@ -323,16 +313,12 @@ impl Widget {
|
||||
.outline(2.0, Color::WHITE)
|
||||
}
|
||||
|
||||
pub(crate) fn duration_plot(plot: Plot<Duration>) -> Widget {
|
||||
Widget::new(WidgetType::DurationPlot(plot))
|
||||
}
|
||||
|
||||
pub(crate) fn usize_plot(plot: Plot<usize>) -> Widget {
|
||||
Widget::new(WidgetType::UsizePlot(plot))
|
||||
pub(crate) fn plot<T: 'static + Yvalue<T>>(plot: Plot<T>) -> 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>) -> Widget {
|
||||
@ -368,21 +354,12 @@ impl Widget {
|
||||
impl Widget {
|
||||
fn event(&mut self, ctx: &mut EventCtx, redo_layout: &mut bool) -> Option<Outcome> {
|
||||
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<Node>) {
|
||||
// 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())
|
||||
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)
|
||||
}
|
||||
}
|
||||
_ => 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<String>) {
|
||||
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<Box<dyn Cloneable>> {
|
||||
pub fn menu<T: 'static + Clone>(&self, name: &str) -> &PopupMenu<T> {
|
||||
match self.find(name).widget {
|
||||
WidgetType::Menu(ref menu) => menu,
|
||||
WidgetType::Generic(ref w) => {
|
||||
if let Some(menu) = w.downcast_ref::<PopupMenu<T>>() {
|
||||
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>() {
|
||||
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::<TextBox>() {
|
||||
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::<Filler>() {
|
||||
widget.rect.clone()
|
||||
} else {
|
||||
panic!("{} isn't a filler", name);
|
||||
}
|
||||
}
|
||||
_ => panic!("{} isn't a filler", name),
|
||||
}
|
||||
}
|
||||
|
@ -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<Outcome> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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 => {
|
||||
|
@ -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<Outcome> {
|
||||
None
|
||||
}
|
||||
fn draw(&self, _g: &mut GfxCtx) {}
|
||||
}
|
||||
|
@ -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<Outcome> {
|
||||
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(
|
||||
|
@ -12,13 +12,26 @@ 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<Outcome> {
|
||||
None
|
||||
}
|
||||
fn draw(&self, _g: &mut GfxCtx) {}
|
||||
}
|
||||
|
||||
downcast_rs::impl_downcast!(WidgetImpl);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ContainerOrientation {
|
||||
|
@ -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<Outcome> {
|
||||
None
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx) {
|
||||
g.redraw_at(self.top_left, &self.draw);
|
||||
}
|
||||
}
|
||||
|
@ -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<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
|
||||
|
||||
(plot, legend, x_axis, y_axis)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn draw(&self, g: &mut GfxCtx) {
|
||||
// TODO Do we still need these two? :\
|
||||
impl Plot<usize> {
|
||||
pub fn new_usize(ctx: &EventCtx, series: Vec<Series<usize>>, 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<Duration> {
|
||||
pub fn new_duration(
|
||||
ctx: &EventCtx,
|
||||
series: Vec<Series<Duration>>,
|
||||
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<T: 'static + Yvalue<T>> WidgetImpl for Plot<T> {
|
||||
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<Outcome> {
|
||||
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<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
|
||||
}
|
||||
}
|
||||
|
||||
impl Plot<usize> {
|
||||
pub fn new_usize(ctx: &EventCtx, series: Vec<Series<usize>>, 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<Duration> {
|
||||
pub fn new_duration(
|
||||
ctx: &EventCtx,
|
||||
series: Vec<Series<Duration>>,
|
||||
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<T> WidgetImpl for Plot<T> {
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
self.dims
|
||||
}
|
||||
|
||||
fn set_pos(&mut self, top_left: ScreenPt) {
|
||||
self.top_left = top_left;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Yvalue<T> {
|
||||
// percent is [0.0, 1.0]
|
||||
fn from_percent(&self, percent: f64) -> T;
|
||||
|
@ -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<T: Clone> PopupMenu<T> {
|
||||
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<T: Clone> PopupMenu<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> WidgetImpl for PopupMenu<T> {
|
||||
impl<T: 'static + Clone> WidgetImpl for PopupMenu<T> {
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
self.dims
|
||||
}
|
||||
@ -190,4 +78,123 @@ impl<T: Clone> WidgetImpl for PopupMenu<T> {
|
||||
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<Outcome> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Outcome> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ impl Wizard {
|
||||
pub fn current_menu_choice<R: 'static + Cloneable>(&self) -> Option<&R> {
|
||||
if let Some(ref comp) = self.menu_comp {
|
||||
let item: &R = comp
|
||||
.menu("menu")
|
||||
.menu::<Box<dyn Cloneable>>("menu")
|
||||
.current_choice()
|
||||
.as_any()
|
||||
.downcast_ref::<R>()?;
|
||||
@ -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::<Box<dyn Cloneable>>("menu")
|
||||
.state
|
||||
{
|
||||
InputResult::Canceled => {
|
||||
self.wizard.alive = false;
|
||||
(None, true)
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user