a bunch of signal and input changes

This commit is contained in:
griffi-gh 2024-03-21 22:23:42 +01:00
parent dd4c71db3b
commit d526928d9b
14 changed files with 510 additions and 248 deletions

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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<C: UiSignal + 'static> {
pub struct Interactable<C: Signal + 'static> {
/// The wrapped element that will be interactable
pub element: Box<dyn UiElement>,
@ -29,7 +29,7 @@ pub struct Interactable<C: UiSignal + 'static> {
pub signal: RefCell<Option<C>>,
}
impl<C: UiSignal + 'static> Interactable<C> {
impl<C: Signal + 'static> Interactable<C> {
pub fn new(element: Box<dyn UiElement>, event: InteractableEvent, signal: C) -> Self {
Self {
element,
@ -39,7 +39,7 @@ impl<C: UiSignal + 'static> Interactable<C> {
}
}
impl<C: UiSignal + 'static> UiElement for Interactable<C> {
impl<C: Signal + 'static> UiElement for Interactable<C> {
fn name(&self) -> &'static str {
"Interactable"
}
@ -71,25 +71,25 @@ impl<C: UiSignal + 'static> UiElement for Interactable<C> {
/// 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<C: UiSignal + 'static>(self, event: InteractableEvent, signal: C) -> Interactable<C>;
fn into_interactable<C: Signal + 'static>(self, event: InteractableEvent, signal: C) -> Interactable<C>;
/// Wrap the element in an [`Interactable`] that will call the given signal when clicked
fn on_click<C: UiSignal + 'static>(self, signal: C) -> Interactable<C>;
fn on_click<C: Signal + 'static>(self, signal: C) -> Interactable<C>;
/// Wrap the element in an [`Interactable`] that will call the given signal while hovered
fn on_hover<C: UiSignal + 'static>(self, signal: C) -> Interactable<C>;
fn on_hover<C: Signal + 'static>(self, signal: C) -> Interactable<C>;
}
impl<T: UiElement + 'static> ElementInteractableExt for T {
fn into_interactable<C: UiSignal + 'static>(self, event: InteractableEvent, signal: C) -> Interactable<C> {
fn into_interactable<C: Signal + 'static>(self, event: InteractableEvent, signal: C) -> Interactable<C> {
Interactable::new(Box::new(self), event, signal)
}
fn on_click<C: UiSignal + 'static>(self, signal: C) -> Interactable<C> {
fn on_click<C: Signal + 'static>(self, signal: C) -> Interactable<C> {
self.into_interactable(InteractableEvent::Click, signal)
}
fn on_hover<C: UiSignal + 'static>(self, signal: C) -> Interactable<C> {
fn on_hover<C: Signal + 'static>(self, signal: C) -> Interactable<C> {
self.into_interactable(InteractableEvent::Hover, signal)
}
}

View file

