panel: per-element interactibility

This commit is contained in:
galister
2025-10-31 17:32:10 +09:00
parent 01d11e8485
commit fa562f7b12
19 changed files with 423 additions and 361 deletions

View File

@@ -1,26 +1,26 @@
use glam::{Vec2, vec2}; use glam::{vec2, Vec2};
use std::sync::Arc; use std::sync::Arc;
use testbed::{Testbed, testbed_any::TestbedAny}; use testbed::{testbed_any::TestbedAny, Testbed};
use timestep::Timestep; use timestep::Timestep;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use vulkan::init_window; use vulkan::init_window;
use vulkano::{ use vulkano::{
Validated, VulkanError,
command_buffer::CommandBufferUsage, command_buffer::CommandBufferUsage,
format::Format, format::Format,
image::{ImageUsage, view::ImageView}, image::{view::ImageView, ImageUsage},
swapchain::{ swapchain::{
CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo, acquire_next_image, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain,
SwapchainPresentInfo, acquire_next_image, SwapchainCreateInfo, SwapchainPresentInfo,
}, },
sync::GpuFuture, sync::GpuFuture,
Validated, VulkanError,
}; };
use wgui::{ use wgui::{
event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent}, event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
gfx::{WGfx, cmd::WGfxClearMode}, gfx::{cmd::WGfxClearMode, WGfx},
renderer_vk::{self}, renderer_vk::{self},
}; };
use winit::{ use winit::{
@@ -32,7 +32,7 @@ use winit::{
use crate::{ use crate::{
rate_limiter::RateLimiter, rate_limiter::RateLimiter,
testbed::{ testbed::{
TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, TestbedUpdateParams,
}, },
}; };
@@ -128,7 +128,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
event: WindowEvent::MouseWheel { delta, .. }, event: WindowEvent::MouseWheel { delta, .. },
.. ..
} => match delta { } => match delta {
MouseScrollDelta::LineDelta(x, y) => testbed MouseScrollDelta::LineDelta(x, y) => {
testbed
.layout() .layout()
.borrow_mut() .borrow_mut()
.push_event( .push_event(
@@ -140,8 +141,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
&mut (), &mut (),
&mut (), &mut (),
) )
.unwrap(), .unwrap();
MouseScrollDelta::PixelDelta(pos) => testbed }
MouseScrollDelta::PixelDelta(pos) => {
testbed
.layout() .layout()
.borrow_mut() .borrow_mut()
.push_event( .push_event(
@@ -153,7 +156,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
&mut (), &mut (),
&mut (), &mut (),
) )
.unwrap(), .unwrap();
}
}, },
Event::WindowEvent { Event::WindowEvent {
event: WindowEvent::MouseInput { state, button, .. }, event: WindowEvent::MouseInput { state, button, .. },

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
animation::{Animation, AnimationEasing}, animation::{Animation, AnimationEasing},
components::{self, Component, ComponentBase, ComponentTrait, InitData, tooltip::ComponentTooltip}, components::{self, tooltip::ComponentTooltip, Component, ComponentBase, ComponentTrait, InitData},
drawing::{self, Boundary, Color}, drawing::{self, Boundary, Color},
event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind}, event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind},
i18n::Translation, i18n::Translation,
@@ -10,10 +10,10 @@ use crate::{
util::centered_matrix, util::centered_matrix,
}, },
widget::{ widget::{
ConstructEssentials, EventResult, WidgetData,
label::{WidgetLabel, WidgetLabelParams}, label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams}, rectangle::{WidgetRectangle, WidgetRectangleParams},
util::WLength, util::WLength,
ConstructEssentials, EventResult, WidgetData,
}, },
}; };
use glam::{Mat4, Vec3}; use glam::{Mat4, Vec3};

View File

