use std::cell::Cell;
use std::rc::Rc;
use crate::anim::{Easing, Transition};
use crate::core::{EventCtx, Widget};
use crate::event::{Event, Key, PointerKind};
use crate::geometry::{Rect, Size};
use crate::render::{Canvas, Paint};
use crate::spec::Align;
use crate::style::Style;
use crate::text::TextEngine;
const BTN_W: i32 = 30;
fn hover_amt(cell: &Cell<Transition<f32>>, on: bool) -> f32 {
let mut tr = cell.get();
let target = if on { 1.0 } else { 0.0 };
if tr.target() != target {
tr.retarget(target, crate::theme::current().anim.fast(), Easing::EaseOut);
}
let v = tr.animate();
cell.set(tr);
v
}
pub struct Stepper {
value: Rc<Cell<f64>>,
min: f64,
max: f64,
step: f64,
decimals: usize,
hover: i8,
hover_l: Cell<Transition<f32>>,
hover_r: Cell<Transition<f32>>,
}
impl Stepper {
pub fn new(value: Rc<Cell<f64>>, min: f64, max: f64, step: f64) -> Self {
let step = if step.abs() < 1e-12 { 1.0 } else { step.abs() };
let (min, max) = if min <= max { (min, max) } else { (max, min) };
let mut decimals = 0;
let mut s = step;
while decimals < 6 && (s - s.round()).abs() > 1e-9 {
s *= 10.0;
decimals += 1;
}
Self {
value,
min,
max,
step,
decimals,
hover: -1,
hover_l: Cell::new(Transition::new(0.0)),
hover_r: Cell::new(Transition::new(0.0)),
}
}
fn display(&self) -> String {
format!("{:.*}", self.decimals, self.value.get())
}
fn zone(&self, ctx: &EventCtx, screen_x: i32) -> i8 {
let b = ctx.bounds();
if screen_x < b.x + BTN_W {
0
} else if screen_x >= b.right() - BTN_W {
1
} else {
-1
}
}
fn adjust(&self, ctx: &mut EventCtx, steps: f64) {
let v = (self.value.get() + steps * self.step).clamp(self.min, self.max);
self.value.set(v);
ctx.mark_dirty();
}
}
impl Widget for Stepper {
fn measure(&self, _avail: Size, style: &Style, _text: &mut dyn TextEngine) -> Size {
Size::new(120, (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, st) = (&th.palette, &th.stepper);
let (x, y, w, h) = (bounds.x as f32, bounds.y as f32, bounds.w as f32, bounds.h as f32);
let corner = th.metrics.corner_md;
let bg = if enabled { st.bg(pal) } else { pal.surface_alt };
let value_color = if enabled { st.text(pal) } else { pal.text_disabled };
canvas.fill_round_rect(x, y, w, h, corner, &Paint::fill(bg));
let border = if focused { pal.accent } else { st.border(pal) };
canvas.stroke_round_rect(x, y, w, h, corner, 1.5, &Paint::fill(border));
let (amt_l, amt_r) = (hover_amt(&self.hover_l, enabled && self.hover == 0), hover_amt(&self.hover_r, enabled && self.hover == 1));
if amt_l > 0.0 {
canvas.fill_round_rect(x + 1.0, y + 1.0, BTN_W as f32 - 2.0, h - 2.0, corner, &Paint::fill(st.button_hover(pal).scale_alpha(amt_l)));
}
if amt_r > 0.0 {
canvas.fill_round_rect(x + w - BTN_W as f32 + 1.0, y + 1.0, BTN_W as f32 - 2.0, h - 2.0, corner, &Paint::fill(st.button_hover(pal).scale_alpha(amt_r)));
}
let family = style.font_family.as_deref();
let fsize = style.font_size;
let div = Paint::fill(st.border(pal));
canvas.draw_line((bounds.x + BTN_W) as f32, y + 4.0, (bounds.x + BTN_W) as f32, y + h - 4.0, 1.0, &div);
canvas.draw_line((bounds.right() - BTN_W) as f32, y + 4.0, (bounds.right() - BTN_W) as f32, y + h - 4.0, 1.0, &div);
let at_min = self.value.get() <= self.min;
let at_max = self.value.get() >= self.max;
let minus_c = if !enabled || at_min { pal.text_disabled } else { st.button(pal) };
let plus_c = if !enabled || at_max { pal.text_disabled } else { st.button(pal) };
let minus_r = Rect::new(bounds.x, bounds.y, BTN_W, bounds.h);
let plus_r = Rect::new(bounds.right() - BTN_W, bounds.y, BTN_W, bounds.h);
canvas.draw_text("\u{2212}", minus_r, minus_c, Align::Center, family, fsize);
canvas.draw_text("+", plus_r, plus_c, Align::Center, family, fsize);
let mid = Rect::new(bounds.x + BTN_W, bounds.y, (bounds.w - 2 * BTN_W).max(0), bounds.h);
canvas.draw_text(&self.display(), mid, value_color, Align::Center, family, fsize);
}
fn on_event(&mut self, ctx: &mut EventCtx, ev: &Event) -> bool {
match ev {
Event::Pointer(p) => match p.kind {
PointerKind::Enter | PointerKind::Move => {
let z = self.zone(ctx, p.pos.x);
if self.hover != z {
self.hover = z;
ctx.mark_dirty();
}
true
}
PointerKind::Leave => {
if self.hover != -1 {
self.hover = -1;
ctx.mark_dirty();
}
true
}
PointerKind::Down => {
ctx.request_focus();
match self.zone(ctx, p.pos.x) {
0 => self.adjust(ctx, -1.0),
1 => self.adjust(ctx, 1.0),
_ => {}
}
true
}
_ => false,
},
Event::Key(k) if k.pressed => match k.key {
Key::Down | Key::Left => {
self.adjust(ctx, -1.0);
true
}
Key::Up | Key::Right => {
self.adjust(ctx, 1.0);
true
}
_ => false,
},
_ => false,
}
}
fn focusable(&self) -> bool {
true
}
}