wgui: event results/blocking for overlapping content, rev iterate events for root node

This commit is contained in:
Aleksander
2025-10-05 20:11:49 +02:00
parent 89c083991f
commit efcc41de19
8 changed files with 131 additions and 76 deletions

View File

@@ -210,7 +210,7 @@ impl TestbedGeneric {
}, },
)?; )?;
let _state = wgui::parser::parse_from_assets( let state = wgui::parser::parse_from_assets(
&ParseDocumentParams { &ParseDocumentParams {
globals, globals,
path: XML_PATH, path: XML_PATH,
@@ -221,6 +221,18 @@ impl TestbedGeneric {
widget.id, widget.id,
)?; )?;
let button = state
.fetch_component_as::<ComponentButton>("button")
.unwrap();
button.on_click(Box::new(move |_common, _e| {
log::info!("click");
Ok(())
}));
// temporary, preserve listeners state
Box::leak(Box::new(state));
Ok(()) Ok(())
} }
} }

View File

@@ -7,7 +7,6 @@
border_color="#778899" border_color="#778899"
color="#001122ee" color="#001122ee"
padding="2"> padding="2">
<!-- window title --> <!-- window title -->
<rectangle width="100%" height="100%" round="4" align_items="center" justify_content="space_between" <rectangle width="100%" height="100%" round="4" align_items="center" justify_content="space_between"
gradient="vertical" color="#224466" color2="#113355"> gradient="vertical" color="#224466" color2="#113355">
@@ -18,7 +17,7 @@
<!-- content itself --> <!-- content itself -->
<div width="100%" height="100%" padding="8" gap="4" flex_direction="column"> <div width="100%" height="100%" padding="8" gap="4" flex_direction="column">
<label text="Window content" /> <label text="Window content" />
<Button color="#9911AA" text="I'm clickable." width="128" height="24" /> <Button id="button" color="#9911AA" text="I'm clickable." width="128" height="24" />
</div> </div>
</rectangle> </rectangle>
</elements> </elements>

View File

@@ -11,7 +11,7 @@ use crate::{
util::centered_matrix, util::centered_matrix,
}, },
widget::{ widget::{
WidgetData, EventResult, WidgetData,
label::{WidgetLabel, WidgetLabelParams}, label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams}, rectangle::{WidgetRectangle, WidgetRectangleParams},
util::WLength, util::WLength,
@@ -159,7 +159,7 @@ fn register_event_mouse_enter<U1, U2>(
true, true,
)); ));
state.borrow_mut().hovered = true; state.borrow_mut().hovered = true;
Ok(()) Ok(EventResult::Pass)
}), }),
); );
} }
@@ -183,7 +183,7 @@ fn register_event_mouse_leave<U1, U2>(
false, false,
)); ));
state.borrow_mut().hovered = false; state.borrow_mut().hovered = false;
Ok(()) Ok(EventResult::Pass)
}), }),
); );
} }
@@ -211,14 +211,15 @@ fn register_event_mouse_press<U1, U2>(
true, true,
); );
if state.hovered {
state.down = true;
}
common.alterables.trigger_haptics(); common.alterables.trigger_haptics();
common.alterables.mark_redraw(); 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<U1, U2>(
false, false,
); );
common.alterables.trigger_haptics();
common.alterables.mark_redraw();
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
if state.down { if state.down {
state.down = false; state.down = false;
@@ -253,12 +257,10 @@ fn register_event_mouse_release<U1, U2>(
{ {
on_click(common, ButtonClickEvent {})?; on_click(common, ButtonClickEvent {})?;
} }
Ok(EventResult::Consumed)
} else {
Ok(EventResult::Pass)
} }
common.alterables.trigger_haptics();
common.alterables.mark_redraw();
Ok(())
}), }),
); );
} }

View File

