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

View file

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

View file

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

View file

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

View file

@ -40,7 +40,9 @@ pub struct ProcessContext<'a> {
}
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;
/// Get the unique id used for internal state management\

View file

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

View file

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

View file

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

View file

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

View file

@ -5,9 +5,8 @@
use crate::{
element::{MeasureContext, ProcessContext, UiElement},
signal::Signal,
signal::{trigger::SignalTrigger, Signal},
};
use std::cell::RefCell;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
@ -15,10 +14,11 @@ pub enum InteractableEvent {
#[default]
Click,
Hover,
Active,
}
/// 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
pub element: Box<dyn UiElement>,
@ -26,22 +26,26 @@ pub struct Interactable<C: Signal + 'static> {
pub event: InteractableEvent,
/// 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> {
pub fn new(element: Box<dyn UiElement>, event: InteractableEvent, signal: C) -> Self {
impl Interactable {
pub fn new<S: Signal, F: Fn() -> S + 'static>(
element: Box<dyn UiElement>,
event: InteractableEvent,
signal: F
) -> Self {
Self {
element,
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 {
"Interactable"
"interactable"
}
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
InteractableEvent::Click => ctx.input.check_click(rect).is_some(),
InteractableEvent::Hover => ctx.input.check_hover(rect),
InteractableEvent::Active => ctx.input.check_active(rect).is_some(),
};
if event_happened {
if let Some(sig) = self.signal.take() {
ctx.signal.add(sig);
}
self.signal.fire(ctx.signal);
}
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`]
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: 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
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
fn on_hover<C: Signal + 'static>(self, signal: C) -> Interactable<C>;
/// Wrap the element in an [`Interactable`] that will call the given signal continuously while hovered
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 {
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)
}
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)
}
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)
}
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 {
fn name(&self) -> &'static str {
"ProgressBar"
"progress_bar"
}
fn measure(&self, ctx: MeasureContext) -> Response {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,8 @@
use crate::rect::FillColor;
pub mod point;
pub mod layer;
use layer::{FrameLayer, RectLayer};
use glam::Vec2;
use layer::{FrameLayer, FrameLayerImpl};
use crate::draw::UiDrawCommandList;
///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)
@ -20,23 +19,49 @@ pub struct Frame {
layers: Vec<FrameLayer>
}
impl<T: Into<FillColor>> From<T> for Frame {
fn from(color: T) -> Self {
impl<T: Into<FrameLayer>> From<T> for Frame {
fn from(layer: T) -> Self {
let mut frame = Self::default();
frame.add(RectLayer::from_color(color));
frame.add(layer.into());
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]
pub fn add(&mut self, layer: impl Into<FrameLayer>) -> &mut Self {
self.layers.push(layer.into());
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]
pub fn finish(&mut self) -> Self {
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]
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)]
#[enum_dispatch(FrameLayerImpl)]
pub enum FrameLayer {
Rect(RectLayer),
Rect(RectFrame),
}
#[derive(Clone, Copy)]
pub struct RectLayer {
color: FillColor,
image: Option<ImageHandle>,
top_left: FramePoint2d,
bottom_right: FramePoint2d,
corner_radius: Corners<f32>,
pub struct RectFrame {
/// Background color of the frame\
///
/// If the container has a background texture, it will be multiplied by this color
pub color: FillColor,
/// 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 {
Self {
color: color.into(),
@ -58,27 +81,45 @@ impl RectLayer {
..Self::default()
}
}
}
impl Default for RectLayer {
fn default() -> Self {
pub fn from_color_image_rounded(color: impl Into<FillColor>, image: ImageHandle, corner_radius: impl Into<Corners<f32>>) -> Self {
Self {
color: FillColor::default(),
image: None,
top_left: FramePoint2d::default(),
bottom_right: FramePoint2d::default(),
corner_radius: Corners::default(),
color: color.into(),
image: Some(image),
corner_radius: corner_radius.into(),
..Self::default()
}
}
/// 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 FrameLayerImpl for RectLayer {
fn draw(&self, draw: &mut UiDrawCommandList, parent_size: Vec2) {
impl Default for RectFrame {
fn default() -> Self {
Self {
color: FillColor::default(),
image: None,
top_left: FramePoint2d::TOP_LEFT,
bottom_right: FramePoint2d::BOTTOM_RIGHT,
corner_radius: Corners::all(0.),
}
}
}
impl FrameLayerImpl for RectFrame {
fn draw(&self, draw: &mut UiDrawCommandList, position: Vec2, parent_size: Vec2) {
//TODO: handle bottom_right < top_left
let top_left = self.top_left.resolve(parent_size);
let bottom_right = self.bottom_right.resolve(parent_size);
draw.add(UiDrawCommand::Rectangle {
position: top_left,
position: position + top_left,
size: bottom_right - top_left,
color: self.color.corners(),
texture: self.image,