starting a new dropdown widget and using it for picking walk buttons.

avoids the cloneable mess. but has lots of new little issues...
This commit is contained in:
Dustin Carlino 2020-03-05 15:00:42 -08:00
parent c1b9684929
commit 476751147c
7 changed files with 198 additions and 59 deletions

View File

@ -1,10 +1,10 @@
use crate::layout::Widget;
use crate::text;
use crate::widgets::{Checkbox, PopupMenu, TextBox};
use crate::widgets::{Checkbox, Dropdown, PopupMenu, TextBox};
use crate::{
Button, Color, Drawable, EventCtx, Filler, GeomBatch, GfxCtx, Histogram, HorizontalAlignment,
JustDraw, Line, MultiKey, Plot, RewriteColor, ScreenDims, ScreenPt, ScreenRectangle, Slider,
Text, VerticalAlignment,
Button, Choice, Color, Drawable, EventCtx, Filler, GeomBatch, GfxCtx, Histogram,
HorizontalAlignment, JustDraw, Line, MultiKey, Plot, RewriteColor, ScreenDims, ScreenPt,
ScreenRectangle, Slider, Text, VerticalAlignment,
};
use abstutil::Cloneable;
use geom::{Distance, Duration, Polygon};
@ -32,6 +32,7 @@ enum WidgetType {
Btn(Button),
Checkbox(Checkbox),
TextBox(TextBox),
Dropdown(Dropdown),
Slider(String),
Menu(String),
Filler(String),
@ -326,6 +327,22 @@ impl ManagedWidget {
ManagedWidget::new(WidgetType::TextBox(TextBox::new(ctx, 50, prefilled))).bg(text::BG_COLOR)
}
pub fn dropdown<T: 'static + PartialEq>(
ctx: &EventCtx,
label: &str,
default_value: T,
choices: Vec<Choice<T>>,
) -> ManagedWidget {
ManagedWidget::new(WidgetType::Dropdown(Dropdown::new(
ctx,
label,
default_value,
choices,
)))
.named(label)
.outline(2.0, Color::WHITE)
}
pub(crate) fn duration_plot(plot: Plot<Duration>) -> ManagedWidget {
ManagedWidget::new(WidgetType::DurationPlot(plot))
}
@ -389,6 +406,9 @@ impl ManagedWidget {
WidgetType::TextBox(ref mut textbox) => {
textbox.event(ctx);
}
WidgetType::Dropdown(ref mut dropdown) => {
dropdown.event(ctx, &self.rect);
}
WidgetType::Slider(ref name) => {
sliders.get_mut(name).unwrap().event(ctx);
}
@ -426,6 +446,7 @@ impl ManagedWidget {
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 name) => {
if name != "horiz scrollbar" && name != "vert scrollbar" {
sliders[name].draw(g);
@ -461,6 +482,7 @@ impl ManagedWidget {
WidgetType::Btn(ref widget) => widget,
WidgetType::Checkbox(ref widget) => widget,
WidgetType::TextBox(ref widget) => widget,
WidgetType::Dropdown(ref widget) => widget,
WidgetType::Slider(ref name) => &sliders[name],
WidgetType::Menu(ref name) => &menus[name],
WidgetType::Filler(ref name) => &fillers[name],
@ -573,6 +595,9 @@ impl ManagedWidget {
WidgetType::TextBox(ref mut widget) => {
widget.set_pos(top_left);
}
WidgetType::Dropdown(ref mut widget) => {
widget.set_pos(top_left);
}
WidgetType::Slider(ref name) => {
sliders.get_mut(name).unwrap().set_pos(top_left);
}
@ -636,6 +661,7 @@ impl ManagedWidget {
| WidgetType::Filler(_)
| WidgetType::Checkbox(_)
| WidgetType::TextBox(_)
| WidgetType::Dropdown(_)
| WidgetType::DurationPlot(_)
| WidgetType::UsizePlot(_) => {}
WidgetType::Histogram(_) => {}
@ -668,9 +694,10 @@ impl ManagedWidget {
fn find(&self, name: &str) -> Option<&ManagedWidget> {
let found = match self.widget {
// TODO Consolidate and just do this
WidgetType::Draw(_) | WidgetType::Checkbox(_) | WidgetType::TextBox(_) => {
self.id == Some(name.to_string())
}
WidgetType::Draw(_)
| WidgetType::Checkbox(_)
| WidgetType::TextBox(_)
| WidgetType::Dropdown(_) => self.id == Some(name.to_string()),
WidgetType::Btn(ref btn) => btn.action == name,
WidgetType::Slider(ref n) => n == name,
WidgetType::Menu(ref n) => n == name,
@ -697,9 +724,10 @@ impl ManagedWidget {
fn find_mut(&mut self, name: &str) -> Option<&mut ManagedWidget> {
let found = match self.widget {
// TODO Consolidate and just do this
WidgetType::Draw(_) | WidgetType::Checkbox(_) | WidgetType::TextBox(_) => {
self.id == Some(name.to_string())
}
WidgetType::Draw(_)
| WidgetType::Checkbox(_)
| WidgetType::TextBox(_)
| WidgetType::Dropdown(_) => self.id == Some(name.to_string()),
WidgetType::Btn(ref btn) => btn.action == name,
WidgetType::Slider(ref n) => n == name,
WidgetType::Menu(ref n) => n == name,
@ -1009,6 +1037,14 @@ impl Composite {
}
}
// TODO This invalidates the dropdown!
pub fn dropdown_value<T: 'static>(&mut self, name: &str) -> T {
match self.find_mut(name).widget {
WidgetType::Dropdown(ref mut dropdown) => dropdown.take_value(),
_ => panic!("{} isn't a dropdown", name),
}
}
pub fn filler_rect(&self, name: &str) -> ScreenRectangle {
let f = &self.fillers[name];
ScreenRectangle::top_left(f.top_left, f.dims)

View File

@ -0,0 +1,125 @@
use crate::layout::Widget;
use crate::widgets::PopupMenu;
use crate::{
Button, Choice, Color, EventCtx, GfxCtx, InputResult, Line, ScreenDims, ScreenPt,
ScreenRectangle, Text,
};
use std::any::Any;
pub struct Dropdown {
current_idx: usize,
btn: Button,
menu: Option<PopupMenu<usize>>,
label: String,
choices: Vec<Choice<Box<dyn Any>>>,
}
impl Dropdown {
pub fn new<T: 'static + PartialEq>(
ctx: &EventCtx,
label: &str,
default_value: T,
choices: Vec<Choice<T>>,
) -> Dropdown {
let current_idx = choices
.iter()
.position(|c| c.data == default_value)
.unwrap();
Dropdown {
current_idx,
btn: make_btn(ctx, &choices[current_idx].label, label),
menu: None,
label: label.to_string(),
choices: choices
.into_iter()
.map(|c| {
// TODO Can't use with_value here :(
let data: Box<dyn Any> = Box::new(c.data);
Choice {
label: c.label,
data,
hotkey: c.hotkey,
active: c.active,
tooltip: c.tooltip,
}
})
.collect(),
}
}
pub fn event(&mut self, ctx: &mut EventCtx, our_rect: &ScreenRectangle) {
if let Some(ref mut m) = self.menu {
m.event(ctx);
match m.state {
InputResult::StillActive => {}
InputResult::Canceled => {
self.menu = None;
}
InputResult::Done(_, idx) => {
self.menu = None;
self.current_idx = idx;
let top_left = self.btn.top_left;
// TODO Recalculate layout when this happens... outline around button should
// change
self.btn = make_btn(ctx, &self.choices[self.current_idx].label, &self.label);
self.btn.set_pos(top_left);
}
}
} else {
self.btn.event(ctx);
if self.btn.clicked() {
// TODO set current idx in menu
// TODO Choice::map_value?
let mut menu = PopupMenu::new(
ctx,
self.choices
.iter()
.enumerate()
.map(|(idx, c)| c.with_value(idx))
.collect(),
);
menu.set_pos(ScreenPt::new(our_rect.x1, our_rect.y2 + 15.0));
self.menu = Some(menu);
}
}
}
pub fn draw(&self, g: &mut GfxCtx) {
self.btn.draw(g);
if let Some(ref m) = self.menu {
m.draw(g);
}
}
// TODO This invalidates the entire widget!
pub fn take_value<T: 'static>(&mut self) -> T {
let data: Box<dyn Any> = self.choices.remove(self.current_idx).data;
let boxed: Box<T> = data.downcast().unwrap();
*boxed
}
}
impl Widget for Dropdown {
fn get_dims(&self) -> ScreenDims {
self.btn.get_dims()
}
fn set_pos(&mut self, top_left: ScreenPt) {
self.btn.set_pos(top_left);
}
}
fn make_btn(ctx: &EventCtx, name: &str, label: &str) -> Button {
let txt = Text::from(Line(format!("{}", name)));
Button::text_no_bg(
txt.clone(),
txt.change_fg(Color::ORANGE),
None,
label,
true,
ctx,
)
}

View File

@ -1,6 +1,7 @@
mod autocomplete;
mod button;
mod checkbox;
mod dropdown;
mod filler;
mod histogram;
mod modal_menu;
@ -16,6 +17,7 @@ mod wizard;
pub use self::autocomplete::Autocomplete;
pub use self::button::Button;
pub use self::checkbox::Checkbox;
pub(crate) use self::dropdown::Dropdown;
pub use self::filler::Filler;
pub use self::histogram::Histogram;
pub use self::modal_menu::ModalMenu;

View File

@ -297,13 +297,7 @@ impl<'a, 'b> WrappedWizard<'a, 'b> {
self.ctx,
choices
.into_iter()
.map(|c| Choice {
label: c.label,
data: c.data.clone_box(),
hotkey: c.hotkey,
active: c.active,
tooltip: c.tooltip,
})
.map(|c| c.with_value(c.data.clone_box()))
.collect(),
),
)
@ -444,7 +438,7 @@ impl<'a, 'b> WrappedWizard<'a, 'b> {
}
}
pub struct Choice<T: Clone> {
pub struct Choice<T> {
pub(crate) label: String,
pub data: T,
pub(crate) hotkey: Option<MultiKey>,
@ -452,7 +446,7 @@ pub struct Choice<T: Clone> {
pub(crate) tooltip: Option<String>,
}
impl<T: Clone> Choice<T> {
impl<T> Choice<T> {
pub fn new<S: Into<String>>(label: S, data: T) -> Choice<T> {
Choice {
label: label.into(),
@ -490,4 +484,14 @@ impl<T: Clone> Choice<T> {
self.tooltip = Some(info.into());
self
}
pub(crate) fn with_value<X>(&self, data: X) -> Choice<X> {
Choice {
label: self.label.clone(),
data,
hotkey: self.hotkey.clone(),
active: self.active,
tooltip: self.tooltip.clone(),
}
}
}

View File

@ -1,13 +1,12 @@
use crate::app::App;
use crate::colors;
use crate::game::{State, Transition, WizardState};
use crate::game::{State, Transition};
use crate::managed::WrappedComposite;
use ezgui::{hotkey, Choice, Composite, EventCtx, GfxCtx, Key, Line, ManagedWidget, Outcome, Text};
use map_model::{ExtraMappingData, IntersectionID};
pub struct EditMetadata {
composite: Composite,
walk_buttons: traffic_signals::WalkButtons,
}
impl EditMetadata {
@ -34,7 +33,6 @@ impl EditMetadata {
});
EditMetadata {
walk_buttons: data.walk_buttons.clone(),
composite: Composite::new(
ManagedWidget::col(vec![
ManagedWidget::row(vec![
@ -46,13 +44,15 @@ impl EditMetadata {
]),
ManagedWidget::row(vec![
ManagedWidget::draw_text(ctx, Text::from(Line("Walk buttons: "))),
WrappedComposite::nice_text_button(
ManagedWidget::dropdown(
ctx,
Text::from(Line(format!("{}", describe(&data.walk_buttons),))),
None,
"change walk buttons",
)
.margin(5),
data.walk_buttons,
traffic_signals::WalkButtons::all()
.into_iter()
.map(|btn| Choice::new(describe(&btn), btn))
.collect(),
),
]),
ManagedWidget::draw_text(ctx, Text::from(Line("The mapper").roboto_bold())),
ManagedWidget::draw_text(
@ -114,36 +114,12 @@ impl State for EditMetadata {
"X" => {
return Transition::Pop;
}
"change walk buttons" => {
return Transition::Push(WizardState::new(Box::new(|wiz, ctx, _| {
let (_, btn) = wiz.wrap(ctx).choose(
"What kind of walk buttons does this intersection have?",
|| {
traffic_signals::WalkButtons::all()
.into_iter()
.map(|btn| Choice::new(describe(&btn), btn))
.collect()
},
)?;
Some(Transition::PopWithData(Box::new(move |state, _, ctx| {
let mut edit = state.downcast_mut::<EditMetadata>().unwrap();
edit.composite.replace(
ctx,
"change walk buttons",
WrappedComposite::nice_text_button(
ctx,
Text::from(Line(format!("{}", describe(&btn)))),
None,
"change walk buttons",
),
);
edit.walk_buttons = btn;
})))
})));
}
"Done" => {
let mut new_data = ExtraMappingData {
walk_buttons: self.walk_buttons.clone(),
walk_buttons: self
.composite
.dropdown_value::<traffic_signals::WalkButtons>("change walk buttons")
.clone(),
observed: traffic_signals::Metadata {
author: self.composite.text_box("observed author"),
datetime: self.composite.text_box("observed datetime"),

View File

@ -5,7 +5,6 @@ authors = ["Dustin Carlino <dabreegster@gmail.com>"]
edition = "2018"
[dependencies]
abstutil = { path = "../abstutil" }
include_dir = "0.5.0"
serde = "1.0.89"
serde_derive = "1.0.98"

View File

@ -120,9 +120,6 @@ impl WalkButtons {
}
}
// TODO Temporary
impl abstutil::Cloneable for WalkButtons {}
static DATA: include_dir::Dir = include_dir::include_dir!("src/data");
/// Returns all traffic signal data compiled into this build, keyed by OSM node ID.