@@ -8,14 +8,14 @@ use std::{
use crate::{ use crate::{
animation::Animations, animation::Animations,
components::{Component, InitData}, 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}, event::{self, CallbackDataCommon, EventAlterables},
globals::WguiGlobals, globals::WguiGlobals,
widget::{self, EventParams, EventResult, WidgetObj, WidgetState, div::WidgetDiv}, widget::{self, div::WidgetDiv, EventParams, EventResult, WidgetObj, WidgetState},
}; };
use glam::{Vec2, vec2}; use glam::{vec2, Vec2};
use slotmap::{HopSlotMap, SecondaryMap, new_key_type}; use slotmap::{new_key_type, HopSlotMap, SecondaryMap};
use taffy::{NodeId, TaffyTree, TraversePartialTree}; use taffy::{NodeId, TaffyTree, TraversePartialTree};
new_key_type! { 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, &self,
parent_node_id: taffy::NodeId, parent_node_id: taffy::NodeId,
event: &event::Event, event: &event::Event,
event_result: &mut EventResult,
alterables: &mut EventAlterables, alterables: &mut EventAlterables,
user_data: &mut (&mut U1, &mut U2), user_data: &mut (&'a mut U1, &'a mut U2),
reverse: bool, reverse: bool,
) -> anyhow::Result<EventResult> { ) -> anyhow::Result<()> {
let mut event_result = EventResult::Pass;
let count = self.state.tree.child_count(parent_node_id); let count = self.state.tree.child_count(parent_node_id);
let mut iter = |idx: usize| -> anyhow::Result<bool> { let mut iter = |idx: usize| -> anyhow::Result<bool> {
let child_id = self.state.tree.get_child_id(parent_node_id, idx); 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)?; self.push_event_widget(child_id, event, event_result, alterables, user_data, false)?;
if child_result != EventResult::Pass { Ok(!event_result.can_propagate())
event_result = child_result;
return Ok(true);
}
Ok(false)
}; };
if reverse { 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, &self,
node_id: taffy::NodeId, node_id: taffy::NodeId,
event: &event::Event, event: &event::Event,
event_result: &mut EventResult,
alterables: &mut EventAlterables, alterables: &mut EventAlterables,
user_data: &mut (&mut U1, &mut U2), user_data: &mut (&'a mut U1, &'a mut U2),
is_root_node: bool, is_root_node: bool,
) -> anyhow::Result<EventResult> { ) -> anyhow::Result<()> {
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");
@@ -410,9 +406,9 @@ impl Layout {
let reverse_iter = is_root_node; let reverse_iter = is_root_node;
// check children first // 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 { let mut params = EventParams {
state: &self.state, state: &self.state,
layout: l, layout: l,
@@ -421,10 +417,7 @@ impl Layout {
style, style,
}; };
let this_evt_result = widget.process_event(widget_id, node_id, event, user_data, &mut params)?; widget.process_event(widget_id, node_id, event, event_result, user_data, &mut params)?;
if this_evt_result != EventResult::Pass {
evt_result = this_evt_result;
}
} }
if scissor_pushed { if scissor_pushed {
@@ -432,7 +425,7 @@ impl Layout {
} }
alterables.transform_stack.pop(); alterables.transform_stack.pop();
Ok(evt_result) Ok(())
} }
pub const fn check_toggle_needs_redraw(&mut self) -> bool { pub const fn check_toggle_needs_redraw(&mut self) -> bool {
@@ -458,12 +451,19 @@ impl Layout {
event: &event::Event, event: &event::Event,
user1: &mut U1, user1: &mut U1,
user2: &mut U2, user2: &mut U2,
) -> anyhow::Result<()> { ) -> anyhow::Result<EventResult> {
let mut alterables = EventAlterables::default(); let mut alterables = EventAlterables::default();
let _event_result = let mut event_result = EventResult::NoHit;
self.push_event_widget(self.tree_root_node, event, &mut alterables, &mut (user1, user2), true)?; self.push_event_widget(
self.tree_root_node,
event,
&mut event_result,
&mut alterables,
&mut (user1, user2),
true,
)?;
self.process_alterables(alterables)?; self.process_alterables(alterables)?;
Ok(()) Ok(event_result)
} }
pub fn new(globals: WguiGlobals, params: &LayoutParams) -> anyhow::Result<Self> { pub fn new(globals: WguiGlobals, params: &LayoutParams) -> anyhow::Result<Self> {
@@ -485,7 +485,10 @@ impl Layout {
&mut state.widgets, &mut state.widgets,
&mut state.nodes, &mut state.nodes,
None, // no parent None, // no parent
WidgetDiv::create(), WidgetState {
interactable: false,
..WidgetDiv::create()
},
taffy::Style { taffy::Style {
size, size,
..Default::default() ..Default::default()
@@ -497,7 +500,10 @@ impl Layout {
&mut state.widgets, &mut state.widgets,
&mut state.nodes, &mut state.nodes,
Some(tree_root_node), Some(tree_root_node),
WidgetDiv::create(), WidgetState {
interactable: false,
..WidgetDiv::create()
},
taffy::Style { taffy::Style {
size, size,
..Default::default() ..Default::default()

View File

@@ -8,7 +8,7 @@ mod widget_rectangle;
mod widget_sprite; mod widget_sprite;
use crate::{ use crate::{
assets::{AssetPath, AssetPathOwned, normalize_path}, assets::{normalize_path, AssetPath, AssetPathOwned},
components::{Component, ComponentWeak}, components::{Component, ComponentWeak},
drawing::{self}, drawing::{self},
globals::WguiGlobals, 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) // Attach a specific widget to name-ID map (just like getElementById)
ctx.insert_id(&pair.value, widget_id); 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);
}
}
_ => {} _ => {}
} }
} }

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
layout::WidgetID, 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, widget::div::WidgetDiv,
}; };

View File

@@ -10,7 +10,7 @@ use crate::{
globals::Globals, globals::Globals,
i18n::{I18n, Translation}, i18n::{I18n, Translation},
layout::WidgetID, layout::WidgetID,
renderer_vk::text::{FONT_SYSTEM, TextStyle}, renderer_vk::text::{TextStyle, FONT_SYSTEM},
}; };
use super::{WidgetObj, WidgetState}; use super::{WidgetObj, WidgetState};

View File

