git.lucas.co / cce-window-manager
window management library
git clone https://git.lucas.co/cce-window-manager.git

commitece79a070596ab377a7fc2b8a6d5f0dd9c7fb4bd
parent4a04656648
authorLucas Galante <[email protected]>
date2026-09-14 13:15
feat(camera): a floating window beside the tiled desk is not recalled

`recalled_origin` pulled every off-view floating window into the middle of
the restored view, on the theory that a window a screen from the camera
is lost. cce-data-editor parked left of the first tiled column came back
mid-view every login: the camera had been left two screens to the right at
logout, so 0% of it was visible, but it was placed, not lost — the columns
beside it are what the user pans along.

New `on_tiled_desk`: the window overlaps the tiled windows' bounding box
inflated by one viewport on every side. `recalled_origin` takes that box
(`None` for no tiled windows) and keeps such a window where it was; the
recall is now only for a window with no tiled neighbour within a screen.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 CLAUDE.md     |  9 ++++---
 src/camera.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 83 insertions(+), 9 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 4b337cd..032bba3 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -189,9 +189,12 @@ The crate owns what a binding *means*; the compositor owns the physical half
   mechanism owns the actual fields and animation; these are pure maps.
   `recalled_origin` decides where a remembered FLOATING window reopens: its
   remembered origin when at least `RESTORE_VISIBLE_MIN` (a quarter) of it
-  would be in view, else centered in the current view — a floating window
-  a screen away from the camera is lost, not remembered. Tiled windows are
-  the grid's and never go through it.
+  would be in view, or when it is `on_tiled_desk` (overlaps the tiled
+  windows' bounding box inflated by one viewport — the caller passes the
+  box, `None` for no tiled windows), else centered in the current view — a
+  floating window a screen away from the camera AND from every tiled window
+  is lost, not remembered; one parked beside a column is placed. Tiled
+  windows are the grid's and never go through it.
 - `ramp.rs` — speed-ramp evaluation for duration-based camera transitions.
   `SpeedRamp::from_spec` parses the DE-wide ramp spec string cce-ui's Ramp
   widget writes and integrates that SPEED profile into a cumulative
