input capture #2 - mouse

This commit is contained in:
galister 2026-07-15 11:48:20 +09:00
parent 968cb230e3
commit c6dd690cb2
3 changed files with 589 additions and 303 deletions

View File

@ -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<ImageView>,
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<f32>,
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<PointerHit> {
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<WvrHitTarget> {
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<WvrHitTarget> {
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<i32, Logical> {
if states.role == Some(SUBSURFACE_ROLE) {
let mut guard = states.cached_state.get::<SubsurfaceCachedState>();
guard.current().location
} else {
(0, 0).into()
}
}
pub fn collect_rendered_surface_tree_at(
root: &WlSurface,
initial_pos: Point<i32, Logical>,
include_root: bool,
) -> Vec<RenderedSurface> {
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::<i32, Logical>::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::<SurfaceAttributes>();
let attrs = guard.current();
match attrs.input_region.as_ref() {
None => true,
Some(region) => {
let point =
Point::<i32, Logical>::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::<SurfaceAttributes>();
let attrs = guard.current();
let point = Point::<i32, Logical>::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::<i32, Logical>::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<f32>) {
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<WvrHitContext> {
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::<XdgPopupSurfaceData>()
.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,
})
}

View File

@ -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<MouseIndex> {
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;

View File

@ -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<ImageView>,
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<str>,
icon: Arc<str>,
@ -166,9 +135,9 @@ pub struct WvrWindowBackend {
popup_outside_button: Option<wayvr::MouseIndex>,
interaction_transform: Option<Affine2>,
window: WindowHandle,
popups: Vec<RenderedSurface>,
popup_roots: Vec<PopupRoot>,
surfaces: Vec<RenderedSurface>,
popups: Rc<[RenderedSurface]>,
surfaces: Rc<[RenderedSurface]>,
hit_context: Option<WvrHitContext>,
just_resumed: bool,
meta: Option<FrameMeta>,
mouse: Option<MouseMeta>,
@ -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<input::PointerHit> {
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<WvrHitTarget> {
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<wayvr::MouseIndex> {
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<i32, Logical> {
if states.role == Some(SUBSURFACE_ROLE) {
let mut guard = states.cached_state.get::<SubsurfaceCachedState>();
guard.current().location
} else {
(0, 0).into()
}
}
fn collect_rendered_surface_tree_at(
root: &WlSurface,
initial_pos: Point<i32, Logical>,
include_root: bool,
) -> Vec<RenderedSurface> {
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<RenderedSurface> {
collect_rendered_surface_tree_at(root, Point::<i32, Logical>::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::<SurfaceAttributes>();
let attrs = guard.current();
match attrs.input_region.as_ref() {
None => true,
Some(region) => {
let point =
Point::<i32, Logical>::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::<SurfaceAttributes>();
let attrs = guard.current();
let point = Point::<i32, Logical>::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::<i32, Logical>::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)