@@ -7,7 +7,7 @@ use crate::{
drawing::{self, PrimitiveExtent}, drawing::{self, PrimitiveExtent},
event::{ event::{
self, CallbackData, CallbackDataCommon, CallbackMetadata, Event, EventAlterables, EventListenerCollection, self, CallbackData, CallbackDataCommon, CallbackMetadata, Event, EventAlterables, EventListenerCollection,
EventListenerKind::{InternalStateChange, MouseEnter, MouseLeave, MouseMotion, MousePress, MouseRelease}, EventListenerKind::{self, InternalStateChange, MouseLeave},
MouseWheelEvent, MouseWheelEvent,
}, },
layout::{Layout, LayoutState, WidgetID}, layout::{Layout, LayoutState, WidgetID},
@@ -78,6 +78,7 @@ 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,
} }
impl WidgetState { impl WidgetState {
@@ -100,6 +101,7 @@ impl WidgetState {
}, },
obj, obj,
event_listeners: EventListenerCollection::default(), event_listeners: EventListenerCollection::default(),
interactable: true,
} }
} }
} }
@@ -164,10 +166,27 @@ pub struct EventParams<'a> {
pub layout: &'a taffy::Layout, pub layout: &'a taffy::Layout,
} }
#[derive(Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq, PartialOrd)]
pub enum EventResult { pub enum EventResult {
Pass, // widget acknowledged it and allows the event to pass further NoHit, // event was pushed but has not found a listener (yet)
Consumed, // widget triggered an action, do not pass further 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) { fn get_scroll_enabled(style: &taffy::Style) -> (bool, bool) {
@@ -212,30 +231,46 @@ impl dyn WidgetObj {
} }
} }
macro_rules! call_event { struct InvokeData<'a, 'b, U1: 'static, U2: 'static> {
($self:ident, $widget_id:ident, $node_id:ident, $params:ident, $kind:ident, $u1:ty, $u2:ty, $user_data:expr, $metadata:expr) => { widget_id: WidgetID,
for listener in $self.event_listeners.iter_filtered::<$u1, $u2>($kind) { node_id: taffy::NodeId,
let mut data = CallbackData { event_result: &'a mut EventResult,
obj: $self.obj.as_mut(), user_data: &'a mut (&'b mut U1, &'b mut U2),
widget_data: &mut $self.data, params: &'a mut EventParams<'a>,
$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);
}
}
};
} }
impl WidgetState { 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) { 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; let currently_animating = self.data.scrolling_cur != self.data.scrolling_cur_prev;
@@ -384,53 +419,48 @@ impl WidgetState {
true true
} }
#[allow(clippy::too_many_lines)] pub fn process_event<'a, 'b, U1: 'static, U2: 'static>(
#[allow(clippy::cognitive_complexity)]
pub fn process_event<'a, U1: 'static, U2: 'static>(
&mut self, &mut self,
widget_id: WidgetID, widget_id: WidgetID,
node_id: taffy::NodeId, node_id: taffy::NodeId,
event: &Event, 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>, params: &'a mut EventParams<'a>,
) -> anyhow::Result<EventResult> { ) -> anyhow::Result<()> {
let hovered = event.test_mouse_within_transform(params.alterables.transform_stack.get()); 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 { 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) {
call_event!( self.invoke_listeners(
self, &mut invoke_data,
widget_id, EventListenerKind::MousePress,
node_id,
params,
MousePress,
U1,
U2,
user_data,
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) {
call_event!( self.invoke_listeners(
self, &mut invoke_data,
widget_id, EventListenerKind::MouseRelease,
node_id,
params,
MouseRelease,
U1,
U2,
user_data,
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) => {
@@ -438,79 +468,42 @@ impl WidgetState {
if hover_state_changed { if hover_state_changed {
if self.data.is_hovered() { if self.data.is_hovered() {
call_event!( self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseEnter, CallbackMetadata::None)?;
self,
widget_id,
node_id,
params,
MouseEnter,
U1,
U2,
user_data,
CallbackMetadata::None
);
} else { } else {
call_event!( self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseLeave, CallbackMetadata::None)?;
self,
widget_id,
node_id,
params,
MouseLeave,
U1,
U2,
user_data,
CallbackMetadata::None
);
} }
} else if hovered {
self.invoke_listeners(
&mut invoke_data,
EventListenerKind::MouseMotion,
CallbackMetadata::MousePosition(event::MousePosition { pos: e.pos }),
)?;
call_event!( if self.interactable {
self, *invoke_data.event_result = invoke_data.event_result.merge(EventResult::Pass);
widget_id, }
node_id,
params,
MouseMotion,
U1,
U2,
user_data,
CallbackMetadata::MousePosition(event::MousePosition { pos: e.pos })
);
} }
} }
Event::MouseWheel(e) => { Event::MouseWheel(e) => {
if hovered && self.process_wheel(params, e) { if hovered && self.process_wheel(invoke_data.params, e) {
return Ok(EventResult::Consumed); *invoke_data.event_result = EventResult::Consumed;
return Ok(());
} }
} }
Event::MouseLeave(e) => { Event::MouseLeave(e) => {
if self.data.set_device_hovered(e.device, false) { if self.data.set_device_hovered(e.device, false) {
call_event!( self.invoke_listeners(&mut invoke_data, MouseLeave, CallbackMetadata::None)?;
self,
widget_id,
node_id,
params,
MouseLeave,
U1,
U2,
user_data,
CallbackMetadata::None
);
} }
} }
Event::InternalStateChange(e) => { Event::InternalStateChange(e) => {
call_event!( self.invoke_listeners(
self, &mut invoke_data,
widget_id,
node_id,
params,
InternalStateChange, InternalStateChange,
U1, CallbackMetadata::Custom(e.metadata),
U2, )?;
user_data,
CallbackMetadata::Custom(e.metadata)
);
} }
} }
Ok(EventResult::Pass) Ok(())
} }
} }

