Skip to main content

screen_app/recp/
tray_positioning.rs

1//! M-RECP.1 / AUT-262 — Multi-display window positioning under the
2//! tray click.
3//!
4//! Pure-Rust helper that picks the right monitor for a given click
5//! position. The Tauri-side caller in `main.rs` invokes
6//! [`pick_monitor`] inside the `on_tray_icon_event` handler before
7//! showing the main window. OS hardware integration (querying real
8//! monitor bounds, applying the clamp) is the deferred follow-up.
9
10/// Axis-aligned rectangle in screen coordinates. Same shape as
11/// Tauri's `tauri::PhysicalRect` but lives here as a `Copy`-friendly
12/// pure-Rust struct so the picker is unit-testable without a Tauri
13/// runtime.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct MonitorBounds {
16    /// Top-left x.
17    pub x: i32,
18    /// Top-left y.
19    pub y: i32,
20    /// Width in pixels.
21    pub width: i32,
22    /// Height in pixels.
23    pub height: i32,
24}
25
26impl MonitorBounds {
27    /// `true` if the point `(x, y)` falls inside this monitor.
28    #[must_use]
29    pub fn contains(self, x: i32, y: i32) -> bool {
30        x >= self.x && x < self.x + self.width && y >= self.y && y < self.y + self.height
31    }
32}
33
34/// Find the monitor whose bounds contain the click position. Returns
35/// the first monitor as a fallback if no bounds contain the click
36/// (defensive — shouldn't normally happen with valid monitor
37/// geometry, but multi-display setups can have negative coords).
38#[must_use]
39pub fn pick_monitor(
40    click_x: i32,
41    click_y: i32,
42    monitors: &[MonitorBounds],
43) -> Option<MonitorBounds> {
44    if monitors.is_empty() {
45        return None;
46    }
47    monitors
48        .iter()
49        .find(|m| m.contains(click_x, click_y))
50        .copied()
51        .or_else(|| monitors.first().copied())
52}
53
54/// Top-left position that anchors the popover's **top-right corner**
55/// to the monitor's top-right corner — flush against the screen edge,
56/// matching the macOS Control-Center / Notification-Center
57/// convention. The click position is only used by the caller to pick
58/// which monitor to anchor on; within that monitor the popover always
59/// lands top-right.
60///
61/// Returns top-left `(x, y)` in screen coordinates. macOS clamps the
62/// window's titlebar below the menubar automatically when `y` falls
63/// in the menubar region, so passing `monitor.y` directly is safe.
64#[must_use]
65pub fn position_window_top_right(window_width: i32, monitor: MonitorBounds) -> (i32, i32) {
66    let x = monitor.x + (monitor.width - window_width).max(0);
67    let y = monitor.y;
68    (x, y)
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    fn mon(x: i32, y: i32, w: i32, h: i32) -> MonitorBounds {
76        MonitorBounds {
77            x,
78            y,
79            width: w,
80            height: h,
81        }
82    }
83
84    #[test]
85    fn contains_includes_top_left_excludes_bottom_right() {
86        let m = mon(0, 0, 100, 100);
87        assert!(m.contains(0, 0));
88        assert!(m.contains(50, 50));
89        assert!(!m.contains(100, 50));
90        assert!(!m.contains(50, 100));
91    }
92
93    #[test]
94    fn pick_monitor_returns_none_for_empty_list() {
95        assert!(pick_monitor(0, 0, &[]).is_none());
96    }
97
98    #[test]
99    fn pick_monitor_finds_the_one_containing_click() {
100        let mons = vec![mon(0, 0, 1920, 1080), mon(1920, 0, 1920, 1080)];
101        assert_eq!(pick_monitor(2000, 100, &mons), Some(mons[1]));
102        assert_eq!(pick_monitor(500, 100, &mons), Some(mons[0]));
103    }
104
105    #[test]
106    fn pick_monitor_falls_back_to_first_if_no_match() {
107        let mons = vec![mon(0, 0, 1920, 1080)];
108        // Negative click way out of bounds — fallback to first.
109        assert_eq!(pick_monitor(-100, -100, &mons), Some(mons[0]));
110    }
111
112    #[test]
113    fn position_window_top_right_anchors_to_monitor_top_right() {
114        let mon = mon(0, 0, 1920, 1080);
115        // Window's top-right should sit at monitor's top-right
116        // (1920, 0) → top-left at (1920 - 800, 0) = (1120, 0).
117        assert_eq!(position_window_top_right(800, mon), (1120, 0));
118    }
119
120    #[test]
121    fn position_window_top_right_respects_offset_monitor_origin() {
122        // Secondary display sitting to the right of the primary.
123        let mon = mon(1920, 0, 2560, 1440);
124        // Top-right of monitor is at (1920 + 2560, 0) = (4480, 0).
125        // Window top-left = (4480 - 600, 0) = (3880, 0).
126        assert_eq!(position_window_top_right(600, mon), (3880, 0));
127    }
128
129    #[test]
130    fn position_window_top_right_clamps_when_window_wider_than_monitor() {
131        // Pathological: window is wider than the monitor. Don't push
132        // the left edge into negative territory inside the monitor —
133        // clamp left edge to monitor.x so the window starts at the
134        // monitor's left edge (and overflows on the right, but the
135        // user will see *some* of it instead of all of it being
136        // pushed off the left side).
137        let mon = mon(0, 0, 800, 600);
138        assert_eq!(position_window_top_right(1000, mon), (0, 0));
139    }
140}