diff --git a/hui-examples/examples/ui_test_6.rs b/hui-examples/examples/ui_test_6.rs new file mode 100644 index 0000000..ac2c6a4 --- /dev/null +++ b/hui-examples/examples/ui_test_6.rs @@ -0,0 +1,66 @@ +use hui::{ + color, size, + draw::TextureFormat, + signal::UiSignal, + layout::{Alignment, Direction}, + element::{ + container::Container, + text::Text, + image::Image, + br::Br, + interactable::ElementInteractableExt, + slider::Slider, + UiElementExt, + }, +}; + +enum CounterSignal { + Increment, + Decrement, +} +impl UiSignal for CounterSignal {} + +#[path = "../boilerplate.rs"] +#[macro_use] +mod boilerplate; + +const IMAGE_DATA: &[u8] = include_bytes!("../assets/icons/visual-studio-code-icon_32x32.rgba"); + +ui_main!( + "hUI: Internal input test", + init: |ui| { + let image = ui.add_image(TextureFormat::Rgba, IMAGE_DATA, 32); + (0, image) + }, + run: |ui, size, (ref mut counter, image)| { + Container::default() + .with_size(size!(100%)) + .with_padding(10.) + .with_align((Alignment::Center, Alignment::Begin)) + .with_direction(Direction::Horizontal) + .with_gap(5.) + .with_background((0.1, 0.1, 0.1)) + .with_wrap(true) + .with_children(|ui| { + Text::new("Number of images:") + .with_text_size(24) + .add_child(ui); + Br.add_child(ui); + Slider::new(0.5) + .with_size(size!(66%, 20)) + .add_child(ui); + Br.add_child(ui); + for _ in 0..*counter { + Image::new(*image) + .with_size(size!(48, 48)) + .add_child(ui); + } + }) + .add_root(ui, size); + + ui.process_signals(|sig| match sig { + CounterSignal::Increment => *counter += 1, + CounterSignal::Decrement => *counter -= 1, + }); + } +); diff --git a/hui/src/element/builtin/interactable.rs b/hui/src/element/builtin/interactable.rs index 1774620..d9d34ea 100644 --- a/hui/src/element/builtin/interactable.rs +++ b/hui/src/element/builtin/interactable.rs @@ -26,8 +26,6 @@ pub struct Interactable { pub event: InteractableEvent, /// Signal that will be called if the element was clicked in the current frame - /// - /// Will be consumed after the first time it's called pub signal: RefCell>, } @@ -55,7 +53,8 @@ impl UiElement for Interactable { //XXX: should we do this AFTER normal process call of wrapped element? let event_happened = match self.event { - InteractableEvent::Click => ctx.input.check_click(rect), + //TODO: actually pass the response + InteractableEvent::Click => ctx.input.check_click(rect).is_some(), InteractableEvent::Hover => ctx.input.check_hover(rect), }; diff --git a/hui/src/element/builtin/slider.rs b/hui/src/element/builtin/slider.rs index 81d6b9f..8b0b6fd 100644 --- a/hui/src/element/builtin/slider.rs +++ b/hui/src/element/builtin/slider.rs @@ -1,7 +1,71 @@ -use crate::element::UiElement; +//! work in progress +use derive_setters::Setters; + +use crate::{ + draw::UiDrawCommand, + element::{MeasureContext, ProcessContext, UiElement}, + layout::{compute_size, Size2d}, + measure::Response, + rectangle::Corners, + signal::UiSignal, +}; + +/// work in progress +#[derive(Default, Debug, Clone, Copy, Setters)] +#[setters(prefix = "with_")] pub struct Slider { pub value: f32, + pub size: Size2d, +} + +impl Slider { + pub const DEFAULT_HEIGHT: f32 = 20.0; + + pub fn new(value: f32) -> Self { + Self { + value, + ..Default::default() + } + } +} + +impl UiElement for Slider { + fn name(&self) -> &'static str { + "Slider" + } + + fn measure(&self, ctx: MeasureContext) -> Response { + Response { + size: compute_size(ctx.layout, self.size, (ctx.layout.max_size.x, Self::DEFAULT_HEIGHT).into()), + ..Default::default() + } + } + + fn process(&self, ctx: ProcessContext) { + ctx.draw.add(UiDrawCommand::Rectangle { + position: ctx.layout.position, + size: ctx.measure.size, + color: Corners::all((1., 0., 0., 1.).into()), + texture: None, + rounded_corners: None, + }); + + let value = self.value.clamp(0., 1.); + ctx.draw.add(UiDrawCommand::Rectangle { + position: ctx.layout.position, + size: (ctx.measure.size.x * value, ctx.measure.size.y).into(), + color: Corners::all((0., 1., 0., 1.).into()), + texture: None, + rounded_corners: None, + }); + + //handle click etc + if let Some(res) = ctx.input.check_click(ctx.measure.rect(ctx.layout.position)) { + let new_value = res.position_in_rect.x / ctx.measure.size.x; + //TODO call signal with new value + } + } } //TODO diff --git a/hui/src/input.rs b/hui/src/input.rs index 506e158..c832fad 100644 --- a/hui/src/input.rs +++ b/hui/src/input.rs @@ -234,6 +234,11 @@ impl UiInputState { } } +#[derive(Clone, Copy, Debug)] +pub struct ClickCheckResponse { + pub position_in_rect: Vec2, +} + #[derive(Clone, Copy)] pub struct InputCtx<'a>(&'a UiInputState); @@ -300,10 +305,12 @@ impl<'a> InputCtx<'a> { /// By default, this function only checks for the primary mouse button\ /// This is a limitation of the current API and may change in the future\ /// (as the current implementation of this function checks for both mouse and touch input, and the touch input quite obviously only supports one "button") - pub fn check_click(&self, rect: Rect) -> bool { + pub fn check_click(&self, rect: Rect) -> Option { let pos = self.0.mouse_pointer.current_position; self.0.mouse_pointer.released_buttons.get(&MouseButton::Primary).map_or(false, |meta| { rect.contains_point(meta.start_position) && rect.contains_point(pos) + }).then_some(ClickCheckResponse { + position_in_rect: pos - rect.position, }) } } diff --git a/hui/src/signal.rs b/hui/src/signal.rs index 62c2416..9cff759 100644 --- a/hui/src/signal.rs +++ b/hui/src/signal.rs @@ -56,16 +56,20 @@ impl SignalStore { //TODO this, simplifies handling signals -pub struct SignalTrigger(pub(crate) Box R>); +// pub struct SignalTrigger(pub(crate) Box R + 'static>); -impl SignalTrigger { - pub fn new R + 'static>(f: F) -> Self { - Self(Box::new(f)) - } -} +// impl SignalTrigger { +// pub fn new R + 'static>(f: F) -> Self { +// Self(Box::new(f)) +// } -impl R + 'static> From for SignalTrigger { - fn from(f: T) -> Self { - Self(Box::new(f)) - } -} +// pub fn call(&self, a: A) -> R { +// (self.0)(a) +// } +// } + +// impl R + 'static> From for SignalTrigger { +// fn from(f: T) -> Self { +// Self(Box::new(f)) +// } +// }