use crate::event::{Event, EventCtx, Key, MouseButton, NamedKey};
use crate::geometry::Rect;
use crate::painter::Painter;
use crate::theme::Theme;
use crate::widget::Widget;
type ChangeHandler = Box<dyn FnMut(&mut EventCtx, i32)>;
const THUMB_W: i32 = 10;
const GROOVE_H: i32 = 4;
pub struct Slider {
rect: Rect,
min: i32,
max: i32,
value: i32,
step: i32,
focused: bool,
dragging: bool,
enabled: bool,
on_change: Option<ChangeHandler>,
}
impl Slider {
pub fn new(rect: Rect, min: i32, max: i32) -> Self {
let (min, max) = if min <= max { (min, max) } else { (max, min) };
Self {
rect,
min,
max,
value: min,
step: 1,
focused: false,
dragging: false,
enabled: true,
on_change: None,
}
}
pub fn with_value(mut self, value: i32) -> Self {
self.set_value(value);
self
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.set_enabled(enabled);
self
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
if !enabled {
self.dragging = false;
}
}
pub fn with_step(mut self, step: i32) -> Self {
self.step = step.max(1);
self
}
pub fn on_change<F>(mut self, handler: F) -> Self
where
F: FnMut(&mut EventCtx, i32) + 'static,
{
self.on_change = Some(Box::new(handler));
self
}
pub fn set_on_change<F>(&mut self, handler: F)
where
F: FnMut(&mut EventCtx, i32) + 'static,
{
self.on_change = Some(Box::new(handler));
}
pub fn value(&self) -> i32 {
self.value
}
pub fn set_value(&mut self, value: i32) {
self.value = value.clamp(self.min, self.max);
}
pub fn min(&self) -> i32 {
self.min
}
pub fn max(&self) -> i32 {
self.max
}
pub fn set_range(&mut self, min: i32, max: i32) {
let (min, max) = if min <= max { (min, max) } else { (max, min) };
self.min = min;
self.max = max;
self.value = self.value.clamp(min, max);
}
pub fn rect(&self) -> Rect {
self.rect
}
pub fn set_rect(&mut self, rect: Rect) {
self.rect = rect;
}
fn travel(&self) -> i32 {
(self.rect.w - THUMB_W).max(0)
}
fn value_span(&self) -> i32 {
(self.max - self.min).max(0)
}
fn thumb_rect(&self) -> Rect {
let travel = self.travel();
let span = self.value_span();
let off = if span == 0 {
0
} else {
((travel as i64 * (self.value - self.min) as i64) / span as i64) as i32
};
Rect::new(self.rect.x + off, self.rect.y, THUMB_W, self.rect.h)
}
fn value_at_x(&self, x: i32) -> i32 {
let travel = self.travel();
if travel <= 0 {
return self.min;
}
let left = (x - self.rect.x - THUMB_W / 2).clamp(0, travel);
let span = self.value_span();
self.min + (((left as i64 * span as i64) + travel as i64 / 2) / travel as i64) as i32
}
fn set_value_notify(&mut self, value: i32, ctx: &mut EventCtx) {
let nv = value.clamp(self.min, self.max);
if nv != self.value {
self.value = nv;
if let Some(handler) = self.on_change.as_mut() {
handler(ctx, nv);
}
}
ctx.request_paint();
}
}
impl Widget for Slider {
fn bounds(&self) -> Rect {
self.rect
}
fn paint(&mut self, painter: &mut Painter, theme: &Theme) {
let gy = self.rect.y + (self.rect.h - GROOVE_H) / 2;
let gx = self.rect.x + THUMB_W / 2;
let gw = (self.rect.w - THUMB_W).max(0);
let groove = Rect::new(gx, gy, gw, GROOVE_H);
let thumb = self.thumb_rect();
let focus = thumb.inset(3);
painter.fill_rect(groove, theme.face);
painter.sunken_bevel(groove, theme.highlight, theme.shadow);
painter.button(thumb, theme, false, false);
if self.focused && self.enabled {
painter.focus_rect(focus, theme.text);
}
}
fn event(&mut self, event: &Event, ctx: &mut EventCtx) {
if !self.enabled {
return;
}
match event {
Event::PointerDown {
pos,
button: MouseButton::Left,
..
} if self.rect.contains(*pos) => {
ctx.request_focus();
self.dragging = true;
let v = self.value_at_x(pos.x);
self.set_value_notify(v, ctx);
}
Event::PointerMove { pos } if self.dragging => {
let v = self.value_at_x(pos.x);
self.set_value_notify(v, ctx);
}
Event::PointerUp {
button: MouseButton::Left,
..
} if self.dragging => {
self.dragging = false;
ctx.request_paint();
}
Event::PointerLeave if self.dragging => {
self.dragging = false;
ctx.request_paint();
}
Event::KeyDown { key, modifiers } if self.focused && !modifiers.has_command() => {
let page = (self.value_span() / 10).max(self.step);
let target = match key {
Key::Named(NamedKey::Left) | Key::Named(NamedKey::Down) => {
Some(self.value - self.step)
}
Key::Named(NamedKey::Right) | Key::Named(NamedKey::Up) => {
Some(self.value + self.step)
}
Key::Named(NamedKey::PageDown) => Some(self.value - page),
Key::Named(NamedKey::PageUp) => Some(self.value + page),
Key::Named(NamedKey::Home) => Some(self.min),
Key::Named(NamedKey::End) => Some(self.max),
_ => None,
};
if let Some(target) = target {
self.set_value_notify(target, ctx);
ctx.consume_event();
}
}
_ => {}
}
}
fn captures_pointer(&self) -> bool {
self.dragging
}
fn focusable(&self) -> bool {
self.enabled
}
fn set_focused(&mut self, focused: bool) {
self.focused = focused;
}
fn layout(&mut self, bounds: Rect) {
self.rect = bounds;
}
}