panel: per-element interactibility
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
components::{self, Component, ComponentBase, ComponentTrait, InitData, tooltip::ComponentTooltip},
|
||||
components::{self, tooltip::ComponentTooltip, Component, ComponentBase, ComponentTrait, InitData},
|
||||
drawing::{self, Boundary, Color},
|
||||
event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind},
|
||||
i18n::Translation,
|
||||
@@ -10,10 +10,10 @@ use crate::{
|
||||
util::centered_matrix,
|
||||
},
|
||||
widget::{
|
||||
ConstructEssentials, EventResult, WidgetData,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
rectangle::{WidgetRectangle, WidgetRectangleParams},
|
||||
util::WLength,
|
||||
ConstructEssentials, EventResult, WidgetData,
|
||||
},
|
||||
};
|
||||
use glam::{Mat4, Vec3};
|
||||
|
||||
@@ -8,14 +8,14 @@ use std::{
|
||||
use crate::{
|
||||
animation::Animations,
|
||||
components::{Component, InitData},
|
||||
drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack},
|
||||
drawing::{self, push_scissor_stack, push_transform_stack, Boundary, ANSI_BOLD_CODE, ANSI_RESET_CODE},
|
||||
event::{self, CallbackDataCommon, EventAlterables},
|
||||
globals::WguiGlobals,
|
||||
widget::{self, EventParams, EventResult, WidgetObj, WidgetState, div::WidgetDiv},
|
||||
widget::{self, div::WidgetDiv, EventParams, EventResult, WidgetObj, WidgetState},
|
||||
};
|
||||
|
||||
use glam::{Vec2, vec2};
|
||||
use slotmap::{HopSlotMap, SecondaryMap, new_key_type};
|
||||
use glam::{vec2, Vec2};
|
||||
use slotmap::{new_key_type, HopSlotMap, SecondaryMap};
|
||||
use taffy::{NodeId, TaffyTree, TraversePartialTree};
|
||||
|
||||
new_key_type! {
|
||||
@@ -328,26 +328,21 @@ impl Layout {
|
||||
)
|
||||
}
|
||||
|
||||
fn push_event_children<U1: 'static, U2: 'static>(
|
||||
fn push_event_children<'a, U1: 'static, U2: 'static>(
|
||||
&self,
|
||||
parent_node_id: taffy::NodeId,
|
||||
event: &event::Event,
|
||||
event_result: &mut EventResult,
|
||||
alterables: &mut EventAlterables,
|
||||
user_data: &mut (&mut U1, &mut U2),
|
||||
user_data: &mut (&'a mut U1, &'a mut U2),
|
||||
reverse: bool,
|
||||
) -> anyhow::Result<EventResult> {
|
||||
let mut event_result = EventResult::Pass;
|
||||
|
||||
) -> anyhow::Result<()> {
|
||||
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(child_id, event, alterables, user_data, false)?;
|
||||
if child_result != EventResult::Pass {
|
||||
event_result = child_result;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
self.push_event_widget(child_id, event, event_result, alterables, user_data, false)?;
|
||||
Ok(!event_result.can_propagate())
|
||||
};
|
||||
|
||||
if reverse {
|
||||
@@ -364,17 +359,18 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(event_result)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_event_widget<U1: 'static, U2: 'static>(
|
||||
fn push_event_widget<'a, U1: 'static, U2: 'static>(
|
||||
&self,
|
||||
node_id: taffy::NodeId,
|
||||
event: &event::Event,
|
||||
event_result: &mut EventResult,
|
||||
alterables: &mut EventAlterables,
|
||||
user_data: &mut (&mut U1, &mut U2),
|
||||
user_data: &mut (&'a mut U1, &'a mut U2),
|
||||
is_root_node: bool,
|
||||
) -> anyhow::Result<EventResult> {
|
||||
) -> 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");
|
||||
@@ -410,9 +406,9 @@ impl Layout {
|
||||
let reverse_iter = is_root_node;
|
||||
|
||||
// check children first
|
||||
let mut evt_result = self.push_event_children(node_id, event, alterables, user_data, reverse_iter)?;
|
||||
self.push_event_children(node_id, event, event_result, alterables, user_data, reverse_iter)?;
|
||||
|
||||
if evt_result == EventResult::Pass {
|
||||
if event_result.can_propagate() {
|
||||
let mut params = EventParams {
|
||||
state: &self.state,
|
||||
layout: l,
|
||||
@@ -421,10 +417,7 @@ impl Layout {
|
||||
style,
|
||||
};
|
||||
|
||||
let this_evt_result = widget.process_event(widget_id, node_id, event, user_data, &mut params)?;
|
||||
if this_evt_result != EventResult::Pass {
|
||||
evt_result = this_evt_result;
|
||||
}
|
||||
widget.process_event(widget_id, node_id, event, event_result, user_data, &mut params)?;
|
||||
}
|
||||
|
||||
if scissor_pushed {
|
||||
@@ -432,7 +425,7 @@ impl Layout {
|
||||
}
|
||||
alterables.transform_stack.pop();
|
||||
|
||||
Ok(evt_result)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub const fn check_toggle_needs_redraw(&mut self) -> bool {
|
||||
@@ -458,12 +451,19 @@ impl Layout {
|
||||
event: &event::Event,
|
||||
user1: &mut U1,
|
||||
user2: &mut U2,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<EventResult> {
|
||||
let mut alterables = EventAlterables::default();
|
||||
let _event_result =
|
||||
self.push_event_widget(self.tree_root_node, event, &mut alterables, &mut (user1, user2), true)?;
|
||||
let mut event_result = EventResult::NoHit;
|
||||
self.push_event_widget(
|
||||
self.tree_root_node,
|
||||
event,
|
||||
&mut event_result,
|
||||
&mut alterables,
|
||||
&mut (user1, user2),
|
||||
true,
|
||||
)?;
|
||||
self.process_alterables(alterables)?;
|
||||
Ok(())
|
||||
Ok(event_result)
|
||||
}
|
||||
|
||||
pub fn new(globals: WguiGlobals, params: &LayoutParams) -> anyhow::Result<Self> {
|
||||
@@ -485,7 +485,10 @@ impl Layout {
|
||||
&mut state.widgets,
|
||||
&mut state.nodes,
|
||||
None, // no parent
|
||||
WidgetDiv::create(),
|
||||
WidgetState {
|
||||
interactable: false,
|
||||
..WidgetDiv::create()
|
||||
},
|
||||
taffy::Style {
|
||||
size,
|
||||
..Default::default()
|
||||
@@ -497,7 +500,10 @@ impl Layout {
|
||||
&mut state.widgets,
|
||||
&mut state.nodes,
|
||||
Some(tree_root_node),
|
||||
WidgetDiv::create(),
|
||||
WidgetState {
|
||||
interactable: false,
|
||||
..WidgetDiv::create()
|
||||
},
|
||||
taffy::Style {
|
||||
size,
|
||||
..Default::default()
|
||||
|
||||
@@ -8,7 +8,7 @@ mod widget_rectangle;
|
||||
mod widget_sprite;
|
||||
|
||||
use crate::{
|
||||
assets::{AssetPath, AssetPathOwned, normalize_path},
|
||||
assets::{normalize_path, AssetPath, AssetPathOwned},
|
||||
components::{Component, ComponentWeak},
|
||||
drawing::{self},
|
||||
globals::WguiGlobals,
|
||||
@@ -782,6 +782,14 @@ fn parse_widget_universal(ctx: &mut ParserContext, widget_id: WidgetID, attribs:
|
||||
// Attach a specific widget to name-ID map (just like getElementById)
|
||||
ctx.insert_id(&pair.value, widget_id);
|
||||
}
|
||||
"interactable" => {
|
||||
if let Ok(0) = &pair.value.parse::<i32>() {
|
||||
log::info!("setting {widget_id:?} to noninteractable.");
|
||||
ctx.layout.state.widgets.get(widget_id).unwrap().state().interactable = false;
|
||||
} else {
|
||||
print_invalid_attrib(&pair.attrib, &pair.value);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1073,4 +1081,4 @@ fn parse_document_root(
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
layout::WidgetID,
|
||||
parser::{AttribPair, ParserContext, ParserFile, parse_children, parse_widget_universal, style::parse_style},
|
||||
parser::{parse_children, parse_widget_universal, style::parse_style, AttribPair, ParserContext, ParserFile},
|
||||
widget::div::WidgetDiv,
|
||||
};
|
||||
|
||||
@@ -19,4 +19,4 @@ pub fn parse_widget_div<'a>(
|
||||
parse_children(file, ctx, node, widget.id)?;
|
||||
|
||||
Ok(widget.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
globals::Globals,
|
||||
i18n::{I18n, Translation},
|
||||
layout::WidgetID,
|
||||
renderer_vk::text::{FONT_SYSTEM, TextStyle},
|
||||
renderer_vk::text::{TextStyle, FONT_SYSTEM},
|
||||
};
|
||||
|
||||
use super::{WidgetObj, WidgetState};
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
drawing::{self, PrimitiveExtent},
|
||||
event::{
|
||||
self, CallbackData, CallbackDataCommon, CallbackMetadata, Event, EventAlterables, EventListenerCollection,
|
||||
EventListenerKind::{InternalStateChange, MouseEnter, MouseLeave, MouseMotion, MousePress, MouseRelease},
|
||||
EventListenerKind::{self, InternalStateChange, MouseLeave},
|
||||
MouseWheelEvent,
|
||||
},
|
||||
layout::{Layout, LayoutState, WidgetID},
|
||||
@@ -78,6 +78,7 @@ pub struct WidgetState {
|
||||
pub data: WidgetData,
|
||||
pub obj: Box<dyn WidgetObj>,
|
||||
pub event_listeners: EventListenerCollection,
|
||||
pub interactable: bool,
|
||||
}
|
||||
|
||||
impl WidgetState {
|
||||
@@ -100,6 +101,7 @@ impl WidgetState {
|
||||
},
|
||||
obj,
|
||||
event_listeners: EventListenerCollection::default(),
|
||||
interactable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,10 +166,27 @@ pub struct EventParams<'a> {
|
||||
pub layout: &'a taffy::Layout,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd)]
|
||||
pub enum EventResult {
|
||||
Pass, // widget acknowledged it and allows the event to pass further
|
||||
Consumed, // widget triggered an action, do not pass further
|
||||
NoHit, // event was pushed but has not found a listener (yet)
|
||||
Pass, // widget acknowledged it and allows the event to propagate further
|
||||
Consumed, // widget triggered an action, do not propagate further
|
||||
}
|
||||
|
||||
impl EventResult {
|
||||
#[must_use]
|
||||
pub const fn can_propagate(self) -> bool {
|
||||
!matches!(self, EventResult::Consumed)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn merge(self, other: Self) -> Self {
|
||||
if self > other {
|
||||
self
|
||||
} else {
|
||||
other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_scroll_enabled(style: &taffy::Style) -> (bool, bool) {
|
||||
@@ -212,30 +231,46 @@ impl dyn WidgetObj {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! call_event {
|
||||
($self:ident, $widget_id:ident, $node_id:ident, $params:ident, $kind:ident, $u1:ty, $u2:ty, $user_data:expr, $metadata:expr) => {
|
||||
for listener in $self.event_listeners.iter_filtered::<$u1, $u2>($kind) {
|
||||
let mut data = CallbackData {
|
||||
obj: $self.obj.as_mut(),
|
||||
widget_data: &mut $self.data,
|
||||
$widget_id,
|
||||
$node_id,
|
||||
metadata: $metadata,
|
||||
};
|
||||
|
||||
let mut common = CallbackDataCommon {
|
||||
state: $params.state,
|
||||
alterables: $params.alterables,
|
||||
};
|
||||
let result = listener.call_with(&mut common, &mut data, $user_data)?;
|
||||
if result == EventResult::Consumed {
|
||||
return Ok(EventResult::Consumed);
|
||||
}
|
||||
}
|
||||
};
|
||||
struct InvokeData<'a, 'b, U1: 'static, U2: 'static> {
|
||||
widget_id: WidgetID,
|
||||
node_id: taffy::NodeId,
|
||||
event_result: &'a mut EventResult,
|
||||
user_data: &'a mut (&'b mut U1, &'b mut U2),
|
||||
params: &'a mut EventParams<'a>,
|
||||
}
|
||||
|
||||
impl WidgetState {
|
||||
fn invoke_listeners<U1: 'static, U2: 'static>(
|
||||
&mut self,
|
||||
call_data: &mut InvokeData<'_, '_, U1, U2>,
|
||||
kind: event::EventListenerKind,
|
||||
metadata: CallbackMetadata,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut data = CallbackData {
|
||||
obj: self.obj.as_mut(),
|
||||
widget_data: &mut self.data,
|
||||
widget_id: call_data.widget_id,
|
||||
node_id: call_data.node_id,
|
||||
metadata,
|
||||
};
|
||||
|
||||
let mut common = CallbackDataCommon {
|
||||
state: call_data.params.state,
|
||||
alterables: call_data.params.alterables,
|
||||
};
|
||||
|
||||
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)?;
|
||||
// Consider all listeners on this widget, even if we had a Consume.
|
||||
// Store the highest value for return.
|
||||
*call_data.event_result = call_data.event_result.merge(new_result);
|
||||
if !call_data.event_result.can_propagate() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_scroll_shift_smooth(&self, info: &ScrollbarInfo, l: &taffy::Layout, timestep_alpha: f32) -> (Vec2, bool) {
|
||||
let currently_animating = self.data.scrolling_cur != self.data.scrolling_cur_prev;
|
||||
|
||||
@@ -384,53 +419,48 @@ impl WidgetState {
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn process_event<'a, U1: 'static, U2: 'static>(
|
||||
pub fn process_event<'a, 'b, U1: 'static, U2: 'static>(
|
||||
&mut self,
|
||||
widget_id: WidgetID,
|
||||
node_id: taffy::NodeId,
|
||||
event: &Event,
|
||||
user_data: &mut (&mut U1, &mut U2),
|
||||
event_result: &'a mut EventResult,
|
||||
user_data: &'a mut (&'b mut U1, &'b mut U2),
|
||||
params: &'a mut EventParams<'a>,
|
||||
) -> anyhow::Result<EventResult> {
|
||||
) -> anyhow::Result<()> {
|
||||
let hovered = event.test_mouse_within_transform(params.alterables.transform_stack.get());
|
||||
|
||||
let mut invoke_data = InvokeData {
|
||||
widget_id,
|
||||
node_id,
|
||||
event_result,
|
||||
user_data,
|
||||
params,
|
||||
};
|
||||
|
||||
match &event {
|
||||
Event::MouseDown(e) => {
|
||||
if hovered && self.data.set_device_pressed(e.device, true) {
|
||||
call_event!(
|
||||
self,
|
||||
widget_id,
|
||||
node_id,
|
||||
params,
|
||||
MousePress,
|
||||
U1,
|
||||
U2,
|
||||
user_data,
|
||||
self.invoke_listeners(
|
||||
&mut invoke_data,
|
||||
EventListenerKind::MousePress,
|
||||
CallbackMetadata::MouseButton(event::MouseButton {
|
||||
index: e.index,
|
||||
pos: e.pos
|
||||
})
|
||||
);
|
||||
pos: e.pos,
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Event::MouseUp(e) => {
|
||||
if self.data.set_device_pressed(e.device, false) {
|
||||
call_event!(
|
||||
self,
|
||||
widget_id,
|
||||
node_id,
|
||||
params,
|
||||
MouseRelease,
|
||||
U1,
|
||||
U2,
|
||||
user_data,
|
||||
self.invoke_listeners(
|
||||
&mut invoke_data,
|
||||
EventListenerKind::MouseRelease,
|
||||
CallbackMetadata::MouseButton(event::MouseButton {
|
||||
index: e.index,
|
||||
pos: e.pos,
|
||||
})
|
||||
);
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Event::MouseMotion(e) => {
|
||||
@@ -438,79 +468,42 @@ impl WidgetState {
|
||||
|
||||
if hover_state_changed {
|
||||
if self.data.is_hovered() {
|
||||
call_event!(
|
||||
self,
|
||||
widget_id,
|
||||
node_id,
|
||||
params,
|
||||
MouseEnter,
|
||||
U1,
|
||||
U2,
|
||||
user_data,
|
||||
CallbackMetadata::None
|
||||
);
|
||||
self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseEnter, CallbackMetadata::None)?;
|
||||
} else {
|
||||
call_event!(
|
||||
self,
|
||||
widget_id,
|
||||
node_id,
|
||||
params,
|
||||
MouseLeave,
|
||||
U1,
|
||||
U2,
|
||||
user_data,
|
||||
CallbackMetadata::None
|
||||
);
|
||||
self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseLeave, CallbackMetadata::None)?;
|
||||
}
|
||||
} else if hovered {
|
||||
self.invoke_listeners(
|
||||
&mut invoke_data,
|
||||
EventListenerKind::MouseMotion,
|
||||
CallbackMetadata::MousePosition(event::MousePosition { pos: e.pos }),
|
||||
)?;
|
||||
|
||||
call_event!(
|
||||
self,
|
||||
widget_id,
|
||||
node_id,
|
||||
params,
|
||||
MouseMotion,
|
||||
U1,
|
||||
U2,
|
||||
user_data,
|
||||
CallbackMetadata::MousePosition(event::MousePosition { pos: e.pos })
|
||||
);
|
||||
if self.interactable {
|
||||
*invoke_data.event_result = invoke_data.event_result.merge(EventResult::Pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::MouseWheel(e) => {
|
||||
if hovered && self.process_wheel(params, e) {
|
||||
return Ok(EventResult::Consumed);
|
||||
if hovered && self.process_wheel(invoke_data.params, e) {
|
||||
*invoke_data.event_result = EventResult::Consumed;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Event::MouseLeave(e) => {
|
||||
if self.data.set_device_hovered(e.device, false) {
|
||||
call_event!(
|
||||
self,
|
||||
widget_id,
|
||||
node_id,
|
||||
params,
|
||||
MouseLeave,
|
||||
U1,
|
||||
U2,
|
||||
user_data,
|
||||
CallbackMetadata::None
|
||||
);
|
||||
self.invoke_listeners(&mut invoke_data, MouseLeave, CallbackMetadata::None)?;
|
||||
}
|
||||
}
|
||||
Event::InternalStateChange(e) => {
|
||||
call_event!(
|
||||
self,
|
||||
widget_id,
|
||||
node_id,
|
||||
params,
|
||||
self.invoke_listeners(
|
||||
&mut invoke_data,
|
||||
InternalStateChange,
|
||||
U1,
|
||||
U2,
|
||||
user_data,
|
||||
CallbackMetadata::Custom(e.metadata)
|
||||
);
|
||||
CallbackMetadata::Custom(e.metadata),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(EventResult::Pass)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::{
|
||||
drawing::{self, PrimitiveExtent},
|
||||
layout::WidgetID,
|
||||
renderer_vk::text::{
|
||||
DEFAULT_METRICS, FONT_SYSTEM,
|
||||
custom_glyph::{CustomGlyph, CustomGlyphData},
|
||||
DEFAULT_METRICS, FONT_SYSTEM,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user