mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-01 19:27:11 +03:00
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:
parent
c1b9684929
commit
476751147c
@ -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)
|
||||
|
125
ezgui/src/widgets/dropdown.rs
Normal file
125
ezgui/src/widgets/dropdown.rs
Normal 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,
|
||||
)
|
||||
}
|
@ -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;
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user