View File

@@ -7,8 +7,8 @@ use crate::{
drawing::{self, PrimitiveExtent}, drawing::{self, PrimitiveExtent},
layout::WidgetID, layout::WidgetID,
renderer_vk::text::{ renderer_vk::text::{
DEFAULT_METRICS, FONT_SYSTEM,
custom_glyph::{CustomGlyph, CustomGlyphData}, custom_glyph::{CustomGlyph, CustomGlyphData},
DEFAULT_METRICS, FONT_SYSTEM,
}, },
}; };

View File

@@ -35,7 +35,7 @@
</template> </template>
<elements> <elements>
<div width="400" height="200"> <div width="460" height="260" padding="30" interactable="0">
<rectangle width="100%" height="100%" padding="4" box_sizing="content_box" flex_wrap="wrap" flex_direction="column" gap="4" color="~bg_color"> <rectangle width="100%" height="100%" padding="4" box_sizing="content_box" flex_wrap="wrap" flex_direction="column" gap="4" color="~bg_color">
<div width="100%" flex_direction="row"> <div width="100%" flex_direction="row">
<Device src="watch/hmd.svg" size="40" device="0" /> <Device src="watch/hmd.svg" size="40" device="0" />
@@ -64,13 +64,13 @@
</div> </div>
</div> </div>
<div width="100%" flex_direction="row"> <div width="100%" flex_direction="row">
<Button macro="button_style" _press="::DashToggle" tooltip="Toggle dashboard" tooltip_side="top"> <Button macro="button_style" _press="::DashToggle" tooltip="Dashboard" tooltip_side="top">
<sprite color="~set_color" width="40" height="40" src="watch/home.svg" /> <sprite color="~set_color" width="40" height="40" src="watch/home.svg" />
</Button> </Button>
<div id="sets"> <div id="sets">
<!-- Will populate <Set> tags at runtime --> <!-- Will populate <Set> tags at runtime -->
</div> </div>
<Button macro="button_style" _press="::EditToggle" tooltip="Edit sets" tooltip_side="top"> <Button macro="button_style" _press="::EditToggle" tooltip="Edit mode" tooltip_side="top">
<sprite color="~set_color" width="40" height="40" src="watch/edit.svg" /> <sprite color="~set_color" width="40" height="40" src="watch/edit.svg" />
</Button> </Button>
</div> </div>

View File

