window management library
git clone https://git.lucas.co/cce-window-manager.git
feat(camera): recall a remembered floating window into view when it is off it
A restored floating window took its remembered virtual origin verbatim. On
a panning desktop that origin is often nowhere near the camera by the time
the window reopens — the session was saved with the camera elsewhere, or
the user has panned since a relaunch — and a floating window a screen away
is lost, not remembered: nothing on screen says it exists. Inkscape's start
screen came back a full viewport above the desk on every login, at the cell
its previous incarnation had been saved in.
recalled_origin(x, y, w, h, cam, vw, vh) is the decision: keep the
remembered origin when at least RESTORE_VISIBLE_MIN (a quarter) of the
window would be in view — a floating window deliberately tucked at an edge
stays tucked — else the origin that centers it in the current view, under
the current zoom, via centered_window_origin. Tiled windows are the grid's
and never go through it; the compositor applies this from try_restore for
Floating windows only.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
CLAUDE.md | 5 +++++
src/camera.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 73 insertions(+)
diff --git a/CLAUDE.md b/CLAUDE.md
index c35116f..4b337cd 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -187,6 +187,11 @@ The crate owns what a binding *means*; the compositor owns the physical half
`center_on`, `fit_bounds` (overview fit), `visible_fraction` +
`FOCUS_VISIBLE_THRESHOLD` (focus-follow panning), `is_overview`. The
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.
- `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 34aba20..febde18 100644
--- a/src/camera.rs
+++ b/src/camera.rs
@@ -207,6 +207,43 @@ pub fn nudge_into_view(
})
}
+/// A remembered floating window whose position would show LESS than this
+/// fraction of it is recalled into view instead of restored where it was.
+pub const RESTORE_VISIBLE_MIN: f64 = 0.25;
+
+/// Where a remembered FLOATING window should reopen: `None` to keep its
+/// remembered origin, or the origin that centers it in the current view.
+///
+/// On a panning desktop a remembered position is often off-view by the
+/// time the window reopens — the camera was somewhere else when the
+/// session was saved, or has moved since. Tiled windows are part of the
+/// grid and belong wherever the grid puts them, so this is for floating
+/// windows only: an Inkscape start screen restored a screen above the
+/// 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.
+pub fn recalled_origin(
+ x: f64,
+ y: f64,
+ w: f64,
+ h: f64,
+ cam: Camera,
+ vw: f64,
+ vh: f64,
+) -> Option<(f64, f64)> {
+ if w <= 0.0 || h <= 0.0 {
+ return None;
+ }
+ if visible_fraction(x, y, w, h, cam, vw, vh) >= RESTORE_VISIBLE_MIN {
+ return None;
+ }
+ let zoom = cam.zoom.max(0.01);
+ Some((
+ centered_window_origin(cam.pan_x, vw, zoom, w),
+ centered_window_origin(cam.pan_y, vh, zoom, h),
+ ))
+}
+
/// Margin kept around the fitted bounds when entering overview, output px.
const OVERVIEW_MARGIN: f64 = 100.0;
/// The margin never shrinks the usable viewport below this, output px.
@@ -247,6 +284,37 @@ mod tests {
Camera { pan_x, pan_y, zoom }
}
+ #[test]
+ fn a_remembered_floating_window_off_view_is_recalled_to_center() {
+ // Camera at (-4708, -3196), zoom 1: the view spans y -3196..-2116.
+ // 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);
+ assert_eq!(got, Some((-4708.0 + (VW - 700.0) / 2.0, -3196.0 + (VH - 666.0) / 2.0)));
+ }
+
+ #[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);
+ // 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);
+ // Only a sliver (10%) on screen: recalled.
+ assert!(recalled_origin(VW - 70.0, 100.0, 700.0, 666.0, c, VW, VH).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);
+ 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);
+ }
+
#[test]
fn overview_is_any_meaningful_zoom() {
assert!(!is_overview(1.0));