Compare commits

...

12 commits

Author SHA1 Message Date
griffi-gh fb2f3c739e remove line 2024-03-23 19:55:04 +01:00
griffi-gh b12c62e06f frame api is a mess... 2024-03-23 19:53:32 +01:00
griffi-gh 3ac83e161a rename stuff... 2024-03-23 19:42:53 +01:00
griffi-gh d1e1325068 fvck 2024-03-23 17:54:47 +01:00
griffi-gh 5ce9dda77b change name function to lower case 2024-03-23 15:54:33 +01:00
griffi-gh fa994e659a rename Br to Break 2024-03-23 15:52:54 +01:00
griffi-gh 328f745c39 add interactable active check 2024-03-23 15:52:14 +01:00
griffi-gh e22fa739c1 use new trigger api for interactable 2024-03-23 15:50:44 +01:00
griffi-gh 89037efebb add todo note 2024-03-23 15:43:22 +01:00
griffi-gh d347f8f7e9 for now, make frame non-pub 2024-03-23 15:41:19 +01:00
griffi-gh 85810a2e59 impl from corners f32 for roundedcorners 2024-03-23 15:10:32 +01:00
griffi-gh 7bfd12749b aaa 2024-03-23 15:09:05 +01:00
17 changed files with 154 additions and 69 deletions

View file