@@ -15,6 +15,13 @@ use crate::windowing::{OverlayID, OverlaySelector};
use super::task::{TaskContainer, TaskType}; use super::task::{TaskContainer, TaskType};
#[derive(Clone, Default)]
pub struct HoverResult {
pub haptics: Option<Haptics>,
/// If true, the laster shows at this position and no further raycasting will be done.
pub consume: bool,
}
pub struct TrackedDevice { pub struct TrackedDevice {
pub soc: Option<f32>, pub soc: Option<f32>,
pub charging: bool, pub charging: bool,
@@ -307,7 +314,6 @@ where
} }
} }
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
fn interact_hand<O>( fn interact_hand<O>(
idx: usize, idx: usize,
overlays: &mut OverlayWindowManager<O>, overlays: &mut OverlayWindowManager<O>,
@@ -316,6 +322,7 @@ fn interact_hand<O>(
where where
O: Default, O: Default,
{ {
// already grabbing, ignore everything else
let mut pointer = &mut app.input_state.pointers[idx]; let mut pointer = &mut app.input_state.pointers[idx];
if let Some(grab_data) = pointer.interaction.grabbed { if let Some(grab_data) = pointer.interaction.grabbed {
if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) { if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) {
@@ -327,45 +334,29 @@ where
return (0.1, None); return (0.1, None);
} }
let Some(mut hit) = pointer.get_nearest_hit(overlays) else { let hovered_id = pointer.interaction.hovered_id.take();
if let Some(hovered_id) = pointer.interaction.hovered_id.take() { let (Some(mut hit), haptics) = get_nearest_hit(idx, overlays, app) else {
if let Some(hovered) = overlays.mut_by_id(hovered_id) { handle_no_hit(idx, hovered_id, overlays, app);
hovered.config.backend.on_left(app, idx);
}
pointer = &mut app.input_state.pointers[idx];
pointer.interaction.hovered_id = None;
}
if !pointer.now.click
&& pointer.before.click
&& let Some(clicked_id) = pointer.interaction.clicked_id.take()
&& let Some(clicked) = overlays.mut_by_id(clicked_id)
{
let hit = PointerHit {
pointer: pointer.idx,
overlay: clicked_id,
mode: pointer.interaction.mode,
..Default::default()
};
clicked.config.backend.on_pointer(app, &hit, false);
}
return (0.0, None); // no hit return (0.0, None); // no hit
}; };
if let Some(hovered_id) = pointer.interaction.hovered_id // focus change
if let Some(hovered_id) = hovered_id
&& hovered_id != hit.overlay && hovered_id != hit.overlay
&& let Some(old_hovered) = overlays.mut_by_id(hovered_id) && let Some(old_hovered) = overlays.mut_by_id(hovered_id)
{ {
if Some(pointer.idx) == old_hovered.primary_pointer { if old_hovered.primary_pointer.is_some_and(|i| i == idx) {
old_hovered.primary_pointer = None; old_hovered.primary_pointer = None;
} }
log::debug!("{} on_left (focus changed)", old_hovered.config.name);
old_hovered.config.backend.on_left(app, idx); old_hovered.config.backend.on_left(app, idx);
pointer = &mut app.input_state.pointers[idx];
} }
let Some(hovered) = overlays.mut_by_id(hit.overlay) else { let Some(hovered) = overlays.mut_by_id(hit.overlay) else {
log::warn!("Hit overlay {:?} does not exist", hit.overlay); log::warn!("Hit overlay {:?} does not exist", hit.overlay);
return (0.0, None); // no hit return (0.0, None); // no hit
}; };
pointer = &mut app.input_state.pointers[idx];
pointer.interaction.hovered_id = Some(hit.overlay); pointer.interaction.hovered_id = Some(hit.overlay);
if let Some(primary_pointer) = hovered.primary_pointer { if let Some(primary_pointer) = hovered.primary_pointer {
@@ -383,6 +374,7 @@ where
let hovered_state = hovered.config.active_state.as_mut().unwrap(); let hovered_state = hovered.config.active_state.as_mut().unwrap();
// grab
if pointer.now.grab && !pointer.before.grab && hovered_state.grabbable { if pointer.now.grab && !pointer.before.grab && hovered_state.grabbable {
update_focus( update_focus(
&mut app.hid_provider.keyboard_focus, &mut app.hid_provider.keyboard_focus,
@@ -400,16 +392,68 @@ where
); );
} }
// Pass mouse motion events only if not scrolling handle_scroll(&hit, hovered, app);
// (allows scrolling on all Chromium-based applications)
let haptics = hovered.config.backend.on_hover(app, &hit);
pointer = &mut app.input_state.pointers[idx]; // click / release
let pointer = &mut app.input_state.pointers[hit.pointer];
if pointer.now.click && !pointer.before.click {
pointer.interaction.clicked_id = Some(hit.overlay);
update_focus(
&mut app.hid_provider.keyboard_focus,
&hovered.config.keyboard_focus,
);
hovered.config.backend.on_pointer(app, &hit, true);
} else if !pointer.now.click && pointer.before.click {
// send release event to overlay that was originally clicked
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {
if let Some(clicked) = overlays.mut_by_id(clicked_id) {
clicked.config.backend.on_pointer(app, &hit, false);
}
} else {
hovered.config.backend.on_pointer(app, &hit, false);
}
}
(hit.dist, haptics)
}
fn handle_no_hit<O>(
pointer_idx: usize,
hovered_id: Option<OverlayID>,
overlays: &mut OverlayWindowManager<O>,
app: &mut AppState,
) {
if let Some(hovered_id) = hovered_id
&& let Some(hovered) = overlays.mut_by_id(hovered_id)
{
log::debug!("{} on_left (no hit)", hovered.config.name);
hovered.config.backend.on_left(app, pointer_idx);
}
// in case click released while not aiming at anything
// send release event to overlay that was originally clicked
let pointer = &mut app.input_state.pointers[pointer_idx];
if !pointer.now.click
&& pointer.before.click
&& let Some(clicked_id) = pointer.interaction.clicked_id.take()
&& let Some(clicked) = overlays.mut_by_id(clicked_id)
{
let hit = PointerHit {
pointer: pointer.idx,
overlay: clicked_id,
mode: pointer.interaction.mode,
..Default::default()
};
clicked.config.backend.on_pointer(app, &hit, false);
}
}
fn handle_scroll<O>(hit: &PointerHit, hovered: &mut OverlayWindowData<O>, app: &mut AppState) {
let pointer = &mut app.input_state.pointers[hit.pointer];
if pointer.now.scroll_x.abs() > 0.1 || pointer.now.scroll_y.abs() > 0.1 { if pointer.now.scroll_x.abs() > 0.1 || pointer.now.scroll_y.abs() > 0.1 {
let scroll_x = pointer.now.scroll_x; let scroll_x = pointer.now.scroll_x;
let scroll_y = pointer.now.scroll_y; let scroll_y = pointer.now.scroll_y;
if app.input_state.pointers[1 - idx] if app.input_state.pointers[1 - hit.pointer]
.interaction .interaction
.grabbed .grabbed
.is_some_and(|x| x.grabbed_id == hit.overlay) .is_some_and(|x| x.grabbed_id == hit.overlay)
@@ -437,33 +481,21 @@ where
.backend .backend
.on_scroll(app, &hit, scroll_y, scroll_x); .on_scroll(app, &hit, scroll_y, scroll_x);
} }
pointer = &mut app.input_state.pointers[idx]; }
} }
if pointer.now.click && !pointer.before.click { fn get_nearest_hit<O>(
pointer.interaction.clicked_id = Some(hit.overlay); pointer_idx: usize,
update_focus( overlays: &mut OverlayWindowManager<O>,
&mut app.hid_provider.keyboard_focus, app: &mut AppState,
&hovered.config.keyboard_focus, ) -> (Option<PointerHit>, Option<Haptics>)
);
hovered.config.backend.on_pointer(app, &hit, true);
} else if !pointer.now.click && pointer.before.click {
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {
if let Some(clicked) = overlays.mut_by_id(clicked_id) {
clicked.config.backend.on_pointer(app, &hit, false);
}
} else {
hovered.config.backend.on_pointer(app, &hit, false);
}
}
(hit.dist, haptics)
}
impl Pointer {
fn get_nearest_hit<O>(&mut self, overlays: &mut OverlayWindowManager<O>) -> Option<PointerHit>
where where
O: Default, O: Default,
{ {
let pointer = &mut app.input_state.pointers[pointer_idx];
let ray_origin = pointer.pose;
let mode = pointer.interaction.mode;
let mut hits: SmallVec<[RayHit; 8]> = smallvec!(); let mut hits: SmallVec<[RayHit; 8]> = smallvec!();
for (id, overlay) in overlays.iter() { for (id, overlay) in overlays.iter() {
@@ -474,17 +506,17 @@ impl Pointer {
continue; continue;
} }
if let Some(hit) = self.ray_test( if let Some(hit) = ray_test(
&ray_origin,
id, id,
&overlay_state.transform, &overlay_state.transform,
overlay_state.curvature.as_ref(), overlay_state.curvature.as_ref(),
) { ) {
if hit.dist.is_infinite() || hit.dist.is_nan() { if hit.dist.is_finite() {
continue;
}
hits.push(hit); hits.push(hit);
} }
} }
}
hits.sort_by(|a, b| a.dist.total_cmp(&b.dist)); hits.sort_by(|a, b| a.dist.total_cmp(&b.dist));
@@ -505,19 +537,25 @@ impl Pointer {
continue; continue;
} }
return Some(PointerHit { let hit = PointerHit {
pointer: self.idx, pointer: pointer_idx,
overlay: hit.overlay, overlay: hit.overlay,
mode: self.interaction.mode, mode,
primary: false, primary: false,
uv, uv,
dist: hit.dist, dist: hit.dist,
}); };
let result = overlay.config.backend.on_hover(app, &hit);
if result.consume {
return (Some(hit), result.haptics);
}
} }
None (None, None)
} }
impl Pointer {
fn start_grab( fn start_grab(
&mut self, &mut self,
id: OverlayID, id: OverlayID,
@@ -608,23 +646,24 @@ impl Pointer {
log::debug!("Hand {}: dropped {}", idx, overlay.config.name); log::debug!("Hand {}: dropped {}", idx, overlay.config.name);
} }
} }
}
fn ray_test( fn ray_test(
&self, ray_origin: &Affine3A,
overlay: OverlayID, overlay: OverlayID,
transform: &Affine3A, overlay_pose: &Affine3A,
curvature: Option<&f32>, curvature: Option<&f32>,
) -> Option<RayHit> { ) -> Option<RayHit> {
let (dist, local_pos) = curvature.map_or_else( let (dist, local_pos) = curvature.map_or_else(
|| { || {
Some(raycast_plane( Some(raycast_plane(
&self.pose, &ray_origin,
Vec3A::NEG_Z, Vec3A::NEG_Z,
transform, overlay_pose,
Vec3A::NEG_Z, Vec3A::NEG_Z,
)) ))
}, },
|curvature| raycast_cylinder(&self.pose, Vec3A::NEG_Z, transform, *curvature), |curvature| raycast_cylinder(ray_origin, Vec3A::NEG_Z, overlay_pose, *curvature),
)?; )?;
if dist < 0.0 { if dist < 0.0 {
@@ -634,12 +673,11 @@ impl Pointer {
Some(RayHit { Some(RayHit {
overlay, overlay,
global_pos: self.pose.transform_point3a(Vec3A::NEG_Z * dist), global_pos: ray_origin.transform_point3a(Vec3A::NEG_Z * dist),
local_pos, local_pos,
dist, dist,
}) })
} }
}
fn raycast_plane( fn raycast_plane(
source: &Affine3A, source: &Affine3A,

View File

@@ -22,7 +22,7 @@ use vulkano::{
}; };
use wgui::gfx::WGfx; use wgui::gfx::WGfx;
use crate::backend::input::{Haptics, PointerHit}; use crate::backend::input::{HoverResult, PointerHit};
use crate::graphics::CommandBuffers; use crate::graphics::CommandBuffers;
use crate::state::AppState; use crate::state::AppState;
use crate::windowing::backend::{FrameMeta, OverlayBackend, ShouldRender}; use crate::windowing::backend::{FrameMeta, OverlayBackend, ShouldRender};
@@ -206,8 +206,8 @@ impl OverlayBackend for LineBackend {
}) })
} }
fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> Option<Haptics> { fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult {
None HoverResult::default()
} }
fn on_left(&mut self, _: &mut AppState, _: usize) {} fn on_left(&mut self, _: &mut AppState, _: usize) {}
fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {} fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {}

