diff options
Diffstat (limited to 'crates/core/src')
| -rw-r--r-- | crates/core/src/manager/dragresize.rs | 49 | ||||
| -rw-r--r-- | crates/core/src/manager/tests.rs | 53 |
2 files changed, 91 insertions, 11 deletions
diff --git a/crates/core/src/manager/dragresize.rs b/crates/core/src/manager/dragresize.rs index b3132a5..912c267 100644 --- a/crates/core/src/manager/dragresize.rs +++ b/crates/core/src/manager/dragresize.rs @@ -43,8 +43,29 @@ impl WindowManager { new_geom.y = new_geom.y.clamp(bounds.y, bounds.bottom() - 40); } + // Live, every motion tick - not just once at `end_drag`, which + // used to be the only place this got corrected (see its own doc + // comment on why `w.monitor` goes stale at all). Between here and + // there, `state/geometry.rs::sync_geometry` - called on every one + // of these same motion ticks while a drag is active - reads this + // exact field to pick which monitor's `scale` converts the + // client's real physical size into the logical points `xdg_ + // toplevel::configure` sends it. Two real monitors at genuinely + // different scales (confirmed live: `1.0` and `~0.84`), a window + // dragged from one onto the other kept computing every mid-drag + // configure against the *origin* monitor's scale for the drag's + // entire remaining duration - the client resizing itself to a + // logical size that doesn't match the physical footprint the + // border/decoration are actually drawing around it, only self- + // correcting the instant the button came up. Reported live as a + // dragged window "looking very messed up" on the other monitor. + let now_on = self.monitors.iter().find(|m| m.geometry.overlaps(&new_geom)).map(|m| m.id); + if let Some(w) = self.windows.get_mut(&drag.window) { w.geometry = new_geom; + if let Some(now_on_id) = now_on { + w.monitor = now_on_id; + } } } @@ -52,17 +73,13 @@ impl WindowManager { /// near a monitor edge. pub fn end_drag(&mut self) { if let Some(drag) = self.drag.take() { - // `w.monitor` only ever gets set at window creation (or by - // `set_monitors`, reactively, on the *next* hotplug) - a drag - // that crossed onto a different monitor leaves it stale - // pointing at wherever the window *started*, same gap - // `set_monitors`'s own doc comment already documents for the - // hotplug-rehoming case. Corrected here, before computing the - // snap zone below, not after - using the stale value there - // would check the *wrong* monitor's snap zones (e.g. still - // snapping against monitor 1's left edge for a window that's - // now actually sitting near monitor 2's), the same bug this is - // fixing for maximize/fullscreen one level up. + // `update_drag` above already keeps `w.monitor` live on every + // motion tick now, so this is normally just confirming what's + // already current - kept anyway as the final word before + // computing the snap zone below (a drag that starts and ends + // between two motion ticks, however unlikely, would otherwise + // check the *wrong* monitor's snap zones), the same bug this + // was originally fixing for maximize/fullscreen one level up. if let Some(w) = self.windows.get(&drag.window) { if let Some(now_on) = self.monitors.iter().find(|m| m.geometry.overlaps(&w.geometry)) { let now_on_id = now_on.id; @@ -95,8 +112,18 @@ impl WindowManager { let Some(r) = &self.resize else { return }; let (dx, dy) = (x - r.start_x, y - r.start_y); let new_geom = r.edge.apply_delta(r.orig, dx, dy, MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT); + // Same live `w.monitor` correction as `update_drag`'s own doc + // comment explains - a resize can cross a monitor boundary at + // the edge being dragged just as easily as a drag can carry the + // whole window across one, and `sync_geometry`'s per-tick scale + // lookup doesn't care which kind of geometry change put the + // window there. + let now_on = self.monitors.iter().find(|m| m.geometry.overlaps(&new_geom)).map(|m| m.id); if let Some(w) = self.windows.get_mut(&r.window) { w.geometry = new_geom; + if let Some(now_on_id) = now_on { + w.monitor = now_on_id; + } } } diff --git a/crates/core/src/manager/tests.rs b/crates/core/src/manager/tests.rs index 88699de..4739278 100644 --- a/crates/core/src/manager/tests.rs +++ b/crates/core/src/manager/tests.rs @@ -123,6 +123,33 @@ } #[test] + fn dragging_across_a_monitor_boundary_updates_monitor_live_not_just_at_end() { + // Reported live: a window dragged onto a second monitor with a + // different scale (confirmed live: 1.0 and ~0.84) "looks very + // messed up" - `state/geometry.rs::sync_geometry` reads `w. + // monitor` on every drag motion tick to pick which scale converts + // the client's physical size into the logical points `xdg_ + // toplevel::configure` sends it, and `w.monitor` used to only get + // corrected once, at `end_drag`, leaving every mid-drag configure + // computed against the wrong monitor's scale for the drag's whole + // remaining duration. + let mut wm = WindowManager::new(); + wm.set_monitors(two_monitors()); + wm.set_layout(wm.current_workspace(), "tiling"); + let a = wm.alloc_window_id(); + let mut w = Window::new(a, "a"); + w.geometry = Rect::new(1000, 100, 200, 150); // fully on monitor 0 + wm.add_window(w); + wm.window_mut(a).unwrap().monitor = 0; + wm.start_drag(a, 1010, 110); + assert_eq!(wm.window(a).unwrap().monitor, 0); + // Dragged fully onto monitor 1 - checked immediately, before + // `end_drag` runs at all. + wm.update_drag(1600, 110); + assert_eq!(wm.window(a).unwrap().monitor, 1, "monitor must update live during the drag, not only once it ends"); + } + + #[test] fn drag_ending_near_edge_snaps_to_half_screen() { let mut wm = wm_with_monitor(); wm.set_layout(wm.current_workspace(), "tiling"); @@ -154,6 +181,32 @@ } #[test] + fn resizing_across_a_monitor_boundary_updates_monitor_live() { + // Same fix as `dragging_across_a_monitor_boundary_updates_monitor_ + // live_not_just_at_end`'s own doc comment - a resize can carry the + // edge being dragged onto a different monitor just as easily as a + // move can carry the whole window, and `update_resize` never + // corrected `w.monitor` at all before this fix, not even at the + // end. + let mut wm = WindowManager::new(); + wm.set_monitors(two_monitors()); + wm.set_layout(wm.current_workspace(), "tiling"); + let a = wm.alloc_window_id(); + let mut w = Window::new(a, "a"); + w.geometry = Rect::new(1000, 100, 500, 150); // fully on monitor 0, right edge at 1500 + wm.add_window(w); + wm.window_mut(a).unwrap().monitor = 0; + wm.start_resize(a, ResizeEdge::Left, 1050, 100); + // Drags the left edge from 1000 to 1290 (right edge anchored at + // 1500, final width 210 - comfortably above MIN_WINDOW_WIDTH), + // landing the whole window past the 1280 boundary on monitor 1. + wm.update_resize(1340, 100); + let g = wm.window(a).unwrap().geometry; + assert_eq!(g, Rect::new(1290, 100, 210, 150), "sanity: resize must actually have cleared the boundary"); + assert_eq!(wm.window(a).unwrap().monitor, 1, "monitor must update live during the resize"); + } + + #[test] fn ending_a_resize_remembers_the_new_size_for_the_apps_next_window() { let mut wm = wm_with_monitor(); // Tiling layout, so `add_window` skips `SmartPlacement`'s grid/ |