use std::cell::Cell;
use std::rc::Rc;
use crate::anim::{Easing, Transition};
use crate::core::{EventCtx, Widget};
use crate::event::{Event, Key, MenuItem, PointerKind};
use crate::geometry::{Color, Point, Rect, Size};
use crate::render::{Canvas, Paint};
use crate::spec::Align;
use crate::style::Style;
use crate::text::TextEngine;
const PAD_X: i32 = 12;
const CHEVRON_W: i32 = 18;
pub struct Dropdown {
options: Vec<String>,
selected: Rc<Cell<usize>>,
hover: bool,
border_anim: Cell<Transition<Color>>,
primed: Cell<bool>,
}
impl Dropdown {
pub fn new(options: Vec<String>, selected: Rc<Cell<usize>>) -> Self {
Self {
options,
selected,
hover: false,
border_anim: Cell::new(Transition::new(Color::rgba(0, 0, 0, 0))),
primed: Cell::new(false),
}
}
fn current(&self) -> &str {
let i = self.selected.get().min(self.options.len().saturating_sub(1));
self.options.get(i).map(|s| s.as_str()).unwrap_or("")
}
fn open(&self, ctx: &mut EventCtx) {
if self.options.is_empty() {
return;
}
let b = ctx.bounds();
let cur = self.selected.get();
let items: Vec<MenuItem> = self
.options
.iter()
.enumerate()
.map(|(i, o)| {
let sel = self.selected.clone();
MenuItem::run(o.clone(), move || sel.set(i), i == cur)
})
.collect();
ctx.show_menu(Point::new(b.x, b.y + b.h), items, b.w);
}
}
impl Widget for Dropdown {
fn measure(&self, _avail: Size, style: &Style, text: &mut dyn TextEngine) -> Size {
let mut w = 0;
for o in &self.options {
w = w.max(text.measure(o, style.font_family.as_deref(), style.font_size, None).w);
}
Size::new(w + 2 * PAD_X + CHEVRON_W, (style.font_size as i32) + 16)
}
fn paint(&self, bounds: Rect, _content: Rect, focused: bool, enabled: bool, canvas: &mut dyn Canvas, style: &Style) {
let th = crate::theme::current();
let (pal, dd) = (&th.palette, &th.dropdown);
let (x, y, w, h) = (bounds.x as f32, bounds.y as f32, bounds.w as f32, bounds.h as f32);
let corner = dd.corner(&th.metrics);
let bg = if enabled { dd.bg(pal) } else { pal.surface_alt };
let text_color = if enabled { dd.text(pal) } else { pal.text_disabled };
let chevron = if enabled { dd.chevron(pal) } else { pal.text_disabled };
canvas.fill_round_rect(x, y, w, h, corner, &Paint::fill(bg));
let target_border = if focused || self.hover { dd.border_focus(pal) } else { dd.border(pal) };
let mut ba = self.border_anim.get();
if !self.primed.get() {
ba = Transition::new(target_border);
self.primed.set(true);
} else if ba.target() != target_border {
ba.retarget(target_border, th.anim.fast(), Easing::EaseOut);
}
let border = ba.animate();
self.border_anim.set(ba);
let bw = if focused { 1.8 } else { 1.5 };
canvas.stroke_round_rect(x, y, w, h, corner, bw, &Paint::fill(border));
let tr = Rect::new(bounds.x + PAD_X, bounds.y, bounds.w - 2 * PAD_X - CHEVRON_W, bounds.h);
canvas.draw_text(self.current(), tr, text_color, Align::Start, style.font_family.as_deref(), style.font_size);
let cx = bounds.x as f32 + bounds.w as f32 - PAD_X as f32 - CHEVRON_W as f32 / 2.0;
let cy = bounds.y as f32 + bounds.h as f32 / 2.0;
let p = Paint::fill(chevron);
canvas.draw_line(cx - 4.0, cy - 2.0, cx, cy + 3.0, 1.6, &p);
canvas.draw_line(cx, cy + 3.0, cx + 4.0, cy - 2.0, 1.6, &p);
}
fn on_event(&mut self, ctx: &mut EventCtx, ev: &Event) -> bool {
match ev {
Event::Pointer(p) => match p.kind {
PointerKind::Enter => {
self.hover = true;
ctx.mark_dirty();
true
}
PointerKind::Leave => {
self.hover = false;
ctx.mark_dirty();
true
}
PointerKind::Down => {
ctx.request_focus();
true
}
PointerKind::Up => {
if ctx.bounds().contains(p.pos) {
self.hover = false;
self.open(ctx);
}
true
}
_ => false,
},
Event::Key(k) if k.pressed => match k.key {
Key::Enter | Key::Space | Key::Down => {
self.open(ctx);
true
}
_ => false,
},
_ => false,
}
}
fn focusable(&self) -> bool {
true
}
}