View File

@@ -15,11 +15,11 @@ use wgui::{
layout::{Layout, LayoutParams, WidgetID}, layout::{Layout, LayoutParams, WidgetID},
parser::ParserState, parser::ParserState,
renderer_vk::context::Context as WguiContext, renderer_vk::context::Context as WguiContext,
widget::{label::WidgetLabel, rectangle::WidgetRectangle}, widget::{label::WidgetLabel, rectangle::WidgetRectangle, EventResult},
}; };
use crate::{ use crate::{
backend::input::{Haptics, PointerHit, PointerMode}, backend::input::{Haptics, HoverResult, PointerHit, PointerMode},
graphics::{CommandBuffers, ExtentExt}, graphics::{CommandBuffers, ExtentExt},
state::AppState, state::AppState,
windowing::backend::{ui_transform, FrameMeta, OverlayBackend, ShouldRender}, windowing::backend::{ui_transform, FrameMeta, OverlayBackend, ShouldRender},
@@ -150,9 +150,13 @@ impl<S: 'static> GuiPanel<S> {
self.layout.update(MAX_SIZE_VEC2, 0.0) self.layout.update(MAX_SIZE_VEC2, 0.0)
} }
pub fn push_event(&mut self, app: &mut AppState, event: &WguiEvent) { pub fn push_event(&mut self, app: &mut AppState, event: &WguiEvent) -> EventResult {
if let Err(e) = self.layout.push_event(event, app, &mut self.state) { match self.layout.push_event(event, app, &mut self.state) {
Ok(r) => r,
Err(e) => {
log::error!("Failed to push event: {e:?}"); log::error!("Failed to push event: {e:?}");
EventResult::NoHit
}
} }
} }
@@ -266,23 +270,28 @@ impl<S: 'static> OverlayBackend for GuiPanel<S> {
self.push_event(app, &e); self.push_event(app, &e);
} }
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics> { fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult {
let e = &WguiEvent::MouseMotion(MouseMotionEvent { let e = &WguiEvent::MouseMotion(MouseMotionEvent {
pos: hit.uv * self.layout.content_size, pos: hit.uv * self.layout.content_size,
device: hit.pointer, device: hit.pointer,
}); });
self.push_event(app, &e); let result = self.push_event(app, &e);
self.layout HoverResult {
consume: result != EventResult::NoHit,
haptics: self
.layout
.check_toggle_haptics_triggered() .check_toggle_haptics_triggered()
.then_some(Haptics { .then_some(Haptics {
intensity: 0.1, intensity: 0.1,
duration: 0.01, duration: 0.01,
frequency: 5.0, frequency: 5.0,
}) }),
}
} }
fn on_left(&mut self, app: &mut AppState, pointer: usize) { fn on_left(&mut self, app: &mut AppState, pointer: usize) {
log::info!("panel: on left");
let e = WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer }); let e = WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer });
self.push_event(app, &e); self.push_event(app, &e);
} }