@ -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<Box<dyn Fn(&mut SignalStore, f32)>>,
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 = 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: UiSignal + 'static, T: Fn(f32) -> S + 'static>(self, f: T) -> Self {
pub fn on_change<S: Signal, T: Fn(f32) -> S + 'static>(self, f: T) -> Self {
Self {
fire_on_shit: Some(Box::new(move |s: &mut SignalStore, x| {
s.add::<S>(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
}

View file

@ -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<MouseButton, MouseButtonMeta, BuildNoHashHasher<u16>>,
@ -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<ClickCheckResponse> {
pub fn check_click(&self, rect: Rect) -> Option<ActiveCheckResponse> {
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<ClickCheckResponse> {
pub fn check_active(&self, rect: Rect) -> Option<ActiveCheckResponse> {
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,
})
}
}

View file

@ -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<T: UiSignal + 'static>(&mut self, signal: T) {
pub fn push_signal<T: Signal + 'static>(&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<T: UiSignal + 'static>(&mut self, f: impl FnMut(T)) {
pub fn process_signals<T: Signal + 'static>(&mut self, f: impl FnMut(T)) {
self.signal.drain::<T>().for_each(f);
}
}

View file

@ -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<Vec2> {
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<T> {
pub top: T,
pub bottom: T,
pub left: T,
pub right: T,
}
impl<T: Clone> Sides<T> {
#[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<T: Clone> From<T> for Sides<T> {
fn from(value: T) -> Self {
Self::all(value)
}
}
impl<T: Clone> From<(T, T)> for Sides<T> {
fn from((horizontal, vertical): (T, T)) -> Self {
Self::horizontal_vertical(horizontal, vertical)
}
}
impl<T> From<(T, T, T, T)> for Sides<T> {
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<T> {
pub top_left: T,
pub top_right: T,
pub bottom_left: T,
pub bottom_right: T,
}
impl<T: Clone> Corners<T> {
#[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 <T: Ord + Clone> Corners<T> {
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<f32> {
pub fn max_f32(&self) -> f32 {
self.top_left
.max(self.top_right)
.max(self.bottom_left)
.max(self.bottom_right)
}
}
impl Corners<f64> {
pub fn max_f64(&self) -> f64 {
self.top_left
.max(self.top_right)
.max(self.bottom_left)
.max(self.bottom_right)
}
}
impl<T: Clone> From<T> for Corners<T> {
fn from(value: T) -> Self {
Self::all(value)
}
}
impl<T> From<(T, T, T, T)> for Corners<T> {
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};

View file

@ -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<T> {
pub top_left: T,
pub top_right: T,
pub bottom_left: T,
pub bottom_right: T,
}
impl<T: Clone> Corners<T> {
#[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 <T: Ord + Clone> Corners<T> {
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<f32> {
pub fn max_f32(&self) -> f32 {
self.top_left
.max(self.top_right)
.max(self.bottom_left)
.max(self.bottom_right)
}
}
impl Corners<f64> {
pub fn max_f64(&self) -> f64 {
self.top_left
.max(self.top_right)
.max(self.bottom_left)
.max(self.bottom_right)
}
}
impl<T: Clone> From<T> for Corners<T> {
fn from(value: T) -> Self {
Self::all(value)
}
}
impl<T> From<(T, T, T, T)> for Corners<T> {
fn from((top_left, top_right, bottom_left, bottom_right): (T, T, T, T)) -> Self {
Self {
top_left,
top_right,
bottom_left,
bottom_right,
}
}
}

View file

@ -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<Vec4>` to be used
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CornersColors(Corners<Vec4>);
impl Default for CornersColors {
fn default() -> Self {
Self(Corners::all(vec4(0.0, 0.0, 0.0, 1.0)))
}
}
impl From<Corners<Vec4>> for CornersColors {
fn from(corners: Corners<Vec4>) -> Self {
Self(corners)
}
}
impl From<CornersColors> for Corners<Vec4> {
fn from(corners: CornersColors) -> Self {
corners.0
}
}
impl From<Vec4> 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<Vec3> 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),
})
}
}

65
hui/src/rectangle/rect.rs Normal file
View file

@ -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<Vec2> {
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,
}
}
}

View file

@ -0,0 +1,49 @@
/// Represents 4 sides of a rectangular shape.
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct Sides<T> {
pub top: T,
pub bottom: T,
pub left: T,
pub right: T,
}
impl<T: Clone> Sides<T> {
#[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<T: Clone> From<T> for Sides<T> {
fn from(value: T) -> Self {
Self::all(value)
}
}
impl<T: Clone> From<(T, T)> for Sides<T> {
fn from((horizontal, vertical): (T, T)) -> Self {
Self::horizontal_vertical(horizontal, vertical)
}
}
impl<T> From<(T, T, T, T)> for Sides<T> {
fn from((top, bottom, left, right): (T, T, T, T)) -> Self {
Self { top, bottom, left, right }
}
}

View file

@ -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<T: UiSignal + 'static>(&mut self) -> &mut Vec<Box<dyn Any>> {
fn internal_store<T: Signal + 'static>(&mut self) -> &mut Vec<Box<dyn Any>> {
let type_id = TypeId::of::<T>();
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<T: UiSignal + 'static>(&mut self, sig: T) {
pub fn add<T: Signal + 'static>(&mut self, sig: T) {
let type_id = TypeId::of::<T>();
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<T: UiSignal + 'static>(&mut self) -> impl Iterator<Item = T> + '_ {
pub(crate) fn drain<T: Signal + 'static>(&mut self) -> impl Iterator<Item = T> + '_ {
self.internal_store::<T>()
.drain(..)
.map(|x| *x.downcast::<T>().unwrap()) //unchecked?
@ -54,7 +56,6 @@ impl SignalStore {
}
}
//TODO this, simplifies handling signals
// pub trait Signal {
// type Arg;

61
hui/src/signal/trigger.rs Normal file
View file

@ -0,0 +1,61 @@
use crate::element::UiElement;
use super::{Signal, SignalStore};
#[allow(clippy::complexity)]
pub struct SignalTrigger(Box<dyn Fn(&mut SignalStore)>);
impl SignalTrigger {
pub fn new<S: Signal + 'static, F: Fn() -> S + 'static>(f: F) -> Self {
Self(Box::new(move |s: &mut SignalStore| {
s.add::<S>(f());
}))
}
pub fn fire(&self, s: &mut SignalStore) {
(self.0)(s);
}
}
#[allow(clippy::complexity)]
pub struct SignalTriggerArg<T>(Box<dyn Fn(&mut SignalStore, T)>);
impl<T> SignalTriggerArg<T> {
pub fn new<S: Signal, F: Fn(T) -> S + 'static>(f: F) -> Self {
Self(Box::new(move |s: &mut SignalStore, x| {
s.add::<S>(f(x));
}))
}
pub fn fire(&self, s: &mut SignalStore, x: T) {
(self.0)(s, x);
}
}
#[allow(clippy::complexity)]
pub struct SignalTriggerElement<E: UiElement>(Box<dyn Fn(&mut SignalStore, &mut E)>);
impl<E: UiElement> SignalTriggerElement<E> {
pub fn new<S: Signal, F: Fn(&mut E) -> S + 'static>(f: F) -> Self {
Self(Box::new(move |s: &mut SignalStore, e: &mut E| {
s.add::<S>(f(e));
}))
}
pub fn fire(&self, s: &mut SignalStore, e: &mut E) {
(self.0)(s, e);
}
}
#[allow(clippy::complexity)]
pub struct SignalTriggerElementArg<E: UiElement, T>(Box<dyn Fn(&mut SignalStore, &mut E, T)>);
impl<E: UiElement, T> SignalTriggerElementArg<E, T> {
pub fn new<S: Signal, F: Fn(T, &mut E) -> S + 'static>(f: F) -> Self {
Self(Box::new(move |s: &mut SignalStore, e: &mut E, x| {
s.add::<S>(f(x, e));
}))
}
pub fn fire(&self, s: &mut SignalStore, e: &mut E, x: T) {
(self.0)(s, e, x);
}
}