GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
fix(input): popover areas veto window drags; render-only popups take no input
Two input holes found dissolving cce-graph's root Backplate:
- point_in_active_popover: a press inside an OPEN popover's plate must
never start a window move — popovers are drawn on top but are not
spatial-grid widgets, and the widget beneath may not block dragging
(Graph's edge-exclusive canvas hit test returns false on empty canvas,
so clicking a menu item over the canvas started an interactive move
and swallowed the click — pre-existing, backplate or not). Both
is_movable_backplate_at and drag_allowed_at now veto there.
- The engine's render-only xdg popups get an EMPTY input region: with
the default full region the popup surface can swallow pointer input
over an open menu that the main surface draws and hit-tests itself.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016MjP3pGQEDLkJbV5WEmYBe
src/backend/window_runner.rs | 13 +++++++++++--
src/context.rs | 25 +++++++++++++++++++++++++
2 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 2e54a7b..b5fec94 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2509,7 +2509,6 @@ impl<A: Application> PointerHandler for EngineState<A> {
}
self.cursor_pos = (lx, ly);
-
match &event.kind {
PointerEventKind::Enter { .. } => {
let is_status_bar = self.inner.as_ref().unwrap().settings().app_id.starts_with("cce-status");
@@ -3336,7 +3335,17 @@ pub fn run<A: Application>() {
&engine_state.xdg_shell_state,
).unwrap();
sctk_popup.wl_surface().set_buffer_scale(engine_state.scale_factor as i32);
-
+ // Render-only popup: give it an EMPTY input region so pointer events pass
+ // through to the main surface beneath (which draws and hit-tests the popover
+ // content itself). With the default full input region the popup swallowed
+ // every click on an open menu — item clicks never reached the app.
+ {
+ let compositor = engine_state.compositor_state.wl_compositor();
+ let empty_region = compositor.create_region(&engine_state.qh, ());
+ sctk_popup.wl_surface().set_input_region(Some(&empty_region));
+ empty_region.destroy();
+ }
+
let display_ptr = conn.backend().display_id().as_ptr() as *mut std::ffi::c_void;
let surface_ptr = sctk_popup.wl_surface().id().as_ptr() as *mut std::ffi::c_void;
let wayland_handle = Box::leak(Box::new(WaylandSurfaceHandle {
diff --git a/src/context.rs b/src/context.rs
index f168524..db0bad1 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -778,7 +778,29 @@ impl UiContext {
crate::widget::context_menu::text_labels()
}
+ /// Whether the point lies inside an OPEN popover's plate. Popovers are drawn on top of
+ /// everything and are interactive UI, but they are not spatial-grid widgets — a press
+ /// there must never start a window move (the widgets beneath may not block dragging,
+ /// e.g. Graph's edge-exclusive canvas hit test).
+ fn point_in_active_popover(&self, px: f32, py: f32) -> bool {
+ for popover_ptr in &self.active_popovers {
+ unsafe {
+ if let Some(p) = popover_ptr.as_ref() {
+ if let Some((x, y, w, h)) = p.popover_rect() {
+ if px >= x && px <= x + w && py >= y && py <= y + h {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ false
+ }
+
pub fn is_movable_backplate_at(&self, px: f32, py: f32) -> bool {
+ if self.point_in_active_popover(px, py) {
+ return false;
+ }
let mut hit_backplate = false;
let scroll_y = crate::widget::hover_animation::get_scroll_offset();
let mut candidate_ids = self.spatial_grid.query(px, py).to_vec();
@@ -819,6 +841,9 @@ impl UiContext {
/// [`is_movable_backplate_at`](UiContext::is_movable_backplate_at) minus the requirement
/// that a registered movable `Backplate` is hit.
pub fn drag_allowed_at(&self, px: f32, py: f32) -> bool {
+ if self.point_in_active_popover(px, py) {
+ return false;
+ }
let scroll_y = crate::widget::hover_animation::get_scroll_offset();
let mut candidate_ids = self.spatial_grid.query(px, py).to_vec();
if scroll_y != 0.0 {