1
1
Fork 0
mirror of https://github.com/griffi-gh/hUI.git synced 2025-04-02 05:56:28 -05:00
hUI/hui/src/element/builtin/slider.rs

142 lines
3.9 KiB
Rust

//! a slider element that allows selecting a value in a range
use derive_setters::Setters;
use glam::{Vec2, vec2};
use crate::{
draw::UiDrawCommand,
element::{MeasureContext, ProcessContext, UiElement},
layout::{Size2d, compute_size},
measure::Response,
rect::FillColor,
signal::{trigger::SignalTriggerArg, Signal},
};
/// Follow mode for the slider
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum SliderFollowMode {
/// Slider will change based on the absolute mouse position in the slider
///
/// This is the default mode and is recommended for most use cases
#[default]
Absolute,
/// Slider will change based on the difference between the current and starting mouse position
///
/// This is an experimental option and does not currently work well for sliders with large step sizes
Relative,
}
/// A slider element that allows selecting a value in a range
#[derive(Setters)]
#[setters(prefix = "with_")]
pub struct Slider {
/// Value of the slider, should be in range 0..1
///
/// Out of range values will be clamped
pub value: f32,
/// Size of the element
#[setters(into)]
pub size: Size2d,
/// Color of the slider handle
#[setters(into)]
pub handle_color: FillColor,
/// Color of the slider track
#[setters(into)]
pub track_color: FillColor,
/// Follow mode
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_color: (0.0, 0.0, 1.0).into(),
track_color: (0.5, 0.5, 0.5).into(),
follow_mode: SliderFollowMode::default(),
on_change: None
}
}
}
impl Slider {
pub const DEFAULT_HEIGHT: f32 = 21.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
}
}
}
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) {
//TODO: unhardcode this
let bgrect_height_ratio = 0.33;
ctx.draw.add(UiDrawCommand::Rectangle {
position: ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - bgrect_height_ratio / 2.),
size: ctx.measure.size * vec2(1., bgrect_height_ratio),
color: self.track_color.into(),
texture: None,
rounded_corners: None,
});
let value = self.value.clamp(0., 1.);
let handle_size = vec2(15., ctx.measure.size.y);
ctx.draw.add(UiDrawCommand::Rectangle {
position: ctx.layout.position + ((ctx.measure.size.x - handle_size.x) * value) * Vec2::X,
size: handle_size,
color: self.handle_color.into(),
texture: None,
rounded_corners: None,
});
//handle events
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);
}
//TODO call signal with new value
}
}
}
//TODO