diff --git a/wayvr/src/backend/wayvr/hit_test.rs b/wayvr/src/backend/wayvr/hit_test.rs new file mode 100644 index 00000000..eb4089be --- /dev/null +++ b/wayvr/src/backend/wayvr/hit_test.rs @@ -0,0 +1,406 @@ +use glam::{Affine2, Vec2}; +use smithay::{ + desktop::PopupManager, + reexports::wayland_server::{Resource, backend::ObjectId, protocol::wl_surface::WlSurface}, + utils::{Logical, Point}, + wayland::{ + compositor::{ + SUBSURFACE_ROLE, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, + with_states, with_surface_tree_upward, + }, + shell::xdg::XdgPopupSurfaceData, + }, +}; +use std::{ops::RangeInclusive, rc::Rc, sync::Arc}; +use vulkano::image::view::ImageView; + +use crate::graphics::ExtentExt; + +use crate::backend::input::PointerHit; + +use super::SurfaceBufWithImage; + +pub const BORDER_SIZE: u32 = 5; +pub const BAR_SIZE: u32 = 48; + +#[derive(Clone)] +pub struct PopupRoot { + pub surface: WlSurface, + pub surface_origin: Vec2, +} + +#[derive(Clone)] +pub struct RenderedSurface { + pub surface: WlSurface, + pub surface_id: ObjectId, + pub image: Arc, + pub pos: Vec2, + pub size: Vec2, +} + +#[derive(Clone)] +pub enum WvrHitTarget { + Panel(PointerHit), + Toplevel { + pos: Vec2, + }, + Surface { + surface: WlSurface, + global_pos: Vec2, + origin: Vec2, + }, + Popup { + surface: WlSurface, + global_pos: Vec2, + origin: Vec2, + }, +} + +pub struct WvrHitContext { + pub surfaces: Rc<[RenderedSurface]>, + pub popup_roots: Rc<[PopupRoot]>, + pub mouse_transform: Affine2, + pub uv_range: RangeInclusive, + pub inner_extent: [u32; 2], + pub panel_height: u32, +} + +impl WvrHitContext { + pub fn popup_hit_at_client_pos(&self, pos: Vec2) -> Option<(WlSurface, Vec2)> { + self.popup_roots.iter().find_map(|popup| { + surface_tree_input_hit_test(&popup.surface, popup.surface_origin, pos, true) + }) + } + + pub fn is_inside_client_area(&self, transformed: Vec2) -> bool { + self.uv_range.contains(&transformed.x) && self.uv_range.contains(&transformed.y) + } + + pub fn transformed_uv_from_hit(&self, hit: &PointerHit) -> Vec2 { + self.mouse_transform.transform_point2(hit.uv) + } + + pub fn client_pos_from_transformed_uv(&self, transformed: Vec2) -> Vec2 { + Vec2::new( + transformed.x * self.inner_extent[0] as f32, + transformed.y * self.inner_extent[1] as f32, + ) + } + + pub fn unclamped_client_pos_from_hit(&self, hit: &PointerHit) -> Vec2 { + let transformed = self.transformed_uv_from_hit(hit); + self.client_pos_from_transformed_uv(transformed) + } + + pub fn panel_hit_from_hit(&self, hit: &PointerHit) -> Option { + if self.panel_height == 0 { + return None; + } + let mut hit2 = *hit; + let total_height = self.inner_extent[1] + self.panel_height; + hit2.uv.y *= total_height as f32 / self.panel_height as f32; + Some(hit2) + } + + pub fn hit_target_at(&self, client_pos: Vec2) -> Option { + let transformed = self.mouse_transform.inverse().transform_point2(Vec2::new( + client_pos.x / self.inner_extent[0] as f32, + client_pos.y / self.inner_extent[1] as f32, + )); + if let Some((surface, surface_origin)) = self.popup_hit_at_client_pos(client_pos) { + return Some(WvrHitTarget::Popup { + surface, + global_pos: client_pos, + origin: surface_origin, + }); + } + + if !self.is_inside_client_area(transformed) { + return None; + } + + let hit_surface = self + .surfaces + .iter() + .rev() + .find(|s| surface_accepts_input(s, client_pos)); + + if let Some(surface) = hit_surface { + return Some(WvrHitTarget::Surface { + surface: surface.surface.clone(), + global_pos: client_pos, + origin: surface.pos, + }); + } + + Some(WvrHitTarget::Toplevel { pos: client_pos }) + } + + pub fn hit_target(&self, hit: &PointerHit) -> Option { + let transformed = self.transformed_uv_from_hit(hit); + let client_pos = self.client_pos_from_transformed_uv(transformed); + + // popups are checked before the panel/client bounds. + if let Some((surface, surface_origin)) = self.popup_hit_at_client_pos(client_pos) { + return Some(WvrHitTarget::Popup { + surface, + global_pos: client_pos, + origin: surface_origin, + }); + } + + if !self.is_inside_client_area(transformed) { + return self.panel_hit_from_hit(hit).map(WvrHitTarget::Panel); + } + + let hit_surface = self + .surfaces + .iter() + .rev() + .find(|s| surface_accepts_input(s, client_pos)); + + if let Some(surface) = hit_surface { + return Some(WvrHitTarget::Surface { + surface: surface.surface.clone(), + global_pos: client_pos, + origin: surface.pos, + }); + } + + let clamped = transformed.clamp(Vec2::ZERO, Vec2::ONE); + let pos = self.client_pos_from_transformed_uv(clamped); + + Some(WvrHitTarget::Toplevel { pos }) + } +} + +fn surface_location(states: &smithay::wayland::compositor::SurfaceData) -> Point { + if states.role == Some(SUBSURFACE_ROLE) { + let mut guard = states.cached_state.get::(); + guard.current().location + } else { + (0, 0).into() + } +} + +pub fn collect_rendered_surface_tree_at( + root: &WlSurface, + initial_pos: Point, + include_root: bool, +) -> Vec { + let mut out = Vec::new(); + let root_id = root.id(); + + with_surface_tree_upward( + root, + initial_pos, + |_, states, parent_pos| { + let pos = *parent_pos + surface_location(states); + + // do not skip even if this surface has no buffer; children may still have buffers + TraversalAction::DoChildren(pos) + }, + |surface, states, parent_pos| { + if !include_root && surface.id() == root_id { + return; + } + + let pos = *parent_pos + surface_location(states); + + if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) { + let extent = surf.image.extent_f32(); + let scale = surf.scale.max(1) as f32; + + out.push(RenderedSurface { + surface: surface.clone(), + surface_id: surface.id(), + image: surf.image, + pos: Vec2::new(pos.x as f32, pos.y as f32), + size: Vec2::new(extent[0] / scale, extent[1] / scale), + }); + } + }, + |_, _, _| true, + ); + + out +} + +pub fn collect_rendered_surface_tree(root: &WlSurface) -> Rc<[RenderedSurface]> { + collect_rendered_surface_tree_at(root, Point::::from((0, 0)), false).into() +} + +fn surface_accepts_input(surface: &RenderedSurface, global_pos: Vec2) -> bool { + let local = global_pos - surface.pos; + + if local.x < 0.0 || local.y < 0.0 || local.x >= surface.size.x || local.y >= surface.size.y { + return false; + } + + with_states(&surface.surface, |states| { + let mut guard = states.cached_state.get::(); + let attrs = guard.current(); + + match attrs.input_region.as_ref() { + None => true, + Some(region) => { + let point = + Point::::from((local.x.floor() as i32, local.y.floor() as i32)); + region.contains(point) + } + } + }) +} + +fn surface_accepts_input_states( + states: &smithay::wayland::compositor::SurfaceData, + local: Vec2, +) -> bool { + if local.x < 0.0 || local.y < 0.0 { + return false; + } + + let mut guard = states.cached_state.get::(); + let attrs = guard.current(); + + let point = Point::::from((local.x.floor() as i32, local.y.floor() as i32)); + + // explicit input region wins, even if no render buffer + if let Some(region) = attrs.input_region.as_ref() { + return region.contains(point); + } + + // fallback for normal rendered surfaces + if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) { + let extent = surf.image.extent_f32(); + let scale = surf.scale.max(1) as f32; + + return local.x < extent[0] / scale && local.y < extent[1] / scale; + } + + false +} + +pub fn surface_tree_input_hit_test( + root: &WlSurface, + root_origin: Vec2, + global_pos: Vec2, + include_root: bool, +) -> Option<(WlSurface, Vec2)> { + let mut hit = None; + let root_id = root.id(); + + with_surface_tree_upward( + root, + Point::::from((root_origin.x as i32, root_origin.y as i32)), + |_, states, parent_pos| { + let pos = *parent_pos + surface_location(states); + TraversalAction::DoChildren(pos) + }, + |surface, states, parent_pos| { + if !include_root && surface.id() == root_id { + return; + } + + let pos = *parent_pos + surface_location(states); + let surface_origin = Vec2::new(pos.x as f32, pos.y as f32); + let local = global_pos - surface_origin; + + if surface_accepts_input_states(states, local) { + // with_surface_tree_upward visits bottom-to-top, so later hits win + hit = Some((surface.clone(), surface_origin)); + } + }, + |_, _, _| true, + ); + + hit +} + +pub fn rendered_surfaces_dirty(old: &[RenderedSurface], new: &[RenderedSurface]) -> bool { + if old.len() != new.len() { + return true; + } + + old.iter().zip(new).any(|(a, b)| { + a.surface_id != b.surface_id + || a.pos != b.pos + || a.size != b.size + || *a.image.image() != *b.image.image() + }) +} + +pub fn compute_transforms(inner_extent: [u32; 2]) -> (Affine2, RangeInclusive) { + let ix = inner_extent[0].max(1) as f32; + let iy = inner_extent[1].max(1) as f32; + + let extent_x = ix + BORDER_SIZE as f32 * 2.0; + let extent_y = iy + BORDER_SIZE as f32 * 2.0 + BAR_SIZE as f32; + + let scale_x = (extent_x + BORDER_SIZE as f32 * 2.0) / extent_x; + let scale_y = (extent_y + BORDER_SIZE as f32 * 2.0 + BAR_SIZE as f32) / extent_y; + + let translation_x = -(BORDER_SIZE as f32) / ix; + let translation_y = -((BORDER_SIZE + BAR_SIZE) as f32) / iy; + + let mouse_transform = Affine2::from_scale_angle_translation( + Vec2::new(scale_x, scale_y), + 0.0, + Vec2::new(translation_x, translation_y), + ); + let uv_range = translation_x..=(1.0 - translation_x); + + (mouse_transform, uv_range) +} + +pub fn build_hit_context( + toplevel: &WlSurface, + _popup_manager: &PopupManager, + inner_extent: [u32; 2], +) -> Option { + let (mouse_transform, uv_range) = compute_transforms(inner_extent); + let panel_height = BORDER_SIZE * 2 + BAR_SIZE; + + let surfaces = collect_rendered_surface_tree(toplevel); + + let mut popup_roots = Vec::new(); + let mut popups = Vec::new(); + + for (popup, point) in PopupManager::popups_for_surface(toplevel) { + let configured = with_states(popup.wl_surface(), |states| { + states + .data_map + .get::() + .unwrap() + .lock() + .unwrap() + .configured + }); + + if !configured { + continue; + } + + let popup_origin = point - popup.geometry().loc; + + popup_roots.push(PopupRoot { + surface: popup.wl_surface().clone(), + surface_origin: Vec2::new(popup_origin.x as f32, popup_origin.y as f32), + }); + + popups.extend(collect_rendered_surface_tree_at( + popup.wl_surface(), + popup_origin, + true, + )); + } + + Some(WvrHitContext { + surfaces, + popup_roots: popup_roots.into(), + mouse_transform, + uv_range, + inner_extent, + panel_height, + }) +} diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 29e05881..cd209e5a 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -1,6 +1,7 @@ pub mod client; mod comp; mod handle; +pub mod hit_test; mod image_importer; mod input_capture; pub mod process; @@ -68,7 +69,7 @@ use crate::{ process::{KillSignal, Process}, }, }, - graphics::WGfxExtras, + graphics::{ExtentExt, WGfxExtras}, ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal}, overlays::wayvr::create_wl_window_overlay, state::AppState, @@ -79,6 +80,8 @@ use crate::{ windowing::{OverlayID, OverlaySelector}, }; +pub use hit_test::{WvrHitContext, WvrHitTarget, build_hit_context}; + #[derive(Debug, Clone)] pub struct WaylandEnv { pub display_num: u32, @@ -675,6 +678,35 @@ impl WvrServerState { self.window_to_overlay.get(&window).copied() } + pub fn hit_target_to_focus( + &self, + target: WvrHitTarget, + hover_window: window::WindowHandle, + _default_pos: Vec2, + ) -> PointerFocusTarget { + match target { + WvrHitTarget::Panel(_) => PointerFocusTarget::None, + WvrHitTarget::Toplevel { .. } => self + .wm + .windows + .get(&hover_window) + .map(|w| { + let surface = w.toplevel.wl_surface().clone(); + PointerFocusTarget::Surface { + surface, + origin: glam::Vec2::ZERO, + } + }) + .unwrap_or(PointerFocusTarget::Toplevel), + WvrHitTarget::Surface { + surface, origin, .. + } => PointerFocusTarget::Surface { surface, origin }, + WvrHitTarget::Popup { + surface, origin, .. + } => PointerFocusTarget::Surface { surface, origin }, + } + } + pub fn pointer_is_grabbed(&self) -> bool { self.manager.seat_pointer.is_grabbed() } @@ -687,18 +719,100 @@ impl WvrServerState { return; }; - #[allow(unused_variables)] //TODO: remove this for ev in input_capture.drain_events() { match ev { input_capture::CapturedEvent::Key { code, pressed } => { self.manager.send_key((code as u32) + 8, pressed); } - input_capture::CapturedEvent::PointerButton { button, pressed } => {} - input_capture::CapturedEvent::PointerMotion { dx, dy } => {} + input_capture::CapturedEvent::PointerButton { button, pressed } => { + let Some(mouse_index) = Self::button_to_mouse_index(button) else { + continue; + }; + let Some(ref hover) = self.wm.mouse else { + continue; + }; + let Some(window) = self.wm.windows.get(&hover.hover_window) else { + continue; + }; + let toplevel = window.toplevel.wl_surface().clone(); + let inner_extent = with_states(&toplevel, |states| { + SurfaceBufWithImage::get_from_surface(states) + .map(|s| s.image.extent_u32arr()) + .unwrap_or([1, 1]) + }); + let Some(hit_ctx) = build_hit_context( + &toplevel, + &self.manager.state.popup_manager, + inner_extent, + ) else { + continue; + }; + let pos = Vec2::new(hover.x as f32, hover.y as f32); + let target = hit_ctx.hit_target_at(pos); + let focus_target = self.hit_target_to_focus( + target.unwrap_or(WvrHitTarget::Toplevel { pos }), + hover.hover_window, + pos, + ); + let click_freeze = 0; + self.send_mouse_button( + focus_target, + pos, + hover.hover_window, + mouse_index, + pressed, + click_freeze, + ); + } + input_capture::CapturedEvent::PointerMotion { dx, dy } => { + let Some(ref hover) = self.wm.mouse else { + continue; + }; + let new_x = hover.x as f64 + dx; + let new_y = hover.y as f64 + dy; + let new_pos = Vec2::new(new_x as f32, new_y as f32); + + let Some(window) = self.wm.windows.get(&hover.hover_window) else { + continue; + }; + let toplevel = window.toplevel.wl_surface().clone(); + let inner_extent = with_states(&toplevel, |states| { + SurfaceBufWithImage::get_from_surface(states) + .map(|s| s.image.extent_u32arr()) + .unwrap_or([1, 1]) + }); + let Some(hit_ctx) = build_hit_context( + &toplevel, + &self.manager.state.popup_manager, + inner_extent, + ) else { + continue; + }; + let target = hit_ctx.hit_target_at(new_pos); + let focus_target = self.hit_target_to_focus( + target.unwrap_or(WvrHitTarget::Toplevel { pos: new_pos }), + hover.hover_window, + new_pos, + ); + self.send_mouse_move(focus_target, new_pos, hover.hover_window); + } input_capture::CapturedEvent::PointerAxis { horizontal_v120, vertical_v120, - } => {} + } => { + let Some(ref hover) = self.wm.mouse else { + continue; + }; + let delta = WheelDelta { + x: horizontal_v120 as f32, + y: vertical_v120 as f32, + }; + self.send_mouse_scroll( + hover.hover_window, + Vec2::new(hover.x as f32, hover.y as f32), + delta, + ); + } input_capture::CapturedEvent::UngrabbedAll => { self.manager.release_all_keys(); self.has_input_focus = false; @@ -707,6 +821,15 @@ impl WvrServerState { } } + fn button_to_mouse_index(button: u32) -> Option { + match button { + 272 => Some(MouseIndex::Left), + 273 => Some(MouseIndex::Right), + 274 => Some(MouseIndex::Center), + _ => None, + } + } + pub fn set_input_focus(&mut self, has_focus: bool) { let Some(input_capture) = self.input_capture.as_mut() else { return; diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index 61a3adb7..6bfcebbf 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -1,18 +1,13 @@ -use glam::{Affine2, Affine3A, Quat, Vec2, Vec3, vec2, vec3}; +use std::{mem, ops::RangeInclusive, rc::Rc, sync::Arc}; + +use glam::{Affine2, Affine3A, Quat, Vec3, vec2, vec3}; use slotmap::Key; use smithay::{ desktop::PopupManager, - reexports::wayland_server::{Resource, backend::ObjectId, protocol::wl_surface::WlSurface}, - utils::{Logical, Point, Size}, - wayland::{ - compositor::{ - SUBSURFACE_ROLE, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, - with_states, with_surface_tree_upward, - }, - shell::xdg::XdgPopupSurfaceData, - }, + reexports::wayland_server::Resource, + utils::{Logical, Size}, + wayland::{compositor::with_states, shell::xdg::XdgPopupSurfaceData}, }; -use std::{mem, ops::RangeInclusive, sync::Arc}; use vulkano::{ buffer::BufferUsage, image::view::ImageView, pipeline::graphics::color_blend::AttachmentBlend, }; @@ -40,7 +35,13 @@ use crate::{ input::{self, HoverResult}, task::{OverlayTask, TaskType}, wayvr::{ - self, PointerFocusTarget, SurfaceBufWithImage, process::KillSignal, + self, PointerFocusTarget, SurfaceBufWithImage, + hit_test::{ + PopupRoot, RenderedSurface, WvrHitContext, WvrHitTarget, + collect_rendered_surface_tree, collect_rendered_surface_tree_at, + rendered_surfaces_dirty, + }, + process::KillSignal, window::WindowHandle, }, }, @@ -126,38 +127,6 @@ pub fn create_wl_window_overlay( }) } -#[derive(Clone)] -struct PopupRoot { - surface: WlSurface, - surface_origin: Vec2, -} - -#[derive(Clone)] -struct RenderedSurface { - surface: WlSurface, - surface_id: ObjectId, - image: Arc, - pos: Vec2, - size: Vec2, -} - -enum WvrHitTarget { - Panel(input::PointerHit), - Toplevel { - pos: Vec2, - }, - Surface { - surface: WlSurface, - global_pos: Vec2, - origin: Vec2, - }, - Popup { - surface: WlSurface, - global_pos: Vec2, - origin: Vec2, - }, -} - pub struct WvrWindowBackend { name: Arc, icon: Arc, @@ -166,9 +135,9 @@ pub struct WvrWindowBackend { popup_outside_button: Option, interaction_transform: Option, window: WindowHandle, - popups: Vec, - popup_roots: Vec, - surfaces: Vec, + popups: Rc<[RenderedSurface]>, + surfaces: Rc<[RenderedSurface]>, + hit_context: Option, just_resumed: bool, meta: Option, mouse: Option, @@ -272,9 +241,9 @@ impl WvrWindowBackend { icon, pipeline: None, window, - popups: vec![], - popup_roots: vec![], - surfaces: vec![], + popups: Default::default(), + surfaces: Default::default(), + hit_context: None, subsurface_pipeline, popup_outside_button: None, interaction_transform: None, @@ -380,83 +349,6 @@ impl WvrWindowBackend { Ok(()) } - fn transformed_uv_from_hit(&self, hit: &input::PointerHit) -> Vec2 { - self.mouse_transform.transform_point2(hit.uv) - } - - fn client_pos_from_transformed_uv(&self, transformed: Vec2) -> Vec2 { - vec2( - transformed.x * self.inner_extent[0] as f32, - transformed.y * self.inner_extent[1] as f32, - ) - } - - fn unclamped_client_pos_from_hit(&self, hit: &input::PointerHit) -> Vec2 { - let transformed = self.transformed_uv_from_hit(hit); - self.client_pos_from_transformed_uv(transformed) - } - - fn is_inside_client_area(&self, transformed: Vec2) -> bool { - self.uv_range.contains(&transformed.x) && self.uv_range.contains(&transformed.y) - } - - fn panel_hit_from_hit(&self, hit: &input::PointerHit) -> Option { - let meta = self.meta.as_ref()?; - - let panel_height = meta.extent[1].checked_sub(self.inner_extent[1])?; - if panel_height == 0 { - return None; - } - - let mut hit2 = *hit; - hit2.uv.y *= meta.extent[1] as f32 / panel_height as f32; - - Some(hit2) - } - - fn popup_hit_at_client_pos(&self, pos: Vec2) -> Option<(WlSurface, Vec2)> { - self.popup_roots.iter().find_map(|popup| { - surface_tree_input_hit_test(&popup.surface, popup.surface_origin, pos, true) - }) - } - - fn hit_target(&self, hit: &input::PointerHit) -> Option { - let transformed = self.transformed_uv_from_hit(hit); - let client_pos = self.client_pos_from_transformed_uv(transformed); - - // popups are checked before the panel/client bounds. - if let Some((surface, surface_origin)) = self.popup_hit_at_client_pos(client_pos) { - return Some(WvrHitTarget::Popup { - surface, - global_pos: client_pos, - origin: surface_origin, - }); - } - - if !self.is_inside_client_area(transformed) { - return self.panel_hit_from_hit(hit).map(WvrHitTarget::Panel); - } - - let hit_surface = self - .surfaces - .iter() - .rev() - .find(|s| surface_accepts_input(s, client_pos)); - - if let Some(surface) = hit_surface { - return Some(WvrHitTarget::Surface { - surface: surface.surface.clone(), - global_pos: client_pos, - origin: surface.pos, - }); - } - - let clamped = transformed.clamp(Vec2::ZERO, Vec2::ONE); - let pos = self.client_pos_from_transformed_uv(clamped); - - Some(WvrHitTarget::Toplevel { pos }) - } - fn mouse_index_from_mode(mode: input::PointerMode) -> Option { match mode { input::PointerMode::Left => Some(wayvr::MouseIndex::Left), @@ -610,28 +502,27 @@ impl OverlayBackend for WvrWindowBackend { )); } - self.popup_roots = popup_roots; - let mut tree_dirty = false; if let Some(wvr_server) = app.wvr_server.as_mut() { let state = &mut wvr_server.manager.state; tree_dirty |= state.take_redraw_request(&surface_id); tree_dirty |= state.has_pending_frame_callbacks(&surface_id); - for surface in &surfaces { + for surface in surfaces.iter() { tree_dirty |= state.take_redraw_request(&surface.surface_id); tree_dirty |= state.has_pending_frame_callbacks(&surface.surface_id); } - for popup in &popups { + for popup in popups.iter() { tree_dirty |= state.take_redraw_request(&popup.surface_id); tree_dirty |= state.has_pending_frame_callbacks(&popup.surface_id); } } - self.surfaces = surfaces; - let force_render = tree_dirty || mem::take(&mut self.just_resumed); + let hit_surfaces = surfaces.clone(); + self.surfaces = surfaces; + let Some(surf) = with_states(toplevel.wl_surface(), SurfaceBufWithImage::get_from_surface) else { log::trace!("{}: no buffer for wl_surface", self.name); @@ -664,6 +555,16 @@ impl OverlayBackend for WvrWindowBackend { let inner_extent = meta.extent; self.sync_committed_toplevel_size(app, inner_extent)?; + let hit_context = WvrHitContext { + surfaces: hit_surfaces, + popup_roots: popup_roots.into(), + mouse_transform: self.mouse_transform, + uv_range: self.uv_range.clone(), + inner_extent, + panel_height: BORDER_SIZE * 2 + BAR_SIZE, + }; + self.hit_context = Some(hit_context); + meta.extent[0] += BORDER_SIZE * 2; meta.extent[1] += BORDER_SIZE * 2 + BAR_SIZE; @@ -707,7 +608,7 @@ impl OverlayBackend for WvrWindowBackend { if !self.scrolling { self.mouse = mouse; } - self.popups = popups; + self.popups = popups.into(); self.meta = Some(meta); if force_render { @@ -755,12 +656,12 @@ impl OverlayBackend for WvrWindowBackend { .unwrap() .render_screen(image, app, rdr)?; - for surface in &self.surfaces { + for surface in self.surfaces.iter() { self.render_subsurface(app, rdr, surface)?; callback_surfaces.push(&surface.surface_id); } - for popup in &self.popups { + for popup in self.popups.iter() { self.render_subsurface(app, rdr, popup)?; callback_surfaces.push(&popup.surface_id); } @@ -776,10 +677,10 @@ impl OverlayBackend for WvrWindowBackend { let surface_id = window.toplevel.wl_surface().id(); state.send_frame_callbacks_for_surface_id(&surface_id); } - for surface in &self.surfaces { + for surface in self.surfaces.iter() { state.send_frame_callbacks_for_surface_id(&surface.surface_id); } - for popup in &self.popups { + for popup in self.popups.iter() { state.send_frame_callbacks_for_surface_id(&popup.surface_id); } } @@ -833,7 +734,11 @@ impl OverlayBackend for WvrWindowBackend { return HoverResult::consume(); } - match self.hit_target(hit) { + let Some(ref ctx) = self.hit_context else { + return HoverResult::default(); + }; + + match ctx.hit_target(hit) { Some(WvrHitTarget::Panel(hit2)) => { self.panel_hovered = true; self.panel.on_hover(app, &hit2) @@ -889,8 +794,12 @@ impl OverlayBackend for WvrWindowBackend { return; }; - let target = self.hit_target(hit); - let outside_pos = self.unclamped_client_pos_from_hit(hit); + let Some(ref ctx) = self.hit_context else { + return; + }; + + let target = ctx.hit_target(hit); + let outside_pos = ctx.unclamped_client_pos_from_hit(hit); let click_freeze = app.session.config.click_freeze_time_ms; // if the press was consumed to dismiss a popup, consume the matching release too. @@ -981,7 +890,11 @@ impl OverlayBackend for WvrWindowBackend { } fn on_scroll(&mut self, app: &mut state::AppState, hit: &input::PointerHit, delta: WheelDelta) { - let target = self.hit_target(hit); + let Some(ref ctx) = self.hit_context else { + return; + }; + + let target = ctx.hit_target(hit); self.scrolling = true; match target { @@ -1059,162 +972,6 @@ impl OverlayBackend for WvrWindowBackend { } } -fn rendered_surfaces_dirty(old: &[RenderedSurface], new: &[RenderedSurface]) -> bool { - if old.len() != new.len() { - return true; - } - - old.iter().zip(new).any(|(a, b)| { - a.surface_id != b.surface_id - || a.pos != b.pos - || a.size != b.size - || *a.image.image() != *b.image.image() - }) -} - -fn surface_location(states: &smithay::wayland::compositor::SurfaceData) -> Point { - if states.role == Some(SUBSURFACE_ROLE) { - let mut guard = states.cached_state.get::(); - guard.current().location - } else { - (0, 0).into() - } -} - -fn collect_rendered_surface_tree_at( - root: &WlSurface, - initial_pos: Point, - include_root: bool, -) -> Vec { - let mut out = Vec::new(); - let root_id = root.id(); - - with_surface_tree_upward( - root, - initial_pos, - |_, states, parent_pos| { - let pos = *parent_pos + surface_location(states); - - // Do not skip even if this surface has no buffer; - // children may still have buffers. - TraversalAction::DoChildren(pos) - }, - |surface, states, parent_pos| { - if !include_root && surface.id() == root_id { - return; - } - - let pos = *parent_pos + surface_location(states); - - if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) { - let extent = surf.image.extent_f32(); - let scale = surf.scale.max(1) as f32; - - out.push(RenderedSurface { - surface: surface.clone(), - surface_id: surface.id(), - image: surf.image, - pos: vec2(pos.x as f32, pos.y as f32), - size: vec2(extent[0] / scale, extent[1] / scale), - }); - } - }, - |_, _, _| true, - ); - - out -} - -fn collect_rendered_surface_tree(root: &WlSurface) -> Vec { - collect_rendered_surface_tree_at(root, Point::::from((0, 0)), false) -} - -fn surface_accepts_input(surface: &RenderedSurface, global_pos: Vec2) -> bool { - let local = global_pos - surface.pos; - - if local.x < 0.0 || local.y < 0.0 || local.x >= surface.size.x || local.y >= surface.size.y { - return false; - } - - with_states(&surface.surface, |states| { - let mut guard = states.cached_state.get::(); - let attrs = guard.current(); - - match attrs.input_region.as_ref() { - None => true, - Some(region) => { - let point = - Point::::from((local.x.floor() as i32, local.y.floor() as i32)); - region.contains(point) - } - } - }) -} - -fn surface_accepts_input_states( - states: &smithay::wayland::compositor::SurfaceData, - local: Vec2, -) -> bool { - if local.x < 0.0 || local.y < 0.0 { - return false; - } - - let mut guard = states.cached_state.get::(); - let attrs = guard.current(); - - let point = Point::::from((local.x.floor() as i32, local.y.floor() as i32)); - - // explicit input region wins, even if no render buffer - if let Some(region) = attrs.input_region.as_ref() { - return region.contains(point); - } - - // fallback for normal rendered surfaces - if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) { - let extent = surf.image.extent_f32(); - let scale = surf.scale.max(1) as f32; - - return local.x < extent[0] / scale && local.y < extent[1] / scale; - } - - false -} -fn surface_tree_input_hit_test( - root: &WlSurface, - root_origin: Vec2, - global_pos: Vec2, - include_root: bool, -) -> Option<(WlSurface, Vec2)> { - let mut hit = None; - let root_id = root.id(); - - with_surface_tree_upward( - root, - Point::::from((root_origin.x as i32, root_origin.y as i32)), - |_, states, parent_pos| { - let pos = *parent_pos + surface_location(states); - TraversalAction::DoChildren(pos) - }, - |surface, states, parent_pos| { - if !include_root && surface.id() == root_id { - return; - } - - let pos = *parent_pos + surface_location(states); - let surface_origin = vec2(pos.x as f32, pos.y as f32); - let local = global_pos - surface_origin; - - if surface_accepts_input_states(states, local) { - // with_surface_tree_upward visits bottom-to-top, so later hits win - hit = Some((surface.clone(), surface_origin)); - } - }, - |_, _, _| true, - ); - - hit -} - fn overlay_scale_from_extent(size: [u32; 2]) -> (f32, f32) { const RELATIVE_SIZE: f32 = 1440.0; // sqrt(1920 * 1080)