trait-ify dropdowns (generic type plumbed in the right place now)

This commit is contained in:
Dustin Carlino 2020-03-22 14:23:01 -07:00
parent 2b2b30a6bb
commit 67b21a334c
2 changed files with 47 additions and 93 deletions

View File

@ -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),
} }

View File

@ -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 {