mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-25 16:38:42 -06:00
Compare commits
13 commits
dd4c71db3b
...
50ea989906
Author | SHA1 | Date | |
---|---|---|---|
griffi-gh | 50ea989906 | ||
griffi-gh | 62f22d9f12 | ||
griffi-gh | 8b3552407a | ||
griffi-gh | 4ad3fed6d3 | ||
griffi-gh | 68114bd7dc | ||
griffi-gh | b5f3fc192e | ||
griffi-gh | 4574cb8862 | ||
griffi-gh | 99527d0dcb | ||
griffi-gh | 36345119a3 | ||
griffi-gh | a4cac48bda | ||
griffi-gh | e8d8951320 | ||
griffi-gh | 7ae72b7a41 | ||
griffi-gh | d526928d9b |
|
@ -10,7 +10,7 @@ use winit::{
|
||||||
use hui::{
|
use hui::{
|
||||||
element::{
|
element::{
|
||||||
container::Container, progress_bar::ProgressBar, fill_rect::FillRect, ElementList, UiElement
|
container::Container, progress_bar::ProgressBar, fill_rect::FillRect, ElementList, UiElement
|
||||||
}, layout::{Alignment, Direction, Size}, rectangle::{Corners, Sides}, UiInstance
|
}, layout::{Alignment, Direction, Size}, rect::{Corners, Sides}, UiInstance
|
||||||
};
|
};
|
||||||
use hui_glium::GliumUiRenderer;
|
use hui_glium::GliumUiRenderer;
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ use hui::{
|
||||||
text::Text, ElementList
|
text::Text, ElementList
|
||||||
},
|
},
|
||||||
layout::{Alignment, Direction, Size},
|
layout::{Alignment, Direction, Size},
|
||||||
rectangle::{Corners, Sides},
|
rect::{Corners, Sides},
|
||||||
UiInstance
|
UiInstance
|
||||||
};
|
};
|
||||||
use hui_glium::GliumUiRenderer;
|
use hui_glium::GliumUiRenderer;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use hui::{
|
||||||
color, size,
|
color, size,
|
||||||
element::{container::Container, progress_bar::ProgressBar, text::Text, UiElementExt},
|
element::{container::Container, progress_bar::ProgressBar, text::Text, UiElementExt},
|
||||||
layout::Alignment,
|
layout::Alignment,
|
||||||
rectangle::Corners,
|
rect::Corners,
|
||||||
text::FontHandle,
|
text::FontHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use hui::{
|
||||||
UiElementExt
|
UiElementExt
|
||||||
},
|
},
|
||||||
layout::Alignment,
|
layout::Alignment,
|
||||||
rectangle::Corners,
|
rect::Corners,
|
||||||
text::FontHandle,
|
text::FontHandle,
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use hui::{
|
use hui::{
|
||||||
color, size,
|
color, size,
|
||||||
draw::TextureFormat,
|
draw::TextureFormat,
|
||||||
signal::UiSignal,
|
signal::Signal,
|
||||||
layout::{Alignment, Direction},
|
layout::{Alignment, Direction},
|
||||||
element::{
|
element::{
|
||||||
container::Container,
|
container::Container,
|
||||||
|
@ -17,7 +17,7 @@ enum CounterSignal {
|
||||||
Increment,
|
Increment,
|
||||||
Decrement,
|
Decrement,
|
||||||
}
|
}
|
||||||
impl UiSignal for CounterSignal {}
|
impl Signal for CounterSignal {}
|
||||||
|
|
||||||
#[path = "../boilerplate.rs"]
|
#[path = "../boilerplate.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
use hui::{
|
use hui::{
|
||||||
color, size,
|
|
||||||
draw::TextureFormat,
|
draw::TextureFormat,
|
||||||
signal::UiSignal,
|
signal::Signal,
|
||||||
layout::{Alignment, Direction},
|
layout::{Alignment, Direction},
|
||||||
element::{
|
element::{
|
||||||
container::Container,
|
container::Container,
|
||||||
text::Text,
|
text::Text,
|
||||||
image::Image,
|
image::Image,
|
||||||
br::Br,
|
br::Br,
|
||||||
interactable::ElementInteractableExt,
|
|
||||||
slider::Slider,
|
slider::Slider,
|
||||||
UiElementExt,
|
UiElementExt,
|
||||||
},
|
},
|
||||||
|
size,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum CounterSignal {
|
enum CounterSignal {
|
||||||
ChangeValue(u32)
|
ChangeValue(u32)
|
||||||
}
|
}
|
||||||
impl UiSignal for CounterSignal {}
|
impl Signal for CounterSignal {}
|
||||||
|
|
||||||
#[path = "../boilerplate.rs"]
|
#[path = "../boilerplate.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -41,7 +40,7 @@ ui_main!(
|
||||||
.with_background((0.1, 0.1, 0.1))
|
.with_background((0.1, 0.1, 0.1))
|
||||||
.with_wrap(true)
|
.with_wrap(true)
|
||||||
.with_children(|ui| {
|
.with_children(|ui| {
|
||||||
Text::new("Number of images:")
|
Text::new(format!("Number of images: {counter}"))
|
||||||
.with_text_size(24)
|
.with_text_size(24)
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
Br.add_child(ui);
|
Br.add_child(ui);
|
|
@ -24,6 +24,7 @@ rect_packer = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
document-features = "0.2"
|
document-features = "0.2"
|
||||||
derive_setters = "0.1"
|
derive_setters = "0.1"
|
||||||
|
derive_more = "0.99"
|
||||||
tinyset = "0.4"
|
tinyset = "0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
//! background color, gradient and texturing
|
|
||||||
|
|
||||||
use glam::{vec4, Vec3, Vec4};
|
|
||||||
use crate::rectangle::Corners;
|
|
||||||
|
|
||||||
//TODO: use this
|
|
||||||
// pub struct Background {
|
|
||||||
// pub color: BackgroundColor,
|
|
||||||
// pub texture: Option<TextureH>
|
|
||||||
// }
|
|
||||||
|
|
||||||
//TODO: move this into the color module?
|
|
||||||
#[derive(Clone, Copy, Default, Debug, PartialEq)]
|
|
||||||
pub enum BackgroundColor {
|
|
||||||
#[default]
|
|
||||||
Transparent,
|
|
||||||
Solid(Vec4),
|
|
||||||
Gradient(Corners<Vec4>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(f32, f32, f32, f32)> for BackgroundColor {
|
|
||||||
fn from(color: (f32, f32, f32, f32)) -> Self {
|
|
||||||
Self::Solid(vec4(color.0, color.1, color.2, color.3))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Corners<Vec4>> for BackgroundColor {
|
|
||||||
fn from(corners: Corners<Vec4>) -> Self {
|
|
||||||
Self::Gradient(corners)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Option<Vec4>> for BackgroundColor {
|
|
||||||
fn from(color: Option<Vec4>) -> Self {
|
|
||||||
match color {
|
|
||||||
Some(color) => Self::Solid(color),
|
|
||||||
None => Self::Transparent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec4> for BackgroundColor {
|
|
||||||
fn from(color: Vec4) -> Self {
|
|
||||||
Self::Solid(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(f32, f32, f32)> for BackgroundColor {
|
|
||||||
fn from(color: (f32, f32, f32)) -> Self {
|
|
||||||
Self::Solid(vec4(color.0, color.1, color.2, 1.))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Corners<Vec3>> for BackgroundColor {
|
|
||||||
fn from(corners: Corners<Vec3>) -> Self {
|
|
||||||
Self::Gradient(Corners {
|
|
||||||
top_left: corners.top_left.extend(1.),
|
|
||||||
top_right: corners.top_right.extend(1.),
|
|
||||||
bottom_left: corners.bottom_left.extend(1.),
|
|
||||||
bottom_right: corners.bottom_right.extend(1.),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Option<Vec3>> for BackgroundColor {
|
|
||||||
fn from(color: Option<Vec3>) -> Self {
|
|
||||||
match color {
|
|
||||||
Some(color) => Self::Solid(color.extend(1.)),
|
|
||||||
None => Self::Transparent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec3> for BackgroundColor {
|
|
||||||
fn from(color: Vec3) -> Self {
|
|
||||||
Self::Solid(color.extend(1.))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BackgroundColor {
|
|
||||||
/// Returns the colors of individual corners
|
|
||||||
pub fn corners(&self) -> Corners<Vec4> {
|
|
||||||
match *self {
|
|
||||||
Self::Transparent => Corners::all(Vec4::ZERO),
|
|
||||||
Self::Solid(color) => Corners::all(color),
|
|
||||||
Self::Gradient(corners) => corners,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the background is `Transparent` or all corners have an alpha value of `0`.
|
|
||||||
pub fn is_transparent(&self) -> bool {
|
|
||||||
match *self {
|
|
||||||
Self::Transparent => true,
|
|
||||||
Self::Solid(color) => color.w == 0.,
|
|
||||||
Self::Gradient(corners) => {
|
|
||||||
let max_alpha =
|
|
||||||
corners.top_left.w
|
|
||||||
.max(corners.top_right.w)
|
|
||||||
.max(corners.bottom_left.w)
|
|
||||||
.max(corners.bottom_right.w);
|
|
||||||
max_alpha == 0.
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@
|
||||||
//TODO: 9-slice draw command
|
//TODO: 9-slice draw command
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
rectangle::Corners,
|
rect::Corners,
|
||||||
text::{FontHandle, TextRenderer}
|
text::{FontHandle, TextRenderer}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use glam::{uvec2, vec2, UVec2, Vec2};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use nohash_hasher::BuildNoHashHasher;
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
use rect_packer::DensePacker;
|
use rect_packer::DensePacker;
|
||||||
use crate::rectangle::Corners;
|
use crate::rect::Corners;
|
||||||
|
|
||||||
const RGBA_CHANNEL_COUNT: u32 = 4;
|
const RGBA_CHANNEL_COUNT: u32 = 4;
|
||||||
//TODO make this work
|
//TODO make this work
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::num::NonZeroU16;
|
use std::num::NonZeroU16;
|
||||||
use crate::rectangle::Corners;
|
use crate::rect::Corners;
|
||||||
|
|
||||||
//TODO uneven corners (separate width/height for each corner)
|
//TODO uneven corners (separate width/height for each corner)
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use glam::{Vec2, vec2};
|
use glam::{Vec2, vec2};
|
||||||
use crate::{
|
use crate::{
|
||||||
background::BackgroundColor,
|
|
||||||
draw::{ImageHandle, RoundedCorners, UiDrawCommand},
|
draw::{ImageHandle, RoundedCorners, UiDrawCommand},
|
||||||
element::{ElementList, MeasureContext, ProcessContext, UiElement},
|
element::{ElementList, MeasureContext, ProcessContext, UiElement},
|
||||||
layout::{Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d},
|
layout::{Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d},
|
||||||
measure::{Hints, Response},
|
measure::{Hints, Response},
|
||||||
rectangle::{Corners, Sides},
|
rect::{Corners, FillColor, Sides},
|
||||||
};
|
};
|
||||||
|
|
||||||
// pub struct Border {
|
// pub struct Border {
|
||||||
|
@ -59,7 +58,7 @@ pub struct Container {
|
||||||
///
|
///
|
||||||
/// If the container has a background texture, it will be multiplied by this color
|
/// If the container has a background texture, it will be multiplied by this color
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub background: BackgroundColor,
|
pub background: FillColor,
|
||||||
|
|
||||||
/// Background texture of the container
|
/// Background texture of the container
|
||||||
///
|
///
|
||||||
|
@ -104,7 +103,7 @@ impl Default for Container {
|
||||||
gap: 0.,
|
gap: 0.,
|
||||||
padding: Sides::all(0.),
|
padding: Sides::all(0.),
|
||||||
align: Alignment2d::default(),
|
align: Alignment2d::default(),
|
||||||
background: Default::default(),
|
background: FillColor::transparent(),
|
||||||
background_image: None,
|
background_image: None,
|
||||||
children: ElementList(Vec::new()),
|
children: ElementList(Vec::new()),
|
||||||
wrap: false,
|
wrap: false,
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use glam::{vec2, Vec4};
|
use glam::{vec2, Vec4};
|
||||||
use crate::{
|
use crate::{
|
||||||
background::BackgroundColor,
|
draw::{RoundedCorners, UiDrawCommand},
|
||||||
draw::{UiDrawCommand, RoundedCorners},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
element::{UiElement, MeasureContext, ProcessContext},
|
|
||||||
layout::{Size, Size2d},
|
layout::{Size, Size2d},
|
||||||
measure::Response,
|
measure::Response,
|
||||||
rectangle::Corners,
|
rect::{Corners, FillColor},
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,7 +21,7 @@ pub struct FillRect {
|
||||||
|
|
||||||
/// Background color of the rectangle
|
/// Background color of the rectangle
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub background: BackgroundColor,
|
pub background: FillColor,
|
||||||
|
|
||||||
/// Corner radius of the rectangle
|
/// Corner radius of the rectangle
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use glam::vec2;
|
use glam::vec2;
|
||||||
use crate::{
|
use crate::{
|
||||||
background::BackgroundColor,
|
|
||||||
draw::{ImageHandle, RoundedCorners, UiDrawCommand},
|
draw::{ImageHandle, RoundedCorners, UiDrawCommand},
|
||||||
element::{MeasureContext, ProcessContext, UiElement},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
layout::{compute_size, Size, Size2d},
|
layout::{compute_size, Size, Size2d},
|
||||||
measure::Response,
|
measure::Response,
|
||||||
rectangle::Corners,
|
rect::{Corners, FillColor},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Setters)]
|
#[derive(Setters)]
|
||||||
|
@ -29,7 +28,7 @@ pub struct Image {
|
||||||
///
|
///
|
||||||
/// Image will get multiplied/tinted by this color or gradient
|
/// Image will get multiplied/tinted by this color or gradient
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub color: BackgroundColor,
|
pub color: FillColor,
|
||||||
|
|
||||||
/// Corner radius of the image
|
/// Corner radius of the image
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
|
@ -44,7 +43,7 @@ impl Image {
|
||||||
width: Size::Auto,
|
width: Size::Auto,
|
||||||
height: Size::Auto,
|
height: Size::Auto,
|
||||||
},
|
},
|
||||||
color: BackgroundColor::from((1., 1., 1., 1.)),
|
color: (1., 1., 1.).into(),
|
||||||
corner_radius: Corners::all(0.),
|
corner_radius: Corners::all(0.),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
element::{MeasureContext, ProcessContext, UiElement},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
signal::UiSignal,
|
signal::Signal,
|
||||||
};
|
};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ pub enum InteractableEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper that allows adding click and hover events to any element
|
/// 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
|
/// The wrapped element that will be interactable
|
||||||
pub element: Box<dyn UiElement>,
|
pub element: Box<dyn UiElement>,
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ pub struct Interactable<C: UiSignal + 'static> {
|
||||||
pub signal: RefCell<Option<C>>,
|
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 {
|
pub fn new(element: Box<dyn UiElement>, event: InteractableEvent, signal: C) -> Self {
|
||||||
Self {
|
Self {
|
||||||
element,
|
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 {
|
fn name(&self) -> &'static str {
|
||||||
"Interactable"
|
"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`]
|
/// Extension trait for [`UiElement`] that adds methods to wrap the element in an [`Interactable`]
|
||||||
pub trait ElementInteractableExt: UiElement {
|
pub trait ElementInteractableExt: UiElement {
|
||||||
/// Wrap the element in an [`Interactable`] that will call the given signal when the specified event occurs
|
/// 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
|
/// 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
|
/// 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 {
|
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)
|
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)
|
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)
|
self.into_interactable(InteractableEvent::Hover, signal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use glam::{vec2, vec4};
|
use glam::{vec2, vec4};
|
||||||
use crate::{
|
use crate::{
|
||||||
background::BackgroundColor,
|
|
||||||
draw::{RoundedCorners, UiDrawCommand},
|
draw::{RoundedCorners, UiDrawCommand},
|
||||||
element::{MeasureContext, ProcessContext, UiElement},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
layout::{compute_size, Size, Size2d},
|
layout::{compute_size, Size, Size2d},
|
||||||
measure::Response,
|
measure::Response,
|
||||||
rectangle::Corners
|
rect::{Corners, FillColor}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Setters)]
|
#[derive(Debug, Clone, Copy, Setters)]
|
||||||
|
@ -21,11 +20,11 @@ pub struct ProgressBar {
|
||||||
|
|
||||||
/// Foreground (bar) color
|
/// Foreground (bar) color
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub foreground: BackgroundColor,
|
pub foreground: FillColor,
|
||||||
|
|
||||||
/// Background color
|
/// Background color
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub background: BackgroundColor,
|
pub background: FillColor,
|
||||||
|
|
||||||
/// Corner radius of the progress bar
|
/// Corner radius of the progress bar
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
|
|
|
@ -1,30 +1,80 @@
|
||||||
//! work in progress
|
//! a slider element that allows selecting a value in a range
|
||||||
|
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use glam::{vec2, Vec2};
|
use glam::{Vec2, vec2};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
draw::{RoundedCorners, UiDrawCommand},
|
draw::UiDrawCommand,
|
||||||
element::{MeasureContext, ProcessContext, UiElement},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
layout::{compute_size, Size2d},
|
layout::{Size2d, compute_size},
|
||||||
measure::Response,
|
measure::Response,
|
||||||
rectangle::Corners,
|
rect::FillColor,
|
||||||
signal::{SignalStore, UiSignal},
|
signal::{trigger::SignalTriggerArg, Signal},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// work in progress
|
/// Follow mode for the slider
|
||||||
#[derive(Default, Setters)]
|
#[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_")]
|
#[setters(prefix = "with_")]
|
||||||
pub struct Slider {
|
pub struct Slider {
|
||||||
|
/// Value of the slider, should be in range 0..1
|
||||||
|
///
|
||||||
|
/// Out of range values will be clamped
|
||||||
pub value: f32,
|
pub value: f32,
|
||||||
|
|
||||||
|
/// Size of the element
|
||||||
|
#[setters(into)]
|
||||||
pub size: Size2d,
|
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,
|
||||||
|
|
||||||
|
/// Color of the "active" part of the slider
|
||||||
|
#[setters(into)]
|
||||||
|
pub track_active_color: FillColor,
|
||||||
|
|
||||||
|
/// Follow mode
|
||||||
|
pub follow_mode: SliderFollowMode,
|
||||||
|
|
||||||
#[setters(skip)]
|
#[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(),
|
||||||
|
track_active_color: (0.0, 0.0, 0.75).into(),
|
||||||
|
follow_mode: SliderFollowMode::default(),
|
||||||
|
on_change: None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Slider {
|
impl Slider {
|
||||||
pub const DEFAULT_HEIGHT: f32 = 20.0;
|
pub const DEFAULT_HEIGHT: f32 = 21.0;
|
||||||
|
|
||||||
pub fn new(value: f32) -> Self {
|
pub fn new(value: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -33,11 +83,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 {
|
Self {
|
||||||
fire_on_shit: Some(Box::new(move |s: &mut SignalStore, x| {
|
on_change: Some(SignalTriggerArg::new(f)),
|
||||||
s.add::<S>(f(x));
|
|
||||||
})),
|
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,36 +104,69 @@ impl UiElement for Slider {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process(&self, ctx: ProcessContext) {
|
fn process(&self, ctx: ProcessContext) {
|
||||||
let bgrect_height_ratio = 0.25;
|
//TODO: unhardcode this
|
||||||
|
let bgrect_height_ratio = 0.33;
|
||||||
|
|
||||||
|
//XXX: some of these assumptions are wrong if the corners are rounded
|
||||||
|
|
||||||
|
//Draw the track
|
||||||
|
//If the active part is opaque and value >= 1., we don't need to draw the background as the active part will cover it
|
||||||
|
//Of corse, if it's fully transparent, we don't need to draw it either
|
||||||
|
if !(self.track_color.is_transparent() || (self.track_active_color.is_opaque() && self.value >= 1.)) {
|
||||||
ctx.draw.add(UiDrawCommand::Rectangle {
|
ctx.draw.add(UiDrawCommand::Rectangle {
|
||||||
position: ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - bgrect_height_ratio / 2.),
|
position: ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - bgrect_height_ratio / 2.),
|
||||||
size: ctx.measure.size * vec2(1., bgrect_height_ratio),
|
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,
|
texture: None,
|
||||||
rounded_corners: 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.);
|
//"Active" part of the track
|
||||||
let handle_size = vec2(15., ctx.measure.size.y);
|
//We can skip drawing it if it's fully transparent or value <= 0.
|
||||||
|
if !(self.track_active_color.is_transparent() || self.value <= 0.) {
|
||||||
ctx.draw.add(UiDrawCommand::Rectangle {
|
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 * vec2(0., 0.5 - bgrect_height_ratio / 2.),
|
||||||
size: handle_size,
|
size: ctx.measure.size * vec2(self.value, bgrect_height_ratio),
|
||||||
color: Corners::all((1., 1., 1., 1.).into()),
|
color: self.track_active_color.into(),
|
||||||
texture: None,
|
texture: None,
|
||||||
rounded_corners: None,
|
rounded_corners: None,
|
||||||
//Some(RoundedCorners::from_radius(Corners::all(handle_size.x / 3.))),
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//handle click etc
|
// The handle
|
||||||
|
// This is kinda counter-intuitive, but if the handle is transparent, we treat it as completely disabled
|
||||||
|
// To prevent confusing offset from the edge of the slider, we set the handle size to 0
|
||||||
|
let handle_size = if self.handle_color.is_transparent() {
|
||||||
|
Vec2::ZERO
|
||||||
|
} else {
|
||||||
|
vec2(15., ctx.measure.size.y)
|
||||||
|
};
|
||||||
|
if handle_size.x != 0. {
|
||||||
|
let value = self.value.clamp(0., 1.);
|
||||||
|
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)) {
|
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.);
|
let new_value = match self.follow_mode {
|
||||||
if let Some(fire) = &self.fire_on_shit {
|
SliderFollowMode::Absolute => ((res.position_in_rect.x - handle_size.x / 2.) / (ctx.measure.size.x - handle_size.x)).clamp(0., 1.),
|
||||||
fire(ctx.signal, new_value);
|
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 call signal with new value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use nohash_hasher::{BuildNoHashHasher, NoHashHasher};
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
use tinyset::{Fits64, Set64, SetU32};
|
use tinyset::{Fits64, Set64};
|
||||||
use crate::{event::{EventQueue, UiEvent}, rectangle::Rect};
|
use crate::{event::{EventQueue, UiEvent}, rect::Rect};
|
||||||
|
|
||||||
/// Represents a mouse button.
|
/// Represents a mouse button.
|
||||||
///
|
///
|
||||||
|
@ -144,6 +144,9 @@ pub struct MouseState {
|
||||||
/// Current position of the mouse pointer
|
/// Current position of the mouse pointer
|
||||||
pub current_position: Vec2,
|
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)
|
/// Current state of each mouse button (if down)
|
||||||
pub buttons: HashMap<MouseButton, MouseButtonMeta, BuildNoHashHasher<u16>>,
|
pub buttons: HashMap<MouseButton, MouseButtonMeta, BuildNoHashHasher<u16>>,
|
||||||
|
|
||||||
|
@ -184,6 +187,7 @@ impl UiInputState {
|
||||||
///
|
///
|
||||||
/// This function should be called exactly once per frame
|
/// This function should be called exactly once per frame
|
||||||
pub fn update_state(&mut self, event_queue: &mut EventQueue) {
|
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.mouse_pointer.released_buttons.clear();
|
||||||
self.just_happened.clear();
|
self.just_happened.clear();
|
||||||
self.just_happened.extend(event_queue.drain());
|
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)]
|
#[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,
|
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)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -297,23 +309,27 @@ impl<'a> InputCtx<'a> {
|
||||||
/// By default, this function only checks for the primary mouse button\
|
/// 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\
|
/// 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")
|
/// (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;
|
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)
|
rect.contains_point(meta.start_position) && rect.contains_point(pos)
|
||||||
}).then_some(ClickCheckResponse {
|
}).map(|mi| ActiveCheckResponse {
|
||||||
position_in_rect: pos - rect.position,
|
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
|
// TODO: write better docs
|
||||||
|
|
||||||
/// Check if a rect is being actively being interacted with (e.g. dragged)
|
/// 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| {
|
self.0.mouse_pointer.buttons.get(&MouseButton::Primary).filter(|mi| {
|
||||||
rect.contains_point(mi.start_position)
|
rect.contains_point(mi.start_position)
|
||||||
}).map(|_| ClickCheckResponse {
|
}).map(|mi| ActiveCheckResponse {
|
||||||
position_in_rect: self.0.mouse_pointer.current_position - rect.position,
|
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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
event::{EventQueue, UiEvent},
|
event::{EventQueue, UiEvent},
|
||||||
input::UiInputState,
|
input::UiInputState,
|
||||||
layout::{Direction, LayoutInfo},
|
layout::{Direction, LayoutInfo},
|
||||||
signal::{SignalStore, UiSignal},
|
signal::{SignalStore, Signal},
|
||||||
state::StateRepo,
|
state::StateRepo,
|
||||||
text::{FontHandle, TextRenderer}
|
text::{FontHandle, TextRenderer}
|
||||||
};
|
};
|
||||||
|
@ -244,7 +244,7 @@ impl UiInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a "fake" signal to the UI signal queue
|
/// 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);
|
self.signal.add(signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +253,7 @@ impl UiInstance {
|
||||||
/// Process all signals of a given type
|
/// Process all signals of a given type
|
||||||
///
|
///
|
||||||
/// This clears the signal queue for the given type and iterates over all signals
|
/// 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);
|
self.signal.drain::<T>().for_each(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
mod instance;
|
mod instance;
|
||||||
mod macros;
|
mod macros;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod rectangle;
|
pub mod rect;
|
||||||
pub mod background;
|
|
||||||
pub mod element;
|
pub mod element;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! element measurement, hints and responses
|
//! element measurement, hints and responses
|
||||||
|
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use crate::rectangle::Rect;
|
use crate::rect::Rect;
|
||||||
|
|
||||||
// #[non_exhaustive]
|
// #[non_exhaustive]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
15
hui/src/rect.rs
Normal file
15
hui/src/rect.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//! contains types which represent the sides and corners of a rectangular shape.
|
||||||
|
|
||||||
|
//XXX: this is kinda a mess, either move the rect struct here or come up with a better name for this module
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
|
mod rect;
|
||||||
|
pub use rect::Rect;
|
||||||
|
|
||||||
|
mod sides;
|
||||||
|
pub use sides::Sides;
|
||||||
|
|
||||||
|
mod corners;
|
||||||
|
pub use corners::Corners;
|
||||||
|
|
||||||
|
mod color;
|
||||||
|
pub use color::FillColor;
|
187
hui/src/rect/color.rs
Normal file
187
hui/src/rect/color.rs
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
use super::Corners;
|
||||||
|
use glam::{Vec3, Vec4, vec4};
|
||||||
|
|
||||||
|
/// Represents the fill color of a rectangle
|
||||||
|
///
|
||||||
|
/// Can be a single color or a simple gradient with different colors for each corner
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct FillColor(Corners<Vec4>);
|
||||||
|
|
||||||
|
impl FillColor {
|
||||||
|
pub const fn new(corners: Corners<Vec4>) -> Self {
|
||||||
|
Self(corners)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transparent background (alpha = 0)
|
||||||
|
pub const TRANSPARENT: Self = Self::rgba(0., 0., 0., 0.);
|
||||||
|
|
||||||
|
/// Transparent background (alpha = 0)
|
||||||
|
pub const fn transparent() -> Self {
|
||||||
|
Self::TRANSPARENT
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the fill color is completely transparent
|
||||||
|
///
|
||||||
|
/// (i.e. all corners have an alpha value of 0.0)
|
||||||
|
pub fn is_transparent(&self) -> bool {
|
||||||
|
self.0.top_left.w == 0. &&
|
||||||
|
self.0.top_right.w == 0. &&
|
||||||
|
self.0.bottom_left.w == 0. &&
|
||||||
|
self.0.bottom_right.w == 0.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the fill color is completely opaque
|
||||||
|
///
|
||||||
|
/// (i.e. all corners have an alpha value of 1.0)
|
||||||
|
pub fn is_opaque(&self) -> bool {
|
||||||
|
self.0.top_left.w == 1. &&
|
||||||
|
self.0.top_right.w == 1. &&
|
||||||
|
self.0.bottom_left.w == 1. &&
|
||||||
|
self.0.bottom_right.w == 1.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a solid color fill from values representing the red, green, blue and alpha channels
|
||||||
|
pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||||
|
Self(Corners {
|
||||||
|
top_left: vec4(r, g, b, a),
|
||||||
|
top_right: vec4(r, g, b, a),
|
||||||
|
bottom_left: vec4(r, g, b, a),
|
||||||
|
bottom_right: vec4(r, g, b, a),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a solid color fill from three values representing the red, green and blue channels
|
||||||
|
pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
|
||||||
|
Self(Corners {
|
||||||
|
top_left: vec4(r, g, b, 1.0),
|
||||||
|
top_right: vec4(r, g, b, 1.0),
|
||||||
|
bottom_left: vec4(r, g, b, 1.0),
|
||||||
|
bottom_right: vec4(r, g, b, 1.0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a solid color fill from colors for each corner
|
||||||
|
pub const fn from_corners(corners: Corners<Vec4>) -> Self {
|
||||||
|
Self(corners)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a list of the colors for each corner
|
||||||
|
pub const fn corners(&self) -> Corners<Vec4> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FillColor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Corners::all(vec4(0.0, 0.0, 0.0, 1.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Corners<Vec4>> for FillColor {
|
||||||
|
fn from(corners: Corners<Vec4>) -> Self {
|
||||||
|
Self(corners)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FillColor> for Corners<Vec4> {
|
||||||
|
fn from(corners: FillColor) -> Self {
|
||||||
|
corners.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec4> for FillColor {
|
||||||
|
fn from(value: Vec4) -> Self {
|
||||||
|
Self(Corners::all(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(f32, f32, f32, f32)> for FillColor {
|
||||||
|
fn from((r, g, b, a): (f32, f32, f32, f32)) -> Self {
|
||||||
|
Self(Corners::all(vec4(r, g, b, a)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[f32; 4]> for FillColor {
|
||||||
|
fn from([r, g, b, a]: [f32; 4]) -> Self {
|
||||||
|
Self(Corners::all(vec4(r, g, b, a)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec3> for FillColor {
|
||||||
|
fn from(value: Vec3) -> Self {
|
||||||
|
Self(Corners::all(vec4(value.x, value.y, value.z, 1.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(f32, f32, f32)> for FillColor {
|
||||||
|
fn from((r, g, b): (f32, f32, f32)) -> Self {
|
||||||
|
Self(Corners::all(vec4(r, g, b, 1.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[f32; 3]> for FillColor {
|
||||||
|
fn from([r, g, b]: [f32; 3]) -> Self {
|
||||||
|
Self(Corners::all(vec4(r, g, b, 1.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(Vec4, Vec4, Vec4, Vec4)> for FillColor {
|
||||||
|
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 FillColor {
|
||||||
|
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 FillColor {
|
||||||
|
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 FillColor {
|
||||||
|
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 FillColor {
|
||||||
|
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 FillColor {
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
85
hui/src/rect/corners.rs
Normal file
85
hui/src/rect/corners.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
hui/src/rect/rect.rs
Normal file
65
hui/src/rect/rect.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
hui/src/rect/sides.rs
Normal file
49
hui/src/rect/sides.rs
Normal 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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,201 +0,0 @@
|
||||||
//! Contains types which represent the sides and corners of a rectangular shape.
|
|
||||||
|
|
||||||
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
|
|
||||||
&& 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,21 @@
|
||||||
|
//! signal handling for UI events
|
||||||
|
|
||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use nohash_hasher::BuildNoHashHasher;
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
|
|
||||||
/// A marker trait for signals
|
pub mod trigger;
|
||||||
pub trait UiSignal: Any {}
|
|
||||||
|
/// A marker trait for UI Signals
|
||||||
|
pub trait Signal: Any {}
|
||||||
|
|
||||||
// #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
// #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
||||||
// pub(crate) struct DummySignal;
|
// pub(crate) struct DummySignal;
|
||||||
// impl UiSignal for DummySignal {}
|
// impl UiSignal for DummySignal {}
|
||||||
|
|
||||||
|
/// Internal storage for signals
|
||||||
pub struct SignalStore {
|
pub struct SignalStore {
|
||||||
|
//TODO use a multithreaded queue instead, to allow easily offloading ui processing to a different thread
|
||||||
///XXX: is this truly the most efficient structure?
|
///XXX: is this truly the most efficient structure?
|
||||||
sig: HashMap<TypeId, Vec<Box<dyn Any>>, BuildNoHashHasher<u64>>
|
sig: HashMap<TypeId, Vec<Box<dyn Any>>, BuildNoHashHasher<u64>>
|
||||||
}
|
}
|
||||||
|
@ -23,7 +29,7 @@ impl SignalStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that store for given signal type exists and return a mutable reference to it
|
/// 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>();
|
let type_id = TypeId::of::<T>();
|
||||||
self.sig.entry(type_id).or_default()
|
self.sig.entry(type_id).or_default()
|
||||||
}
|
}
|
||||||
|
@ -31,7 +37,7 @@ impl SignalStore {
|
||||||
/// Add a signal to the store
|
/// Add a signal to the store
|
||||||
///
|
///
|
||||||
/// Signals are stored in the order they are added
|
/// 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>();
|
let type_id = TypeId::of::<T>();
|
||||||
if let Some(v) = self.sig.get_mut(&type_id) {
|
if let Some(v) = self.sig.get_mut(&type_id) {
|
||||||
v.push(Box::new(sig));
|
v.push(Box::new(sig));
|
||||||
|
@ -41,7 +47,7 @@ impl SignalStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drain all signals of a given type
|
/// 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>()
|
self.internal_store::<T>()
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.map(|x| *x.downcast::<T>().unwrap()) //unchecked?
|
.map(|x| *x.downcast::<T>().unwrap()) //unchecked?
|
||||||
|
@ -54,7 +60,6 @@ impl SignalStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO this, simplifies handling signals
|
|
||||||
|
|
||||||
// pub trait Signal {
|
// pub trait Signal {
|
||||||
// type Arg;
|
// type Arg;
|
||||||
|
|
71
hui/src/signal/trigger.rs
Normal file
71
hui/src/signal/trigger.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
//! Contains the implementation of signal triggers, which simplify creation of custom elements
|
||||||
|
|
||||||
|
use super::{Signal, SignalStore};
|
||||||
|
//use crate::element::UiElement;
|
||||||
|
|
||||||
|
/// Signal trigger that does not take any arguments
|
||||||
|
#[allow(clippy::complexity)]
|
||||||
|
pub struct SignalTrigger(Box<dyn Fn(&mut SignalStore)>);
|
||||||
|
|
||||||
|
impl SignalTrigger {
|
||||||
|
/// Create a new signal trigger from a function or a constructor
|
||||||
|
pub fn new<S: Signal + 'static, F: Fn() -> S + 'static>(f: F) -> Self {
|
||||||
|
Self(Box::new(move |s: &mut SignalStore| {
|
||||||
|
s.add::<S>(f());
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fire the signal
|
||||||
|
pub fn fire(&self, s: &mut SignalStore) {
|
||||||
|
(self.0)(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signal trigger that takes a single argument and passes it to the signal
|
||||||
|
#[allow(clippy::complexity)]
|
||||||
|
pub struct SignalTriggerArg<T>(Box<dyn Fn(&mut SignalStore, T)>);
|
||||||
|
|
||||||
|
impl<T> SignalTriggerArg<T> {
|
||||||
|
/// Create a new signal trigger from a function or a constructor
|
||||||
|
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));
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fire the signal with the given argument
|
||||||
|
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);
|
||||||
|
// }
|
||||||
|
// }
|
Loading…
Reference in a new issue