@ -7,7 +7,7 @@ use hui::{
container::Container, container::Container,
text::Text, text::Text,
image::Image, image::Image,
br::Br, br::Break,
interactable::ElementInteractableExt, interactable::ElementInteractableExt,
UiElementExt, UiElementExt,
}, },
@ -44,7 +44,7 @@ ui_main!(
Text::new("Number of images:") Text::new("Number of images:")
.with_text_size(24) .with_text_size(24)
.add_child(ui); .add_child(ui);
Br.add_child(ui); Break.add_child(ui);
Container::default() Container::default()
.with_padding(10.) .with_padding(10.)
.with_background(color::ORANGE) .with_background(color::ORANGE)
@ -53,7 +53,7 @@ ui_main!(
.with_text_size(32) .with_text_size(32)
.add_child(ui); .add_child(ui);
}) })
.on_click(CounterSignal::Decrement) .on_click(|| CounterSignal::Decrement)
.add_child(ui); .add_child(ui);
Container::default() Container::default()
.with_size(size!(60, auto)) .with_size(size!(60, auto))
@ -72,9 +72,9 @@ ui_main!(
.with_text_size(32) .with_text_size(32)
.add_child(ui); .add_child(ui);
}) })
.on_click(CounterSignal::Increment) .on_click(|| CounterSignal::Increment)
.add_child(ui); .add_child(ui);
Br.add_child(ui); Break.add_child(ui);
for _ in 0..*counter { for _ in 0..*counter {
Image::new(*image) Image::new(*image)
.with_size(size!(48, 48)) .with_size(size!(48, 48))

View file

@ -6,7 +6,7 @@ use hui::{
container::Container, container::Container,
text::Text, text::Text,
image::Image, image::Image,
br::Br, br::Break,
slider::Slider, slider::Slider,
UiElementExt, UiElementExt,
}, },
@ -43,22 +43,22 @@ ui_main!(
Text::new(format!("Number of images: {counter}")) Text::new(format!("Number of images: {counter}"))
.with_text_size(32) .with_text_size(32)
.add_child(ui); .add_child(ui);
Br.add_child(ui); Break.add_child(ui);
Text::new("Absolute tracking slider:") Text::new("Absolute tracking slider:")
.with_text_size(16) .with_text_size(16)
.add_child(ui); .add_child(ui);
Br.add_child(ui); Break.add_child(ui);
Slider::new(*counter as f32 / 100.) Slider::new(*counter as f32 / 100.)
.with_size(size!(66%, 20)) .with_size(size!(66%, 20))
.on_change(|x| { .on_change(|x| {
CounterSignal::ChangeValue((x * 100.).round() as u32) CounterSignal::ChangeValue((x * 100.).round() as u32)
}) })
.add_child(ui); .add_child(ui);
Br.add_child(ui); Break.add_child(ui);
Text::new("Relative tracking slider (Experimental):") Text::new("Relative tracking slider (Experimental):")
.with_text_size(16) .with_text_size(16)
.add_child(ui); .add_child(ui);
Br.add_child(ui); Break.add_child(ui);
Slider::new(*counter as f32 / 100.) Slider::new(*counter as f32 / 100.)
.with_size(size!(66%, 20)) .with_size(size!(66%, 20))
.with_follow_mode(hui::element::slider::SliderFollowMode::Relative) .with_follow_mode(hui::element::slider::SliderFollowMode::Relative)
@ -66,7 +66,7 @@ ui_main!(
CounterSignal::ChangeValue((x * 100.).round() as u32) CounterSignal::ChangeValue((x * 100.).round() as u32)
}) })
.add_child(ui); .add_child(ui);
Br.add_child(ui); Break.add_child(ui);
for _ in 0..*counter { for _ in 0..*counter {
Image::new(*image) Image::new(*image)
.with_size(size!(48, 48)) .with_size(size!(48, 48))

View file

@ -55,6 +55,7 @@ pub enum UiDrawCommand {
PushTransform(Affine2), PushTransform(Affine2),
/// Pop a transformation matrix from the stack /// Pop a transformation matrix from the stack
PopTransform, PopTransform,
//TODO PushClip PopClip
} }
/// List of draw commands /// List of draw commands

View file

@ -17,6 +17,12 @@ pub struct RoundedCorners {
pub point_count: NonZeroU16, pub point_count: NonZeroU16,
} }
impl From<Corners<f32>> for RoundedCorners {
fn from(radius: Corners<f32>) -> Self {
Self::from_radius(radius)
}
}
impl RoundedCorners { impl RoundedCorners {
pub fn from_radius(radius: Corners<f32>) -> Self { pub fn from_radius(radius: Corners<f32>) -> Self {
Self { Self {

View file

@ -40,7 +40,9 @@ pub struct ProcessContext<'a> {
} }
pub trait UiElement { pub trait UiElement {
/// Get the name of the element, for example "Button" or "ProgressBar" /// Get the name of the element (in lower case)
///
/// For example, "button" or "progress_bar"
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
/// Get the unique id used for internal state management\ /// Get the unique id used for internal state management\

View file

@ -4,11 +4,11 @@ use crate::{
}; };
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
pub struct Br; pub struct Break;
impl UiElement for Br { impl UiElement for Break {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Br" "break"
} }
fn measure(&self, _: MeasureContext) -> Response { fn measure(&self, _: MeasureContext) -> Response {

View file

@ -133,7 +133,7 @@ impl Container {
impl UiElement for Container { impl UiElement for Container {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Container" "container"
} }
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {

View file

@ -40,7 +40,7 @@ impl Default for FillRect {
impl UiElement for FillRect { impl UiElement for FillRect {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"FillRect" "fill_rect"
} }
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {

View file

@ -51,7 +51,7 @@ impl Image {
impl UiElement for Image { impl UiElement for Image {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Image" "image"
} }
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {

View file

@ -5,9 +5,8 @@
use crate::{ use crate::{
element::{MeasureContext, ProcessContext, UiElement}, element::{MeasureContext, ProcessContext, UiElement},
signal::Signal, signal::{trigger::SignalTrigger, Signal},
}; };
use std::cell::RefCell;
#[non_exhaustive] #[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
@ -15,10 +14,11 @@ pub enum InteractableEvent {
#[default] #[default]
Click, Click,
Hover, Hover,
Active,
} }
/// 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: Signal + 'static> { pub struct Interactable {
/// The wrapped element that will be interactable /// The wrapped element that will be interactable
pub element: Box<dyn UiElement>, pub element: Box<dyn UiElement>,
@ -26,22 +26,26 @@ pub struct Interactable<C: Signal + 'static> {
pub event: InteractableEvent, pub event: InteractableEvent,
/// Signal that will be called if the element was clicked in the current frame /// Signal that will be called if the element was clicked in the current frame
pub signal: RefCell<Option<C>>, pub signal: SignalTrigger,
} }
impl<C: Signal + 'static> Interactable<C> { impl Interactable {
pub fn new(element: Box<dyn UiElement>, event: InteractableEvent, signal: C) -> Self { pub fn new<S: Signal, F: Fn() -> S + 'static>(
element: Box<dyn UiElement>,
event: InteractableEvent,
signal: F
) -> Self {
Self { Self {
element, element,
event, event,
signal: RefCell::new(Some(signal)), signal: SignalTrigger::new(signal),
} }
} }
} }
impl<C: Signal + 'static> UiElement for Interactable<C> { impl UiElement for Interactable {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Interactable" "interactable"
} }
fn measure(&self, ctx: MeasureContext) -> crate::measure::Response { fn measure(&self, ctx: MeasureContext) -> crate::measure::Response {
@ -56,12 +60,11 @@ impl<C: Signal + 'static> UiElement for Interactable<C> {
//TODO: actually pass the response //TODO: actually pass the response
InteractableEvent::Click => ctx.input.check_click(rect).is_some(), InteractableEvent::Click => ctx.input.check_click(rect).is_some(),
InteractableEvent::Hover => ctx.input.check_hover(rect), InteractableEvent::Hover => ctx.input.check_hover(rect),
InteractableEvent::Active => ctx.input.check_active(rect).is_some(),
}; };
if event_happened { if event_happened {
if let Some(sig) = self.signal.take() { self.signal.fire(ctx.signal);
ctx.signal.add(sig);
}
} }
self.element.process(ctx) self.element.process(ctx)
@ -71,25 +74,32 @@ impl<C: Signal + '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: Signal + 'static>(self, event: InteractableEvent, signal: C) -> Interactable<C>; fn into_interactable<S: Signal, F: Fn() -> S + 'static>(self, event: InteractableEvent, signal: F) -> Interactable;
/// 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: Signal + 'static>(self, signal: C) -> Interactable<C>; fn on_click<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable;
/// 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 continuously while hovered
fn on_hover<C: Signal + 'static>(self, signal: C) -> Interactable<C>; fn on_hover<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable;
/// Wrap the element in an [`Interactable`] that will call the given signal continuously while active
fn on_active<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable;
} }
impl<T: UiElement + 'static> ElementInteractableExt for T { impl<T: UiElement + 'static> ElementInteractableExt for T {
fn into_interactable<C: Signal + 'static>(self, event: InteractableEvent, signal: C) -> Interactable<C> { fn into_interactable<S: Signal, F: Fn() -> S + 'static>(self, event: InteractableEvent, signal: F) -> Interactable {
Interactable::new(Box::new(self), event, signal) Interactable::new(Box::new(self), event, signal)
} }
fn on_click<C: Signal + 'static>(self, signal: C) -> Interactable<C> { fn on_click<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable {
self.into_interactable(InteractableEvent::Click, signal) self.into_interactable(InteractableEvent::Click, signal)
} }
fn on_hover<C: Signal + 'static>(self, signal: C) -> Interactable<C> { fn on_hover<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable {
self.into_interactable(InteractableEvent::Hover, signal) self.into_interactable(InteractableEvent::Hover, signal)
} }
fn on_active<S: Signal, F: Fn() -> S + 'static>(self, signal: F) -> Interactable {
self.into_interactable(InteractableEvent::Active, signal)
}
} }

