use geom::{CornerRadii, Distance, Polygon, Pt2D};
use crate::{
Button, Choice, Color, ControlState, CornerRounding, EdgeInsets, EventCtx, GeomBatch, GfxCtx,
Menu, Outcome, ScreenDims, ScreenPt, ScreenRectangle, WidgetImpl, WidgetOutput,
};
pub struct Dropdown<T: Clone> {
current_idx: usize,
btn: Button,
menu: Option<Menu<usize>>,
label: String,
is_persisten_split: bool,
choices: Vec<Choice<T>>,
}
impl<T: 'static + PartialEq + Clone + std::fmt::Debug> Dropdown<T> {
pub fn new(
ctx: &EventCtx,
label: &str,
default_value: T,
choices: Vec<Choice<T>>,
is_persisten_split: bool,
) -> Dropdown<T> {
let current_idx = if let Some(idx) = choices.iter().position(|c| c.data == default_value) {
idx
} else {
panic!(
"Dropdown {} has default_value {:?}, but none of the choices match that",
label, default_value
);
};
Dropdown {
current_idx,
btn: make_btn(ctx, &choices[current_idx].label, label, is_persisten_split),
menu: None,
label: label.to_string(),
is_persisten_split,
choices,
}
}
}
impl<T: 'static + PartialEq + Clone> Dropdown<T> {
pub fn current_value(&self) -> T {
self.choices[self.current_idx].data.clone()
}
pub(crate) fn current_value_label(&self) -> &str {
&self.choices[self.current_idx].label
}
}
impl<T: 'static + Clone> Dropdown<T> {
fn open_menu(&mut self, ctx: &mut EventCtx) {
let mut menu = Menu::new(
ctx,
self.choices
.iter()
.enumerate()
.map(|(idx, c)| c.with_value(idx))
.collect(),
);
menu.set_current(self.current_idx);
let y1_below = self.btn.top_left.y + self.btn.dims.height + 15.0;
menu.set_pos(ScreenPt::new(
self.btn.top_left.x,
if y1_below + menu.get_dims().height < ctx.canvas.window_height {
y1_below
} else {
self.btn.top_left.y - 15.0 - menu.get_dims().height
},
));
self.menu = Some(menu);
}
}
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, output: &mut WidgetOutput) {
if let Some(ref mut m) = self.menu {
let mut tmp_ouput = WidgetOutput::new();
m.event(ctx, &mut tmp_ouput);
if let Outcome::Clicked(_) = tmp_ouput.outcome {
self.current_idx = self.menu.take().unwrap().take_current_choice();
output.outcome = Outcome::Changed(self.label.clone());
let top_left = self.btn.top_left;
self.btn = make_btn(
ctx,
&self.choices[self.current_idx].label,
&self.label,
self.is_persisten_split,
);
self.btn.set_pos(top_left);
output.redo_layout = true;
} else if ctx.normal_left_click() {
if let Some(pt) = ctx.canvas.get_cursor_in_screen_space() {
if !ScreenRectangle::top_left(m.top_left, m.get_dims()).contains(pt) {
self.menu = None;
}
} else {
self.menu = None;
}
if self.menu.is_some() {
ctx.input.unconsume_event();
}
}
} else {
self.btn.event(ctx, output);
if let Outcome::Clicked(_) = output.outcome {
output.outcome = Outcome::Nothing;
self.open_menu(ctx);
}
}
if self.menu.is_some() {
output.outcome = Outcome::Focused(self.label.clone());
}
}
fn draw(&self, g: &mut GfxCtx) {
self.btn.draw(g);
if let Some(ref m) = self.menu {
let pad = 5.0;
let width = m.get_dims().width + 2.0 * pad;
let height = m.get_dims().height + 2.0 * pad;
let rect = Polygon::rounded_rectangle(width, height, 5.0);
let mut batch = GeomBatch::new();
batch.push(g.style().field_bg, rect.clone());
batch.push(
g.style().dropdown_border,
rect.to_outline(Distance::meters(1.0)),
);
let draw_bg = g.upload(batch);
g.fork(
Pt2D::new(0.0, 0.0),
ScreenPt::new(m.top_left.x - pad, m.top_left.y - pad),
1.0,
Some(crate::drawing::MENU_Z),
);
g.redraw(&draw_bg);
g.unfork();
m.draw(g);
g.canvas
.mark_covered_area(ScreenRectangle::top_left(m.top_left, m.get_dims()));
}
}
fn can_restore(&self) -> bool {
true
}
fn restore(&mut self, ctx: &mut EventCtx, prev: &dyn WidgetImpl) {
let prev = prev.downcast_ref::<Dropdown<T>>().unwrap();
if prev.menu.is_some() {
self.open_menu(ctx);
}
}
}
fn make_btn(ctx: &EventCtx, label: &str, tooltip: &str, is_persisten_split: bool) -> Button {
let builder = if is_persisten_split {
ctx.style()
.btn_solid
.dropdown()
.padding(EdgeInsets {
top: 15.0,
bottom: 15.0,
left: 8.0,
right: 8.0,
})
.corner_rounding(CornerRounding::CornerRadii(CornerRadii {
top_left: 0.0,
bottom_left: 0.0,
bottom_right: 2.0,
top_right: 2.0,
}))
.outline((0.0, Color::CLEAR), ControlState::Default)
} else {
ctx.style().btn_outline.dropdown().label_text(label)
};
builder.build(ctx, tooltip)
}