wvr_server: popup input to respect surface tree

This commit is contained in:
galister 2026-07-05 02:24:57 +09:00
parent c6117d99de
commit ea6a6dc6e7
2 changed files with 111 additions and 50 deletions

View File

@ -668,9 +668,7 @@ impl WvrServerState {
pub fn send_mouse_scroll_to_surface(
&mut self,
surface: WlSurface,
global_pos: Vec2,
surface_origin: Vec2,
hover_window: window::WindowHandle,
delta: WheelDelta,
) {

View File

@ -108,6 +108,12 @@ pub fn create_wl_window_overlay(
})
}
#[derive(Clone)]
struct PopupRoot {
surface: WlSurface,
surface_origin: Vec2,
}
#[derive(Clone)]
struct RenderedSurface {
surface: WlSurface,
@ -143,6 +149,7 @@ pub struct WvrWindowBackend {
interaction_transform: Option<Affine2>,
window: WindowHandle,
popups: Vec<RenderedSurface>,
popup_roots: Vec<PopupRoot>,
surfaces: Vec<RenderedSurface>,
just_resumed: bool,
meta: Option<FrameMeta>,
@ -242,6 +249,7 @@ impl WvrWindowBackend {
pipeline: None,
window,
popups: vec![],
popup_roots: vec![],
surfaces: vec![],
subsurface_pipeline,
popup_outside_button: None,
@ -336,19 +344,8 @@ impl WvrWindowBackend {
}
fn popup_hit_at_client_pos(&self, pos: Vec2) -> Option<(WlSurface, Vec2)> {
self.popups.iter().rev().find_map(|popup| {
let local = pos - popup.pos;
let hit = local.x >= 0.0
&& local.y >= 0.0
&& local.x < popup.size.x
&& local.y < popup.size.y;
if hit {
Some((popup.surface.clone(), popup.pos))
} else {
None
}
self.popup_roots.iter().find_map(|popup| {
surface_tree_input_hit_test(&popup.surface, popup.surface_origin, pos, true)
})
}
@ -476,25 +473,41 @@ impl OverlayBackend for WvrWindowBackend {
let surfaces = collect_rendered_surface_tree(toplevel.wl_surface());
let should_render_panel = self.panel.should_render(app)?;
let popups = PopupManager::popups_for_surface(toplevel.wl_surface())
.flat_map(|(popup, point)| {
let configured = with_states(popup.wl_surface(), |states| {
states
.data_map
.get::<XdgPopupSurfaceData>()
.unwrap()
.lock()
.unwrap()
.configured
});
let mut popup_roots = Vec::new();
let mut popups = Vec::new();
if !configured {
return Vec::new();
}
let popup_origin = point - popup.geometry().loc;
collect_rendered_surface_tree_at(popup.wl_surface(), popup_origin, true)
})
.collect::<Vec<_>>();
for (popup, point) in PopupManager::popups_for_surface(toplevel.wl_surface()) {
let configured = with_states(popup.wl_surface(), |states| {
states
.data_map
.get::<XdgPopupSurfaceData>()
.unwrap()
.lock()
.unwrap()
.configured
});
if !configured {
continue;
}
// Same logic you already use. `point` is the popup tree position;
// `geometry().loc` accounts for xdg_surface window geometry offset.
let popup_origin = point - popup.geometry().loc;
popup_roots.push(PopupRoot {
surface: popup.wl_surface().clone(),
surface_origin: vec2(popup_origin.x as f32, popup_origin.y as f32),
});
popups.extend(collect_rendered_surface_tree_at(
popup.wl_surface(),
popup_origin,
true,
));
}
self.popup_roots = popup_roots;
let mut tree_dirty = false;
@ -879,24 +892,10 @@ impl OverlayBackend for WvrWindowBackend {
let _ = hit2;
}
Some(WvrHitTarget::Popup {
surface,
global_pos,
surface_origin,
})
| Some(WvrHitTarget::Surface {
surface,
global_pos,
surface_origin,
}) => {
Some(WvrHitTarget::Popup { global_pos, .. })
| Some(WvrHitTarget::Surface { global_pos, .. }) => {
let wvr_server = app.wvr_server.as_mut().unwrap();
wvr_server.send_mouse_scroll_to_surface(
surface,
global_pos,
surface_origin,
self.window,
delta,
);
wvr_server.send_mouse_scroll_to_surface(global_pos, self.window, delta);
}
Some(WvrHitTarget::Toplevel { pos }) => {
let wvr_server = app.wvr_server.as_mut().unwrap();
@ -1045,3 +1044,67 @@ fn surface_accepts_input(surface: &RenderedSurface, global_pos: Vec2) -> bool {
}
})
}
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
}