use derive_setters::Setters;
use glam::{Vec2, vec2};
use crate::{
element::{MeasureContext, ProcessContext, UiElement},
frame::{Frame, RectFrame},
layout::{compute_size, Size2d},
measure::Response,
signal::{trigger::SignalTriggerArg, Signal},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum SliderFollowMode {
#[default]
Absolute,
Relative,
}
#[derive(Setters)]
#[setters(prefix = "with_")]
pub struct Slider {
pub value: f32,
#[setters(into)]
pub size: Size2d,
#[setters(skip)]
pub track: Box<dyn Frame>,
#[setters(skip)]
pub track_active: Box<dyn Frame>,
#[setters(skip)]
pub handle: Box<dyn Frame>,
pub track_height: f32,
pub handle_size: (f32, f32),
pub follow_mode: SliderFollowMode,
#[setters(skip)]
pub on_change: Option<SignalTriggerArg<f32>>,
}
impl Default for Slider {
fn default() -> Self {
Self {
value: 0.0,
size: Size2d::default(),
handle: Box::new(RectFrame::color((0.0, 0.0, 1.))),
track: Box::new(RectFrame::color((0.5, 0.5, 0.5))),
track_active: Box::new(RectFrame::color((0.0, 0.0, 0.75))),
track_height: 0.25,
handle_size: (15.0, 1.),
follow_mode: SliderFollowMode::default(),
on_change: None
}
}
}
impl Slider {
pub const DEFAULT_HEIGHT: f32 = 20.0;
pub fn new(value: f32) -> Self {
Self {
value,
..Default::default()
}
}
pub fn on_change<S: Signal, T: Fn(f32) -> S + 'static>(self, f: T) -> Self {
Self {
on_change: Some(SignalTriggerArg::new(f)),
..self
}
}
pub fn with_track(mut self, track: impl Frame + 'static) -> Self {
self.track = Box::new(track);
self
}
pub fn with_track_active(mut self, track_active: impl Frame + 'static) -> Self {
self.track_active = Box::new(track_active);
self
}
pub fn with_handle(mut self, handle: impl Frame + 'static) -> Self {
self.handle = Box::new(handle);
self
}
}
impl UiElement for Slider {
fn name(&self) -> &'static str {
"slider"
}
fn measure(&self, ctx: MeasureContext) -> Response {
Response {
size: compute_size(ctx.layout, self.size, (ctx.layout.max_size.x, Self::DEFAULT_HEIGHT).into()),
..Default::default()
}
}
fn process(&self, ctx: ProcessContext) {
let handle_size = vec2(self.handle_size.0, self.handle_size.1 * ctx.measure.size.y);
if !(self.track_active.covers_opaque() && self.handle.covers_opaque() && (self.handle_size.1 >= self.track_height) && self.value >= 1.) {
self.track.draw(
ctx.draw,
(
ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - self.track_height / 2.),
ctx.measure.size * vec2(1., self.track_height),
).into()
);
}
if !(self.handle.covers_opaque() && (self.handle_size.1 >= self.track_height) && self.value <= 0.) {
self.track_active.draw(
ctx.draw,
(
ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - self.track_height / 2.),
(ctx.measure.size - handle_size * Vec2::X) * vec2(self.value, self.track_height) + handle_size * Vec2::X / 2.,
).into()
);
}
if (self.handle_size.0 > 0. && self.handle_size.1 > 0.) {
self.handle.draw(
ctx.draw,
(
ctx.layout.position +
((ctx.measure.size.x - handle_size.x) * self.value) * Vec2::X +
ctx.measure.size.y * ((1. - self.handle_size.1) * 0.5) * Vec2::Y,
handle_size,
).into()
);
}
if let Some(res) = ctx.input.check_active(ctx.measure.rect(ctx.layout.position)) {
let new_value = match self.follow_mode {
SliderFollowMode::Absolute => {
((res.position_in_rect.x - handle_size.x / 2.) / (ctx.measure.size.x - handle_size.x)).clamp(0., 1.)
},
SliderFollowMode::Relative => {
let delta = res.position_in_rect.x - res.last_position_in_rect.x;
let delta_ratio = delta / (ctx.measure.size.x - handle_size.x);
(self.value + delta_ratio).clamp(0., 1.)
}
};
if let Some(signal) = &self.on_change {
signal.fire(ctx.signal, new_value);
}
}
}
}