Skip to main content

computer_use/
geometry.rs

1/// A point in logical (non-Retina) screen points, origin at the top-left of the main display.
2#[derive(Debug, Clone, Copy, PartialEq, Default)]
3pub struct Point {
4    /// Horizontal coordinate.
5    pub x: f64,
6    /// Vertical coordinate, growing downwards.
7    pub y: f64,
8}
9
10impl Point {
11    /// Creates a point.
12    #[must_use]
13    pub const fn new(x: f64, y: f64) -> Self {
14        Self { x, y }
15    }
16
17    /// Linear interpolation between `self` (t = 0) and `other` (t = 1).
18    #[must_use]
19    pub fn lerp(self, other: Self, t: f64) -> Self {
20        Self::new((other.x - self.x).mul_add(t, self.x), (other.y - self.y).mul_add(t, self.y))
21    }
22}
23
24/// A width and height in logical points.
25#[derive(Debug, Clone, Copy, PartialEq, Default)]
26pub struct Size {
27    /// Horizontal extent.
28    pub width: f64,
29    /// Vertical extent.
30    pub height: f64,
31}
32
33impl Size {
34    /// Creates a size.
35    #[must_use]
36    pub const fn new(width: f64, height: f64) -> Self {
37        Self { width, height }
38    }
39}
40
41/// An axis-aligned rectangle in logical points.
42#[derive(Debug, Clone, Copy, PartialEq, Default)]
43pub struct Rect {
44    /// Top-left corner.
45    pub origin: Point,
46    /// Extent.
47    pub size: Size,
48}
49
50impl Rect {
51    /// Creates a rectangle from its top-left corner and size.
52    #[must_use]
53    pub const fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
54        Self { origin: Point::new(x, y), size: Size::new(width, height) }
55    }
56
57    /// Smallest x coordinate.
58    #[must_use]
59    pub const fn min_x(&self) -> f64 {
60        self.origin.x
61    }
62
63    /// Smallest y coordinate.
64    #[must_use]
65    pub const fn min_y(&self) -> f64 {
66        self.origin.y
67    }
68
69    /// Largest x coordinate.
70    #[must_use]
71    pub fn max_x(&self) -> f64 {
72        self.origin.x + self.size.width
73    }
74
75    /// Largest y coordinate.
76    #[must_use]
77    pub fn max_y(&self) -> f64 {
78        self.origin.y + self.size.height
79    }
80
81    /// Center point.
82    #[must_use]
83    pub fn center(&self) -> Point {
84        Point::new(self.size.width.mul_add(0.5, self.origin.x), self.size.height.mul_add(0.5, self.origin.y))
85    }
86
87    /// Area; zero for degenerate rectangles.
88    #[must_use]
89    pub fn area(&self) -> f64 {
90        self.size.width.max(0.0) * self.size.height.max(0.0)
91    }
92
93    /// True when the rectangle has no area.
94    #[must_use]
95    pub fn is_empty(&self) -> bool {
96        self.size.width <= 0.0 || self.size.height <= 0.0
97    }
98
99    /// True when `point` lies inside the rectangle (inclusive of the top/left edge).
100    #[must_use]
101    pub fn contains(&self, point: Point) -> bool {
102        point.x >= self.min_x() && point.x < self.max_x() && point.y >= self.min_y() && point.y < self.max_y()
103    }
104
105    /// The overlapping region of two rectangles, if any.
106    #[must_use]
107    pub fn intersection(&self, other: &Self) -> Option<Self> {
108        let x0 = self.min_x().max(other.min_x());
109        let y0 = self.min_y().max(other.min_y());
110        let x1 = self.max_x().min(other.max_x());
111        let y1 = self.max_y().min(other.max_y());
112        (x1 > x0 && y1 > y0).then(|| Self::new(x0, y0, x1 - x0, y1 - y0))
113    }
114
115    /// Moves the rectangle by `dx`, `dy`.
116    #[must_use]
117    pub fn offset(&self, dx: f64, dy: f64) -> Self {
118        Self::new(self.origin.x + dx, self.origin.y + dy, self.size.width, self.size.height)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn contains_and_center() {
128        let r = Rect::new(10.0, 20.0, 100.0, 50.0);
129        assert!(r.contains(Point::new(10.0, 20.0)));
130        assert!(!r.contains(Point::new(110.0, 20.0)));
131        assert_eq!(r.center(), Point::new(60.0, 45.0));
132    }
133
134    #[test]
135    fn intersection() {
136        let a = Rect::new(0.0, 0.0, 10.0, 10.0);
137        let b = Rect::new(5.0, 5.0, 10.0, 10.0);
138        assert_eq!(a.intersection(&b), Some(Rect::new(5.0, 5.0, 5.0, 5.0)));
139        assert_eq!(a.intersection(&Rect::new(20.0, 20.0, 1.0, 1.0)), None);
140    }
141}