leftwm_layouts/geometry/
rect.rs

1/// Represents a rectangle with a position ([`Rect::x`], [`Rect::y`])
2/// and dimensions ([`Rect::w`], [`Rect::h`]).
3///
4/// ## Demonstration
5/// ```txt
6/// (x/y)
7///   x-------. ^
8///   |       | |
9///   |       | | h
10///   |       | |
11///   '-------' v
12///   <------->
13///       w
14/// ```
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct Rect {
17    /// X-Coordinate, can be negative
18    pub x: i32,
19
20    /// Y-Coordinate, can be negative
21    pub y: i32,
22
23    /// Width, can not be negative
24    pub w: u32,
25
26    /// Height, can not be negative
27    pub h: u32,
28}
29
30impl Rect {
31    /// Shorthand method to create a new [`Rect`] with
32    /// the provided `x`, `y`, `w`, and `h`.
33    pub fn new(x: i32, y: i32, w: u32, h: u32) -> Self {
34        Self { x, y, w, h }
35    }
36
37    /// Calculate the surface area of the [`Rect`]
38    pub fn surface_area(&self) -> u32 {
39        self.w * self.h
40    }
41
42    /// Get the coordinate at the center of the [`Rect`].
43    ///
44    /// The center coordinate is rounded to the nearest integer
45    /// and might not be at the exact center position.
46    pub fn center(&self) -> (i32, i32) {
47        let x = self.x + (self.w as f32 / 2.0).round() as i32;
48        let y = self.y + (self.h as f32 / 2.0).round() as i32;
49        (x, y)
50    }
51
52    /// Check whether a point is contained in a [`Rect`].
53    ///
54    /// The boundary counts as part of the [`Rect`].
55    pub fn contains(&self, point: (i32, i32)) -> bool {
56        self.x <= point.0
57            && point.0 <= self.x + self.w as i32
58            && self.y <= point.1
59            && point.1 <= self.y + self.h as i32
60    }
61
62    /// Get the top left corner point of the [`Rect`].
63    ///
64    /// ```txt
65    /// O---------+
66    /// |         |
67    /// |         |
68    /// |         |
69    /// +---------+
70    /// ```
71    pub fn top_left_corner(&self) -> (i32, i32) {
72        (self.x, self.y)
73    }
74
75    /// Get the top right corner point of the [`Rect`].
76    ///
77    /// ```txt
78    /// +---------O
79    /// |         |
80    /// |         |
81    /// |         |
82    /// +---------+
83    /// ```
84    pub fn top_right_corner(&self) -> (i32, i32) {
85        (self.x + self.w as i32, self.y)
86    }
87
88    /// Get the bottom right corner point of the [`Rect`].
89    ///
90    /// ```txt
91    /// +---------+
92    /// |         |
93    /// |         |
94    /// |         |
95    /// +---------O
96    /// ```
97    pub fn bottom_right_corner(&self) -> (i32, i32) {
98        (self.x + self.w as i32, self.y + self.h as i32)
99    }
100
101    /// Get the bottom left corner point of the [`Rect`].
102    ///
103    /// ```txt
104    /// +---------+
105    /// |         |
106    /// |         |
107    /// |         |
108    /// O---------+
109    /// ```
110    pub fn bottom_left_corner(&self) -> (i32, i32) {
111        (self.x, self.y + self.h as i32)
112    }
113
114    /// Get the top edge of the [`Rect`].
115    ///
116    /// ```txt
117    /// ^
118    /// |
119    /// V
120    /// +---------+
121    /// |         |
122    /// |         |
123    /// |         |
124    /// +---------+
125    /// ```
126    pub fn top_edge(&self) -> i32 {
127        self.y
128    }
129
130    /// Get the right edge of the [`Rect`].
131    ///
132    /// ```txt
133    /// <--------->
134    /// +---------+
135    /// |         |
136    /// |         |
137    /// |         |
138    /// +---------+
139    /// ```
140    pub fn right_edge(&self) -> i32 {
141        self.x + self.w as i32
142    }
143
144    /// Get the bottom edge of the [`Rect`].
145    ///
146    /// ```txt
147    /// +---------+ ^
148    /// |         | |
149    /// |         | |
150    /// |         | |
151    /// +---------+ V
152    /// ```
153    pub fn bottom_edge(&self) -> i32 {
154        self.y + self.h as i32
155    }
156
157    /// Get the left edge of the [`Rect`].
158    ///
159    /// ```txt
160    /// <---> +---------+
161    ///       |         |
162    ///       |         |
163    ///       |         |
164    ///       +---------+
165    /// ```
166    pub fn left_edge(&self) -> i32 {
167        self.x
168    }
169}
170
171impl Default for Rect {
172    fn default() -> Self {
173        Self {
174            x: 0,
175            y: 0,
176            w: 500,
177            h: 250,
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::Rect;
185
186    #[test]
187    fn surface_area_calculation() {
188        let rect = Rect::new(0, 0, 1920, 1080);
189        assert_eq!(rect.surface_area(), 2_073_600);
190    }
191
192    #[test]
193    fn center_calculation() {
194        let rect = Rect::new(0, 0, 1920, 1080);
195        assert_eq!(rect.center(), (960, 540));
196    }
197
198    #[test]
199    fn center_calculation_with_offset() {
200        let rect = Rect::new(200, 120, 1920, 1080);
201        assert_eq!(rect.center(), (1160, 660));
202    }
203
204    #[test]
205    fn center_calculation_with_negative_offset() {
206        let rect = Rect::new(-200, -120, 1920, 1080);
207        assert_eq!(rect.center(), (760, 420));
208    }
209
210    #[test]
211    fn center_calculation_at_rounded_position() {
212        let rect = Rect::new(100, 100, 387, 399);
213        assert_eq!(rect.center(), (294, 300));
214    }
215
216    #[test]
217    fn contains_boundary() {
218        let rect = Rect::new(100, 100, 400, 100);
219        assert!(rect.contains((100, 100)));
220        assert!(rect.contains((500, 100)));
221        assert!(rect.contains((500, 200)));
222        assert!(rect.contains((100, 200)));
223    }
224
225    #[test]
226    fn does_not_contain_points_outside_rect() {
227        let rect = Rect::new(100, 100, 400, 100);
228        assert!(!rect.contains((99, 100)));
229        assert!(!rect.contains((501, 100)));
230        assert!(!rect.contains((501, 200)));
231        assert!(!rect.contains((99, 200)));
232        assert!(!rect.contains((100, 99)));
233        assert!(!rect.contains((500, 99)));
234        assert!(!rect.contains((500, 201)));
235        assert!(!rect.contains((100, 201)));
236    }
237}