use std::f64::EPSILON;
use std::time::Duration;
use crate::kurbo::BezPath;
use crate::piet::{LinearGradient, RenderContext, UnitPoint};
use crate::widget::prelude::*;
use crate::{theme, Point, Rect, TimerToken};
const STEPPER_REPEAT_DELAY: Duration = Duration::from_millis(500);
const STEPPER_REPEAT: Duration = Duration::from_millis(200);
pub struct Stepper {
max: f64,
min: f64,
step: f64,
wrap: bool,
increase_active: bool,
decrease_active: bool,
timer_id: TimerToken,
}
impl Stepper {
pub fn new() -> Self {
Stepper {
max: std::f64::MAX,
min: std::f64::MIN,
step: 1.0,
wrap: false,
increase_active: false,
decrease_active: false,
timer_id: TimerToken::INVALID,
}
}
pub fn with_range(mut self, min: f64, max: f64) -> Self {
self.min = min;
self.max = max;
self
}
pub fn with_step(mut self, step: f64) -> Self {
self.step = step;
self
}
pub fn with_wraparound(mut self, wrap: bool) -> Self {
self.wrap = wrap;
self
}
fn increment(&mut self, data: &mut f64) {
let next = *data + self.step;
let was_greater = *data + EPSILON >= self.max;
let is_greater = next + EPSILON > self.max;
*data = match (self.wrap, was_greater, is_greater) {
(true, true, true) => self.min,
(true, false, true) => self.max,
(false, _, true) => self.max,
_ => next,
}
}
fn decrement(&mut self, data: &mut f64) {
let next = *data - self.step;
let was_less = *data - EPSILON <= self.min;
let is_less = next - EPSILON < self.min;
*data = match (self.wrap, was_less, is_less) {
(true, true, true) => self.max,
(true, false, true) => self.min,
(false, _, true) => self.min,
_ => next,
}
}
}
impl Default for Stepper {
fn default() -> Self {
Self::new()
}
}
impl Widget<f64> for Stepper {
fn paint(&mut self, ctx: &mut PaintCtx, _data: &f64, env: &Env) {
let stroke_width = 2.0;
let rounded_rect = ctx
.size()
.to_rect()
.inset(-stroke_width / 2.0)
.to_rounded_rect(4.0);
let height = ctx.size().height;
let width = env.get(theme::BASIC_WIDGET_HEIGHT);
let button_size = Size::new(width, height / 2.);
ctx.stroke(rounded_rect, &env.get(theme::BORDER_DARK), stroke_width);
ctx.clip(rounded_rect);
let increase_button_origin = Point::ORIGIN;
let decrease_button_origin = Point::new(0., height / 2.0);
let increase_button_rect = Rect::from_origin_size(increase_button_origin, button_size);
let decrease_button_rect = Rect::from_origin_size(decrease_button_origin, button_size);
let active_gradient = LinearGradient::new(
UnitPoint::TOP,
UnitPoint::BOTTOM,
(env.get(theme::PRIMARY_LIGHT), env.get(theme::PRIMARY_DARK)),
);
let inactive_gradient = LinearGradient::new(
UnitPoint::TOP,
UnitPoint::BOTTOM,
(env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)),
);
if self.increase_active {
ctx.fill(increase_button_rect, &active_gradient);
} else {
ctx.fill(increase_button_rect, &inactive_gradient);
};
if self.decrease_active {
ctx.fill(decrease_button_rect, &active_gradient);
} else {
ctx.fill(decrease_button_rect, &inactive_gradient);
};
let mut arrows = BezPath::new();
arrows.move_to(Point::new(4., height / 2. - 4.));
arrows.line_to(Point::new(width - 4., height / 2. - 4.));
arrows.line_to(Point::new(width / 2., 4.));
arrows.close_path();
arrows.move_to(Point::new(4., height / 2. + 4.));
arrows.line_to(Point::new(width - 4., height / 2. + 4.));
arrows.line_to(Point::new(width / 2., height - 4.));
arrows.close_path();
ctx.fill(arrows, &env.get(theme::LABEL_COLOR));
}
fn layout(
&mut self,
_layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &f64,
env: &Env,
) -> Size {
bc.constrain(Size::new(
env.get(theme::BASIC_WIDGET_HEIGHT),
env.get(theme::BORDERED_WIDGET_HEIGHT),
))
}
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut f64, env: &Env) {
let height = env.get(theme::BORDERED_WIDGET_HEIGHT);
match event {
Event::MouseDown(mouse) => {
ctx.set_active(true);
if mouse.pos.y > height / 2. {
self.decrease_active = true;
self.decrement(data);
} else {
self.increase_active = true;
self.increment(data);
}
self.timer_id = ctx.request_timer(STEPPER_REPEAT_DELAY);
ctx.request_paint();
}
Event::MouseUp(_) => {
ctx.set_active(false);
self.decrease_active = false;
self.increase_active = false;
self.timer_id = TimerToken::INVALID;
ctx.request_paint();
}
Event::Timer(id) if *id == self.timer_id => {
if self.increase_active {
self.increment(data);
}
if self.decrease_active {
self.decrement(data);
}
self.timer_id = ctx.request_timer(STEPPER_REPEAT);
}
_ => (),
}
}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &f64, _env: &Env) {}
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &f64, data: &f64, _env: &Env) {
if (*data - old_data).abs() > EPSILON {
ctx.request_paint();
}
}
}