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 {
Btn(Button),
Dropdown(Dropdown),
Slider(Slider),
Row(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,
label: &str,
default_value: T,
choices: Vec<Choice<T>>,
) -> Widget {
Widget::new(WidgetType::Dropdown(Dropdown::new(
Widget::new(WidgetType::Generic(Box::new(Dropdown::new(
ctx,
label,
default_value,
choices,
)))
))))
.named(label)
.outline(2.0, Color::WHITE)
}
@ -360,11 +359,6 @@ impl Widget {
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) => {
slider.event(ctx);
}
@ -392,7 +386,6 @@ impl Widget {
match self.widget {
WidgetType::Btn(ref btn) => btn.draw(g),
WidgetType::Dropdown(ref dropdown) => dropdown.draw(g),
WidgetType::Slider(ref slider) => {
if self.id != Some("horiz 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>) {
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 {
@ -507,9 +499,6 @@ impl Widget {
WidgetType::Btn(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);
}
@ -549,7 +538,7 @@ impl Widget {
fn get_all_click_actions(&self, actions: &mut HashSet<String>) {
match self.widget {
WidgetType::Slider(_) | WidgetType::Dropdown(_) => {}
WidgetType::Slider(_) => {}
WidgetType::Btn(ref btn) => {
if actions.contains(&btn.action) {
panic!(
@ -915,15 +904,14 @@ impl Composite {
}
}
pub fn dropdown_value<T: 'static + Clone>(&mut self, name: &str) -> T {
match self.find_mut(name).widget {
WidgetType::Dropdown(ref mut dropdown) => {
// Amusing little pattern here.
// TODO I think this entire hack goes away when WidgetImpl is just a trait.
let choice: Choice<T> = dropdown.take_value();
let value = choice.data.clone();
dropdown.return_value(choice);
value
pub fn dropdown_value<T: 'static + PartialEq + Clone>(&mut self, name: &str) -> T {
match self.find(name).widget {
WidgetType::Generic(ref w) => {
if let Some(dropdown) = w.downcast_ref::<Dropdown<T>>() {
dropdown.current_value()
} else {
panic!("{} isn't a dropdown", name);
}
}
_ => panic!("{} isn't a dropdown", name),
}

View File

@ -1,26 +1,25 @@
use crate::{
Btn, Button, Choice, Color, EventCtx, GfxCtx, InputResult, PopupMenu, ScreenDims, ScreenPt,
ScreenRectangle, WidgetImpl,
Btn, Button, Choice, Color, EventCtx, GfxCtx, InputResult, Outcome, PopupMenu, ScreenDims,
ScreenPt, ScreenRectangle, WidgetImpl,
};
use geom::{Polygon, Pt2D};
use std::any::Any;
pub struct Dropdown {
pub struct Dropdown<T: Clone> {
current_idx: usize,
btn: Button,
menu: Option<PopupMenu<usize>>,
label: String,
choices: Vec<Choice<Box<dyn Any>>>,
choices: Vec<Choice<T>>,
}
impl Dropdown {
pub fn new<T: 'static + PartialEq>(
impl<T: 'static + PartialEq + Clone> Dropdown<T> {
pub fn new(
ctx: &EventCtx,
label: &str,
default_value: T,
choices: Vec<Choice<T>>,
) -> Dropdown {
) -> Dropdown<T> {
let current_idx = choices
.iter()
.position(|c| c.data == default_value)
@ -31,30 +30,34 @@ impl Dropdown {
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(),
choices,
}
}
// If true, widgets should be recomputed.
pub fn event(&mut self, ctx: &mut EventCtx, our_rect: &ScreenRectangle) -> bool {
pub fn current_value(&self) -> T {
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 {
// TODO wraaaaaaaaaaaawng
let mut redo_layout = false;
m.event(ctx, our_rect, &mut redo_layout);
// TODO Pass in the dropdown's rectangle, not the menu's. This is a lie! But the menu
// doesn't use it, so fine?
m.event(ctx, rect, redo_layout);
match m.state {
InputResult::StillActive => {}
InputResult::Canceled => {
@ -68,7 +71,7 @@ impl Dropdown {
// change
self.btn = make_btn(ctx, &self.choices[self.current_idx].label, &self.label);
self.btn.set_pos(top_left);
return true;
*redo_layout = true;
}
}
} else {
@ -84,15 +87,15 @@ impl Dropdown {
.map(|(idx, c)| c.with_value(idx))
.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);
}
}
false
None
}
pub fn draw(&self, g: &mut GfxCtx) {
fn draw(&self, g: &mut GfxCtx) {
self.btn.draw(g);
if let Some(ref m) = self.menu {
// We need a background too!
@ -106,43 +109,6 @@ impl Dropdown {
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 {