@@ -13,6 +13,7 @@ use crate::{
layout::{self, Layout, LayoutState, WidgetID, WidgetPair}, layout::{self, Layout, LayoutState, WidgetID, WidgetPair},
renderer_vk::text::{FontWeight, TextStyle}, renderer_vk::text::{FontWeight, TextStyle},
widget::{ widget::{
EventResult,
label::{WidgetLabel, WidgetLabelParams}, label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams}, rectangle::{WidgetRectangle, WidgetRectangleParams},
util::WLength, util::WLength,
@@ -154,7 +155,7 @@ fn register_event_mouse_enter<U1, U2>(
.alterables .alterables
.animate(anim_hover_in(state.clone(), event_data.widget_id)); .animate(anim_hover_in(state.clone(), event_data.widget_id));
state.borrow_mut().hovered = true; state.borrow_mut().hovered = true;
Ok(()) Ok(EventResult::Pass)
}), }),
); );
} }
@@ -175,7 +176,7 @@ fn register_event_mouse_leave<U1, U2>(
.alterables .alterables
.animate(anim_hover_out(state.clone(), event_data.widget_id)); .animate(anim_hover_out(state.clone(), event_data.widget_id));
state.borrow_mut().hovered = false; state.borrow_mut().hovered = false;
Ok(()) Ok(EventResult::Pass)
}), }),
); );
} }
@@ -196,14 +197,15 @@ fn register_event_mouse_press<U1, U2>(
let rect = event_data.obj.get_as_mut::<WidgetRectangle>().unwrap(); let rect = event_data.obj.get_as_mut::<WidgetRectangle>().unwrap();
anim_hover(rect, 1.0, true); anim_hover(rect, 1.0, true);
if state.hovered {
state.down = true;
}
common.alterables.trigger_haptics(); common.alterables.trigger_haptics();
common.alterables.mark_redraw(); 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<U1, U2>(
let rect = event_data.obj.get_as_mut::<WidgetRectangle>().unwrap(); let rect = event_data.obj.get_as_mut::<WidgetRectangle>().unwrap();
anim_hover(rect, 1.0, false); anim_hover(rect, 1.0, false);
common.alterables.trigger_haptics();
common.alterables.mark_redraw();
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
if state.down { if state.down {
state.down = false; state.down = false;
@@ -234,12 +239,10 @@ fn register_event_mouse_release<U1, U2>(
{ {
on_toggle(common, CheckboxToggleEvent { checked: state.checked })?; on_toggle(common, CheckboxToggleEvent { checked: state.checked })?;
} }
Ok(EventResult::Consumed)
} else {
Ok(EventResult::Pass)
} }
common.alterables.trigger_haptics();
common.alterables.mark_redraw();
Ok(())
}), }),
); );
} }

View File

@@ -15,6 +15,7 @@ use crate::{
util, util,
}, },
widget::{ widget::{
EventResult,
div::WidgetDiv, div::WidgetDiv,
label::{WidgetLabel, WidgetLabelParams}, label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams}, rectangle::{WidgetRectangle, WidgetRectangleParams},
@@ -210,7 +211,7 @@ fn register_event_mouse_enter<U1, U2>(
common.alterables.trigger_haptics(); common.alterables.trigger_haptics();
state.borrow_mut().hovered = true; state.borrow_mut().hovered = true;
on_enter_anim(common, data.slider_handle_rect_id); on_enter_anim(common, data.slider_handle_rect_id);
Ok(()) Ok(EventResult::Pass)
}), }),
); );
} }
@@ -229,7 +230,7 @@ fn register_event_mouse_leave<U1, U2>(
common.alterables.trigger_haptics(); common.alterables.trigger_haptics();
state.borrow_mut().hovered = false; state.borrow_mut().hovered = false;
on_leave_anim(common, data.slider_handle_rect_id); on_leave_anim(common, data.slider_handle_rect_id);
Ok(()) Ok(EventResult::Pass)
}), }),
); );
} }
@@ -249,9 +250,10 @@ fn register_event_mouse_motion<U1, U2>(
if state.dragging { if state.dragging {
state.update_value_to_mouse(event_data, &data, common); 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<U1, U2>(
if state.hovered { if state.hovered {
state.dragging = true; state.dragging = true;
state.update_value_to_mouse(event_data, &data, common); 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<U1, U2>(
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
if state.dragging { if state.dragging {
state.dragging = false; state.dragging = false;
Ok(EventResult::Consumed)
} else {
Ok(EventResult::Pass)
} }
Ok(())
}), }),
); );
} }

View File