View file

@ -49,7 +49,7 @@ impl Default for ProgressBar {
impl UiElement for ProgressBar { impl UiElement for ProgressBar {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"ProgressBar" "progress_bar"
} }
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {

View file

@ -103,7 +103,7 @@ impl Slider {
impl UiElement for Slider { impl UiElement for Slider {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Slider" "slider"
} }
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {

View file

@ -19,7 +19,7 @@ impl Default for Spacer {
impl UiElement for Spacer { impl UiElement for Spacer {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Spacer" "spacer"
} }
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {

View file

@ -71,7 +71,7 @@ impl Text {
impl UiElement for Text { impl UiElement for Text {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Text" "text"
} }
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {

View file

@ -38,7 +38,7 @@ impl Transformer {
impl UiElement for Transformer { impl UiElement for Transformer {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Transformer" "transformer"
} }
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {

View file

@ -1,9 +1,8 @@
use crate::rect::FillColor;
pub mod point; pub mod point;
pub mod layer; pub mod layer;
use glam::Vec2;
use layer::{FrameLayer, RectLayer}; use layer::{FrameLayer, FrameLayerImpl};
use crate::draw::UiDrawCommandList;
///XXX: this is not used yet, and also kinda a mess, simplify? ///XXX: this is not used yet, and also kinda a mess, simplify?
///Maybe limit to a single layer? (aka `Frame` will be just one of the options) ///Maybe limit to a single layer? (aka `Frame` will be just one of the options)
@ -20,23 +19,49 @@ pub struct Frame {
layers: Vec<FrameLayer> layers: Vec<FrameLayer>
} }
impl<T: Into<FillColor>> From<T> for Frame { impl<T: Into<FrameLayer>> From<T> for Frame {
fn from(color: T) -> Self { fn from(layer: T) -> Self {
let mut frame = Self::default(); let mut frame = Self::default();
frame.add(RectLayer::from_color(color)); frame.add(layer.into());
frame frame
} }
} }
impl Frame { impl Frame {
/// Get the layer with the given index
#[inline]
pub fn layer(&self, index: usize) -> Option<&FrameLayer> {
self.layers.get(index)
}
/// Get a mutable reference to the layer with the given index
#[inline]
pub fn layer_mut(&mut self, index: usize) -> Option<&mut FrameLayer> {
self.layers.get_mut(index)
}
/// Add a layer to the frame
#[inline] #[inline]
pub fn add(&mut self, layer: impl Into<FrameLayer>) -> &mut Self { pub fn add(&mut self, layer: impl Into<FrameLayer>) -> &mut Self {
self.layers.push(layer.into()); self.layers.push(layer.into());
self self
} }
/// Add a layer to the back of the frame
#[inline]
pub fn add_back(&mut self, layer: impl Into<FrameLayer>) -> &mut Self {
self.layers.insert(0, layer.into());
self
}
#[inline] #[inline]
pub fn finish(&mut self) -> Self { pub fn finish(&mut self) -> Self {
self.clone() self.clone()
} }
pub(crate) fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
for layer in &self.layers {
layer.draw(draw, position, parent_size);
}
}
} }

View file

@ -9,25 +9,48 @@ use super::point::FramePoint2d;
#[enum_dispatch] #[enum_dispatch]
pub(crate) trait FrameLayerImpl { pub(crate) trait FrameLayerImpl {
fn draw(&self, draw: &mut UiDrawCommandList, parent_size: Vec2); fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2);
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[enum_dispatch(FrameLayerImpl)] #[enum_dispatch(FrameLayerImpl)]
pub enum FrameLayer { pub enum FrameLayer {
Rect(RectLayer), Rect(RectFrame),
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct RectLayer { pub struct RectFrame {
color: FillColor, /// Background color of the frame\
image: Option<ImageHandle>, ///
top_left: FramePoint2d, /// If the container has a background texture, it will be multiplied by this color
bottom_right: FramePoint2d, pub color: FillColor,
corner_radius: Corners<f32>,
/// Background texture of the frame
///
/// Can be used in conjunction with the background color\
/// In this case, the texture will be shaded by the color
///
/// Please note that if the background color is NOT set (or set to transparent), the texture will NOT be visible\
/// This is because the texture is multiplied by the color, and if the color is transparent, the texture will be too\
pub image: Option<ImageHandle>,
/// Top left corner of the rectangle
pub top_left: FramePoint2d,
/// Bottom right corner of the rectangle
pub bottom_right: FramePoint2d,
/// Corner radius of the frame
pub corner_radius: Corners<f32>,
} }
impl RectLayer { impl<T: Into<FillColor>> From<T> for RectFrame {
fn from(color: T) -> Self {
Self::from_color(color)
}
}
impl RectFrame {
pub fn from_color(color: impl Into<FillColor>) -> Self { pub fn from_color(color: impl Into<FillColor>) -> Self {
Self { Self {
color: color.into(), color: color.into(),
@ -58,27 +81,45 @@ impl RectLayer {
..Self::default() ..Self::default()
} }
} }
pub fn from_color_image_rounded(color: impl Into<FillColor>, image: ImageHandle, corner_radius: impl Into<Corners<f32>>) -> Self {
Self {
color: color.into(),
image: Some(image),
corner_radius: corner_radius.into(),
..Self::default()
}
} }
impl Default for RectLayer { /// Inset the rectangle by the given amount
pub fn inset(self, inset: f32) -> Self {
Self {
top_left: self.top_left + Vec2::splat(inset).into(),
bottom_right: self.bottom_right - Vec2::splat(inset).into(),
..self
}
}
}
impl Default for RectFrame {
fn default() -> Self { fn default() -> Self {
Self { Self {
color: FillColor::default(), color: FillColor::default(),
image: None, image: None,
top_left: FramePoint2d::default(), top_left: FramePoint2d::TOP_LEFT,
bottom_right: FramePoint2d::default(), bottom_right: FramePoint2d::BOTTOM_RIGHT,
corner_radius: Corners::default(), corner_radius: Corners::all(0.),
} }
} }
} }
impl FrameLayerImpl for RectLayer { impl FrameLayerImpl for RectFrame {
fn draw(&self, draw: &mut UiDrawCommandList, parent_size: Vec2) { fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
//TODO: handle bottom_right < top_left //TODO: handle bottom_right < top_left
let top_left = self.top_left.resolve(parent_size); let top_left = self.top_left.resolve(parent_size);
let bottom_right = self.bottom_right.resolve(parent_size); let bottom_right = self.bottom_right.resolve(parent_size);
draw.add(UiDrawCommand::Rectangle { draw.add(UiDrawCommand::Rectangle {
position: top_left, position: position + top_left,
size: bottom_right - top_left, size: bottom_right - top_left,
color: self.color.corners(), color: self.color.corners(),
texture: self.image, texture: self.image,