Skip to main content

a7n_core/input/
geometry.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use a7n_proto::v1::{Edge, Landing};
4
5use super::clamp_position;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct Bounds {
9    pub x: i32,
10    pub y: i32,
11    pub width: u32,
12    pub height: u32,
13}
14
15impl Bounds {
16    #[must_use]
17    pub const fn rect(self) -> (i32, i32, u32, u32) {
18        (self.x, self.y, self.width, self.height)
19    }
20
21    /// Whether a desktop point is inside this rectangle.
22    #[must_use]
23    pub fn contains(self, x: i64, y: i64) -> bool {
24        x >= i64::from(self.x)
25            && y >= i64::from(self.y)
26            && x < i64::from(self.x) + i64::from(self.width)
27            && y < i64::from(self.y) + i64::from(self.height)
28    }
29}
30
31impl From<(i32, i32, u32, u32)> for Bounds {
32    fn from((x, y, width, height): (i32, i32, u32, u32)) -> Self {
33        Self {
34            x,
35            y,
36            width,
37            height,
38        }
39    }
40}
41
42pub(super) fn nearest_monitor(
43    bounds: (i32, i32, u32, u32),
44    monitors: &[Bounds],
45    x: i32,
46    y: i32,
47) -> Bounds {
48    monitors
49        .iter()
50        .filter(|m| m.width > 0 && m.height > 0)
51        .min_by_key(|m| {
52            let (px, py) = clamp_position(m.rect(), x, y);
53            let dx = i128::from(x) - i128::from(px);
54            let dy = i128::from(y) - i128::from(py);
55            dx * dx + dy * dy
56        })
57        .copied()
58        .unwrap_or_else(|| bounds.into())
59}
60
61/// Project a bbox-relative landing onto the outer monitor edge in that direction.
62#[must_use]
63#[allow(clippy::cast_possible_truncation)] // Offsets are clamped and float-to-int casts saturate.
64pub fn landing_point(bounds: Bounds, monitors: &[Bounds], landing: &Landing) -> (i32, i32) {
65    let edge = Edge::try_from(landing.edge).unwrap_or(Edge::Unspecified);
66    let offset = f64::from(landing.offset.clamp(0.0, 1.0));
67    let right = bounds
68        .x
69        .saturating_add_unsigned(bounds.width.saturating_sub(1));
70    let bottom = bounds
71        .y
72        .saturating_add_unsigned(bounds.height.saturating_sub(1));
73    let x = match edge {
74        Edge::Left => bounds.x,
75        Edge::Right => right,
76        _ => (f64::from(bounds.x) + f64::from(bounds.width.saturating_sub(1)) * offset) as i32,
77    };
78    let y = match edge {
79        Edge::Top => bounds.y,
80        Edge::Bottom => bottom,
81        _ => (f64::from(bounds.y) + f64::from(bounds.height.saturating_sub(1)) * offset) as i32,
82    };
83    let horizontal = matches!(edge, Edge::Left | Edge::Right);
84    let monitor = monitors
85        .iter()
86        .filter(|m| m.width > 0 && m.height > 0)
87        .min_by_key(|m| {
88            let (px, py) = clamp_position(m.rect(), x, y);
89            let distance = if horizontal {
90                i64::from(y) - i64::from(py)
91            } else {
92                i64::from(x) - i64::from(px)
93            };
94            let boundary = match edge {
95                Edge::Left => i64::from(m.x),
96                Edge::Right => -(i64::from(m.x) + i64::from(m.width) - 1),
97                Edge::Top => i64::from(m.y),
98                Edge::Bottom => -(i64::from(m.y) + i64::from(m.height) - 1),
99                Edge::Unspecified => 0,
100            };
101            (distance.abs(), boundary)
102        })
103        .copied()
104        .unwrap_or(bounds);
105    let (px, py) = clamp_position(monitor.rect(), x, y);
106    match edge {
107        Edge::Left => (monitor.x, py),
108        Edge::Right => (
109            monitor
110                .x
111                .saturating_add_unsigned(monitor.width.saturating_sub(1)),
112            py,
113        ),
114        Edge::Top => (px, monitor.y),
115        Edge::Bottom => (
116            px,
117            monitor
118                .y
119                .saturating_add_unsigned(monitor.height.saturating_sub(1)),
120        ),
121        Edge::Unspecified => (x, y),
122    }
123}