mirror of
https://github.com/a-b-street/abstreet.git
synced 2025-01-01 19:04:50 +03:00
trait-ify dropdowns (generic type plumbed in the right place now)
This commit is contained in:
parent
2b2b30a6bb
commit
67b21a334c
@ -23,7 +23,6 @@ pub struct Widget {
|
|||||||
|
|
||||||
enum WidgetType {
|
enum WidgetType {
|
||||||
Btn(Button),
|
Btn(Button),
|
||||||
Dropdown(Dropdown),
|
|
||||||
Slider(Slider),
|
Slider(Slider),
|
||||||
Row(Vec<Widget>),
|
Row(Vec<Widget>),
|
||||||
Column(Vec<Widget>),
|
Column(Vec<Widget>),
|
||||||
@ -297,18 +296,18 @@ impl Widget {
|
|||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dropdown<T: 'static + PartialEq>(
|
pub fn dropdown<T: 'static + PartialEq + Clone>(
|
||||||
ctx: &EventCtx,
|
ctx: &EventCtx,
|
||||||
label: &str,
|
label: &str,
|
||||||
default_value: T,
|
default_value: T,
|
||||||
choices: Vec<Choice<T>>,
|
choices: Vec<Choice<T>>,
|
||||||
) -> Widget {
|
) -> Widget {
|
||||||
Widget::new(WidgetType::Dropdown(Dropdown::new(
|
Widget::new(WidgetType::Generic(Box::new(Dropdown::new(
|
||||||
ctx,
|
ctx,
|
||||||
label,
|
label,
|
||||||
default_value,
|
default_value,
|
||||||
choices,
|
choices,
|
||||||
)))
|
))))
|
||||||
.named(label)
|
.named(label)
|
||||||
.outline(2.0, Color::WHITE)
|
.outline(2.0, Color::WHITE)
|
||||||
}
|
}
|
||||||
@ -360,11 +359,6 @@ impl Widget {
|
|||||||
return Some(Outcome::Clicked(btn.action.clone()));
|
return Some(Outcome::Clicked(btn.action.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WidgetType::Dropdown(ref mut dropdown) => {
|
|
||||||
if dropdown.event(ctx, &self.rect) {
|
|
||||||
*redo_layout = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WidgetType::Slider(ref mut slider) => {
|
WidgetType::Slider(ref mut slider) => {
|
||||||
slider.event(ctx);
|
slider.event(ctx);
|
||||||
}
|
}
|
||||||
@ -392,7 +386,6 @@ impl Widget {
|
|||||||
|
|
||||||
match self.widget {
|
match self.widget {
|
||||||
WidgetType::Btn(ref btn) => btn.draw(g),
|
WidgetType::Btn(ref btn) => btn.draw(g),
|
||||||
WidgetType::Dropdown(ref dropdown) => dropdown.draw(g),
|
|
||||||
WidgetType::Slider(ref slider) => {
|
WidgetType::Slider(ref slider) => {
|
||||||
if self.id != Some("horiz scrollbar".to_string())
|
if self.id != Some("horiz scrollbar".to_string())
|
||||||
&& self.id != Some("vert scrollbar".to_string())
|
&& self.id != Some("vert scrollbar".to_string())
|
||||||
@ -414,7 +407,6 @@ impl Widget {
|
|||||||
fn get_flexbox(&self, parent: Node, stretch: &mut Stretch, nodes: &mut Vec<Node>) {
|
fn get_flexbox(&self, parent: Node, stretch: &mut Stretch, nodes: &mut Vec<Node>) {
|
||||||
let dims = match self.widget {
|
let dims = match self.widget {
|
||||||
WidgetType::Btn(ref widget) => widget.get_dims(),
|
WidgetType::Btn(ref widget) => widget.get_dims(),
|
||||||
WidgetType::Dropdown(ref widget) => widget.get_dims(),
|
|
||||||
WidgetType::Slider(ref widget) => widget.get_dims(),
|
WidgetType::Slider(ref widget) => widget.get_dims(),
|
||||||
WidgetType::Row(ref widgets) => {
|
WidgetType::Row(ref widgets) => {
|
||||||
let mut style = Style {
|
let mut style = Style {
|
||||||
@ -507,9 +499,6 @@ impl Widget {
|
|||||||
WidgetType::Btn(ref mut widget) => {
|
WidgetType::Btn(ref mut widget) => {
|
||||||
widget.set_pos(top_left);
|
widget.set_pos(top_left);
|
||||||
}
|
}
|
||||||
WidgetType::Dropdown(ref mut widget) => {
|
|
||||||
widget.set_pos(top_left);
|
|
||||||
}
|
|
||||||
WidgetType::Slider(ref mut widget) => {
|
WidgetType::Slider(ref mut widget) => {
|
||||||
widget.set_pos(top_left);
|
widget.set_pos(top_left);
|
||||||
}
|
}
|
||||||
@ -549,7 +538,7 @@ impl Widget {
|
|||||||
|
|
||||||
fn get_all_click_actions(&self, actions: &mut HashSet<String>) {
|
fn get_all_click_actions(&self, actions: &mut HashSet<String>) {
|
||||||
match self.widget {
|
match self.widget {
|
||||||
WidgetType::Slider(_) | WidgetType::Dropdown(_) => {}
|
WidgetType::Slider(_) => {}
|
||||||
WidgetType::Btn(ref btn) => {
|
WidgetType::Btn(ref btn) => {
|
||||||
if actions.contains(&btn.action) {
|
if actions.contains(&btn.action) {
|
||||||
panic!(
|
panic!(
|
||||||
@ -915,15 +904,14 @@ impl Composite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dropdown_value<T: 'static + Clone>(&mut self, name: &str) -> T {
|
pub fn dropdown_value<T: 'static + PartialEq + Clone>(&mut self, name: &str) -> T {
|
||||||
match self.find_mut(name).widget {
|
match self.find(name).widget {
|
||||||
WidgetType::Dropdown(ref mut dropdown) => {
|
WidgetType::Generic(ref w) => {
|
||||||
// Amusing little pattern here.
|
if let Some(dropdown) = w.downcast_ref::<Dropdown<T>>() {
|
||||||
// TODO I think this entire hack goes away when WidgetImpl is just a trait.
|
dropdown.current_value()
|
||||||
let choice: Choice<T> = dropdown.take_value();
|
} else {
|
||||||
let value = choice.data.clone();
|
panic!("{} isn't a dropdown", name);
|
||||||
dropdown.return_value(choice);
|
}
|
||||||
value
|
|
||||||
}
|
}
|
||||||
_ => panic!("{} isn't a dropdown", name),
|
_ => panic!("{} isn't a dropdown", name),
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Btn, Button, Choice, Color, EventCtx, GfxCtx, InputResult, PopupMenu, ScreenDims, ScreenPt,
|
Btn, Button, Choice, Color, EventCtx, GfxCtx, InputResult, Outcome, PopupMenu, ScreenDims,
|
||||||
ScreenRectangle, WidgetImpl,
|
ScreenPt, ScreenRectangle, WidgetImpl,
|
||||||
};
|
};
|
||||||
use geom::{Polygon, Pt2D};
|
use geom::{Polygon, Pt2D};
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
pub struct Dropdown {
|
pub struct Dropdown<T: Clone> {
|
||||||
current_idx: usize,
|
current_idx: usize,
|
||||||
btn: Button,
|
btn: Button,
|
||||||
menu: Option<PopupMenu<usize>>,
|
menu: Option<PopupMenu<usize>>,
|
||||||
label: String,
|
label: String,
|
||||||
|
|
||||||
choices: Vec<Choice<Box<dyn Any>>>,
|
choices: Vec<Choice<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dropdown {
|
impl<T: 'static + PartialEq + Clone> Dropdown<T> {
|
||||||
pub fn new<T: 'static + PartialEq>(
|
pub fn new(
|
||||||
ctx: &EventCtx,
|
ctx: &EventCtx,
|
||||||
label: &str,
|
label: &str,
|
||||||
default_value: T,
|
default_value: T,
|
||||||
choices: Vec<Choice<T>>,
|
choices: Vec<Choice<T>>,
|
||||||
) -> Dropdown {
|
) -> Dropdown<T> {
|
||||||
let current_idx = choices
|
let current_idx = choices
|
||||||
.iter()
|
.iter()
|
||||||
.position(|c| c.data == default_value)
|
.position(|c| c.data == default_value)
|
||||||
@ -31,30 +30,34 @@ impl Dropdown {
|
|||||||
btn: make_btn(ctx, &choices[current_idx].label, label),
|
btn: make_btn(ctx, &choices[current_idx].label, label),
|
||||||
menu: None,
|
menu: None,
|
||||||
label: label.to_string(),
|
label: label.to_string(),
|
||||||
|
choices,
|
||||||
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(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If true, widgets should be recomputed.
|
pub fn current_value(&self) -> T {
|
||||||
pub fn event(&mut self, ctx: &mut EventCtx, our_rect: &ScreenRectangle) -> bool {
|
self.choices[self.current_idx].data.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static + Clone> WidgetImpl for Dropdown<T> {
|
||||||
|
fn get_dims(&self) -> ScreenDims {
|
||||||
|
self.btn.get_dims()
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
if let Some(ref mut m) = self.menu {
|
if let Some(ref mut m) = self.menu {
|
||||||
// TODO wraaaaaaaaaaaawng
|
// TODO Pass in the dropdown's rectangle, not the menu's. This is a lie! But the menu
|
||||||
let mut redo_layout = false;
|
// doesn't use it, so fine?
|
||||||
m.event(ctx, our_rect, &mut redo_layout);
|
m.event(ctx, rect, redo_layout);
|
||||||
match m.state {
|
match m.state {
|
||||||
InputResult::StillActive => {}
|
InputResult::StillActive => {}
|
||||||
InputResult::Canceled => {
|
InputResult::Canceled => {
|
||||||
@ -68,7 +71,7 @@ impl Dropdown {
|
|||||||
// change
|
// change
|
||||||
self.btn = make_btn(ctx, &self.choices[self.current_idx].label, &self.label);
|
self.btn = make_btn(ctx, &self.choices[self.current_idx].label, &self.label);
|
||||||
self.btn.set_pos(top_left);
|
self.btn.set_pos(top_left);
|
||||||
return true;
|
*redo_layout = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -84,15 +87,15 @@ impl Dropdown {
|
|||||||
.map(|(idx, c)| c.with_value(idx))
|
.map(|(idx, c)| c.with_value(idx))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
menu.set_pos(ScreenPt::new(our_rect.x1, our_rect.y2 + 15.0));
|
menu.set_pos(ScreenPt::new(rect.x1, rect.y2 + 15.0));
|
||||||
self.menu = Some(menu);
|
self.menu = Some(menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&self, g: &mut GfxCtx) {
|
fn draw(&self, g: &mut GfxCtx) {
|
||||||
self.btn.draw(g);
|
self.btn.draw(g);
|
||||||
if let Some(ref m) = self.menu {
|
if let Some(ref m) = self.menu {
|
||||||
// We need a background too!
|
// We need a background too!
|
||||||
@ -106,43 +109,6 @@ impl Dropdown {
|
|||||||
m.draw(g);
|
m.draw(g);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This invalidates the entire widget! Have to call return_value
|
|
||||||
pub fn take_value<T: 'static>(&mut self) -> Choice<T> {
|
|
||||||
let c = self.choices.remove(self.current_idx);
|
|
||||||
let data: Box<dyn Any> = c.data;
|
|
||||||
let boxed: Box<T> = data.downcast().unwrap();
|
|
||||||
Choice {
|
|
||||||
label: c.label,
|
|
||||||
data: *boxed,
|
|
||||||
hotkey: c.hotkey,
|
|
||||||
active: c.active,
|
|
||||||
tooltip: c.tooltip,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn return_value<T: 'static>(&mut self, c: Choice<T>) {
|
|
||||||
let data: Box<dyn Any> = Box::new(c.data);
|
|
||||||
self.choices.insert(
|
|
||||||
self.current_idx,
|
|
||||||
Choice {
|
|
||||||
label: c.label,
|
|
||||||
data,
|
|
||||||
hotkey: c.hotkey,
|
|
||||||
active: c.active,
|
|
||||||
tooltip: c.tooltip,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetImpl 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 {
|
fn make_btn(ctx: &EventCtx, name: &str, label: &str) -> Button {
|
||||||
|
Loading…
Reference in New Issue
Block a user