From 33d161500f7ef09665b8fec5e97c7c6e85bcea51 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 21 Mar 2024 18:41:28 +0100 Subject: [PATCH] slider and some input stuff --- hui-examples/examples/ui_test_6.rs | 11 ++++---- hui/src/element/builtin/slider.rs | 42 ++++++++++++++++++++++-------- hui/src/input.rs | 21 ++++++++------- hui/src/instance.rs | 2 ++ hui/src/rectangle.rs | 18 +++++++++++++ hui/src/signal.rs | 22 ++++++++++++++++ 6 files changed, 91 insertions(+), 25 deletions(-) diff --git a/hui-examples/examples/ui_test_6.rs b/hui-examples/examples/ui_test_6.rs index ac2c6a4..ad51855 100644 --- a/hui-examples/examples/ui_test_6.rs +++ b/hui-examples/examples/ui_test_6.rs @@ -15,8 +15,7 @@ use hui::{ }; enum CounterSignal { - Increment, - Decrement, + ChangeValue(f32) } impl UiSignal for CounterSignal {} @@ -46,8 +45,9 @@ ui_main!( .with_text_size(24) .add_child(ui); Br.add_child(ui); - Slider::new(0.5) + Slider::new(*counter as f32 / 100.) .with_size(size!(66%, 20)) + .on_change(CounterSignal::ChangeValue) .add_child(ui); Br.add_child(ui); for _ in 0..*counter { @@ -59,8 +59,9 @@ ui_main!( .add_root(ui, size); ui.process_signals(|sig| match sig { - CounterSignal::Increment => *counter += 1, - CounterSignal::Decrement => *counter -= 1, + CounterSignal::ChangeValue(v) => { + *counter = (v * 100.).round() as usize; + } }); } ); diff --git a/hui/src/element/builtin/slider.rs b/hui/src/element/builtin/slider.rs index 8b0b6fd..1142dba 100644 --- a/hui/src/element/builtin/slider.rs +++ b/hui/src/element/builtin/slider.rs @@ -1,22 +1,26 @@ //! work in progress use derive_setters::Setters; +use glam::{vec2, Vec2}; use crate::{ - draw::UiDrawCommand, + draw::{RoundedCorners, UiDrawCommand}, element::{MeasureContext, ProcessContext, UiElement}, layout::{compute_size, Size2d}, measure::Response, rectangle::Corners, - signal::UiSignal, + signal::{SignalStore, UiSignal}, }; /// work in progress -#[derive(Default, Debug, Clone, Copy, Setters)] +#[derive(Default, Setters)] #[setters(prefix = "with_")] pub struct Slider { pub value: f32, pub size: Size2d, + + #[setters(skip)] + fire_on_shit: Option>, } impl Slider { @@ -28,6 +32,15 @@ impl Slider { ..Default::default() } } + + pub fn on_change S + 'static>(self, f: T) -> Self { + Self { + fire_on_shit: Some(Box::new(move |s: &mut SignalStore, x| { + s.add::(f(x)); + })), + ..self + } + } } impl UiElement for Slider { @@ -43,26 +56,33 @@ impl UiElement for Slider { } fn process(&self, ctx: ProcessContext) { + let bgrect_height_ratio = 0.25; ctx.draw.add(UiDrawCommand::Rectangle { - position: ctx.layout.position, - size: ctx.measure.size, - color: Corners::all((1., 0., 0., 1.).into()), + 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: Corners::all((1., 1., 1., 0.7).into()), texture: None, rounded_corners: None, + //Some(RoundedCorners::from_radius(Corners::all(bgrect_height_ratio * ctx.measure.size.y * 0.4))), }); 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, - size: (ctx.measure.size.x * value, ctx.measure.size.y).into(), - color: Corners::all((0., 1., 0., 1.).into()), + position: ctx.layout.position + (ctx.measure.size.x * value - handle_size.x / 2.) * Vec2::X, + size: handle_size, + color: Corners::all((1., 1., 1., 1.).into()), texture: None, rounded_corners: None, + //Some(RoundedCorners::from_radius(Corners::all(handle_size.x / 3.))), }); //handle click etc - if let Some(res) = ctx.input.check_click(ctx.measure.rect(ctx.layout.position)) { - let new_value = res.position_in_rect.x / ctx.measure.size.x; + if let Some(res) = ctx.input.check_active(ctx.measure.rect(ctx.layout.position)) { + let new_value = (res.position_in_rect.x / ctx.measure.size.x).clamp(0., 1.); + if let Some(fire) = &self.fire_on_shit { + fire(ctx.signal, new_value); + } //TODO call signal with new value } } diff --git a/hui/src/input.rs b/hui/src/input.rs index c832fad..70c8599 100644 --- a/hui/src/input.rs +++ b/hui/src/input.rs @@ -286,15 +286,7 @@ impl<'a> InputCtx<'a> { rect.contains_point(self.0.mouse_pointer.current_position) } - /// Check if a rect can be considered "active" (i.e. held down) - /// - /// WIP: Not implemented yet, always returns `false` - pub fn check_active(&self, _rect: Rect) -> bool { - //TODO `check_active` - false - } - - /// Check if a rect can be considered "clicked" + /// Check if a rect can be considered "clicked" in the current frame /// /// This can be triggered by multiple input sources, such as mouse, touch, etc.\ /// In case of a mouse, these conditions must be met: @@ -313,4 +305,15 @@ impl<'a> InputCtx<'a> { position_in_rect: pos - rect.position, }) } + + // TODO: write better docs + + /// Check if a rect is being actively being interacted with (e.g. dragged) + pub fn check_active(&self, rect: Rect) -> Option { + self.0.mouse_pointer.buttons.get(&MouseButton::Primary).filter(|mi| { + rect.contains_point(mi.start_position) + }).map(|_| ClickCheckResponse { + position_in_rect: self.0.mouse_pointer.current_position - rect.position, + }) + } } diff --git a/hui/src/instance.rs b/hui/src/instance.rs index c70f723..0bbed97 100644 --- a/hui/src/instance.rs +++ b/hui/src/instance.rs @@ -248,6 +248,8 @@ impl UiInstance { self.signal.add(signal); } + //TODO: offer a non-consuming version of this function for T: Clone + /// Process all signals of a given type /// /// This clears the signal queue for the given type and iterates over all signals diff --git a/hui/src/rectangle.rs b/hui/src/rectangle.rs index 3b22884..40ed4d2 100644 --- a/hui/src/rectangle.rs +++ b/hui/src/rectangle.rs @@ -2,18 +2,23 @@ use glam::Vec2; +/// Represents a rectangle/AABB with specified position and size #[derive(Clone, Copy, Debug, PartialEq, Default)] pub struct Rect { + /// Position of the top-left corner of the rect. pub position: Vec2, + /// Size of the rect, should not be negative. pub size: Vec2, } impl Rect { + /// Check if the rect contains a point. pub fn contains_point(&self, point: Vec2) -> bool { point.cmpge(self.position).all() && point.cmple(self.position + self.size).all() } //TODO: return intersect rect + /// Check if the rect intersects with another rect. pub fn intersects_rect(&self, other: Rect) -> bool { self.position.x < other.position.x + other.size.x && self.position.x + self.size.x > other.position.x @@ -21,22 +26,35 @@ impl Rect { && self.position.y + self.size.y > other.position.y } + /// Get width of the rectangle. + /// + /// To get both width and height, use the `size` property instead. pub fn width(&self) -> f32 { self.size.x } + /// Get height of the rectangle. + /// + /// To get both width and height, use the `size` property instead. pub fn height(&self) -> f32 { self.size.y } + /// Get position of the top-left corner of the rectangle on the x-axis. + /// + /// To get both x and y, use the `position` property instead. pub fn x(&self) -> f32 { self.position.x } + /// Get position of the top-left corner of the rectangle on the y-axis. + /// + /// To get both x and y, use the `position` property instead. pub fn y(&self) -> f32 { self.position.y } + /// Get positions of all 4 corners of the rectangle. pub fn corners(&self) -> Corners { Corners { top_left: self.position, diff --git a/hui/src/signal.rs b/hui/src/signal.rs index 9cff759..6a49159 100644 --- a/hui/src/signal.rs +++ b/hui/src/signal.rs @@ -56,6 +56,28 @@ impl SignalStore { //TODO this, simplifies handling signals +// pub trait Signal { +// type Arg; +// type Output; +// fn call(&self, arg: Self::Arg) -> Self::Output; +// } + +// impl T, T> Signal for F { +// type Arg = (); +// type Output = T; +// fn call(&self, _: Self::Arg) -> Self::Output { +// self() +// } +// } + +// // impl T, A, T> Signal for F { +// // type Arg = A; +// // type Output = T; +// // fn call(&self, a: Self::Arg) -> Self::Output { +// // self(a) +// // } +// // } + // pub struct SignalTrigger(pub(crate) Box R + 'static>); // impl SignalTrigger {