use std::rc::Rc;
use crate::event::Event;
use crate::event::EventContext;
use crate::geometry::Color;
use crate::layout::style::FlexStyle;
use crate::layout::types::FlexAlign;
use crate::state::State;
use crate::theme::current;
use crate::view::node::{Listener, ViewNode};
use crate::view::paint::PaintStyle;
use crate::widget::{BuildContext, Widget};
#[derive(Clone)]
pub struct Slider {
value: State<f32>,
track_height: f32,
fill_color: Color,
handle_color: Color,
track_color: Color,
on_change: Option<Rc<dyn Fn(f32)>>,
}
impl Slider {
pub fn new(value: State<f32>) -> Self {
Self {
value,
track_height: 6.0,
fill_color: current().background.brand_default,
handle_color: Color::WHITE,
track_color: current().background.secondary_default,
on_change: None,
}
}
pub fn track_height(mut self, h: f32) -> Self {
self.track_height = h;
self
}
pub fn fill_color(mut self, c: Color) -> Self {
self.fill_color = c;
self
}
pub fn handle_color(mut self, c: Color) -> Self {
self.handle_color = c;
self
}
pub fn track_color(mut self, c: Color) -> Self {
self.track_color = c;
self
}
pub fn on_change<F: Fn(f32) + 'static>(mut self, f: F) -> Self {
self.on_change = Some(Rc::new(f));
self
}
}
impl Widget for Slider {
fn build(&self, ctx: &mut BuildContext) -> ViewNode {
let v = (*self.value.get()).clamp(0.0, 1.0);
let h = self.track_height;
let handle = h + 8.0;
let r = h / 2.0;
let dragging = ctx.use_state::<bool>(|| false);
let value = self.value.clone();
let dragging_c = dragging.clone();
let dragging_m = dragging.clone();
let value_m = self.value.clone();
let on_change = self.on_change.clone();
let on_change_down = on_change.clone();
let on_change_move = on_change;
let on_down = Rc::new(move |ctx: &mut EventContext| {
if let Some(Event::MouseDown { x, .. }) = ctx.event() {
let rect = match ctx.current_rect() {
Some(r) => r,
None => return,
};
let w = rect.width;
if w <= 0.0 {
return;
}
let ratio = ((x - rect.x) / w).clamp(0.0, 1.0);
value.set(ratio);
if let Some(cb) = &on_change_down {
cb(ratio);
}
dragging_c.set(true);
ctx.capture_mouse();
}
});
let on_move = Rc::new(move |ctx: &mut EventContext| {
if !*dragging_m.get() {
return;
}
if let Some(Event::MouseMove { x, .. }) = ctx.event() {
let rect = match ctx.current_rect() {
Some(r) => r,
None => return,
};
let w = rect.width;
if w <= 0.0 {
return;
}
let ratio = ((x - rect.x) / w).clamp(0.0, 1.0);
value_m.set(ratio);
if let Some(cb) = &on_change_move {
cb(ratio);
}
}
});
let on_up = Rc::new(move |_ctx: &mut EventContext| {
dragging.set(false);
});
let listeners = vec![
Listener::on_mouse_down(on_down).builtin(),
Listener::on_mouse_move(on_move).builtin(),
Listener::on_mouse_up(on_up).builtin(),
];
let track = FlexStyle::row()
.align_self(FlexAlign::Stretch)
.align_items(FlexAlign::Center)
.height(h);
let fill = FlexStyle::default().flex_grow(v).height(h);
let spacer = FlexStyle::default()
.flex_grow((1.0_f32 - v).max(0.0_f32))
.height(h);
let handle_style = FlexStyle::default().width(handle).height(handle);
ViewNode::Div {
layout: track,
paint: PaintStyle::new().background(self.track_color).radius(r),
children: vec![
ViewNode::Div {
layout: fill,
paint: PaintStyle::new().background(self.fill_color),
children: vec![],
listeners: vec![],
key: None,
},
ViewNode::Div {
layout: handle_style,
paint: PaintStyle::new()
.background(self.handle_color)
.radius(handle / 2.0)
.border(2.0, current().background.brand_default),
children: vec![],
listeners: vec![],
key: None,
},
ViewNode::Div {
layout: spacer,
paint: PaintStyle::new(),
children: vec![],
listeners: vec![],
key: None,
},
],
listeners,
key: None,
}
}
}