Skip to main content

apple_cf/cg/
rect.rs

1//! `CGRect` type for 2D rectangles
2
3use std::fmt;
4
5use super::{CGPoint, CGSize};
6
7/// `CGRect` representation
8///
9/// Represents a rectangle with an `origin` point and `size` dimensions.
10///
11/// # Examples
12///
13/// ```
14/// use apple_cf::cg::CGRect;
15///
16/// let rect = CGRect::new(10.0, 20.0, 100.0, 200.0);
17/// assert_eq!(rect.origin.x, 10.0);
18/// assert_eq!(rect.size.width, 100.0);
19/// assert_eq!(rect.max_x(), 110.0);
20/// ```
21#[repr(C)]
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct CGRect {
24    pub origin: CGPoint,
25    pub size: CGSize,
26}
27
28impl std::hash::Hash for CGRect {
29    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30        self.origin.hash(state);
31        self.size.hash(state);
32    }
33}
34
35impl Eq for CGRect {}
36
37impl CGRect {
38    /// Create a new rectangle
39    ///
40    /// # Examples
41    ///
42    /// ```
43    /// use apple_cf::cg::CGRect;
44    ///
45    /// let rect = CGRect::new(0.0, 0.0, 1920.0, 1080.0);
46    /// assert_eq!(rect.size.width, 1920.0);
47    /// ```
48    #[must_use]
49    pub const fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
50        Self {
51            origin: CGPoint::new(x, y),
52            size: CGSize::new(width, height),
53        }
54    }
55
56    /// Create a zero-sized rectangle at origin
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use apple_cf::cg::CGRect;
62    ///
63    /// let rect = CGRect::zero();
64    /// assert!(rect.is_null());
65    /// ```
66    #[must_use]
67    pub const fn zero() -> Self {
68        Self {
69            origin: CGPoint::zero(),
70            size: CGSize::zero(),
71        }
72    }
73
74    /// Create a rect with origin and size.
75    #[must_use]
76    pub const fn from_origin_size(origin: CGPoint, size: CGSize) -> Self {
77        Self { origin, size }
78    }
79
80    /// Create a rect with origin and size.
81    #[must_use]
82    pub const fn with_origin_and_size(origin: CGPoint, size: CGSize) -> Self {
83        Self::from_origin_size(origin, size)
84    }
85
86    /// Get the origin point
87    #[must_use]
88    pub const fn origin(&self) -> CGPoint {
89        self.origin
90    }
91
92    /// Get the size
93    #[must_use]
94    pub const fn size(&self) -> CGSize {
95        self.size
96    }
97
98    /// Get the center point
99    #[must_use]
100    pub const fn center(&self) -> CGPoint {
101        CGPoint::new(
102            self.origin.x + self.size.width / 2.0,
103            self.origin.y + self.size.height / 2.0,
104        )
105    }
106
107    /// Get the minimum X coordinate
108    #[must_use]
109    pub const fn min_x(&self) -> f64 {
110        self.origin.x
111    }
112
113    /// Get the minimum Y coordinate
114    #[must_use]
115    pub const fn min_y(&self) -> f64 {
116        self.origin.y
117    }
118
119    /// Get the maximum X coordinate
120    #[must_use]
121    pub const fn max_x(&self) -> f64 {
122        self.origin.x + self.size.width
123    }
124
125    /// Get the maximum Y coordinate
126    #[must_use]
127    pub const fn max_y(&self) -> f64 {
128        self.origin.y + self.size.height
129    }
130
131    /// Get the mid X coordinate
132    #[must_use]
133    pub const fn mid_x(&self) -> f64 {
134        self.origin.x + self.size.width / 2.0
135    }
136
137    /// Get the mid Y coordinate
138    #[must_use]
139    pub const fn mid_y(&self) -> f64 {
140        self.origin.y + self.size.height / 2.0
141    }
142
143    /// Returns whether the rectangle has a non-positive width or height.
144    #[must_use]
145    pub fn is_empty(&self) -> bool {
146        self.size.width <= 0.0 || self.size.height <= 0.0
147    }
148
149    /// Returns whether the rectangle contains the provided point.
150    #[must_use]
151    pub fn contains_point(&self, p: CGPoint) -> bool {
152        !self.is_empty()
153            && p.x >= self.min_x()
154            && p.x < self.max_x()
155            && p.y >= self.min_y()
156            && p.y < self.max_y()
157    }
158
159    /// Check if rect is null (both position and size are zero)
160    #[must_use]
161    pub const fn is_null(&self) -> bool {
162        self.origin.is_zero() && self.size.is_null()
163    }
164}
165
166impl Default for CGRect {
167    fn default() -> Self {
168        Self::zero()
169    }
170}
171
172impl fmt::Display for CGRect {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        write!(
175            f,
176            "({}, {}, {}, {})",
177            self.origin.x, self.origin.y, self.size.width, self.size.height
178        )
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::{CGPoint, CGRect, CGSize};
185
186    #[test]
187    fn from_origin_size_preserves_components() {
188        let rect = CGRect::from_origin_size(CGPoint::new(10.0, 20.0), CGSize::new(30.0, 40.0));
189
190        assert_eq!(rect.origin, CGPoint::new(10.0, 20.0));
191        assert_eq!(rect.size, CGSize::new(30.0, 40.0));
192    }
193
194    #[test]
195    fn contains_point_matches_rect_bounds() {
196        let rect = CGRect::new(10.0, 20.0, 30.0, 40.0);
197
198        assert!(rect.contains_point(CGPoint::new(10.0, 20.0)));
199        assert!(rect.contains_point(CGPoint::new(39.999, 59.999)));
200        assert!(!rect.contains_point(CGPoint::new(40.0, 20.0)));
201        assert!(!rect.contains_point(CGPoint::new(10.0, 60.0)));
202        assert!(!rect.contains_point(CGPoint::new(9.999, 20.0)));
203    }
204
205    #[test]
206    fn empty_rect_does_not_contain_points() {
207        let rect = CGRect::from_origin_size(CGPoint::new(10.0, 20.0), CGSize::new(0.0, 40.0));
208
209        assert!(rect.is_empty());
210        assert!(!rect.contains_point(CGPoint::new(10.0, 20.0)));
211    }
212}