diff --git a/uidev/src/main.rs b/uidev/src/main.rs index c9d2489..8e178b0 100644 --- a/uidev/src/main.rs +++ b/uidev/src/main.rs @@ -1,26 +1,26 @@ -use glam::{Vec2, vec2}; +use glam::{vec2, Vec2}; use std::sync::Arc; -use testbed::{Testbed, testbed_any::TestbedAny}; +use testbed::{testbed_any::TestbedAny, Testbed}; use timestep::Timestep; -use tracing_subscriber::EnvFilter; use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::EnvFilter; use vulkan::init_window; use vulkano::{ - Validated, VulkanError, command_buffer::CommandBufferUsage, format::Format, - image::{ImageUsage, view::ImageView}, + image::{view::ImageView, ImageUsage}, swapchain::{ - CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo, - SwapchainPresentInfo, acquire_next_image, + acquire_next_image, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, + SwapchainCreateInfo, SwapchainPresentInfo, }, sync::GpuFuture, + Validated, VulkanError, }; use wgui::{ event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent}, - gfx::{WGfx, cmd::WGfxClearMode}, + gfx::{cmd::WGfxClearMode, WGfx}, renderer_vk::{self}, }; use winit::{ @@ -32,7 +32,7 @@ use winit::{ use crate::{ rate_limiter::RateLimiter, testbed::{ - TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, + testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, TestbedUpdateParams, }, }; @@ -128,32 +128,36 @@ fn main() -> Result<(), Box> { event: WindowEvent::MouseWheel { delta, .. }, .. } => match delta { - MouseScrollDelta::LineDelta(x, y) => testbed - .layout() - .borrow_mut() - .push_event( - &wgui::event::Event::MouseWheel(MouseWheelEvent { - shift: Vec2::new(x, y), - pos: mouse / scale, - device: 0, - }), - &mut (), - &mut (), - ) - .unwrap(), - MouseScrollDelta::PixelDelta(pos) => testbed - .layout() - .borrow_mut() - .push_event( - &wgui::event::Event::MouseWheel(MouseWheelEvent { - shift: Vec2::new(pos.x as f32 / 5.0, pos.y as f32 / 5.0), - pos: mouse / scale, - device: 0, - }), - &mut (), - &mut (), - ) - .unwrap(), + MouseScrollDelta::LineDelta(x, y) => { + testbed + .layout() + .borrow_mut() + .push_event( + &wgui::event::Event::MouseWheel(MouseWheelEvent { + shift: Vec2::new(x, y), + pos: mouse / scale, + device: 0, + }), + &mut (), + &mut (), + ) + .unwrap(); + } + MouseScrollDelta::PixelDelta(pos) => { + testbed + .layout() + .borrow_mut() + .push_event( + &wgui::event::Event::MouseWheel(MouseWheelEvent { + shift: Vec2::new(pos.x as f32 / 5.0, pos.y as f32 / 5.0), + pos: mouse / scale, + device: 0, + }), + &mut (), + &mut (), + ) + .unwrap(); + } }, Event::WindowEvent { event: WindowEvent::MouseInput { state, button, .. }, diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index 85caccc..529ac35 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -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}; diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 7c2b468..15e4c18 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -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( + 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 { - 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 { 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( + 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 { + ) -> 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 { 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 { @@ -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() diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index 1246fc5..8242915 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -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::() { + 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(()) -} \ No newline at end of file +} diff --git a/wgui/src/parser/widget_div.rs b/wgui/src/parser/widget_div.rs index e26d188..bf5c94a 100644 --- a/wgui/src/parser/widget_div.rs +++ b/wgui/src/parser/widget_div.rs @@ -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) -} \ No newline at end of file +} diff --git a/wgui/src/widget/label.rs b/wgui/src/widget/label.rs index eb6a2ad..a22de38 100644 --- a/wgui/src/widget/label.rs +++ b/wgui/src/widget/label.rs @@ -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}; diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs index ebec601..5371fba 100644 --- a/wgui/src/widget/mod.rs +++ b/wgui/src/widget/mod.rs @@ -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, 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( + &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::(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 { + ) -> 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(()) } } diff --git a/wgui/src/widget/sprite.rs b/wgui/src/widget/sprite.rs index 9b44749..7bc9ec3 100644 --- a/wgui/src/widget/sprite.rs +++ b/wgui/src/widget/sprite.rs @@ -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, }, }; diff --git a/wlx-overlay-s/src/assets/gui/watch.xml b/wlx-overlay-s/src/assets/gui/watch.xml index d717bc2..b166350 100644 --- a/wlx-overlay-s/src/assets/gui/watch.xml +++ b/wlx-overlay-s/src/assets/gui/watch.xml @@ -35,7 +35,7 @@ -
+
@@ -64,13 +64,13 @@
-
-
diff --git a/wlx-overlay-s/src/backend/input.rs b/wlx-overlay-s/src/backend/input.rs index 7bae2ab..dff21fb 100644 --- a/wlx-overlay-s/src/backend/input.rs +++ b/wlx-overlay-s/src/backend/input.rs @@ -15,6 +15,13 @@ use crate::windowing::{OverlayID, OverlaySelector}; use super::task::{TaskContainer, TaskType}; +#[derive(Clone, Default)] +pub struct HoverResult { + pub haptics: Option, + /// If true, the laster shows at this position and no further raycasting will be done. + pub consume: bool, +} + pub struct TrackedDevice { pub soc: Option, pub charging: bool, @@ -307,7 +314,6 @@ where } } -#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] fn interact_hand( idx: usize, overlays: &mut OverlayWindowManager, @@ -316,6 +322,7 @@ fn interact_hand( where O: Default, { + // already grabbing, ignore everything else let mut pointer = &mut app.input_state.pointers[idx]; if let Some(grab_data) = pointer.interaction.grabbed { if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) { @@ -327,45 +334,29 @@ where return (0.1, None); } - let Some(mut hit) = pointer.get_nearest_hit(overlays) else { - if let Some(hovered_id) = pointer.interaction.hovered_id.take() { - if let Some(hovered) = overlays.mut_by_id(hovered_id) { - 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); - } + let hovered_id = pointer.interaction.hovered_id.take(); + let (Some(mut hit), haptics) = get_nearest_hit(idx, overlays, app) else { + handle_no_hit(idx, hovered_id, overlays, app); 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 && 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; } + log::debug!("{} on_left (focus changed)", old_hovered.config.name); 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 { log::warn!("Hit overlay {:?} does not exist", hit.overlay); return (0.0, None); // no hit }; - + pointer = &mut app.input_state.pointers[idx]; pointer.interaction.hovered_id = Some(hit.overlay); if let Some(primary_pointer) = hovered.primary_pointer { @@ -383,6 +374,7 @@ where let hovered_state = hovered.config.active_state.as_mut().unwrap(); + // grab if pointer.now.grab && !pointer.before.grab && hovered_state.grabbable { update_focus( &mut app.hid_provider.keyboard_focus, @@ -400,16 +392,68 @@ where ); } - // Pass mouse motion events only if not scrolling - // (allows scrolling on all Chromium-based applications) - let haptics = hovered.config.backend.on_hover(app, &hit); + handle_scroll(&hit, hovered, app); - 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( + pointer_idx: usize, + hovered_id: Option, + overlays: &mut OverlayWindowManager, + 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(hit: &PointerHit, hovered: &mut OverlayWindowData, 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 { let scroll_x = pointer.now.scroll_x; let scroll_y = pointer.now.scroll_y; - if app.input_state.pointers[1 - idx] + if app.input_state.pointers[1 - hit.pointer] .interaction .grabbed .is_some_and(|x| x.grabbed_id == hit.overlay) @@ -437,87 +481,81 @@ where .backend .on_scroll(app, &hit, scroll_y, scroll_x); } - pointer = &mut app.input_state.pointers[idx]; } - - 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 { - 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(&mut self, overlays: &mut OverlayWindowManager) -> Option - where - O: Default, - { - let mut hits: SmallVec<[RayHit; 8]> = smallvec!(); +fn get_nearest_hit( + pointer_idx: usize, + overlays: &mut OverlayWindowManager, + app: &mut AppState, +) -> (Option, Option) +where + O: Default, +{ + let pointer = &mut app.input_state.pointers[pointer_idx]; + let ray_origin = pointer.pose; + let mode = pointer.interaction.mode; - for (id, overlay) in overlays.iter() { - let Some(overlay_state) = overlay.config.active_state.as_ref() else { - continue; - }; - if !overlay_state.interactable { - continue; - } + let mut hits: SmallVec<[RayHit; 8]> = smallvec!(); - if let Some(hit) = self.ray_test( - id, - &overlay_state.transform, - overlay_state.curvature.as_ref(), - ) { - if hit.dist.is_infinite() || hit.dist.is_nan() { - continue; - } + for (id, overlay) in overlays.iter() { + let Some(overlay_state) = overlay.config.active_state.as_ref() else { + continue; + }; + if !overlay_state.interactable { + continue; + } + + if let Some(hit) = ray_test( + &ray_origin, + id, + &overlay_state.transform, + overlay_state.curvature.as_ref(), + ) { + if hit.dist.is_finite() { hits.push(hit); } } - - hits.sort_by(|a, b| a.dist.total_cmp(&b.dist)); - - for hit in &hits { - let overlay = overlays.mut_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay - - let Some(uv) = overlay - .config - .backend - .as_mut() - .get_interaction_transform() - .map(|a| a.transform_point2(hit.local_pos)) - else { - continue; - }; - - if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 { - continue; - } - - return Some(PointerHit { - pointer: self.idx, - overlay: hit.overlay, - mode: self.interaction.mode, - primary: false, - uv, - dist: hit.dist, - }); - } - - None } + hits.sort_by(|a, b| a.dist.total_cmp(&b.dist)); + + for hit in &hits { + let overlay = overlays.mut_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay + + let Some(uv) = overlay + .config + .backend + .as_mut() + .get_interaction_transform() + .map(|a| a.transform_point2(hit.local_pos)) + else { + continue; + }; + + if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 { + continue; + } + + let hit = PointerHit { + pointer: pointer_idx, + overlay: hit.overlay, + mode, + primary: false, + uv, + dist: hit.dist, + }; + + let result = overlay.config.backend.on_hover(app, &hit); + if result.consume { + return (Some(hit), result.haptics); + } + } + + (None, None) +} + +impl Pointer { fn start_grab( &mut self, id: OverlayID, @@ -608,37 +646,37 @@ impl Pointer { log::debug!("Hand {}: dropped {}", idx, overlay.config.name); } } +} - fn ray_test( - &self, - overlay: OverlayID, - transform: &Affine3A, - curvature: Option<&f32>, - ) -> Option { - let (dist, local_pos) = curvature.map_or_else( - || { - Some(raycast_plane( - &self.pose, - Vec3A::NEG_Z, - transform, - Vec3A::NEG_Z, - )) - }, - |curvature| raycast_cylinder(&self.pose, Vec3A::NEG_Z, transform, *curvature), - )?; +fn ray_test( + ray_origin: &Affine3A, + overlay: OverlayID, + overlay_pose: &Affine3A, + curvature: Option<&f32>, +) -> Option { + let (dist, local_pos) = curvature.map_or_else( + || { + Some(raycast_plane( + &ray_origin, + Vec3A::NEG_Z, + overlay_pose, + Vec3A::NEG_Z, + )) + }, + |curvature| raycast_cylinder(ray_origin, Vec3A::NEG_Z, overlay_pose, *curvature), + )?; - if dist < 0.0 { - // hit is behind us - return None; - } - - Some(RayHit { - overlay, - global_pos: self.pose.transform_point3a(Vec3A::NEG_Z * dist), - local_pos, - dist, - }) + if dist < 0.0 { + // hit is behind us + return None; } + + Some(RayHit { + overlay, + global_pos: ray_origin.transform_point3a(Vec3A::NEG_Z * dist), + local_pos, + dist, + }) } fn raycast_plane( diff --git a/wlx-overlay-s/src/backend/openvr/lines.rs b/wlx-overlay-s/src/backend/openvr/lines.rs index b4b8470..2b0a867 100644 --- a/wlx-overlay-s/src/backend/openvr/lines.rs +++ b/wlx-overlay-s/src/backend/openvr/lines.rs @@ -22,7 +22,7 @@ use vulkano::{ }; use wgui::gfx::WGfx; -use crate::backend::input::{Haptics, PointerHit}; +use crate::backend::input::{HoverResult, PointerHit}; use crate::graphics::CommandBuffers; use crate::state::AppState; use crate::windowing::backend::{FrameMeta, OverlayBackend, ShouldRender}; @@ -206,8 +206,8 @@ impl OverlayBackend for LineBackend { }) } - fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> Option { - None + fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult { + HoverResult::default() } fn on_left(&mut self, _: &mut AppState, _: usize) {} fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {} diff --git a/wlx-overlay-s/src/gui/panel/mod.rs b/wlx-overlay-s/src/gui/panel/mod.rs index 4fca98d..c507096 100644 --- a/wlx-overlay-s/src/gui/panel/mod.rs +++ b/wlx-overlay-s/src/gui/panel/mod.rs @@ -15,11 +15,11 @@ use wgui::{ layout::{Layout, LayoutParams, WidgetID}, parser::ParserState, renderer_vk::context::Context as WguiContext, - widget::{label::WidgetLabel, rectangle::WidgetRectangle}, + widget::{label::WidgetLabel, rectangle::WidgetRectangle, EventResult}, }; use crate::{ - backend::input::{Haptics, PointerHit, PointerMode}, + backend::input::{Haptics, HoverResult, PointerHit, PointerMode}, graphics::{CommandBuffers, ExtentExt}, state::AppState, windowing::backend::{ui_transform, FrameMeta, OverlayBackend, ShouldRender}, @@ -150,9 +150,13 @@ impl GuiPanel { self.layout.update(MAX_SIZE_VEC2, 0.0) } - pub fn push_event(&mut self, app: &mut AppState, event: &WguiEvent) { - if let Err(e) = self.layout.push_event(event, app, &mut self.state) { - log::error!("Failed to push event: {e:?}"); + pub fn push_event(&mut self, app: &mut AppState, event: &WguiEvent) -> EventResult { + match self.layout.push_event(event, app, &mut self.state) { + Ok(r) => r, + Err(e) => { + log::error!("Failed to push event: {e:?}"); + EventResult::NoHit + } } } @@ -266,23 +270,28 @@ impl OverlayBackend for GuiPanel { self.push_event(app, &e); } - fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option { + fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult { let e = &WguiEvent::MouseMotion(MouseMotionEvent { pos: hit.uv * self.layout.content_size, device: hit.pointer, }); - self.push_event(app, &e); + let result = self.push_event(app, &e); - self.layout - .check_toggle_haptics_triggered() - .then_some(Haptics { - intensity: 0.1, - duration: 0.01, - frequency: 5.0, - }) + HoverResult { + consume: result != EventResult::NoHit, + haptics: self + .layout + .check_toggle_haptics_triggered() + .then_some(Haptics { + intensity: 0.1, + duration: 0.01, + frequency: 5.0, + }), + } } fn on_left(&mut self, app: &mut AppState, pointer: usize) { + log::info!("panel: on left"); let e = WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer }); self.push_event(app, &e); } diff --git a/wlx-overlay-s/src/overlays/keyboard/mod.rs b/wlx-overlay-s/src/overlays/keyboard/mod.rs index a5daf37..180c37d 100644 --- a/wlx-overlay-s/src/overlays/keyboard/mod.rs +++ b/wlx-overlay-s/src/overlays/keyboard/mod.rs @@ -11,11 +11,11 @@ use wgui::{ }; use crate::{ - backend::input::{Haptics, PointerHit}, + backend::input::{HoverResult, PointerHit}, graphics::CommandBuffers, gui::panel::GuiPanel, 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}, }; @@ -75,7 +75,7 @@ impl OverlayBackend for KeyboardBackend { fn on_left(&mut self, app: &mut AppState, pointer: usize) { self.panel.on_left(app, pointer); } - fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option { + fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult { self.panel.on_hover(app, hit) } fn get_interaction_transform(&mut self) -> Option { diff --git a/wlx-overlay-s/src/overlays/mirror.rs b/wlx-overlay-s/src/overlays/mirror.rs index 370800b..428cd05 100644 --- a/wlx-overlay-s/src/overlays/mirror.rs +++ b/wlx-overlay-s/src/overlays/mirror.rs @@ -10,7 +10,7 @@ use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSel use crate::{ backend::{ - input::{Haptics, PointerHit}, + input::{HoverResult, PointerHit}, task::TaskType, }, graphics::CommandBuffers, @@ -130,8 +130,11 @@ impl OverlayBackend for MirrorBackend { self.renderer.as_mut().and_then(ScreenBackend::frame_meta) } - fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> Option { - None + fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult { + HoverResult { + consume: true, + ..HoverResult::default() + } } fn on_left(&mut self, _: &mut AppState, _: usize) {} fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {} diff --git a/wlx-overlay-s/src/overlays/screen/backend.rs b/wlx-overlay-s/src/overlays/screen/backend.rs index 74e513a..fef4fdd 100644 --- a/wlx-overlay-s/src/overlays/screen/backend.rs +++ b/wlx-overlay-s/src/overlays/screen/backend.rs @@ -1,21 +1,21 @@ use std::{ - sync::{Arc, LazyLock, atomic::AtomicU64}, + sync::{atomic::AtomicU64, Arc, LazyLock}, time::Instant, }; -use glam::{Affine2, Vec2, vec2}; +use glam::{vec2, Affine2, Vec2}; use vulkano::image::view::ImageView; -use wlx_capture::{WlxCapture, frame::Transform}; +use wlx_capture::{frame::Transform, WlxCapture}; use crate::{ - backend::input::{Haptics, PointerHit, PointerMode}, + backend::input::{HoverResult, PointerHit, PointerMode}, graphics::{CommandBuffers, ExtentExt}, state::AppState, subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT}, 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.; @@ -211,7 +211,7 @@ impl OverlayBackend for ScreenBackend { self.meta } - fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option { + fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult { #[cfg(debug_assertions)] log::trace!("Hover: {:?}", hit.uv); if can_move() @@ -222,7 +222,10 @@ impl OverlayBackend for ScreenBackend { app.hid_provider.inner.mouse_move(pos); 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) { let btn = match hit.mode { diff --git a/wlx-overlay-s/src/overlays/wayvr.rs b/wlx-overlay-s/src/overlays/wayvr.rs index b6ec372..09369f6 100644 --- a/wlx-overlay-s/src/overlays/wayvr.rs +++ b/wlx-overlay-s/src/overlays/wayvr.rs @@ -17,7 +17,7 @@ use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane}; use crate::{ backend::{ - input::{self}, + input::{self, HoverResult}, task::TaskType, wayvr::{ self, display, @@ -716,11 +716,7 @@ impl OverlayBackend for WayVRBackend { }) } - fn on_hover( - &mut self, - _app: &mut state::AppState, - hit: &input::PointerHit, - ) -> Option { + fn on_hover(&mut self, _app: &mut state::AppState, hit: &input::PointerHit) -> HoverResult { let ctx = self.context.borrow(); 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); } - wayvr.pending_haptics.take() + HoverResult { + haptics: wayvr.pending_haptics.take(), + consume: true, + } } fn on_left(&mut self, _app: &mut state::AppState, _pointer: usize) { diff --git a/wlx-overlay-s/src/windowing/backend.rs b/wlx-overlay-s/src/windowing/backend.rs index 75ec77d..ef2848f 100644 --- a/wlx-overlay-s/src/windowing/backend.rs +++ b/wlx-overlay-s/src/windowing/backend.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use vulkano::{format::Format, image::view::ImageView}; use crate::{ - backend::input::{Haptics, PointerHit}, + backend::input::{HoverResult, PointerHit}, graphics::CommandBuffers, state::AppState, }; @@ -48,7 +48,7 @@ pub trait OverlayBackend { /// Must be true if should_render was also true on the same frame. fn frame_meta(&mut self) -> Option; - fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option; + fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult; fn on_left(&mut self, app: &mut AppState, pointer: usize); 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); diff --git a/wlx-overlay-s/src/windowing/manager.rs b/wlx-overlay-s/src/windowing/manager.rs index e0b6759..3ca21ab 100644 --- a/wlx-overlay-s/src/windowing/manager.rs +++ b/wlx-overlay-s/src/windowing/manager.rs @@ -91,7 +91,9 @@ where Ok(me) } +} +impl OverlayWindowManager { pub fn mut_by_selector( &mut self, selector: &OverlaySelector, diff --git a/wlx-overlay-s/src/windowing/window.rs b/wlx-overlay-s/src/windowing/window.rs index 956e49c..093389c 100644 --- a/wlx-overlay-s/src/windowing/window.rs +++ b/wlx-overlay-s/src/windowing/window.rs @@ -63,10 +63,7 @@ where } } -impl OverlayWindowData -where - T: Default, -{ +impl OverlayWindowData { pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { //TODO: load state?