diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs index 20a2e24..77d866b 100644 --- a/uidev/src/testbed/testbed_generic.rs +++ b/uidev/src/testbed/testbed_generic.rs @@ -210,7 +210,7 @@ impl TestbedGeneric { }, )?; - let _state = wgui::parser::parse_from_assets( + let state = wgui::parser::parse_from_assets( &ParseDocumentParams { globals, path: XML_PATH, @@ -221,6 +221,18 @@ impl TestbedGeneric { widget.id, )?; + let button = state + .fetch_component_as::("button") + .unwrap(); + + button.on_click(Box::new(move |_common, _e| { + log::info!("click"); + Ok(()) + })); + + // temporary, preserve listeners state + Box::leak(Box::new(state)); + Ok(()) } } diff --git a/wgui/assets/wgui/window_frame.xml b/wgui/assets/wgui/window_frame.xml index cb62dcd..08e99a3 100644 --- a/wgui/assets/wgui/window_frame.xml +++ b/wgui/assets/wgui/window_frame.xml @@ -7,7 +7,6 @@ border_color="#778899" color="#001122ee" padding="2"> - @@ -18,7 +17,7 @@
diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index b8d52b8..0d118e7 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -11,7 +11,7 @@ use crate::{ util::centered_matrix, }, widget::{ - WidgetData, + EventResult, WidgetData, label::{WidgetLabel, WidgetLabelParams}, rectangle::{WidgetRectangle, WidgetRectangleParams}, util::WLength, @@ -159,7 +159,7 @@ fn register_event_mouse_enter( true, )); state.borrow_mut().hovered = true; - Ok(()) + Ok(EventResult::Pass) }), ); } @@ -183,7 +183,7 @@ fn register_event_mouse_leave( false, )); state.borrow_mut().hovered = false; - Ok(()) + Ok(EventResult::Pass) }), ); } @@ -211,14 +211,15 @@ fn register_event_mouse_press( true, ); - if state.hovered { - state.down = true; - } - common.alterables.trigger_haptics(); common.alterables.mark_redraw(); - Ok(()) + if state.hovered { + state.down = true; + Ok(EventResult::Consumed) + } else { + Ok(EventResult::Pass) + } }), ); } @@ -244,6 +245,9 @@ fn register_event_mouse_release( false, ); + common.alterables.trigger_haptics(); + common.alterables.mark_redraw(); + let mut state = state.borrow_mut(); if state.down { state.down = false; @@ -253,12 +257,10 @@ fn register_event_mouse_release( { on_click(common, ButtonClickEvent {})?; } + Ok(EventResult::Consumed) + } else { + Ok(EventResult::Pass) } - - common.alterables.trigger_haptics(); - common.alterables.mark_redraw(); - - Ok(()) }), ); } diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 5b4f9b7..81c94f9 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -13,6 +13,7 @@ use crate::{ layout::{self, Layout, LayoutState, WidgetID, WidgetPair}, renderer_vk::text::{FontWeight, TextStyle}, widget::{ + EventResult, label::{WidgetLabel, WidgetLabelParams}, rectangle::{WidgetRectangle, WidgetRectangleParams}, util::WLength, @@ -154,7 +155,7 @@ fn register_event_mouse_enter( .alterables .animate(anim_hover_in(state.clone(), event_data.widget_id)); state.borrow_mut().hovered = true; - Ok(()) + Ok(EventResult::Pass) }), ); } @@ -175,7 +176,7 @@ fn register_event_mouse_leave( .alterables .animate(anim_hover_out(state.clone(), event_data.widget_id)); state.borrow_mut().hovered = false; - Ok(()) + Ok(EventResult::Pass) }), ); } @@ -196,14 +197,15 @@ fn register_event_mouse_press( let rect = event_data.obj.get_as_mut::().unwrap(); anim_hover(rect, 1.0, true); - if state.hovered { - state.down = true; - } - common.alterables.trigger_haptics(); common.alterables.mark_redraw(); - Ok(()) + if state.hovered { + state.down = true; + Ok(EventResult::Consumed) + } else { + Ok(EventResult::Pass) + } }), ); } @@ -222,6 +224,9 @@ fn register_event_mouse_release( let rect = event_data.obj.get_as_mut::().unwrap(); anim_hover(rect, 1.0, false); + common.alterables.trigger_haptics(); + common.alterables.mark_redraw(); + let mut state = state.borrow_mut(); if state.down { state.down = false; @@ -234,12 +239,10 @@ fn register_event_mouse_release( { on_toggle(common, CheckboxToggleEvent { checked: state.checked })?; } + Ok(EventResult::Consumed) + } else { + Ok(EventResult::Pass) } - - common.alterables.trigger_haptics(); - common.alterables.mark_redraw(); - - Ok(()) }), ); } diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index fee9930..a59195e 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -15,6 +15,7 @@ use crate::{ util, }, widget::{ + EventResult, div::WidgetDiv, label::{WidgetLabel, WidgetLabelParams}, rectangle::{WidgetRectangle, WidgetRectangleParams}, @@ -210,7 +211,7 @@ fn register_event_mouse_enter( common.alterables.trigger_haptics(); state.borrow_mut().hovered = true; on_enter_anim(common, data.slider_handle_rect_id); - Ok(()) + Ok(EventResult::Pass) }), ); } @@ -229,7 +230,7 @@ fn register_event_mouse_leave( common.alterables.trigger_haptics(); state.borrow_mut().hovered = false; on_leave_anim(common, data.slider_handle_rect_id); - Ok(()) + Ok(EventResult::Pass) }), ); } @@ -249,9 +250,10 @@ fn register_event_mouse_motion( if state.dragging { state.update_value_to_mouse(event_data, &data, common); + Ok(EventResult::Consumed) + } else { + Ok(EventResult::Pass) } - - Ok(()) }), ); } @@ -273,9 +275,10 @@ fn register_event_mouse_press( if state.hovered { state.dragging = true; state.update_value_to_mouse(event_data, &data, common); + Ok(EventResult::Consumed) + } else { + Ok(EventResult::Pass) } - - Ok(()) }), ); } @@ -296,9 +299,10 @@ fn register_event_mouse_release( let mut state = state.borrow_mut(); if state.dragging { state.dragging = false; + Ok(EventResult::Consumed) + } else { + Ok(EventResult::Pass) } - - Ok(()) }), ); } diff --git a/wgui/src/event.rs b/wgui/src/event.rs index ccbb84d..28eda91 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -12,7 +12,7 @@ use crate::{ i18n::I18n, layout::{LayoutState, WidgetID}, stack::{ScissorStack, Transform, TransformStack}, - widget::{WidgetData, WidgetObj}, + widget::{EventResult, WidgetData, WidgetObj}, }; #[derive(Debug, Clone, Copy)] @@ -192,7 +192,7 @@ pub enum EventListenerKind { } pub type EventCallback = - Box anyhow::Result<()>>; + Box anyhow::Result>; //for ref-counting pub struct ListenerHandle { @@ -224,7 +224,7 @@ impl EventListener { pub fn callback_for_kind( &self, kind: EventListenerKind, - ) -> Option<&impl Fn(&mut CallbackDataCommon, &mut CallbackData, &mut U1, &mut U2) -> anyhow::Result<()>> { + ) -> Option<&impl Fn(&mut CallbackDataCommon, &mut CallbackData, &mut U1, &mut U2) -> anyhow::Result> { if self.kind == kind { Some(&self.callback) } else { None } } } diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 44a1e24..ff16531 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -10,7 +10,7 @@ use crate::{ drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack}, event::{self, CallbackDataCommon, EventAlterables, EventListenerCollection}, globals::WguiGlobals, - widget::{self, EventParams, WidgetObj, WidgetState, div::WidgetDiv}, + widget::{self, EventParams, EventResult, WidgetObj, WidgetState, div::WidgetDiv}, }; use glam::{Vec2, vec2}; @@ -275,12 +275,37 @@ impl Layout { event: &event::Event, alterables: &mut EventAlterables, user_data: &mut (&mut U1, &mut U2), - ) -> anyhow::Result<()> { - for child_id in self.state.tree.child_ids(parent_node_id) { - self.push_event_widget(listeners, child_id, event, alterables, user_data)?; + reverse: bool, + ) -> anyhow::Result { + let mut event_result = EventResult::Pass; + + let count = self.state.tree.child_count(parent_node_id); + + let mut iter = |idx: usize| -> anyhow::Result { + let child_id = self.state.tree.get_child_id(parent_node_id, idx); + let child_result = self.push_event_widget(listeners, child_id, event, alterables, user_data, false)?; + if child_result != EventResult::Pass { + event_result = child_result; + return Ok(true); + } + Ok(false) + }; + + if reverse { + for idx in (0..count).rev() { + if iter(idx)? { + break; + } + } + } else { + for idx in 0..count { + if iter(idx)? { + break; + } + } } - Ok(()) + Ok(event_result) } fn push_event_widget( @@ -290,7 +315,8 @@ impl Layout { event: &event::Event, alterables: &mut EventAlterables, user_data: &mut (&mut U1, &mut U2), - ) -> anyhow::Result<()> { + is_root_node: bool, + ) -> anyhow::Result { let l = self.state.tree.layout(node_id)?; let Some(widget_id) = self.state.tree.get_node_context(node_id).copied() else { anyhow::bail!("invalid widget ID"); @@ -320,40 +346,35 @@ impl Layout { style, ); - let mut iter_children = true; + // topmost widgets are iterated in reverse order + let reverse_iter = is_root_node; - let mut params = EventParams { - state: &self.state, - layout: l, - alterables, - node_id, - style, - }; + // check children first + let mut evt_result = self.push_event_children(listeners, node_id, event, alterables, user_data, reverse_iter)?; - let listeners_vec = listeners.get(widget_id); + if evt_result == EventResult::Pass { + let mut params = EventParams { + state: &self.state, + layout: l, + alterables, + node_id, + style, + }; - match widget.process_event(widget_id, listeners_vec, node_id, event, user_data, &mut params)? { - widget::EventResult::Pass => { - // go on + let listeners_vec = listeners.get(widget_id); + + let this_evt_result = widget.process_event(widget_id, listeners_vec, node_id, event, user_data, &mut params)?; + if this_evt_result != EventResult::Pass { + evt_result = this_evt_result; } - widget::EventResult::Consumed | widget::EventResult::Outside | widget::EventResult::Unused => { - iter_children = false; - } - } - - drop(widget); // free mutex - - if iter_children { - self.push_event_children(listeners, node_id, event, alterables, user_data)?; } if scissor_pushed { alterables.scissor_stack.pop(); } - alterables.transform_stack.pop(); - Ok(()) + Ok(evt_result) } pub const fn check_toggle_needs_redraw(&mut self) -> bool { @@ -381,7 +402,14 @@ impl Layout { mut user_data: (&mut U1, &mut U2), ) -> anyhow::Result<()> { let mut alterables = EventAlterables::default(); - self.push_event_widget(listeners, self.tree_root_node, event, &mut alterables, &mut user_data)?; + let _event_result = self.push_event_widget( + listeners, + self.tree_root_node, + event, + &mut alterables, + &mut user_data, + true, + )?; self.process_alterables(alterables)?; listeners.gc(); @@ -551,7 +579,10 @@ impl Layout { pub fn print_tree(&self) { let mut buf = Vec::::new(); self.print_tree_recur(&mut buf, 0, self.tree_root_node); - let str = format!("\n{}", unsafe { str::from_utf8_unchecked(&buf) }); + let str = format!( + "\n=== tree ===\n[widget type] [WidgetID] [taffy NodeID] [other data]\n{}", + unsafe { str::from_utf8_unchecked(&buf) } + ); std::io::stdout().write_all(str.as_bytes()).unwrap(); } @@ -577,11 +608,13 @@ impl Layout { }; let line = format!( - "{}{}{}{}: [pos: {}x{}][size: {}x{}]{}\n", + "{}{}{}{} 0x{:x?} 0x{:x?}: [pos: {}x{}][size: {}x{}]{}\n", ANSI_BOLD_CODE, type_color.debug_ansi_format(), state.obj.get_type().as_str(), ANSI_RESET_CODE, + state.obj.get_id().0.as_ffi(), + u64::from(node_id), layout.location.x, layout.location.y, layout.content_size.width, diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs index 7dbdb5a..c602477 100644 --- a/wgui/src/widget/mod.rs +++ b/wgui/src/widget/mod.rs @@ -159,11 +159,10 @@ pub struct EventParams<'a> { pub layout: &'a taffy::Layout, } +#[derive(Eq, PartialEq)] pub enum EventResult { - Pass, // widget acknowledged it and allows the event to pass to the children - Consumed, // widget triggered an action, do not pass to children - Outside, // widget acknowledged this event but ignores it due the fact the mouse is not hovered over it - Unused, // widget doesn't have any events attached + Pass, // widget acknowledged it and allows the event to pass further + Consumed, // widget triggered an action, do not pass further } fn get_scroll_enabled(style: &taffy::Style) -> (bool, bool) { @@ -225,7 +224,10 @@ macro_rules! call_event { alterables: $params.alterables, }; - callback(&mut common, &mut data, $user_data.0, $user_data.1)?; + let result = callback(&mut common, &mut data, $user_data.0, $user_data.1)?; + if result == EventResult::Consumed { + return Ok(EventResult::Consumed); + } } } };