//! A restored window fits the monitor it opens on. A saved state can come from a larger or a //! differently arranged monitor (2720x1800 saved on a big screen, restored on a smaller second //! one): the window then hangs past the work area, its titlebar out of reach, and it cannot be //! moved. [`fit`] picks the monitor the window is mostly on (the primary one when it is on none), //! shrinks the window to that monitor's work area when it is larger, and moves it inside. //! Physical pixels throughout, as the OS reports monitors. /// A rectangle in physical pixels (a window's outer frame, a monitor's work area). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rect { pub x: i32, pub y: i32, pub width: u32, pub height: u32, } impl Rect { pub const fn new(x: i32, y: i32, width: u32, height: u32) -> Self { Self { x, y, width, height } } fn right(&self) -> i64 { i64::from(self.x) + i64::from(self.width) } fn bottom(&self) -> i64 { i64::from(self.y) + i64::from(self.height) } /// The area two rectangles share (0 when they do not meet). fn overlap(&self, other: &Rect) -> i64 { let width = self.right().min(other.right()) - i64::from(self.x.max(other.x)); let height = self.bottom().min(other.bottom()) - i64::from(self.y.max(other.y)); if width <= 0 || height <= 0 { 0 } else { width * height } } } /// Where `window` should be so it lies inside a work area: `None` when it already does. /// /// The work area is the one of the monitor the window overlaps most; when it overlaps none (that /// monitor is gone), `primary`, else the first one. The size shrinks to the work area (never /// grows); the position moves just enough to bring the whole frame inside. pub fn fit(window: Rect, work_areas: &[Rect], primary: Option) -> Option { let best = work_areas .iter() .map(|area| (window.overlap(area), area)) .filter(|(shared, _)| *shared > 0) .max_by_key(|(shared, _)| *shared) .map(|(_, area)| *area); let area = best.or(primary).or_else(|| work_areas.first().copied())?; if area.width == 0 || area.height == 0 { return None; } let width = window.width.min(area.width); let height = window.height.min(area.height); let clamp = |at: i32, len: u32, start: i32, room: u32| -> i32 { let last = i64::from(start) + i64::from(room) - i64::from(len); i64::from(at).clamp(i64::from(start), last.max(i64::from(start))) as i32 }; let fitted = Rect::new(clamp(window.x, width, area.x, area.width), clamp(window.y, height, area.y, area.height), width, height); (fitted != window).then_some(fitted) } #[cfg(test)] mod tests { use super::*; // A 2560x1600 main monitor (taskbar 48 px) and a smaller 1920x1080 one to its right. const MAIN: Rect = Rect::new(0, 0, 2560, 1552); const SECOND: Rect = Rect::new(2560, 0, 1920, 1032); #[test] fn a_window_saved_on_a_bigger_screen_shrinks_into_the_smaller_monitor_it_opens_on() { // The reported case: 2720x1800 at x=3347, mostly on the second monitor; its titlebar was off-screen. let fitted = fit(Rect::new(3347, 40, 2720, 1800), &[MAIN, SECOND], Some(MAIN)).unwrap(); assert_eq!(fitted, SECOND); } #[test] fn a_window_hanging_past_an_edge_moves_inside_and_keeps_its_size() { let fitted = fit(Rect::new(3800, -30, 1200, 800), &[MAIN, SECOND], Some(MAIN)).unwrap(); assert_eq!(fitted, Rect::new(2560 + 1920 - 1200, 0, 1200, 800)); // Past the start edges too. assert_eq!(fit(Rect::new(-500, 900, 1200, 800), &[MAIN, SECOND], Some(MAIN)), Some(Rect::new(0, 1552 - 800, 1200, 800))); } #[test] fn a_window_whose_monitor_is_gone_opens_on_the_primary_one() { let fitted = fit(Rect::new(6000, 200, 1400, 900), &[MAIN], Some(MAIN)).unwrap(); assert_eq!(fitted, Rect::new(2560 - 1400, 200, 1400, 900)); // No primary reported: the first monitor. assert_eq!(fit(Rect::new(6000, 200, 1400, 900), &[SECOND], None), Some(Rect::new(2560 + 1920 - 1400, 1032 - 900, 1400, 900))); } #[test] fn a_window_inside_its_work_area_is_left_alone() { assert_eq!(fit(Rect::new(100, 80, 1600, 1000), &[MAIN, SECOND], Some(MAIN)), None); assert_eq!(fit(SECOND, &[MAIN, SECOND], Some(MAIN)), None); assert_eq!(fit(Rect::new(100, 80, 1600, 1000), &[], None), None); } #[test] fn monitors_left_of_and_above_the_main_one_work_too() { let left = Rect::new(-1920, -200, 1920, 1040); let fitted = fit(Rect::new(-2100, -260, 2000, 1200), &[MAIN, left], Some(MAIN)).unwrap(); assert_eq!(fitted, left); } }