From d526928d9b9c8e30178d0edbdead84b3d5ac3de3 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 21 Mar 2024 22:23:42 +0100 Subject: [PATCH] a bunch of signal and input changes --- hui-examples/examples/ui_test_5_input.rs | 4 +- hui-examples/examples/ui_test_6.rs | 4 +- hui/Cargo.toml | 1 + hui/src/element/builtin/interactable.rs | 20 +- hui/src/element/builtin/slider.rs | 96 ++++++--- hui/src/input.rs | 28 ++- hui/src/instance.rs | 6 +- hui/src/rectangle.rs | 203 +------------------- hui/src/rectangle/corners.rs | 88 +++++++++ hui/src/rectangle/corners/corners_colors.rs | 122 ++++++++++++ hui/src/rectangle/rect.rs | 65 +++++++ hui/src/rectangle/sides.rs | 49 +++++ hui/src/signal.rs | 11 +- hui/src/signal/trigger.rs | 61 ++++++ 14 files changed, 510 insertions(+), 248 deletions(-) create mode 100644 hui/src/rectangle/corners.rs create mode 100644 hui/src/rectangle/corners/corners_colors.rs create mode 100644 hui/src/rectangle/rect.rs create mode 100644 hui/src/rectangle/sides.rs create mode 100644 hui/src/signal/trigger.rs diff --git a/hui-examples/examples/ui_test_5_input.rs b/hui-examples/examples/ui_test_5_input.rs index 8cd696c..619012f 100644 --- a/hui-examples/examples/ui_test_5_input.rs +++ b/hui-examples/examples/ui_test_5_input.rs @@ -1,7 +1,7 @@ use hui::{ color, size, draw::TextureFormat, - signal::UiSignal, + signal::Signal, layout::{Alignment, Direction}, element::{ container::Container, @@ -17,7 +17,7 @@ enum CounterSignal { Increment, Decrement, } -impl UiSignal for CounterSignal {} +impl Signal for CounterSignal {} #[path = "../boilerplate.rs"] #[macro_use] diff --git a/hui-examples/examples/ui_test_6.rs b/hui-examples/examples/ui_test_6.rs index 5252fd9..0c81616 100644 --- a/hui-examples/examples/ui_test_6.rs +++ b/hui-examples/examples/ui_test_6.rs @@ -1,7 +1,7 @@ use hui::{ color, size, draw::TextureFormat, - signal::UiSignal, + signal::Signal, layout::{Alignment, Direction}, element::{ container::Container, @@ -17,7 +17,7 @@ use hui::{ enum CounterSignal { ChangeValue(u32) } -impl UiSignal for CounterSignal {} +impl Signal for CounterSignal {} #[path = "../boilerplate.rs"] #[macro_use] diff --git a/hui/Cargo.toml b/hui/Cargo.toml index 481c73c..62b8bbb 100644 --- a/hui/Cargo.toml +++ b/hui/Cargo.toml @@ -24,6 +24,7 @@ rect_packer = "0.2" log = "0.4" document-features = "0.2" derive_setters = "0.1" +derive_more = "0.99" tinyset = "0.4" [features] diff --git a/hui/src/element/builtin/interactable.rs b/hui/src/element/builtin/interactable.rs index d9d34ea..c2e63a3 100644 --- a/hui/src/element/builtin/interactable.rs +++ b/hui/src/element/builtin/interactable.rs @@ -5,7 +5,7 @@ use crate::{ element::{MeasureContext, ProcessContext, UiElement}, - signal::UiSignal, + signal::Signal, }; use std::cell::RefCell; @@ -18,7 +18,7 @@ pub enum InteractableEvent { } /// Wrapper that allows adding click and hover events to any element -pub struct Interactable { +pub struct Interactable { /// The wrapped element that will be interactable pub element: Box, @@ -29,7 +29,7 @@ pub struct Interactable { pub signal: RefCell>, } -impl Interactable { +impl Interactable { pub fn new(element: Box, event: InteractableEvent, signal: C) -> Self { Self { element, @@ -39,7 +39,7 @@ impl Interactable { } } -impl UiElement for Interactable { +impl UiElement for Interactable { fn name(&self) -> &'static str { "Interactable" } @@ -71,25 +71,25 @@ impl UiElement for Interactable { /// Extension trait for [`UiElement`] that adds methods to wrap the element in an [`Interactable`] pub trait ElementInteractableExt: UiElement { /// Wrap the element in an [`Interactable`] that will call the given signal when the specified event occurs - fn into_interactable(self, event: InteractableEvent, signal: C) -> Interactable; + fn into_interactable(self, event: InteractableEvent, signal: C) -> Interactable; /// Wrap the element in an [`Interactable`] that will call the given signal when clicked - fn on_click(self, signal: C) -> Interactable; + fn on_click(self, signal: C) -> Interactable; /// Wrap the element in an [`Interactable`] that will call the given signal while hovered - fn on_hover(self, signal: C) -> Interactable; + fn on_hover(self, signal: C) -> Interactable; } impl ElementInteractableExt for T { - fn into_interactable(self, event: InteractableEvent, signal: C) -> Interactable { + fn into_interactable(self, event: InteractableEvent, signal: C) -> Interactable { Interactable::new(Box::new(self), event, signal) } - fn on_click(self, signal: C) -> Interactable { + fn on_click(self, signal: C) -> Interactable { self.into_interactable(InteractableEvent::Click, signal) } - fn on_hover(self, signal: C) -> Interactable { + fn on_hover(self, signal: C) -> Interactable { self.into_interactable(InteractableEvent::Hover, signal) } } diff --git a/hui/src/element/builtin/slider.rs b/hui/src/element/builtin/slider.rs index 1142dba..fcabfe3 100644 --- a/hui/src/element/builtin/slider.rs +++ b/hui/src/element/builtin/slider.rs @@ -1,30 +1,75 @@ //! work in progress use derive_setters::Setters; -use glam::{vec2, Vec2}; +use glam::{Vec2, vec2}; use crate::{ - draw::{RoundedCorners, UiDrawCommand}, + draw::UiDrawCommand, element::{MeasureContext, ProcessContext, UiElement}, - layout::{compute_size, Size2d}, + layout::{Size2d, compute_size}, measure::Response, - rectangle::Corners, - signal::{SignalStore, UiSignal}, + rectangle::CornersColors, + signal::{trigger::SignalTriggerArg, Signal}, }; -/// work in progress -#[derive(Default, Setters)] +/// 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: CornersColors, + + /// Color of the slider track + #[setters(into)] + pub track_color: CornersColors, + + /// Follow mode + pub follow_mode: SliderFollowMode, + #[setters(skip)] - fire_on_shit: Option>, + pub on_change: Option>, +} + +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 = 20.0; + pub const DEFAULT_HEIGHT: f32 = 21.0; pub fn new(value: f32) -> Self { Self { @@ -33,11 +78,9 @@ impl Slider { } } - pub fn on_change S + 'static>(self, f: T) -> Self { + 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)); - })), + on_change: Some(SignalTriggerArg::new(f)), ..self } } @@ -56,32 +99,39 @@ impl UiElement for Slider { } fn process(&self, ctx: ProcessContext) { - let bgrect_height_ratio = 0.25; + //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: Corners::all((1., 1., 1., 0.7).into()), + color: self.track_color.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 + (ctx.measure.size.x * value - handle_size.x / 2.) * Vec2::X, + position: ctx.layout.position + ((ctx.measure.size.x - handle_size.x) * value) * Vec2::X, size: handle_size, - color: Corners::all((1., 1., 1., 1.).into()), + color: self.handle_color.into(), texture: None, rounded_corners: None, - //Some(RoundedCorners::from_radius(Corners::all(handle_size.x / 3.))), }); - //handle click etc + //handle events 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); + 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 } diff --git a/hui/src/input.rs b/hui/src/input.rs index 70c8599..37d5596 100644 --- a/hui/src/input.rs +++ b/hui/src/input.rs @@ -144,6 +144,9 @@ pub struct MouseState { /// Current position of the mouse pointer pub current_position: Vec2, + /// Position of the mouse pointer on the previous frame + pub prev_position: Vec2, + /// Current state of each mouse button (if down) pub buttons: HashMap>, @@ -184,6 +187,7 @@ impl UiInputState { /// /// This function should be called exactly once per frame pub fn update_state(&mut self, event_queue: &mut EventQueue) { + self.mouse_pointer.prev_position = self.mouse_pointer.current_position; self.mouse_pointer.released_buttons.clear(); self.just_happened.clear(); self.just_happened.extend(event_queue.drain()); @@ -234,9 +238,17 @@ impl UiInputState { } } +/// Response for checks that involve an active pointer #[derive(Clone, Copy, Debug)] -pub struct ClickCheckResponse { +pub struct ActiveCheckResponse { + /// Current position of the pointer inside the target rectangle's coordinate space pub position_in_rect: Vec2, + + /// Position of the pointer at the time the start of the input inside the target rectangle's coordinate space + pub start_position_in_rect: Vec2, + + /// Position of the pointer on the previous frame inside the target rectangle's coordinate space + pub last_position_in_rect: Vec2, } #[derive(Clone, Copy)] @@ -297,23 +309,27 @@ impl<'a> InputCtx<'a> { /// By default, this function only checks for the primary mouse button\ /// This is a limitation of the current API and may change in the future\ /// (as the current implementation of this function checks for both mouse and touch input, and the touch input quite obviously only supports one "button") - pub fn check_click(&self, rect: Rect) -> Option { + pub fn check_click(&self, rect: Rect) -> Option { let pos = self.0.mouse_pointer.current_position; - self.0.mouse_pointer.released_buttons.get(&MouseButton::Primary).map_or(false, |meta| { + self.0.mouse_pointer.released_buttons.get(&MouseButton::Primary).filter(|meta| { rect.contains_point(meta.start_position) && rect.contains_point(pos) - }).then_some(ClickCheckResponse { + }).map(|mi| ActiveCheckResponse { position_in_rect: pos - rect.position, + start_position_in_rect: mi.start_position - rect.position, + last_position_in_rect: self.0.mouse_pointer.prev_position - 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 { + 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 { + }).map(|mi| ActiveCheckResponse { position_in_rect: self.0.mouse_pointer.current_position - rect.position, + start_position_in_rect: mi.start_position - rect.position, + last_position_in_rect: self.0.mouse_pointer.prev_position - rect.position, }) } } diff --git a/hui/src/instance.rs b/hui/src/instance.rs index 0bbed97..96bf38d 100644 --- a/hui/src/instance.rs +++ b/hui/src/instance.rs @@ -8,7 +8,7 @@ use crate::{ event::{EventQueue, UiEvent}, input::UiInputState, layout::{Direction, LayoutInfo}, - signal::{SignalStore, UiSignal}, + signal::{SignalStore, Signal}, state::StateRepo, text::{FontHandle, TextRenderer} }; @@ -244,7 +244,7 @@ impl UiInstance { } /// Push a "fake" signal to the UI signal queue - pub fn push_signal(&mut self, signal: T) { + pub fn push_signal(&mut self, signal: T) { self.signal.add(signal); } @@ -253,7 +253,7 @@ impl UiInstance { /// Process all signals of a given type /// /// This clears the signal queue for the given type and iterates over all signals - pub fn process_signals(&mut self, f: impl FnMut(T)) { + pub fn process_signals(&mut self, f: impl FnMut(T)) { self.signal.drain::().for_each(f); } } diff --git a/hui/src/rectangle.rs b/hui/src/rectangle.rs index 40ed4d2..3213bba 100644 --- a/hui/src/rectangle.rs +++ b/hui/src/rectangle.rs @@ -1,201 +1,10 @@ //! Contains types which represent the sides and corners of a rectangular shape. -use glam::Vec2; +mod rect; +pub use rect::Rect; -/// 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, -} +mod sides; +pub use sides::Sides; -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 - && self.position.y < other.position.y + other.size.y - && 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, - top_right: self.position + Vec2::new(self.size.x, 0.0), - bottom_left: self.position + Vec2::new(0.0, self.size.y), - bottom_right: self.position + self.size, - } - } -} - -/// Represents 4 sides of a rectangular shape. -#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] -pub struct Sides { - pub top: T, - pub bottom: T, - pub left: T, - pub right: T, -} - -impl Sides { - #[inline] - pub fn all(value: T) -> Self { - Self { - top: value.clone(), - bottom: value.clone(), - left: value.clone(), - right: value, - } - } - - #[inline] - pub fn horizontal_vertical(horizontal: T, vertical: T) -> Self { - Self { - top: vertical.clone(), - bottom: vertical, - left: horizontal.clone(), - right: horizontal, - } - } -} - -impl From for Sides { - fn from(value: T) -> Self { - Self::all(value) - } -} - -impl From<(T, T)> for Sides { - fn from((horizontal, vertical): (T, T)) -> Self { - Self::horizontal_vertical(horizontal, vertical) - } -} - -impl From<(T, T, T, T)> for Sides { - fn from((top, bottom, left, right): (T, T, T, T)) -> Self { - Self { top, bottom, left, right } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] -pub struct Corners { - pub top_left: T, - pub top_right: T, - pub bottom_left: T, - pub bottom_right: T, -} - -impl Corners { - #[inline] - pub fn all(value: T) -> Self { - Self { - top_left: value.clone(), - top_right: value.clone(), - bottom_left: value.clone(), - bottom_right: value, - } - } - - #[inline] - pub fn top_bottom(top: T, bottom: T) -> Self { - Self { - top_left: top.clone(), - top_right: top, - bottom_left: bottom.clone(), - bottom_right: bottom, - } - } - - #[inline] - pub fn left_right(left: T, right: T) -> Self { - Self { - top_left: left.clone(), - top_right: right.clone(), - bottom_left: left, - bottom_right: right, - } - } -} - -impl Corners { - pub fn max(&self) -> T { - self.top_left.clone() - .max(self.top_right.clone()) - .max(self.bottom_left.clone()) - .max(self.bottom_right.clone()) - .clone() - } -} - -/// Represents 4 corners of a rectangular shape. -impl Corners { - pub fn max_f32(&self) -> f32 { - self.top_left - .max(self.top_right) - .max(self.bottom_left) - .max(self.bottom_right) - } -} - -impl Corners { - pub fn max_f64(&self) -> f64 { - self.top_left - .max(self.top_right) - .max(self.bottom_left) - .max(self.bottom_right) - } -} - -impl From for Corners { - fn from(value: T) -> Self { - Self::all(value) - } -} - -impl From<(T, T, T, T)> for Corners { - fn from((top_left, top_right, bottom_left, bottom_right): (T, T, T, T)) -> Self { - Self { - top_left, - top_right, - bottom_left, - bottom_right, - } - } -} +mod corners; +pub use corners::{Corners, CornersColors}; diff --git a/hui/src/rectangle/corners.rs b/hui/src/rectangle/corners.rs new file mode 100644 index 0000000..ea683f8 --- /dev/null +++ b/hui/src/rectangle/corners.rs @@ -0,0 +1,88 @@ +mod corners_colors; +pub use corners_colors::CornersColors; + +/// Represents 4 corners of a rectangular shape. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub struct Corners { + pub top_left: T, + pub top_right: T, + pub bottom_left: T, + pub bottom_right: T, +} + +impl Corners { + #[inline] + pub fn all(value: T) -> Self { + Self { + top_left: value.clone(), + top_right: value.clone(), + bottom_left: value.clone(), + bottom_right: value, + } + } + + #[inline] + pub fn top_bottom(top: T, bottom: T) -> Self { + Self { + top_left: top.clone(), + top_right: top, + bottom_left: bottom.clone(), + bottom_right: bottom, + } + } + + #[inline] + pub fn left_right(left: T, right: T) -> Self { + Self { + top_left: left.clone(), + top_right: right.clone(), + bottom_left: left, + bottom_right: right, + } + } +} + +impl Corners { + pub fn max(&self) -> T { + self.top_left.clone() + .max(self.top_right.clone()) + .max(self.bottom_left.clone()) + .max(self.bottom_right.clone()) + .clone() + } +} + +impl Corners { + pub fn max_f32(&self) -> f32 { + self.top_left + .max(self.top_right) + .max(self.bottom_left) + .max(self.bottom_right) + } +} + +impl Corners { + pub fn max_f64(&self) -> f64 { + self.top_left + .max(self.top_right) + .max(self.bottom_left) + .max(self.bottom_right) + } +} + +impl From for Corners { + fn from(value: T) -> Self { + Self::all(value) + } +} + +impl From<(T, T, T, T)> for Corners { + fn from((top_left, top_right, bottom_left, bottom_right): (T, T, T, T)) -> Self { + Self { + top_left, + top_right, + bottom_left, + bottom_right, + } + } +} diff --git a/hui/src/rectangle/corners/corners_colors.rs b/hui/src/rectangle/corners/corners_colors.rs new file mode 100644 index 0000000..267954a --- /dev/null +++ b/hui/src/rectangle/corners/corners_colors.rs @@ -0,0 +1,122 @@ +use super::Corners; +use glam::{Vec3, Vec4, vec4}; + +/// Like Corners, but specialized for colors\ +/// Opaque type, needs to be casted to `Corners` to be used +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct CornersColors(Corners); + +impl Default for CornersColors { + fn default() -> Self { + Self(Corners::all(vec4(0.0, 0.0, 0.0, 1.0))) + } +} + +impl From> for CornersColors { + fn from(corners: Corners) -> Self { + Self(corners) + } +} + +impl From for Corners { + fn from(corners: CornersColors) -> Self { + corners.0 + } +} + +impl From for CornersColors { + fn from(value: Vec4) -> Self { + Self(Corners::all(value)) + } +} + +impl From<(f32, f32, f32, f32)> for CornersColors { + fn from((r, g, b, a): (f32, f32, f32, f32)) -> Self { + Self(Corners::all(vec4(r, g, b, a))) + } +} + +impl From<[f32; 4]> for CornersColors { + fn from([r, g, b, a]: [f32; 4]) -> Self { + Self(Corners::all(vec4(r, g, b, a))) + } +} + +impl From for CornersColors { + fn from(value: Vec3) -> Self { + Self(Corners::all(vec4(value.x, value.y, value.z, 1.0))) + } +} + +impl From<(f32, f32, f32)> for CornersColors { + fn from((r, g, b): (f32, f32, f32)) -> Self { + Self(Corners::all(vec4(r, g, b, 1.0))) + } +} + +impl From<[f32; 3]> for CornersColors { + fn from([r, g, b]: [f32; 3]) -> Self { + Self(Corners::all(vec4(r, g, b, 1.0))) + } +} + +impl From<(Vec4, Vec4, Vec4, Vec4)> for CornersColors { + fn from((top_left, top_right, bottom_left, bottom_right): (Vec4, Vec4, Vec4, Vec4)) -> Self { + Self(Corners { top_left, top_right, bottom_left, bottom_right }) + } +} + +impl From<((f32, f32, f32, f32), (f32, f32, f32, f32), (f32, f32, f32, f32), (f32, f32, f32, f32))> for CornersColors { + fn from(value: ((f32, f32, f32, f32), (f32, f32, f32, f32), (f32, f32, f32, f32), (f32, f32, f32, f32))) -> Self { + Self(Corners { + top_left: vec4(value.0.0, value.0.1, value.0.2, value.0.3), + top_right: vec4(value.1.0, value.1.1, value.1.2, value.1.3), + bottom_left: vec4(value.2.0, value.2.1, value.2.2, value.2.3), + bottom_right: vec4(value.3.0, value.3.1, value.3.2, value.3.3), + }) + } +} + +impl From<[[f32; 4]; 4]> for CornersColors { + fn from(value: [[f32; 4]; 4]) -> Self { + Self(Corners { + top_left: vec4(value[0][0], value[0][1], value[0][2], value[0][3]), + top_right: vec4(value[1][0], value[1][1], value[1][2], value[1][3]), + bottom_left: vec4(value[2][0], value[2][1], value[2][2], value[2][3]), + bottom_right: vec4(value[3][0], value[3][1], value[3][2], value[3][3]), + }) + } +} + +impl From<(Vec3, Vec3, Vec3, Vec3)> for CornersColors { + fn from((top_left, top_right, bottom_left, bottom_right): (Vec3, Vec3, Vec3, Vec3)) -> Self { + Self(Corners { + top_left: vec4(top_left.x, top_left.y, top_left.z, 1.0), + top_right: vec4(top_right.x, top_right.y, top_right.z, 1.0), + bottom_left: vec4(bottom_left.x, bottom_left.y, bottom_left.z, 1.0), + bottom_right: vec4(bottom_right.x, bottom_right.y, bottom_right.z, 1.0), + }) + } +} + +impl From<((f32, f32, f32), (f32, f32, f32), (f32, f32, f32), (f32, f32, f32))> for CornersColors { + fn from(value: ((f32, f32, f32), (f32, f32, f32), (f32, f32, f32), (f32, f32, f32))) -> Self { + Self(Corners { + top_left: vec4(value.0.0, value.0.1, value.0.2, 1.0), + top_right: vec4(value.1.0, value.1.1, value.1.2, 1.0), + bottom_left: vec4(value.2.0, value.2.1, value.2.2, 1.0), + bottom_right: vec4(value.3.0, value.3.1, value.3.2, 1.0), + }) + } +} + +impl From<[[f32; 3]; 4]> for CornersColors { + fn from(value: [[f32; 3]; 4]) -> Self { + Self(Corners { + top_left: vec4(value[0][0], value[0][1], value[0][2], 1.0), + top_right: vec4(value[1][0], value[1][1], value[1][2], 1.0), + bottom_left: vec4(value[2][0], value[2][1], value[2][2], 1.0), + bottom_right: vec4(value[3][0], value[3][1], value[3][2], 1.0), + }) + } +} diff --git a/hui/src/rectangle/rect.rs b/hui/src/rectangle/rect.rs new file mode 100644 index 0000000..902a7af --- /dev/null +++ b/hui/src/rectangle/rect.rs @@ -0,0 +1,65 @@ +use glam::Vec2; +use super::Corners; + +/// 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 + && self.position.y < other.position.y + other.size.y + && 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, + top_right: self.position + Vec2::new(self.size.x, 0.0), + bottom_left: self.position + Vec2::new(0.0, self.size.y), + bottom_right: self.position + self.size, + } + } +} diff --git a/hui/src/rectangle/sides.rs b/hui/src/rectangle/sides.rs new file mode 100644 index 0000000..999f3a5 --- /dev/null +++ b/hui/src/rectangle/sides.rs @@ -0,0 +1,49 @@ + +/// Represents 4 sides of a rectangular shape. +#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] +pub struct Sides { + pub top: T, + pub bottom: T, + pub left: T, + pub right: T, +} + +impl Sides { + #[inline] + pub fn all(value: T) -> Self { + Self { + top: value.clone(), + bottom: value.clone(), + left: value.clone(), + right: value, + } + } + + #[inline] + pub fn horizontal_vertical(horizontal: T, vertical: T) -> Self { + Self { + top: vertical.clone(), + bottom: vertical, + left: horizontal.clone(), + right: horizontal, + } + } +} + +impl From for Sides { + fn from(value: T) -> Self { + Self::all(value) + } +} + +impl From<(T, T)> for Sides { + fn from((horizontal, vertical): (T, T)) -> Self { + Self::horizontal_vertical(horizontal, vertical) + } +} + +impl From<(T, T, T, T)> for Sides { + fn from((top, bottom, left, right): (T, T, T, T)) -> Self { + Self { top, bottom, left, right } + } +} diff --git a/hui/src/signal.rs b/hui/src/signal.rs index 6a49159..2016d5e 100644 --- a/hui/src/signal.rs +++ b/hui/src/signal.rs @@ -2,8 +2,10 @@ use std::any::{Any, TypeId}; use hashbrown::HashMap; use nohash_hasher::BuildNoHashHasher; +pub mod trigger; + /// A marker trait for signals -pub trait UiSignal: Any {} +pub trait Signal: Any {} // #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] // pub(crate) struct DummySignal; @@ -23,7 +25,7 @@ impl SignalStore { } /// Ensure that store for given signal type exists and return a mutable reference to it - fn internal_store(&mut self) -> &mut Vec> { + fn internal_store(&mut self) -> &mut Vec> { let type_id = TypeId::of::(); self.sig.entry(type_id).or_default() } @@ -31,7 +33,7 @@ impl SignalStore { /// Add a signal to the store /// /// Signals are stored in the order they are added - pub fn add(&mut self, sig: T) { + pub fn add(&mut self, sig: T) { let type_id = TypeId::of::(); if let Some(v) = self.sig.get_mut(&type_id) { v.push(Box::new(sig)); @@ -41,7 +43,7 @@ impl SignalStore { } /// Drain all signals of a given type - pub(crate) fn drain(&mut self) -> impl Iterator + '_ { + pub(crate) fn drain(&mut self) -> impl Iterator + '_ { self.internal_store::() .drain(..) .map(|x| *x.downcast::().unwrap()) //unchecked? @@ -54,7 +56,6 @@ impl SignalStore { } } -//TODO this, simplifies handling signals // pub trait Signal { // type Arg; diff --git a/hui/src/signal/trigger.rs b/hui/src/signal/trigger.rs new file mode 100644 index 0000000..c5d3b31 --- /dev/null +++ b/hui/src/signal/trigger.rs @@ -0,0 +1,61 @@ +use crate::element::UiElement; +use super::{Signal, SignalStore}; + +#[allow(clippy::complexity)] +pub struct SignalTrigger(Box); + +impl SignalTrigger { + pub fn new S + 'static>(f: F) -> Self { + Self(Box::new(move |s: &mut SignalStore| { + s.add::(f()); + })) + } + + pub fn fire(&self, s: &mut SignalStore) { + (self.0)(s); + } +} + +#[allow(clippy::complexity)] +pub struct SignalTriggerArg(Box); + +impl SignalTriggerArg { + pub fn new S + 'static>(f: F) -> Self { + Self(Box::new(move |s: &mut SignalStore, x| { + s.add::(f(x)); + })) + } + + pub fn fire(&self, s: &mut SignalStore, x: T) { + (self.0)(s, x); + } +} +#[allow(clippy::complexity)] +pub struct SignalTriggerElement(Box); + +impl SignalTriggerElement { + pub fn new S + 'static>(f: F) -> Self { + Self(Box::new(move |s: &mut SignalStore, e: &mut E| { + s.add::(f(e)); + })) + } + + pub fn fire(&self, s: &mut SignalStore, e: &mut E) { + (self.0)(s, e); + } +} + +#[allow(clippy::complexity)] +pub struct SignalTriggerElementArg(Box); + +impl SignalTriggerElementArg { + pub fn new S + 'static>(f: F) -> Self { + Self(Box::new(move |s: &mut SignalStore, e: &mut E, x| { + s.add::(f(x, e)); + })) + } + + pub fn fire(&self, s: &mut SignalStore, e: &mut E, x: T) { + (self.0)(s, e, x); + } +}