use std::marker::PhantomData;
use winit::keyboard::{Key, NamedKey};
use crate::debugger::SourceLocation;
use crate::style::{AlignItems, FlexDirection, JustifyContent, Size, Style};
use crate::ui::event::EventResult;
use crate::ui::{Event, View};
use crate::{AccessibleInfo, Context, Node, rgb};
pub struct Slider<Msg, F = fn(f32) -> Msg> {
pub(crate) value: f32,
pub(crate) min: f32,
pub(crate) max: f32,
pub(crate) step: Option<f32>,
pub(crate) on_change: Option<F>,
pub(crate) disabled: bool,
pub(crate) width: Option<Size>,
pub(crate) source_loc: Option<SourceLocation>,
_marker: PhantomData<Msg>,
}
#[track_caller]
pub fn slider<Msg>(value: f32, min: f32, max: f32) -> Slider<Msg, fn(f32) -> Msg> {
Slider {
value,
min,
max,
step: None,
on_change: None,
disabled: false,
width: None,
source_loc: Some(SourceLocation::here("Slider")),
_marker: PhantomData,
}
}
impl<Msg, F> Slider<Msg, F> {
pub fn step(mut self, step: f32) -> Self {
self.step = Some(step);
self
}
pub fn on_change<NewF: Fn(f32) -> Msg>(self, on_change: NewF) -> Slider<Msg, NewF> {
Slider {
value: self.value,
min: self.min,
max: self.max,
step: self.step,
on_change: Some(on_change),
disabled: self.disabled,
width: self.width,
source_loc: self.source_loc,
_marker: PhantomData,
}
}
pub fn width(mut self, width: Size) -> Self {
self.width = Some(width);
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
fn calculate_pct(&self) -> f32 {
if self.max <= self.min {
0.0
} else {
((self.value - self.min) / (self.max - self.min)).clamp(0.0, 1.0)
}
}
}
pub struct SliderElement {
container_node: Node,
track_node: Node,
fill_node: Node,
is_dragging: bool,
}
impl<State, Msg, F> View<State> for Slider<Msg, F>
where
F: Fn(f32) -> Msg,
{
type Element = SliderElement;
type Message = Msg;
fn build(&self, ctx: &mut Context) -> Self::Element {
let container_node = ctx.create_node();
if let Some(loc) = self.source_loc {
ctx.set_node_source(container_node, loc);
}
let track_node = ctx.create_node();
let fill_node = ctx.create_node();
let pct = self.calculate_pct();
Style::new()
.width(self.width.unwrap_or(Size::Fixed(160)))
.height(Size::Fixed(24))
.justify_content(JustifyContent::Center)
.align_items(AlignItems::Center)
.apply_to_node(ctx, container_node);
Style::new()
.flex_direction(FlexDirection::Row)
.width(Size::Percent(1.0))
.height(Size::Fixed(6))
.corner_radius(3.0)
.bg_color(if self.disabled {
rgb!(226, 232, 240)
} else {
rgb!(226, 232, 240)
})
.apply_to_node(ctx, track_node);
Style::new()
.width(Size::Percent(pct))
.height(Size::Fixed(6))
.corner_radius(3.0)
.bg_color(if self.disabled {
rgb!(148, 163, 184)
} else {
rgb!(59, 130, 246)
})
.apply_to_node(ctx, fill_node);
track_node.append(ctx, fill_node);
container_node.append(ctx, track_node);
if !self.disabled {
ctx.register_focusable(container_node);
}
ctx.set_accessible(
container_node,
AccessibleInfo::new(accesskit::Role::Slider)
.with_numeric_range(
self.value as f64,
self.min as f64,
self.max as f64,
self.step.map(|s| s as f64),
)
.with_disabled(self.disabled)
.with_action(accesskit::Action::SetValue)
.with_action(accesskit::Action::Increment)
.with_action(accesskit::Action::Decrement)
.with_action(accesskit::Action::Focus),
);
SliderElement {
container_node,
track_node,
fill_node,
is_dragging: false,
}
}
fn rebuild(&self, prev: &Self, ctx: &mut Context, element: &mut Self::Element) {
if (self.value - prev.value).abs() > 0.0001
|| self.min != prev.min
|| self.max != prev.max
|| self.disabled != prev.disabled
{
let pct = self.calculate_pct();
element.fill_node.update_constraints(ctx, |c| {
c.width = Size::Percent(pct);
});
ctx.set_accessible(
element.container_node,
AccessibleInfo::new(accesskit::Role::Slider)
.with_numeric_range(
self.value as f64,
self.min as f64,
self.max as f64,
self.step.map(|s| s as f64),
)
.with_disabled(self.disabled)
.with_action(accesskit::Action::SetValue)
.with_action(accesskit::Action::Increment)
.with_action(accesskit::Action::Decrement)
.with_action(accesskit::Action::Focus),
);
}
}
fn teardown(&self, ctx: &mut Context, element: &mut Self::Element) {
ctx.remove_accessible(element.container_node);
ctx.unregister_focusable(element.container_node);
element.fill_node.remove(ctx);
ctx.destroy_node(element.fill_node);
element.track_node.remove(ctx);
ctx.destroy_node(element.track_node);
element.container_node.remove(ctx);
ctx.destroy_node(element.container_node);
}
fn get_node(&self, element: &Self::Element) -> Node {
element.container_node
}
fn handle_event(
&self,
element: &mut Self::Element,
_state: &State,
event: Event,
ctx: &mut Context,
) -> (EventResult, Option<Self::Message>) {
if self.disabled {
return (EventResult::Ignored, None);
}
match event {
Event::MouseInput {
pressed,
x,
hit_nodes,
..
} => {
let is_hit = hit_nodes.contains(&element.container_node)
|| hit_nodes.contains(&element.track_node)
|| hit_nodes.contains(&element.fill_node);
if is_hit && pressed {
element.is_dragging = true;
ctx.request_focus(element.container_node);
if let Some(computed) = element.container_node.get_computed(ctx) {
let rel_x = (x - computed.x).clamp(0.0, computed.w);
let pct = if computed.w > 0.0 {
rel_x / computed.w
} else {
0.0
};
let mut val = self.min + pct * (self.max - self.min);
if let Some(step) = self.step {
if step > 0.0 {
val = (val / step).round() * step;
}
}
val = val.clamp(self.min, self.max);
let msg = self.on_change.as_ref().map(|f| f(val));
return (EventResult::Handled, msg);
}
(EventResult::Handled, None)
} else if !pressed && element.is_dragging {
element.is_dragging = false;
(EventResult::Handled, None)
} else {
(EventResult::Ignored, None)
}
}
Event::CursorMoved { x, .. } => {
if element.is_dragging {
if let Some(computed) = element.container_node.get_computed(ctx) {
let rel_x = (x - computed.x).clamp(0.0, computed.w);
let pct = if computed.w > 0.0 {
rel_x / computed.w
} else {
0.0
};
let mut val = self.min + pct * (self.max - self.min);
if let Some(step) = self.step {
if step > 0.0 {
val = (val / step).round() * step;
}
}
val = val.clamp(self.min, self.max);
let msg = self.on_change.as_ref().map(|f| f(val));
return (EventResult::Handled, msg);
}
}
(EventResult::Ignored, None)
}
Event::KeyboardInput { event: k_event, .. } => {
if Some(element.container_node) == ctx.focused_node() && k_event.state.is_pressed()
{
let delta = self.step.unwrap_or((self.max - self.min) * 0.05);
match k_event.logical_key {
Key::Named(NamedKey::ArrowLeft) | Key::Named(NamedKey::ArrowDown) => {
let val = (self.value - delta).clamp(self.min, self.max);
let msg = self.on_change.as_ref().map(|f| f(val));
(EventResult::Handled, msg)
}
Key::Named(NamedKey::ArrowRight) | Key::Named(NamedKey::ArrowUp) => {
let val = (self.value + delta).clamp(self.min, self.max);
let msg = self.on_change.as_ref().map(|f| f(val));
(EventResult::Handled, msg)
}
_ => (EventResult::Ignored, None),
}
} else {
(EventResult::Ignored, None)
}
}
Event::Action { node, action, data } => {
if node == element.container_node {
let step = self.step.unwrap_or((self.max - self.min) * 0.05);
match action {
accesskit::Action::SetValue => {
if let Some(accesskit::ActionData::NumericValue(val)) = data {
let clamped = (val as f32).clamp(self.min, self.max);
let msg = self.on_change.as_ref().map(|f| f(clamped));
(EventResult::Handled, msg)
} else {
(EventResult::Ignored, None)
}
}
accesskit::Action::Increment => {
let val = (self.value + step).clamp(self.min, self.max);
let msg = self.on_change.as_ref().map(|f| f(val));
(EventResult::Handled, msg)
}
accesskit::Action::Decrement => {
let val = (self.value - step).clamp(self.min, self.max);
let msg = self.on_change.as_ref().map(|f| f(val));
(EventResult::Handled, msg)
}
accesskit::Action::Focus => {
ctx.request_focus(element.container_node);
(EventResult::Handled, None)
}
_ => (EventResult::Ignored, None),
}
} else {
(EventResult::Ignored, None)
}
}
_ => (EventResult::Ignored, None),
}
}
}