use std::cell::Cell;
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::signal::Signal;
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: Signal<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: Signal<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
}
}