1use core::fmt::Debug;
4use core::ops::{Bound::*, Range, RangeBounds, RangeFull, Sub};
5
6use crate::math::Point2u;
7
8#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
18pub struct Rect<T = u32> {
19 pub left: Option<T>,
21 pub top: Option<T>,
23 pub right: Option<T>,
25 pub bottom: Option<T>,
27}
28
29impl<T: Copy> Rect<T> {
30 pub fn width(&self) -> Option<T::Output>
34 where
35 T: Ord + Sub,
36 {
37 let (r, l) = (self.right?, self.left?);
38 Some(r - r.min(l)) }
40 pub fn height(&self) -> Option<T::Output>
44 where
45 T: Ord + Sub,
46 {
47 let (b, t) = (self.bottom?, self.top?);
48 Some(b - b.min(t)) }
50
51 pub fn is_empty(&self) -> bool
53 where
54 T: PartialOrd,
55 {
56 let Rect { left, top, right, bottom } = self;
57 let h_empty = left.is_some() && right.is_some() && left >= right;
59 let v_empty = top.is_some() && bottom.is_some() && top >= bottom;
60 h_empty || v_empty
61 }
62
63 pub fn contains(&self, x: T, y: T) -> bool
65 where
66 T: PartialOrd,
67 {
68 let [horiz, vert] = self.bounds();
69 horiz.contains(&x) && vert.contains(&y)
70 }
71
72 pub fn bounds(&self) -> [impl RangeBounds<T>; 2] {
74 let left = self.left.map(Included).unwrap_or(Unbounded);
75 let top = self.top.map(Included).unwrap_or(Unbounded);
76 let right = self.right.map(Excluded).unwrap_or(Unbounded);
77 let bottom = self.bottom.map(Excluded).unwrap_or(Unbounded);
78
79 [(left, right), (top, bottom)]
80 }
81
82 #[must_use]
89 pub fn intersect(&self, other: &Self) -> Self
90 where
91 T: Ord,
92 {
93 fn extremum<T>(
94 a: Option<T>,
95 b: Option<T>,
96 f: fn(T, T) -> T,
97 ) -> Option<T> {
98 match (a, b) {
99 (None, None) => None,
100 (some, None) | (None, some) => some,
101 (Some(s), Some(o)) => Some(f(s, o)),
102 }
103 }
104 Self {
105 left: extremum(self.left, other.left, T::max),
106 top: extremum(self.top, other.top, T::max),
107 right: extremum(self.right, other.right, T::min),
108 bottom: extremum(self.bottom, other.bottom, T::min),
109 }
110 }
111}
112
113impl<H: RangeBounds<u32>, V: RangeBounds<u32>> From<(H, V)> for Rect<u32> {
114 fn from((horiz, vert): (H, V)) -> Self {
117 let resolve = |b, i, e| match b {
118 Included(&x) => Some(x + i),
119 Excluded(&x) => Some(x + e),
120 Unbounded => None,
121 };
122 let left = resolve(horiz.start_bound(), 0, 1);
123 let top = resolve(vert.start_bound(), 0, 1);
124 let right = resolve(horiz.end_bound(), 1, 0);
125 let bottom = resolve(vert.end_bound(), 1, 0);
126
127 Self { left, top, right, bottom }
128 }
129}
130
131impl From<Range<Point2u>> for Rect<u32> {
132 fn from(r: Range<Point2u>) -> Self {
135 Self {
136 left: Some(r.start.x()),
137 top: Some(r.start.y()),
138 right: Some(r.end.x()),
139 bottom: Some(r.end.y()),
140 }
141 }
142}
143
144impl<T> From<RangeFull> for Rect<T> {
145 fn from(_: RangeFull) -> Self {
147 Self {
148 left: None,
149 top: None,
150 right: None,
151 bottom: None,
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use crate::math::pt2;
159
160 use super::*;
161
162 fn rect<T>(
163 l: impl Into<Option<T>>,
164 t: impl Into<Option<T>>,
165 r: impl Into<Option<T>>,
166 b: impl Into<Option<T>>,
167 ) -> Rect<T> {
168 Rect {
169 left: l.into(),
170 top: t.into(),
171 right: r.into(),
172 bottom: b.into(),
173 }
174 }
175
176 #[test]
177 fn extents_bounded() {
178 let r = rect(10, 20, 100, 120);
179 assert_eq!(r.width(), Some(90));
180 assert_eq!(r.height(), Some(100));
181 }
182 #[test]
183 fn extents_unbounded() {
184 let r = rect(None, 20, 100, None);
185 assert_eq!(r.width(), None);
186 assert_eq!(r.height(), None);
187 }
188 #[test]
189 fn extents_empty() {
190 let r = rect(10, 20, -100, 20);
191 assert_eq!(r.width(), Some(0));
192 assert_eq!(r.height(), Some(0));
193 }
194 #[test]
195 fn contains() {
196 let r = rect(None, -10i32, 100, None);
197 assert!(r.contains(0, 0));
198 assert!(!r.contains(0, -20));
199 assert!(r.contains(-9999, 9999));
200 assert!(r.contains(99, 0));
201 assert!(!r.contains(100, 0));
202 }
203
204 #[test]
205 fn is_empty_bounded() {
206 assert!(rect(10, 10, 10, 20).is_empty());
207 assert!(rect(10, 20, 10, 10).is_empty());
208 }
209 #[test]
210 fn is_empty_negative_extent() {
211 assert!(rect(20, 10, 10, 20).is_empty());
212 assert!(rect(10, 20, 20, 10).is_empty());
213 }
214
215 #[test]
216 fn is_empty_unbounded() {
217 assert!(rect(10, 10, 10, None).is_empty());
218 assert!(rect(10, 10, None, 10).is_empty());
219 assert!(rect(10, None, 10, 10).is_empty());
220 assert!(rect(None, 10, 10, 10).is_empty());
221 }
222 #[test]
223 fn is_empty_bounded_not_empty() {
224 assert!(!rect(10, 10, 20, 20).is_empty());
225 assert!(!rect(10, -10, 20, 10).is_empty());
226 }
227
228 #[test]
229 fn is_empty_unbounded_not_empty() {
230 assert!(!rect(10, 10, 20, None).is_empty());
231 assert!(!rect(10, 10, None, 20).is_empty());
232 assert!(!rect(10, None, 20, 10).is_empty());
233 assert!(!rect(None, 10, 10, 20).is_empty());
234
235 assert!(!rect(10, None, 20, None).is_empty());
236 assert!(!rect(None, 10, 10, None).is_empty());
237 assert!(!rect::<i32>(None, None, None, None).is_empty());
238 }
239
240 #[test]
241 fn bounds() {
242 let r = rect(None, -10i32, 100, None);
243 let [h, v] = r.bounds();
244
245 assert_eq!(h.start_bound(), Unbounded);
246 assert_eq!(v.start_bound(), Included(&-10));
247 assert_eq!(h.end_bound(), Excluded(&100));
248 assert_eq!(v.end_bound(), Unbounded);
249 }
250
251 #[test]
252 fn intersect_two_bounded() {
253 let r = rect(10, 20, 100, 40);
254 let s = rect(30, 0, 60, 50);
255
256 assert_eq!(r.intersect(&s), rect(30, 20, 60, 40));
257 }
258 #[test]
259 fn intersect_bounded_and_unbounded() {
260 let r = rect(10, 20, 50, 40);
261 let s = rect(30, 0, None, 50);
262
263 assert_eq!(r.intersect(&s), rect(30, 20, 50, 40));
264 }
265 #[test]
266 fn intersect_two_unbounded_to_unbounded() {
267 let r = rect(0, 0, 10, None);
268 let s = rect(0, 10, 10, None);
269
270 assert_eq!(r.intersect(&s), rect(0, 10, 10, None));
271 }
272 #[test]
273 fn intersect_two_unbounded_to_bounded() {
274 let r = rect(None, 0, 30, 10);
275 let s = rect(10, 0, None, 10);
276
277 assert_eq!(r.intersect(&s), rect(10, 0, 30, 10));
278 }
279 #[test]
280 fn intersect_empty() {
281 let r = rect(0, 0, 20, 20);
282 let s = rect(10, 10, 10, 10);
283 assert!(r.intersect(&s).is_empty());
284 }
285 #[test]
286 fn intersect_full() {
287 let r = rect(0, 0, 10, 20);
288 let s = (..).into();
289 assert_eq!(r.intersect(&s), r);
290 }
291 #[test]
292 fn intersect_disjoint() {
293 let r = rect(None, -10i32, 100, None);
294 let s = rect(100, None, None, None);
295
296 let t = r.intersect(&s);
297 assert!(t.is_empty());
298 assert_eq!(t.width(), Some(0));
299 assert_eq!(t.height(), None);
300 }
301
302 #[test]
303 fn from_pair_of_ranges() {
304 assert_eq!(Rect::from((2..5, 4..8)), rect(2, 4, 5, 8));
305 assert_eq!(Rect::from((2.., 4..8)), rect(2, 4, None, 8));
306 assert_eq!(Rect::from((2..5, ..8)), rect(2, None, 5, 8));
307 assert_eq!(Rect::from((2..=5, 4..=8)), rect(2, 4, 6, 9));
308 assert_eq!(Rect::from((.., ..)), rect(None, None, None, None));
309 }
310
311 #[test]
312 fn from_range_full() {
313 assert_eq!(Rect::<()>::from(..), rect(None, None, None, None));
314 }
315
316 #[test]
317 fn from_range_of_vecs() {
318 assert_eq!(Rect::from(pt2(10, 20)..pt2(40, 80)), rect(10, 20, 40, 80));
319 }
320}