diff --git a/hui-examples/boilerplate.rs b/hui-examples/boilerplate.rs index 146fb1e..e40b817 100644 --- a/hui-examples/boilerplate.rs +++ b/hui-examples/boilerplate.rs @@ -42,7 +42,7 @@ pub fn ui( let mut hui = UiInstance::new(); let mut backend = GliumUiRenderer::new(&display); - let result = init(&mut hui); + let mut result = init(&mut hui); event_loop.run(|event, window_target| { window.request_redraw(); @@ -60,7 +60,7 @@ pub fn ui( hui.begin(); let size = UVec2::from(display.get_framebuffer_dimensions()).as_vec2(); - draw(&mut hui, size, &result); + draw(&mut hui, size, &mut result); hui.end(); diff --git a/hui-examples/examples/ui_test_5_input.rs b/hui-examples/examples/ui_test_5_input.rs new file mode 100644 index 0000000..22237b2 --- /dev/null +++ b/hui-examples/examples/ui_test_5_input.rs @@ -0,0 +1,38 @@ +use std::time::Instant; +use hui::{ + color, size, + layout::{Alignment, Direction}, + element::{ + container::Container, + fill_rect::FillRect, + interactable::ElementInteractableExt, + UiElementExt + }, +}; + +#[path = "../boilerplate.rs"] +#[macro_use] +mod boilerplate; + +ui_main!( + "hUI: Internal input test", + init: |_| {}, + run: |ui, size, _| { + Container::default() + .with_size(size!(100%)) + .with_align(Alignment::Center) + .with_background(color::WHITE) + .with_children(|ui| { + FillRect::default() + .with_size(size!(40)) + .with_corner_radius(8.) + .with_background(color::DARK_RED) + .into_interactable() + .on_click(|| { + println!("clicked"); + }) + .add_child(ui); + }) + .add_root(ui, size); + } +); diff --git a/hui/src/element/builtin.rs b/hui/src/element/builtin.rs index 469a5ca..1724c2c 100644 --- a/hui/src/element/builtin.rs +++ b/hui/src/element/builtin.rs @@ -1,3 +1,5 @@ +// "The essentials": + #[cfg(feature = "builtin_container")] pub mod container; @@ -7,17 +9,24 @@ pub mod fill_rect; #[cfg(feature = "builtin_elements")] pub mod spacer; -#[cfg(feature = "builtin_elements")] -pub mod progress_bar; +// "The basics": #[cfg(feature = "builtin_elements")] pub mod text; +#[cfg(feature = "builtin_elements")] +pub mod image; + +#[cfg(feature = "builtin_elements")] +pub mod progress_bar; + +// Wrappers: + #[cfg(feature = "builtin_elements")] pub mod transformer; #[cfg(feature = "builtin_elements")] -pub mod image; +pub mod interactable; //TODO add: Image //TODO add: OverlayContainer (for simply laying multiple elements on top of each other) diff --git a/hui/src/element/builtin/interactable.rs b/hui/src/element/builtin/interactable.rs index f9ef1ec..d59a3b9 100644 --- a/hui/src/element/builtin/interactable.rs +++ b/hui/src/element/builtin/interactable.rs @@ -1,55 +1,79 @@ -//TODO this thing? -//not sure if this is a good idea... -//but having the ability to add a click event to any element would be nice, and this is a naive way to do it +//! wrapper that allows adding click and hover events to any element -// use crate::element::{UiElement, MeasureContext, ProcessContext}; +// not sure if this is a good idea... +// but having the ability to add a click event to any element would be nice, and this is a naive way to do it -// pub struct Interactable { -// pub element: T, -// pub hovered: Option>, -// pub clicked: Option>, -// } +use crate::element::{UiElement, MeasureContext, ProcessContext}; +use std::cell::RefCell; -// impl Interactable { -// pub fn new(element: T) -> Self { -// Self { -// element, -// hovered: None, -// clicked: None, -// } -// } +/// Wrapper that allows adding click and hover events to any element +pub struct Interactable { + /// The wrapped element that will be interactable + pub element: Box, + /// Function that will be called if the element is hovered in the current frame + /// + /// Will be consumed after the first time it's called + pub hovered: RefCell>>, + /// Function that will be called if the element was clicked in the current frame + /// + /// Will be consumed after the first time it's called + pub clicked: RefCell>>, +} -// pub fn on_click(self, clicked: impl FnOnce() + 'static) -> Self { -// Self { -// clicked: Some(Box::new(clicked)), -// ..self -// } -// } +impl Interactable { + pub fn new(element: Box) -> Self { + Self { + element, + hovered: RefCell::new(None), + clicked: RefCell::new(None), + } + } -// pub fn on_hover(self, clicked: impl FnOnce() + 'static) -> Self { -// Self { -// clicked: Some(Box::new(clicked)), -// ..self -// } -// } -// } + pub fn on_click(self, clicked: impl FnOnce() + 'static) -> Self { + Self { + clicked: RefCell::new(Some(Box::new(clicked))), + ..self + } + } -// impl UiElement for Interactable { -// fn measure(&self, ctx: MeasureContext) -> crate::measure::Response { -// self.element.measure(ctx) -// } + pub fn on_hover(self, clicked: impl FnOnce() + 'static) -> Self { + Self { + clicked: RefCell::new(Some(Box::new(clicked))), + ..self + } + } +} -// fn process(&self, ctx: ProcessContext) { -// self.element.process(ctx) -// } -// } +impl UiElement for Interactable { + fn name(&self) -> &'static str { + "Interactable" + } -// pub trait IntoInteractable: UiElement { -// fn into_interactable(self) -> Interactable; -// } + fn measure(&self, ctx: MeasureContext) -> crate::measure::Response { + self.element.measure(ctx) + } -// impl IntoInteractable for T { -// fn into_interactable(self) -> Interactable { -// Interactable::new(self) -// } -// } + fn process(&self, ctx: ProcessContext) { + let rect = ctx.measure.rect(ctx.layout.position); + + //XXX: should we do this AFTER normal process call of wrapped element? + //TODO other events... + if ctx.input.check_click(rect) { + //TODO better error message + let clicked = self.clicked.borrow_mut().take().expect("you fucked up"); + clicked(); + } + + self.element.process(ctx) + } +} + +pub trait ElementInteractableExt: UiElement { + fn into_interactable(self) -> Interactable; +} + +impl ElementInteractableExt for T { + fn into_interactable(self) -> Interactable { + Interactable::new(Box::new(self)) + } +} diff --git a/hui/src/input.rs b/hui/src/input.rs index 2ba3681..506e158 100644 --- a/hui/src/input.rs +++ b/hui/src/input.rs @@ -203,7 +203,9 @@ impl UiInputState { button.start_position = self.mouse_pointer.current_position; }, ButtonState::Released => { + //log::trace!("Button {:?} was released", button); if let Some(button_meta) = self.mouse_pointer.buttons.remove(button) { + //log::trace!("start pos was: {:?} current pos is: {:?}", button_meta.start_position, self.mouse_pointer.current_position); self.mouse_pointer.released_buttons.insert(*button, button_meta); } else { //huh diff --git a/hui/src/measure.rs b/hui/src/measure.rs index b0cd7ac..17607f2 100644 --- a/hui/src/measure.rs +++ b/hui/src/measure.rs @@ -1,6 +1,7 @@ //! element measurement, hints and responses use glam::Vec2; +use crate::rectangle::Rect; // #[non_exhaustive] #[derive(Default)] @@ -27,3 +28,12 @@ pub struct Response { /// You should almost never set this, and the exact behavior may change in the future pub should_wrap: bool, } + +impl Response { + pub fn rect(&self, position: Vec2) -> Rect { + Rect { + position, + size: self.size, + } + } +}