Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat(camera): focus-follow takes the minimal pan at every distance
Both sites that panned for a focus change carried the same two-branch rule:
centre the window when less than three quarters of it was visible, nudge it
in when it was merely clipped. Both now call policy::camera::pan_into_view,
which is that nudge generalized to any distance (cce-window-manager
8f7f4a3 has the reasoning).
So focusing the window immediately to the right slides the desktop just far
enough to show it whole with a margin, instead of swinging it into the
middle of the screen and taking every other window with it.
The second site is pan_to_virtual_rect, which pans for restore
placeholders; its doc already said it mirrors what windows get, and sharing
one policy call is what keeps that true.
Measured in a headless session, an 800-wide window entirely off the right
edge of a 1280 viewport at vx=1876: before, pan_x 1636, the centre-on value
exactly, window at screen x=240; after, pan_x 1420, the minimal value, window
at screen x=456 with its right edge 24px in. Re-focusing an already-visible
window moves the camera not at all, and focus-left onto a window off the
other edge lands it at screen x=24.
Co-Authored-By: Claude Opus 5 <[email protected]>
src/server/seat.rs | 33 +++++++--------------------------
src/server/window_manager.rs | 20 ++++----------------
2 files changed, 11 insertions(+), 42 deletions(-)
diff --git a/src/server/seat.rs b/src/server/seat.rs
index bf25138..0d9e050 100644
--- a/src/server/seat.rs
+++ b/src/server/seat.rs
@@ -1079,28 +1079,13 @@ impl Seat {
// whenever zoom != 1 and mistargeted the pan.
let vw_w = fw;
let vw_h = fh;
- let visible = crate::policy::camera::visible_fraction(
- (*window).virtual_x,
- (*window).virtual_y,
- vw_w,
- vw_h,
- cam,
- viewport_w,
- viewport_h,
- );
-
- if visible < crate::policy::camera::FOCUS_VISIBLE_THRESHOLD {
- let target = crate::policy::camera::center_on(
- (*window).virtual_x + vw_w / 2.0,
- (*window).virtual_y + vw_h / 2.0,
- viewport_w,
- viewport_h,
- cam.zoom,
- );
- wm.target_desk_pan_x = Some(target.pan_x);
- wm.target_desk_pan_y = Some(target.pan_y);
- wm.start_panning_animation();
- } else if let Some(target) = crate::policy::camera::nudge_into_view(
+ // The camera moves as little as the focus demands: enough to
+ // show the whole window with a margin, and no further. A window
+ // half off the edge and one a screen away take the same rule —
+ // `policy::camera::pan_into_view` carries the reasoning, and
+ // `WindowManager::pan_to_virtual_rect` applies it to the
+ // non-window rects (restore placeholders) from the same place.
+ if let Some(target) = crate::policy::camera::pan_into_view(
(*window).virtual_x,
(*window).virtual_y,
vw_w,
@@ -1109,10 +1094,6 @@ impl Seat {
viewport_w,
viewport_h,
) {
- // Mostly visible but clipped: slide the
- // clipped edge on-screen instead of
- // recentering — focusing a window should
- // never leave part of it hanging off.
wm.target_desk_pan_x = Some(target.pan_x);
wm.target_desk_pan_y = Some(target.pan_y);
wm.start_panning_animation();
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index d7946de..360522c 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -735,9 +735,9 @@ impl WindowManager {
None
}
- /// Focus-follow camera rules for a bare virtual rect — the same
- /// center-when-mostly-hidden / nudge-when-clipped behavior windows get,
- /// for things that are not windows (restore placeholders).
+ /// Focus-follow camera rules for a bare virtual rect — the same minimal
+ /// pan-into-view windows get, for things that are not windows (restore
+ /// placeholders).
pub unsafe fn pan_to_virtual_rect(&mut self, vx: f64, vy: f64, w: f64, h: f64) {
let outputs_list = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
let mut curr_out = (*outputs_list).next;
@@ -753,19 +753,7 @@ impl WindowManager {
let Some(viewport) = viewport else { return };
let (vw, vh) = (viewport.width as f64, viewport.height as f64);
let cam = self.camera();
- let visible = crate::policy::camera::visible_fraction(vx, vy, w, h, cam, vw, vh);
- let target = if visible < crate::policy::camera::FOCUS_VISIBLE_THRESHOLD {
- Some(crate::policy::camera::center_on(
- vx + w / 2.0,
- vy + h / 2.0,
- vw,
- vh,
- cam.zoom,
- ))
- } else {
- crate::policy::camera::nudge_into_view(vx, vy, w, h, cam, vw, vh)
- };
- if let Some(target) = target {
+ if let Some(target) = crate::policy::camera::pan_into_view(vx, vy, w, h, cam, vw, vh) {
self.target_desk_pan_x = Some(target.pan_x);
self.target_desk_pan_y = Some(target.pan_y);
self.start_panning_animation();