diff --git a/src/camera.rs b/src/camera.rs
index c4dce2d..7a70184 100644
--- a/src/camera.rs
+++ b/src/camera.rs
@@ -216,6 +216,31 @@ pub fn pan_into_view(
 /// fraction of it is recalled into view instead of restored where it was.
 pub const RESTORE_VISIBLE_MIN: f64 = 0.25;
 
+/// Whether a remembered window rect is ON THE DESK: it overlaps the tiled
+/// windows' bounding box inflated by one viewport on every side (virtual
+/// units, so at zoom 1 a viewport is `vw` x `vh`). `None` for the desk means
+/// there are no tiled windows to be beside, and nothing is on the desk.
+///
+/// A floating window parked beside a tiled column, or one screen past the
+/// desk's edge, is at most one pan away from content the user navigates
+/// by — it is placed, not lost. Only a window with no tiled neighbour
+/// within a screen has nothing on the desk to say where it is.
+pub fn on_tiled_desk(
+    x: f64,
+    y: f64,
+    w: f64,
+    h: f64,
+    desk: Option<(f64, f64, f64, f64)>,
+    vw: f64,
+    vh: f64,
+) -> bool {
+    let Some((min_x, min_y, max_x, max_y)) = desk else { return false };
+    if w <= 0.0 || h <= 0.0 || max_x <= min_x || max_y <= min_y {
+        return false;
+    }
+    x < max_x + vw && x + w > min_x - vw && y < max_y + vh && y + h > min_y - vh
+}
+
 /// Where a remembered FLOATING window should reopen: `None` to keep its
 /// remembered origin, or the origin that centers it in the current view.
 ///
@@ -227,6 +252,13 @@ pub const RESTORE_VISIBLE_MIN: f64 = 0.25;
 /// viewport is not "remembered", it is lost, with nothing on screen to say
 /// it exists. A window that would still be mostly visible keeps its spot —
 /// a floating window deliberately tucked at an edge stays tucked.
+///
+/// So does a window ON THE DESK (`on_tiled_desk` against `desk`, the tiled
+/// windows' bounding box): a data editor parked beside the leftmost tiled
+/// column was recalled into the middle of the view every login because the
+/// camera had been left two screens to the right at logout. Off-view is
+/// not lost when the tiled desk is right there to pan along; the recall is
+/// for a window with no neighbour at all.
 pub fn recalled_origin(
     x: f64,
     y: f64,
@@ -235,6 +267,7 @@ pub fn recalled_origin(
     cam: Camera,
     vw: f64,
     vh: f64,
+    desk: Option<(f64, f64, f64, f64)>,
 ) -> Option<(f64, f64)> {
     if w <= 0.0 || h <= 0.0 {
         return None;
@@ -242,6 +275,9 @@ pub fn recalled_origin(
     if visible_fraction(x, y, w, h, cam, vw, vh) >= RESTORE_VISIBLE_MIN {
         return None;
     }
+    if on_tiled_desk(x, y, w, h, desk, vw, vh) {
+        return None;
+    }
     let zoom = cam.zoom.max(0.01);
     Some((
         centered_window_origin(cam.pan_x, vw, zoom, w),
@@ -295,29 +331,64 @@ mod tests {
         // A 700x666 window remembered at y=-4422 ends at -3756 — a whole
         // screen above. It comes back centered in the view.
         let c = cam(-4708.0, -3196.0, 1.0);
-        let got = recalled_origin(-3272.0, -4422.0, 700.0, 666.0, c, VW, VH);
+        let got = recalled_origin(-3272.0, -4422.0, 700.0, 666.0, c, VW, VH, None);
         assert_eq!(got, Some((-4708.0 + (VW - 700.0) / 2.0, -3196.0 + (VH - 666.0) / 2.0)));
     }
 
+    #[test]
+    fn a_remembered_window_beside_the_tiled_desk_keeps_its_spot() {
+        // The 2026-09-14 login, at output scale 2 (1920x1200 logical):
+        // camera (-5928, -3244), the data editor 952x904 remembered at
+        // (-8461, -3564) — two and a half screens left, 0% visible — and
+        // the leftmost tiled column at x=-7380, 129 px to its right.
+        let (vw, vh) = (1920.0, 1200.0);
+        let c = cam(-5928.0, -3244.0, 1.0);
+        let desk = Some((-7380.0, -4380.0, -2984.0, -2076.0));
+        assert_eq!(recalled_origin(-8461.0, -3564.0, 952.0, 904.0, c, vw, vh, desk), None);
+        // Without a tiled desk the same window is lost, and recalled.
+        assert!(recalled_origin(-8461.0, -3564.0, 952.0, 904.0, c, vw, vh, None).is_some());
+        // More than a viewport past the desk's edge: nothing to be beside.
+        assert!(recalled_origin(-7380.0 - vw - 952.0 - 1.0, -3564.0, 952.0, 904.0, c, vw, vh, desk).is_some());
+        // Exactly one viewport past still counts — the pan that reaches
+        // the desk's edge shows it.
+        assert_eq!(recalled_origin(-7380.0 - vw - 952.0 + 1.0, -3564.0, 952.0, 904.0, c, vw, vh, desk), None);
+    }
+
+    #[test]
+    fn on_tiled_desk_is_the_inflated_bounding_box() {
+        let desk = Some((0.0, 0.0, 1000.0, 1000.0));
+        // Inside, overlapping, and within a viewport of every side.
+        assert!(on_tiled_desk(100.0, 100.0, 200.0, 200.0, desk, VW, VH));
+        assert!(on_tiled_desk(-VW - 100.0, 0.0, 200.0, 200.0, desk, VW, VH));
+        assert!(on_tiled_desk(0.0, 1000.0 + VH - 1.0, 200.0, 200.0, desk, VW, VH));
+        // Past the inflated box on either axis.
+        assert!(!on_tiled_desk(-VW - 200.0, 0.0, 200.0, 200.0, desk, VW, VH));
+        assert!(!on_tiled_desk(0.0, 1000.0 + VH, 200.0, 200.0, desk, VW, VH));
+        // No desk, a sizeless window, or a degenerate desk: never on it.
+        assert!(!on_tiled_desk(100.0, 100.0, 200.0, 200.0, None, VW, VH));
+        assert!(!on_tiled_desk(100.0, 100.0, 0.0, 0.0, desk, VW, VH));
+        assert!(!on_tiled_desk(100.0, 100.0, 200.0, 200.0, Some((5.0, 5.0, 5.0, 5.0)), VW, VH));
+    }
+
     #[test]
     fn a_remembered_window_mostly_in_view_keeps_its_spot() {
         let c = cam(0.0, 0.0, 1.0);
         // Fully visible.
-        assert_eq!(recalled_origin(100.0, 100.0, 700.0, 666.0, c, VW, VH), None);
+        assert_eq!(recalled_origin(100.0, 100.0, 700.0, 666.0, c, VW, VH, None), None);
         // Half off the right edge: 50% visible, above the quarter floor.
-        assert_eq!(recalled_origin(VW - 350.0, 100.0, 700.0, 666.0, c, VW, VH), None);
+        assert_eq!(recalled_origin(VW - 350.0, 100.0, 700.0, 666.0, c, VW, VH, None), None);
         // Only a sliver (10%) on screen: recalled.
-        assert!(recalled_origin(VW - 70.0, 100.0, 700.0, 666.0, c, VW, VH).is_some());
+        assert!(recalled_origin(VW - 70.0, 100.0, 700.0, 666.0, c, VW, VH, None).is_some());
     }
 
     #[test]
     fn recall_centers_under_the_current_zoom() {
         // Zoomed out to 0.5 the view covers twice the virtual extent.
         let c = cam(1000.0, 1000.0, 0.5);
-        let got = recalled_origin(-9000.0, -9000.0, 400.0, 300.0, c, VW, VH);
+        let got = recalled_origin(-9000.0, -9000.0, 400.0, 300.0, c, VW, VH, None);
         assert_eq!(got, Some((1000.0 + (VW / 0.5 - 400.0) / 2.0, 1000.0 + (VH / 0.5 - 300.0) / 2.0)));
         // A sizeless window has nothing to place.
-        assert_eq!(recalled_origin(-9000.0, -9000.0, 0.0, 0.0, c, VW, VH), None);
+        assert_eq!(recalled_origin(-9000.0, -9000.0, 0.0, 0.0, c, VW, VH, None), None);
     }
 
     #[test]