srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/core/src')
-rw-r--r--crates/core/src/window.rs79
1 files changed, 70 insertions, 9 deletions
diff --git a/crates/core/src/window.rs b/crates/core/src/window.rs
index 272d772..21b7ab4 100644
--- a/crates/core/src/window.rs
+++ b/crates/core/src/window.rs
@@ -454,6 +454,28 @@ pub const RESIZE_MARGIN: i32 = 6;
/// diagonal drag easier to land on a window srdwm itself has no competing
/// content in, which is never true here.
pub const UNDECORATED_RESIZE_MARGIN: i32 = 3;
+
+/// How far *outside* a window's own frame the resize grab zone reaches,
+/// regardless of border width.
+///
+/// The inward margin has to stay narrow - see
+/// [`UNDECORATED_RESIZE_MARGIN`] above for the clicks it was stealing from
+/// Nemo's own buttons. Pixels outside the frame have no such problem:
+/// there is no client content there to take a click from, so the band can
+/// be as generous as it needs to be.
+///
+/// This used to be `border_width` alone, which meant a borderless window
+/// had no outward band at all and its entire resize target was the narrow
+/// inward margin - 3px on an undecorated window. Reported as resizing
+/// working "from one direction" and feeling "very cheap": a 3px target is
+/// missed far more often than it is hit, so it reads as an edge that
+/// sometimes resizes rather than one that always does. Turning borders off
+/// for the macOS look made it strictly worse, since the border had been
+/// quietly providing the only outward reach.
+///
+/// macOS and GNOME both let a pointer grab slightly outside a window's
+/// visible edge for the same reason.
+pub const RESIZE_OUTSET: i32 = 5;
/// Top-edge resize margin for a *decorated* window's own titlebar band --
/// unlike [`UNDECORATED_RESIZE_MARGIN`], this has no client content to
/// avoid stealing a click from (the whole titlebar band is srdwm's own
@@ -557,8 +579,11 @@ impl ResizeEdge {
// itself needs no matching change - its margin comparisons
// (`x <= frame.x + m`, etc.) already treat anything at or outside
// `frame`'s own edge as maximally "near", border pixels included.
- let bw = border_width as i32;
- let outer = Rect::new(frame.x - bw, frame.y - bw, frame.width + 2 * border_width, frame.height + 2 * border_width);
+ // Whichever reaches further: the drawn border, or the fixed outward
+ // grab band. See `RESIZE_OUTSET`.
+ let bw = (border_width as i32).max(RESIZE_OUTSET);
+ let reach = bw as u32;
+ let outer = Rect::new(frame.x - bw, frame.y - bw, frame.width + 2 * reach, frame.height + 2 * reach);
if !outer.contains_point(x, y) {
return None;
}
@@ -1283,21 +1308,57 @@ mod tests {
// though it's what visually reads as the window's edge.
let f = frame();
let (_, cy) = f.center();
- let border_width = 2;
- // One pixel into the border strip, past the left edge.
+ // A border wider than the fixed outward band, so this test is
+ // actually exercising the border and not `RESIZE_OUTSET`.
+ let border_width = (RESIZE_OUTSET + 3) as u32;
let x = f.x - 1;
- assert_eq!(ResizeEdge::hit_test(f, x, cy, true, 0, RESIZE_MARGIN, false, None, false, true), None, "sanity check: with no border, this point really is outside the window");
assert_eq!(
ResizeEdge::hit_test(f, x, cy, true, border_width, RESIZE_MARGIN, false, None, false, true),
Some(TitlebarHit::Resize(ResizeEdge::Left)),
- "one pixel into the actual drawn border must still register as the left edge"
+ "one pixel into the actual drawn border must register as the left edge"
+ );
+ assert_eq!(
+ ResizeEdge::hit_test(f, f.x - border_width as i32 + 1, cy, true, border_width, RESIZE_MARGIN, false, None, false, true),
+ Some(TitlebarHit::Resize(ResizeEdge::Left)),
+ "the far side of a wide border is still the window's edge"
);
- // Just past the border entirely (border_width + 1 outside frame) is
- // still nothing - the fix widens the dead zone's boundary, it
- // doesn't remove it.
+ // Past the border entirely: the grab zone has a boundary, it is
+ // just further out than the frame.
assert_eq!(ResizeEdge::hit_test(f, f.x - border_width as i32 - 1, cy, true, border_width, RESIZE_MARGIN, false, None, false, true), None);
}
+ /// A borderless window still has somewhere to grab. Before
+ /// `RESIZE_OUTSET` the outward band was `border_width` alone, so
+ /// turning borders off left only the inward margin - 3px on an
+ /// undecorated window, which is missed more often than hit.
+ #[test]
+ fn a_borderless_window_can_still_be_grabbed_from_outside_its_edge() {
+ let f = frame();
+ let (_, cy) = f.center();
+ for dx in 1..=RESIZE_OUTSET {
+ assert_eq!(
+ ResizeEdge::hit_test(f, f.x - dx, cy, false, 0, RESIZE_MARGIN, false, None, false, true),
+ Some(TitlebarHit::Resize(ResizeEdge::Left)),
+ "{dx}px outside a borderless window's left edge must still resize"
+ );
+ }
+ assert_eq!(
+ ResizeEdge::hit_test(f, f.x - RESIZE_OUTSET - 1, cy, false, 0, RESIZE_MARGIN, false, None, false, true),
+ None,
+ "one pixel past the band is not the window"
+ );
+ }
+
+ #[test]
+ fn the_outward_band_reaches_every_edge_not_just_the_left() {
+ let f = frame();
+ let (cx, cy) = f.center();
+ let probe = |x, y| ResizeEdge::hit_test(f, x, y, false, 0, RESIZE_MARGIN, false, None, false, true);
+ assert_eq!(probe(f.right() + RESIZE_OUTSET - 1, cy), Some(TitlebarHit::Resize(ResizeEdge::Right)));
+ assert_eq!(probe(cx, f.y - RESIZE_OUTSET + 1), Some(TitlebarHit::Resize(ResizeEdge::Top)));
+ assert_eq!(probe(cx, f.bottom() + RESIZE_OUTSET - 1), Some(TitlebarHit::Resize(ResizeEdge::Bottom)));
+ }
+
#[test]
fn corner_resize_reaches_further_than_a_plain_edge_would() {
// Reported live: corners felt like they had no priority over sides.