@@ -12,7 +12,7 @@ use crate::{
i18n::I18n, i18n::I18n,
layout::{LayoutState, WidgetID}, layout::{LayoutState, WidgetID},
stack::{ScissorStack, Transform, TransformStack}, stack::{ScissorStack, Transform, TransformStack},
widget::{WidgetData, WidgetObj}, widget::{EventResult, WidgetData, WidgetObj},
}; };
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@@ -192,7 +192,7 @@ pub enum EventListenerKind {
} }
pub type EventCallback<U1, U2> = pub type EventCallback<U1, U2> =
Box<dyn Fn(&mut CallbackDataCommon, &mut CallbackData, &mut U1, &mut U2) -> anyhow::Result<()>>; Box<dyn Fn(&mut CallbackDataCommon, &mut CallbackData, &mut U1, &mut U2) -> anyhow::Result<EventResult>>;
//for ref-counting //for ref-counting
pub struct ListenerHandle { pub struct ListenerHandle {
@@ -224,7 +224,7 @@ impl<U1, U2> EventListener<U1, U2> {
pub fn callback_for_kind( pub fn callback_for_kind(
&self, &self,
kind: EventListenerKind, 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<EventResult>> {
if self.kind == kind { Some(&self.callback) } else { None } if self.kind == kind { Some(&self.callback) } else { None }
} }
} }

View File

@@ -10,7 +10,7 @@ use crate::{
drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack}, drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack},
event::{self, CallbackDataCommon, EventAlterables, EventListenerCollection}, event::{self, CallbackDataCommon, EventAlterables, EventListenerCollection},
globals::WguiGlobals, globals::WguiGlobals,
widget::{self, EventParams, WidgetObj, WidgetState, div::WidgetDiv}, widget::{self, EventParams, EventResult, WidgetObj, WidgetState, div::WidgetDiv},
}; };
use glam::{Vec2, vec2}; use glam::{Vec2, vec2};
@@ -275,12 +275,37 @@ impl Layout {
event: &event::Event, event: &event::Event,
alterables: &mut EventAlterables, alterables: &mut EventAlterables,
user_data: &mut (&mut U1, &mut U2), user_data: &mut (&mut U1, &mut U2),
) -> anyhow::Result<()> { reverse: bool,
for child_id in self.state.tree.child_ids(parent_node_id) { ) -> anyhow::Result<EventResult> {
self.push_event_widget(listeners, child_id, event, alterables, user_data)?; let mut event_result = EventResult::Pass;
let count = self.state.tree.child_count(parent_node_id);
let mut iter = |idx: usize| -> anyhow::Result<bool> {
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<U1, U2>( fn push_event_widget<U1, U2>(
@@ -290,7 +315,8 @@ impl Layout {
event: &event::Event, event: &event::Event,
alterables: &mut EventAlterables, alterables: &mut EventAlterables,
user_data: &mut (&mut U1, &mut U2), user_data: &mut (&mut U1, &mut U2),
) -> anyhow::Result<()> { is_root_node: bool,
) -> anyhow::Result<EventResult> {
let l = self.state.tree.layout(node_id)?; let l = self.state.tree.layout(node_id)?;
let Some(widget_id) = self.state.tree.get_node_context(node_id).copied() else { let Some(widget_id) = self.state.tree.get_node_context(node_id).copied() else {
anyhow::bail!("invalid widget ID"); anyhow::bail!("invalid widget ID");
@@ -320,8 +346,13 @@ impl Layout {
style, style,
); );
let mut iter_children = true; // topmost widgets are iterated in reverse order
let reverse_iter = is_root_node;
// check children first
let mut evt_result = self.push_event_children(listeners, node_id, event, alterables, user_data, reverse_iter)?;
if evt_result == EventResult::Pass {
let mut params = EventParams { let mut params = EventParams {
state: &self.state, state: &self.state,
layout: l, layout: l,
@@ -332,28 +363,18 @@ impl Layout {
let listeners_vec = listeners.get(widget_id); let listeners_vec = listeners.get(widget_id);
match widget.process_event(widget_id, listeners_vec, node_id, event, user_data, &mut params)? { let this_evt_result = widget.process_event(widget_id, listeners_vec, node_id, event, user_data, &mut params)?;
widget::EventResult::Pass => { if this_evt_result != EventResult::Pass {
// go on 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 { if scissor_pushed {
alterables.scissor_stack.pop(); alterables.scissor_stack.pop();
} }
alterables.transform_stack.pop(); alterables.transform_stack.pop();
Ok(()) Ok(evt_result)
} }
pub const fn check_toggle_needs_redraw(&mut self) -> bool { pub const fn check_toggle_needs_redraw(&mut self) -> bool {
@@ -381,7 +402,14 @@ impl Layout {
mut user_data: (&mut U1, &mut U2), mut user_data: (&mut U1, &mut U2),
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut alterables = EventAlterables::default(); 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)?; self.process_alterables(alterables)?;
listeners.gc(); listeners.gc();
@@ -551,7 +579,10 @@ impl Layout {
pub fn print_tree(&self) { pub fn print_tree(&self) {
let mut buf = Vec::<u8>::new(); let mut buf = Vec::<u8>::new();
self.print_tree_recur(&mut buf, 0, self.tree_root_node); 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(); std::io::stdout().write_all(str.as_bytes()).unwrap();
} }
@@ -577,11 +608,13 @@ impl Layout {
}; };
let line = format!( let line = format!(
"{}{}{}{}: [pos: {}x{}][size: {}x{}]{}\n", "{}{}{}{} 0x{:x?} 0x{:x?}: [pos: {}x{}][size: {}x{}]{}\n",
ANSI_BOLD_CODE, ANSI_BOLD_CODE,
type_color.debug_ansi_format(), type_color.debug_ansi_format(),
state.obj.get_type().as_str(), state.obj.get_type().as_str(),
ANSI_RESET_CODE, ANSI_RESET_CODE,
state.obj.get_id().0.as_ffi(),
u64::from(node_id),
layout.location.x, layout.location.x,
layout.location.y, layout.location.y,
layout.content_size.width, layout.content_size.width,

View File

@@ -159,11 +159,10 @@ pub struct EventParams<'a> {
pub layout: &'a taffy::Layout, pub layout: &'a taffy::Layout,
} }
#[derive(Eq, PartialEq)]
pub enum EventResult { pub enum EventResult {
Pass, // widget acknowledged it and allows the event to pass to the children Pass, // widget acknowledged it and allows the event to pass further
Consumed, // widget triggered an action, do not pass to children Consumed, // widget triggered an action, do not pass further
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
} }
fn get_scroll_enabled(style: &taffy::Style) -> (bool, bool) { fn get_scroll_enabled(style: &taffy::Style) -> (bool, bool) {
@@ -225,7 +224,10 @@ macro_rules! call_event {
alterables: $params.alterables, 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);
}
} }
} }
}; };