wgui: pass motion events further, add consume_mouse_events parameter to widgets (Closes #306), always reverse iter events

This commit is contained in:
Aleksander
2025-12-18 22:02:17 +01:00
parent 5463b6490d
commit 9dbd86db39
12 changed files with 124 additions and 61 deletions

View File

@@ -3,6 +3,7 @@
<elements> <elements>
<div <div
consume_mouse_events="1"
id="root" id="root"
new_pass="1" new_pass="1"
width="100%" width="100%"

View File

@@ -6,6 +6,7 @@
and WINDOW_DECORATION_HEADER_HEIGHT in the source code accordingly and WINDOW_DECORATION_HEADER_HEIGHT in the source code accordingly
--> -->
<rectangle <rectangle
consume_mouse_events="1"
flex_direction="column" flex_direction="column"
round="16" round="16"
border="2" border="2"

View File

@@ -66,6 +66,10 @@ _They can be used in any widget/component._
_Set to 0 if you want to exclude this widget from altering the event state_ _Set to 0 if you want to exclude this widget from altering the event state_
`consume_mouse_events`: "1" | "0"
_Used in case of overlapping pop-ups or windows, most notably applied to various backgrounds_
`new_pass`: "1" | "0" `new_pass`: "1" | "0"
_Set to 1 if you want to render overlapping pop-ups to properly render your widgets in order. Wgui renders with as few Vulkan drawcalls as possible, so this is your responsibility._ _Set to 1 if you want to render overlapping pop-ups to properly render your widgets in order. Wgui renders with as few Vulkan drawcalls as possible, so this is your responsibility._

View File

@@ -127,7 +127,7 @@ impl Color {
} }
#[must_use] #[must_use]
pub fn with_alpha(&self, n: f32) -> Self { pub const fn with_alpha(&self, n: f32) -> Self {
Self { Self {
r: self.r, r: self.r,
g: self.g, g: self.g,
@@ -267,7 +267,7 @@ fn draw_widget(
let mut widget_state = widget.state(); let mut widget_state = widget.state();
if widget_state.new_pass { if widget_state.flags.new_pass {
state.primitives.push(RenderPrimitive::NewPass); state.primitives.push(RenderPrimitive::NewPass);
} }

View File

@@ -11,7 +11,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}, event::{self, CallbackDataCommon, EventAlterables},
globals::WguiGlobals, globals::WguiGlobals,
widget::{self, EventParams, EventResult, WidgetObj, WidgetState, div::WidgetDiv}, widget::{self, EventParams, EventResult, WidgetObj, WidgetState, WidgetStateFlags, div::WidgetDiv},
}; };
use glam::{Vec2, vec2}; use glam::{Vec2, vec2};
@@ -377,7 +377,6 @@ impl Layout {
event_result: &mut EventResult, event_result: &mut EventResult,
alterables: &mut EventAlterables, alterables: &mut EventAlterables,
user_data: &mut (&'a mut U1, &'a mut U2), user_data: &mut (&'a mut U1, &'a mut U2),
reverse: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let count = self.state.tree.child_count(parent_node_id); let count = self.state.tree.child_count(parent_node_id);
@@ -387,17 +386,10 @@ impl Layout {
Ok(!event_result.can_propagate()) Ok(!event_result.can_propagate())
}; };
if reverse { // reverse iter
for idx in (0..count).rev() { for idx in (0..count).rev() {
if iter(idx)? { if iter(idx)? {
break; break;
}
}
} else {
for idx in 0..count {
if iter(idx)? {
break;
}
} }
} }
@@ -448,11 +440,8 @@ impl Layout {
style, style,
); );
// topmost widgets are iterated in reverse order
let reverse_iter = is_root_node;
// check children first // check children first
self.push_event_children(node_id, event, event_result, alterables, user_data, reverse_iter)?; self.push_event_children(node_id, event, event_result, alterables, user_data)?;
if event_result.can_propagate() { if event_result.can_propagate() {
let mut params = EventParams { let mut params = EventParams {
@@ -532,7 +521,10 @@ impl Layout {
&mut state.nodes, &mut state.nodes,
None, // no parent None, // no parent
WidgetState { WidgetState {
interactable: false, flags: WidgetStateFlags {
interactable: false,
..Default::default()
},
..WidgetDiv::create() ..WidgetDiv::create()
}, },
taffy::Style { taffy::Style {
@@ -547,7 +539,10 @@ impl Layout {
&mut state.nodes, &mut state.nodes,
Some(tree_root_node), Some(tree_root_node),
WidgetState { WidgetState {
interactable: false, flags: WidgetStateFlags {
interactable: false,
..Default::default()
},
..WidgetDiv::create() ..WidgetDiv::create()
}, },
taffy::Style { taffy::Style {

View File

@@ -831,14 +831,21 @@ fn parse_widget_universal(ctx: &mut ParserContext, widget: &WidgetPair, attribs:
} }
"new_pass" => { "new_pass" => {
if let Some(num) = parse_i32(&pair.value) { if let Some(num) = parse_i32(&pair.value) {
widget.widget.state().new_pass = num != 0; widget.widget.state().flags.new_pass = num != 0;
} else { } else {
print_invalid_attrib(&pair.attrib, &pair.value); print_invalid_attrib(&pair.attrib, &pair.value);
} }
} }
"interactable" => { "interactable" => {
if let Some(num) = parse_i32(&pair.value) { if let Some(num) = parse_i32(&pair.value) {
widget.widget.state().interactable = num != 0; widget.widget.state().flags.interactable = num != 0;
} else {
print_invalid_attrib(&pair.attrib, &pair.value);
}
}
"consume_mouse_events" => {
if let Some(num) = parse_i32(&pair.value) {
widget.widget.state().flags.consume_mouse_events = num != 0;
} else { } else {
print_invalid_attrib(&pair.attrib, &pair.value); print_invalid_attrib(&pair.attrib, &pair.value);
} }

View File

@@ -1,6 +1,6 @@
use slotmap::Key; use slotmap::Key;
use crate::layout::WidgetID; use crate::{layout::WidgetID, widget::WidgetStateFlags};
use super::{WidgetObj, WidgetState}; use super::{WidgetObj, WidgetState};
@@ -10,7 +10,7 @@ pub struct WidgetDiv {
impl WidgetDiv { impl WidgetDiv {
pub fn create() -> WidgetState { pub fn create() -> WidgetState {
WidgetState::new(Box::new(Self { id: WidgetID::null() })) WidgetState::new(WidgetStateFlags::default(), Box::new(Self { id: WidgetID::null() }))
} }
} }

View File

@@ -11,6 +11,7 @@ use crate::{
i18n::Translation, i18n::Translation,
layout::WidgetID, layout::WidgetID,
renderer_vk::text::TextStyle, renderer_vk::text::TextStyle,
widget::WidgetStateFlags,
}; };
use super::{WidgetObj, WidgetState}; use super::{WidgetObj, WidgetState};
@@ -53,12 +54,15 @@ impl WidgetLabel {
); );
} }
WidgetState::new(Box::new(Self { WidgetState::new(
params, WidgetStateFlags::default(),
buffer: Rc::new(RefCell::new(buffer)), Box::new(Self {
last_boundary: Boundary::default(), params,
id: WidgetID::null(), buffer: Rc::new(RefCell::new(buffer)),
})) last_boundary: Boundary::default(),
id: WidgetID::null(),
}),
)
} }
// set text without layout/re-render update. // set text without layout/re-render update.

View File

@@ -76,12 +76,31 @@ impl WidgetData {
} }
} }
pub struct WidgetStateFlags {
pub interactable: bool,
// consume any incoming mouse event which is hovered at given widget
pub consume_mouse_events: bool,
// force a new render pass before rendering this widget
pub new_pass: bool,
}
impl Default for WidgetStateFlags {
fn default() -> Self {
Self {
interactable: true,
consume_mouse_events: false,
new_pass: false,
}
}
}
pub struct WidgetState { pub struct WidgetState {
pub data: WidgetData, pub data: WidgetData,
pub obj: Box<dyn WidgetObj>, pub obj: Box<dyn WidgetObj>,
pub event_listeners: EventListenerCollection, pub event_listeners: EventListenerCollection,
pub interactable: bool, pub flags: WidgetStateFlags,
pub new_pass: bool, // force a new render pass
} }
impl WidgetState { impl WidgetState {
@@ -91,7 +110,7 @@ impl WidgetState {
(data, obj) (data, obj)
} }
fn new(obj: Box<dyn WidgetObj>) -> Self { fn new(flags: WidgetStateFlags, obj: Box<dyn WidgetObj>) -> Self {
Self { Self {
data: WidgetData { data: WidgetData {
hovered: 0, hovered: 0,
@@ -104,8 +123,7 @@ impl WidgetState {
}, },
obj, obj,
event_listeners: EventListenerCollection::default(), event_listeners: EventListenerCollection::default(),
interactable: true, flags,
new_pass: false,
} }
} }
} }
@@ -241,13 +259,19 @@ struct InvokeData<'a, 'b, U1: 'static, U2: 'static> {
params: &'a mut EventParams<'a>, params: &'a mut EventParams<'a>,
} }
#[must_use]
enum InvokeListenersResult {
NobodyListened,
AtLeastOneCalled,
}
impl WidgetState { impl WidgetState {
fn invoke_listeners<U1: 'static, U2: 'static>( fn invoke_listeners<U1: 'static, U2: 'static>(
&mut self, &mut self,
call_data: &mut InvokeData<'_, '_, U1, U2>, call_data: &mut InvokeData<'_, '_, U1, U2>,
kind: event::EventListenerKind, kind: event::EventListenerKind,
metadata: CallbackMetadata, metadata: CallbackMetadata,
) -> anyhow::Result<()> { ) -> anyhow::Result<InvokeListenersResult> {
let mut data = CallbackData { let mut data = CallbackData {
obj: self.obj.as_mut(), obj: self.obj.as_mut(),
widget_data: &mut self.data, widget_data: &mut self.data,
@@ -261,13 +285,17 @@ impl WidgetState {
alterables: call_data.params.alterables, alterables: call_data.params.alterables,
}; };
let mut res = InvokeListenersResult::NobodyListened;
for listener in self.event_listeners.iter_filtered::<U1, U2>(kind) { for listener in self.event_listeners.iter_filtered::<U1, U2>(kind) {
let new_result = listener.call_with(&mut common, &mut data, call_data.user_data)?; let new_result = listener.call_with(&mut common, &mut data, call_data.user_data)?;
res = InvokeListenersResult::AtLeastOneCalled;
// Consider all listeners on this widget, even if we had a Consume. // Consider all listeners on this widget, even if we had a Consume.
// Store the highest value for return. // Store the highest value for return.
*call_data.event_result = call_data.event_result.merge(new_result); *call_data.event_result = call_data.event_result.merge(new_result);
} }
Ok(())
Ok(res)
} }
pub fn get_scroll_shift_smooth(&self, info: &ScrollbarInfo, l: &taffy::Layout, timestep_alpha: f32) -> (Vec2, bool) { pub fn get_scroll_shift_smooth(&self, info: &ScrollbarInfo, l: &taffy::Layout, timestep_alpha: f32) -> (Vec2, bool) {
@@ -449,29 +477,31 @@ impl WidgetState {
params, params,
}; };
let mut res: Option<InvokeListenersResult> = None;
match &event { match &event {
Event::MouseDown(e) => { Event::MouseDown(e) => {
if hovered && self.data.set_device_pressed(e.device, true) { if hovered && self.data.set_device_pressed(e.device, true) {
self.invoke_listeners( res = Some(self.invoke_listeners(
&mut invoke_data, &mut invoke_data,
EventListenerKind::MousePress, EventListenerKind::MousePress,
CallbackMetadata::MouseButton(event::MouseButton { CallbackMetadata::MouseButton(event::MouseButton {
index: e.index, index: e.index,
pos: e.pos, pos: e.pos,
}), }),
)?; )?);
} }
} }
Event::MouseUp(e) => { Event::MouseUp(e) => {
if self.data.set_device_pressed(e.device, false) { if self.data.set_device_pressed(e.device, false) {
self.invoke_listeners( res = Some(self.invoke_listeners(
&mut invoke_data, &mut invoke_data,
EventListenerKind::MouseRelease, EventListenerKind::MouseRelease,
CallbackMetadata::MouseButton(event::MouseButton { CallbackMetadata::MouseButton(event::MouseButton {
index: e.index, index: e.index,
pos: e.pos, pos: e.pos,
}), }),
)?; )?);
} }
} }
Event::MouseMotion(e) => { Event::MouseMotion(e) => {
@@ -479,18 +509,20 @@ impl WidgetState {
if hover_state_changed { if hover_state_changed {
if self.data.is_hovered() { if self.data.is_hovered() {
self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseEnter, CallbackMetadata::None)?; res =
Some(self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseEnter, CallbackMetadata::None)?);
} else { } else {
self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseLeave, CallbackMetadata::None)?; res =
Some(self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseLeave, CallbackMetadata::None)?);
} }
} else if hovered { } else {
self.invoke_listeners( res = Some(self.invoke_listeners(
&mut invoke_data, &mut invoke_data,
EventListenerKind::MouseMotion, EventListenerKind::MouseMotion,
CallbackMetadata::MousePosition(event::MousePosition { pos: e.pos }), CallbackMetadata::MousePosition(event::MousePosition { pos: e.pos }),
)?; )?);
if self.interactable { if self.flags.interactable {
*invoke_data.event_result = invoke_data.event_result.merge(EventResult::Pass); *invoke_data.event_result = invoke_data.event_result.merge(EventResult::Pass);
} }
} }
@@ -503,17 +535,29 @@ impl WidgetState {
} }
Event::MouseLeave(e) => { Event::MouseLeave(e) => {
if self.data.set_device_hovered(e.device, false) { if self.data.set_device_hovered(e.device, false) {
self.invoke_listeners(&mut invoke_data, MouseLeave, CallbackMetadata::None)?; res = Some(self.invoke_listeners(&mut invoke_data, MouseLeave, CallbackMetadata::None)?);
} }
} }
Event::InternalStateChange(e) => { Event::InternalStateChange(e) => {
self.invoke_listeners( res = Some(self.invoke_listeners(
&mut invoke_data, &mut invoke_data,
InternalStateChange, InternalStateChange,
CallbackMetadata::Custom(e.metadata), CallbackMetadata::Custom(e.metadata),
)?; )?);
} }
} }
if let Some(res) = res {
match res {
InvokeListenersResult::NobodyListened => {
if hovered && self.flags.consume_mouse_events {
*invoke_data.event_result = EventResult::Consumed;
}
}
InvokeListenersResult::AtLeastOneCalled => {}
}
}
Ok(()) Ok(())
} }
} }

View File

@@ -3,7 +3,7 @@ use slotmap::Key;
use crate::{ use crate::{
drawing::{self, GradientMode, PrimitiveExtent}, drawing::{self, GradientMode, PrimitiveExtent},
layout::WidgetID, layout::WidgetID,
widget::util::WLength, widget::{WidgetStateFlags, util::WLength},
}; };
use super::{WidgetObj, WidgetState}; use super::{WidgetObj, WidgetState};
@@ -27,10 +27,13 @@ pub struct WidgetRectangle {
impl WidgetRectangle { impl WidgetRectangle {
pub fn create(params: WidgetRectangleParams) -> WidgetState { pub fn create(params: WidgetRectangleParams) -> WidgetState {
WidgetState::new(Box::new(Self { WidgetState::new(
params, WidgetStateFlags::default(),
id: WidgetID::null(), Box::new(Self {
})) params,
id: WidgetID::null(),
}),
)
} }
} }

View File

@@ -11,6 +11,7 @@ use crate::{
DEFAULT_METRICS, DEFAULT_METRICS,
custom_glyph::{CustomGlyph, CustomGlyphData}, custom_glyph::{CustomGlyph, CustomGlyphData},
}, },
widget::WidgetStateFlags,
}; };
use super::{WidgetObj, WidgetState}; use super::{WidgetObj, WidgetState};
@@ -29,10 +30,13 @@ pub struct WidgetSprite {
impl WidgetSprite { impl WidgetSprite {
pub fn create(params: WidgetSpriteParams) -> WidgetState { pub fn create(params: WidgetSpriteParams) -> WidgetState {
WidgetState::new(Box::new(Self { WidgetState::new(
params, WidgetStateFlags::default(),
id: WidgetID::null(), Box::new(Self {
})) params,
id: WidgetID::null(),
}),
)
} }
pub fn set_color(&mut self, color: drawing::Color) { pub fn set_color(&mut self, color: drawing::Color) {

View File

@@ -50,7 +50,7 @@ impl<T> OverlayWindowData<T> {
} }
} }
#[derive(Debug, Clone, Copy, IntegerId, PartialEq)] #[derive(Debug, Clone, Copy, IntegerId, PartialEq, Eq)]
pub enum OverlayCategory { pub enum OverlayCategory {
Internal, Internal,
Keyboard, Keyboard,