1use std::fmt;
4
5use super::{CGPoint, CGSize};
6
7#[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 #[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 #[must_use]
67 pub const fn zero() -> Self {
68 Self {
69 origin: CGPoint::zero(),
70 size: CGSize::zero(),
71 }
72 }
73
74 #[must_use]
76 pub const fn from_origin_size(origin: CGPoint, size: CGSize) -> Self {
77 Self { origin, size }
78 }
79
80 #[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 #[must_use]
88 pub const fn origin(&self) -> CGPoint {
89 self.origin
90 }
91
92 #[must_use]
94 pub const fn size(&self) -> CGSize {
95 self.size
96 }
97
98 #[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 #[must_use]
109 pub const fn min_x(&self) -> f64 {
110 self.origin.x
111 }
112
113 #[must_use]
115 pub const fn min_y(&self) -> f64 {
116 self.origin.y
117 }
118
119 #[must_use]
121 pub const fn max_x(&self) -> f64 {
122 self.origin.x + self.size.width
123 }
124
125 #[must_use]
127 pub const fn max_y(&self) -> f64 {
128 self.origin.y + self.size.height
129 }
130
131 #[must_use]
133 pub const fn mid_x(&self) -> f64 {
134 self.origin.x + self.size.width / 2.0
135 }
136
137 #[must_use]
139 pub const fn mid_y(&self) -> f64 {
140 self.origin.y + self.size.height / 2.0
141 }
142
143 #[must_use]
145 pub fn is_empty(&self) -> bool {
146 self.size.width <= 0.0 || self.size.height <= 0.0
147 }
148
149 #[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 #[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}