From 305c28a9d8df5b091728a07ce310fcbbcb296040 Mon Sep 17 00:00:00 2001 From: odtgit Date: Sun, 15 Feb 2026 21:19:57 +0100 Subject: [PATCH] fix: prioritize activation region hits over CSD overflow in scrolling hit testing --- src/layout/mod.rs | 11 +++++++++++ src/layout/scrolling.rs | 25 +++++++++++++++++++++++++ src/layout/tile.rs | 21 +++++++++++++++++++++ src/layout/workspace.rs | 2 +- 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 010fe65b7c..dd18b7658f 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -594,6 +594,17 @@ impl HitType { .map(|hit| (tile.window(), hit.offset_win_pos(tile_pos))) } + /// Like `hit_tile()`, but clipped to the tile's activation region. + pub fn hit_tile_within_activation_region( + tile: &Tile, + tile_pos: Point, + point: Point, + ) -> Option<(&W, Self)> { + let pos_within_tile = point - tile_pos; + tile.hit_within_activation_region(pos_within_tile) + .map(|hit| (tile.window(), hit.offset_win_pos(tile_pos))) + } + pub fn to_activate(self) -> Self { match self { HitType::Input { .. } => HitType::Activate { diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index 69d0ed62a7..a334854e59 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -2998,6 +2998,31 @@ impl ScrollingSpace { // Round to physical pixels. let tile_pos = tile_pos.to_physical_precise_round(scale).to_logical(scale); + // Prefer hits within the activation region so CSD shadows can't steal + // hits from adjacent tiles (e.g. peeking columns in strut zones). + if let Some(rv) = + HitType::hit_tile_within_activation_region(tile, tile_pos, pos) + { + return Some(rv); + } + } + } + + // Fall back to CSD input regions extending beyond tile bounds (shadows, resize + // handles, GTK 3 subsurface popups) so normal pointer input still reaches them. + for (col, col_x) in self.columns_in_render_order() { + let col_off = Point::from((col_x, 0.)); + let col_render_off = col.render_offset(); + + for (tile, tile_off, visible) in col.tiles_in_render_order() { + if !visible { + continue; + } + + let tile_pos = + view_off + col_off + col_render_off + tile_off + tile.render_offset(); + let tile_pos = tile_pos.to_physical_precise_round(scale).to_logical(scale); + if let Some(rv) = HitType::hit_tile(tile, tile_pos, pos) { return Some(rv); } diff --git a/src/layout/tile.rs b/src/layout/tile.rs index bb0d61e873..a384351ef8 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -867,6 +867,27 @@ impl Tile { activation_region.contains(point) } + /// Like `hit()`, but only returns a result if the point is within the tile's activation + /// region (i.e. within tile bounds). This prevents CSD input regions (shadows, resize handles) + /// from claiming hits outside the tile. + pub fn hit_within_activation_region(&self, point: Point) -> Option { + let offset = self.bob_offset(); + let point = point - offset; + + if self.is_in_activation_region(point) { + if self.is_in_input_region(point) { + let win_pos = self.buf_loc() + offset; + Some(HitType::Input { win_pos }) + } else { + Some(HitType::Activate { + is_tab_indicator: false, + }) + } + } else { + None + } + } + pub fn hit(&self, point: Point) -> Option { let offset = self.bob_offset(); let point = point - offset; diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 0df4662096..df3e4db91b 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -1768,7 +1768,7 @@ impl Workspace { let pos_within_tile = pos - tile_pos; - if tile.hit(pos_within_tile).is_some() { + if tile.hit_within_activation_region(pos_within_tile).is_some() { let size = tile.tile_size().to_f64(); let mut edges = ResizeEdge::empty();