View File

@@ -11,11 +11,11 @@ use wgui::{
}; };
use crate::{ use crate::{
backend::input::{Haptics, PointerHit}, backend::input::{HoverResult, PointerHit},
graphics::CommandBuffers, graphics::CommandBuffers,
gui::panel::GuiPanel, gui::panel::GuiPanel,
state::AppState, state::AppState,
subsystem::hid::{ALT, CTRL, KeyModifier, META, SHIFT, SUPER, VirtualKey}, subsystem::hid::{KeyModifier, VirtualKey, ALT, CTRL, META, SHIFT, SUPER},
windowing::backend::{FrameMeta, OverlayBackend, ShouldRender}, windowing::backend::{FrameMeta, OverlayBackend, ShouldRender},
}; };
@@ -75,7 +75,7 @@ impl OverlayBackend for KeyboardBackend {
fn on_left(&mut self, app: &mut AppState, pointer: usize) { fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.panel.on_left(app, pointer); self.panel.on_left(app, pointer);
} }
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics> { fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult {
self.panel.on_hover(app, hit) self.panel.on_hover(app, hit)
} }
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> { fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {

View File

@@ -10,7 +10,7 @@ use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSel
use crate::{ use crate::{
backend::{ backend::{
input::{Haptics, PointerHit}, input::{HoverResult, PointerHit},
task::TaskType, task::TaskType,
}, },
graphics::CommandBuffers, graphics::CommandBuffers,
@@ -130,8 +130,11 @@ impl OverlayBackend for MirrorBackend {
self.renderer.as_mut().and_then(ScreenBackend::frame_meta) self.renderer.as_mut().and_then(ScreenBackend::frame_meta)
} }
fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> Option<Haptics> { fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult {
None HoverResult {
consume: true,
..HoverResult::default()
}
} }
fn on_left(&mut self, _: &mut AppState, _: usize) {} fn on_left(&mut self, _: &mut AppState, _: usize) {}
fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {} fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {}

View File

@@ -1,21 +1,21 @@
use std::{ use std::{
sync::{Arc, LazyLock, atomic::AtomicU64}, sync::{atomic::AtomicU64, Arc, LazyLock},
time::Instant, time::Instant,
}; };
use glam::{Affine2, Vec2, vec2}; use glam::{vec2, Affine2, Vec2};
use vulkano::image::view::ImageView; use vulkano::image::view::ImageView;
use wlx_capture::{WlxCapture, frame::Transform}; use wlx_capture::{frame::Transform, WlxCapture};
use crate::{ use crate::{
backend::input::{Haptics, PointerHit, PointerMode}, backend::input::{HoverResult, PointerHit, PointerMode},
graphics::{CommandBuffers, ExtentExt}, graphics::{CommandBuffers, ExtentExt},
state::AppState, state::AppState,
subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT}, subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
windowing::backend::{FrameMeta, OverlayBackend, ShouldRender}, windowing::backend::{FrameMeta, OverlayBackend, ShouldRender},
}; };
use super::capture::{ScreenPipeline, WlxCaptureIn, WlxCaptureOut, receive_callback}; use super::capture::{receive_callback, ScreenPipeline, WlxCaptureIn, WlxCaptureOut};
const CURSOR_SIZE: f32 = 16. / 1440.; const CURSOR_SIZE: f32 = 16. / 1440.;
@@ -211,7 +211,7 @@ impl OverlayBackend for ScreenBackend {
self.meta self.meta
} }
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics> { fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
log::trace!("Hover: {:?}", hit.uv); log::trace!("Hover: {:?}", hit.uv);
if can_move() if can_move()
@@ -222,7 +222,10 @@ impl OverlayBackend for ScreenBackend {
app.hid_provider.inner.mouse_move(pos); app.hid_provider.inner.mouse_move(pos);
set_next_move(u64::from(app.session.config.mouse_move_interval_ms)); set_next_move(u64::from(app.session.config.mouse_move_interval_ms));
} }
None HoverResult {
consume: true,
..HoverResult::default()
}
} }
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
let btn = match hit.mode { let btn = match hit.mode {

View File

@@ -17,7 +17,7 @@ use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
use crate::{ use crate::{
backend::{ backend::{
input::{self}, input::{self, HoverResult},
task::TaskType, task::TaskType,
wayvr::{ wayvr::{
self, display, self, display,
@@ -716,11 +716,7 @@ impl OverlayBackend for WayVRBackend {
}) })
} }
fn on_hover( fn on_hover(&mut self, _app: &mut state::AppState, hit: &input::PointerHit) -> HoverResult {
&mut self,
_app: &mut state::AppState,
hit: &input::PointerHit,
) -> Option<input::Haptics> {
let ctx = self.context.borrow(); let ctx = self.context.borrow();
let wayvr = &mut ctx.wayvr.borrow_mut(); let wayvr = &mut ctx.wayvr.borrow_mut();
@@ -737,7 +733,10 @@ impl OverlayBackend for WayVRBackend {
.send_mouse_move(ctx.display, x as u32, y as u32); .send_mouse_move(ctx.display, x as u32, y as u32);
} }
wayvr.pending_haptics.take() HoverResult {
haptics: wayvr.pending_haptics.take(),
consume: true,
}
} }
fn on_left(&mut self, _app: &mut state::AppState, _pointer: usize) { fn on_left(&mut self, _app: &mut state::AppState, _pointer: usize) {

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use vulkano::{format::Format, image::view::ImageView}; use vulkano::{format::Format, image::view::ImageView};
use crate::{ use crate::{
backend::input::{Haptics, PointerHit}, backend::input::{HoverResult, PointerHit},
graphics::CommandBuffers, graphics::CommandBuffers,
state::AppState, state::AppState,
}; };
@@ -48,7 +48,7 @@ pub trait OverlayBackend {
/// Must be true if should_render was also true on the same frame. /// Must be true if should_render was also true on the same frame.
fn frame_meta(&mut self) -> Option<FrameMeta>; fn frame_meta(&mut self) -> Option<FrameMeta>;
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>; fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult;
fn on_left(&mut self, app: &mut AppState, pointer: usize); fn on_left(&mut self, app: &mut AppState, pointer: usize);
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool); fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32); fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);

View File

@@ -91,7 +91,9 @@ where
Ok(me) Ok(me)
} }
}
impl<T> OverlayWindowManager<T> {
pub fn mut_by_selector( pub fn mut_by_selector(
&mut self, &mut self,
selector: &OverlaySelector, selector: &OverlaySelector,

View File

@@ -63,10 +63,7 @@ where
} }
} }
impl<T> OverlayWindowData<T> impl<T> OverlayWindowData<T> {
where
T: Default,
{
pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
//TODO: load state? //TODO: load state?