1use core::convert::TryFrom;
7
8use crate::{FiniteF32, IntSize, LengthU32, Point, SaturateRound};
9
10#[allow(missing_docs)]
17#[derive(Copy, Clone, PartialEq, Debug)]
18pub struct IntRect {
19 x: i32,
20 y: i32,
21 width: LengthU32,
22 height: LengthU32,
23}
24
25impl IntRect {
26 pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Option<Self> {
28 x.checked_add(i32::try_from(width).ok()?)?;
29 y.checked_add(i32::try_from(height).ok()?)?;
30
31 Some(IntRect {
32 x,
33 y,
34 width: LengthU32::new(width)?,
35 height: LengthU32::new(height)?,
36 })
37 }
38
39 pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Option<Self> {
41 let width = u32::try_from(right.checked_sub(left)?).ok()?;
42 let height = u32::try_from(bottom.checked_sub(top)?).ok()?;
43 IntRect::from_xywh(left, top, width, height)
44 }
45
46 pub fn x(&self) -> i32 {
48 self.x
49 }
50
51 pub fn y(&self) -> i32 {
53 self.y
54 }
55
56 pub fn width(&self) -> u32 {
58 self.width.get()
59 }
60
61 pub fn height(&self) -> u32 {
63 self.height.get()
64 }
65
66 pub fn left(&self) -> i32 {
68 self.x
69 }
70
71 pub fn top(&self) -> i32 {
73 self.y
74 }
75
76 pub fn right(&self) -> i32 {
78 self.x + self.width.get() as i32
80 }
81
82 pub fn bottom(&self) -> i32 {
84 self.y + self.height.get() as i32
86 }
87
88 pub fn size(&self) -> IntSize {
90 IntSize {
91 width: self.width,
92 height: self.height,
93 }
94 }
95
96 pub fn contains(&self, other: &Self) -> bool {
98 self.x <= other.x
99 && self.y <= other.y
100 && self.right() >= other.right()
101 && self.bottom() >= other.bottom()
102 }
103
104 pub fn intersect(&self, other: &Self) -> Option<Self> {
108 let left = self.x.max(other.x);
109 let top = self.y.max(other.y);
110
111 let right = self.right().min(other.right());
112 let bottom = self.bottom().min(other.bottom());
113
114 let w = u32::try_from(right.checked_sub(left)?).ok()?;
115 let h = u32::try_from(bottom.checked_sub(top)?).ok()?;
116
117 IntRect::from_xywh(left, top, w, h)
118 }
119
120 pub fn inset(&self, dx: i32, dy: i32) -> Option<Self> {
122 IntRect::from_ltrb(
123 self.left() + dx,
124 self.top() + dy,
125 self.right() - dx,
126 self.bottom() - dy,
127 )
128 }
129
130 pub fn make_outset(&self, dx: i32, dy: i32) -> Option<Self> {
132 IntRect::from_ltrb(
133 self.left().saturating_sub(dx),
134 self.top().saturating_sub(dy),
135 self.right().saturating_add(dx),
136 self.bottom().saturating_add(dy),
137 )
138 }
139
140 pub fn to_rect(&self) -> Rect {
142 Rect::from_ltrb(
144 self.x as f32,
145 self.y as f32,
146 self.x as f32 + self.width.get() as f32,
147 self.y as f32 + self.height.get() as f32,
148 )
149 .unwrap()
150 }
151
152 pub fn to_screen_int_rect(&self) -> Option<ScreenIntRect> {
159 let x = u32::try_from(self.x).ok()?;
160 let y = u32::try_from(self.y).ok()?;
161 Some(ScreenIntRect::from_xywh_safe(x, y, self.width, self.height))
162 }
163}
164
165#[cfg(test)]
166mod int_rect_tests {
167 use super::*;
168
169 #[test]
170 fn tests() {
171 assert_eq!(IntRect::from_xywh(0, 0, 0, 0), None);
172 assert_eq!(IntRect::from_xywh(0, 0, 1, 0), None);
173 assert_eq!(IntRect::from_xywh(0, 0, 0, 1), None);
174
175 assert_eq!(
176 IntRect::from_xywh(0, 0, core::u32::MAX, core::u32::MAX),
177 None
178 );
179 assert_eq!(IntRect::from_xywh(0, 0, 1, core::u32::MAX), None);
180 assert_eq!(IntRect::from_xywh(0, 0, core::u32::MAX, 1), None);
181
182 assert_eq!(IntRect::from_xywh(core::i32::MAX, 0, 1, 1), None);
183 assert_eq!(IntRect::from_xywh(0, core::i32::MAX, 1, 1), None);
184
185 let r = IntRect::from_xywh(1, 2, 3, 4).unwrap();
186 assert_eq!(
187 r.to_screen_int_rect().unwrap(),
188 ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap()
189 );
190
191 let r = IntRect::from_xywh(-1, -1, 3, 4).unwrap();
192 assert_eq!(r.to_screen_int_rect(), None);
193
194 {
195 let r1 = IntRect::from_xywh(1, 2, 3, 4).unwrap();
197 let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
198 assert_eq!(r1.intersect(&r2), None);
199 }
200
201 {
202 let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
204 let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
205 assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 13, 14));
206 }
207
208 {
209 let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
211 let r2 = IntRect::from_xywh(11, 12, 50, 60).unwrap();
212 assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 20, 30));
213 }
214 }
215}
216
217#[allow(missing_docs)]
225#[derive(Copy, Clone, PartialEq, Debug)]
226pub struct ScreenIntRect {
227 x: u32,
228 y: u32,
229 width: LengthU32,
230 height: LengthU32,
231}
232
233impl ScreenIntRect {
234 pub fn from_xywh(x: u32, y: u32, width: u32, height: u32) -> Option<Self> {
236 i32::try_from(x).ok()?;
237 i32::try_from(y).ok()?;
238 i32::try_from(width).ok()?;
239 i32::try_from(height).ok()?;
240
241 x.checked_add(width)?;
242 y.checked_add(height)?;
243
244 let width = LengthU32::new(width)?;
245 let height = LengthU32::new(height)?;
246
247 Some(ScreenIntRect {
248 x,
249 y,
250 width,
251 height,
252 })
253 }
254
255 pub const fn from_xywh_safe(x: u32, y: u32, width: LengthU32, height: LengthU32) -> Self {
257 ScreenIntRect {
258 x,
259 y,
260 width,
261 height,
262 }
263 }
264
265 pub fn x(&self) -> u32 {
267 self.x
268 }
269
270 pub fn y(&self) -> u32 {
272 self.y
273 }
274
275 pub fn width(&self) -> u32 {
277 self.width.get()
278 }
279
280 pub fn height(&self) -> u32 {
282 self.height.get()
283 }
284
285 pub fn width_safe(&self) -> LengthU32 {
287 self.width
288 }
289
290 pub fn left(&self) -> u32 {
292 self.x
293 }
294
295 pub fn top(&self) -> u32 {
297 self.y
298 }
299
300 pub fn right(&self) -> u32 {
304 self.x + self.width.get()
306 }
307
308 pub fn bottom(&self) -> u32 {
312 self.y + self.height.get()
314 }
315
316 pub fn size(&self) -> IntSize {
318 IntSize {
319 width: self.width,
320 height: self.height,
321 }
322 }
323
324 pub fn contains(&self, other: &Self) -> bool {
326 self.x <= other.x
327 && self.y <= other.y
328 && self.right() >= other.right()
329 && self.bottom() >= other.bottom()
330 }
331
332 pub fn to_int_rect(&self) -> IntRect {
334 IntRect::from_xywh(
336 self.x as i32,
337 self.y as i32,
338 self.width.get(),
339 self.height.get(),
340 )
341 .unwrap()
342 }
343
344 pub fn to_rect(&self) -> Rect {
346 Rect::from_ltrb(
349 self.x as f32,
350 self.y as f32,
351 self.x as f32 + self.width.get() as f32,
352 self.y as f32 + self.height.get() as f32,
353 )
354 .unwrap()
355 }
356}
357
358#[cfg(test)]
359mod screen_int_rect_tests {
360 use super::*;
361
362 #[test]
363 fn tests() {
364 assert_eq!(ScreenIntRect::from_xywh(0, 0, 0, 0), None);
365 assert_eq!(ScreenIntRect::from_xywh(0, 0, 1, 0), None);
366 assert_eq!(ScreenIntRect::from_xywh(0, 0, 0, 1), None);
367
368 assert_eq!(
369 ScreenIntRect::from_xywh(0, 0, core::u32::MAX, core::u32::MAX),
370 None
371 );
372 assert_eq!(ScreenIntRect::from_xywh(0, 0, 1, core::u32::MAX), None);
373 assert_eq!(ScreenIntRect::from_xywh(0, 0, core::u32::MAX, 1), None);
374
375 assert_eq!(ScreenIntRect::from_xywh(core::u32::MAX, 0, 1, 1), None);
376 assert_eq!(ScreenIntRect::from_xywh(0, core::u32::MAX, 1, 1), None);
377
378 assert_eq!(
379 ScreenIntRect::from_xywh(
380 core::u32::MAX,
381 core::u32::MAX,
382 core::u32::MAX,
383 core::u32::MAX
384 ),
385 None
386 );
387
388 let r = ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap();
389 assert_eq!(r.x(), 1);
390 assert_eq!(r.y(), 2);
391 assert_eq!(r.width(), 3);
392 assert_eq!(r.height(), 4);
393 assert_eq!(r.right(), 4);
394 assert_eq!(r.bottom(), 6);
395 }
396}
397
398#[allow(missing_docs)]
409#[derive(Copy, Clone, PartialEq)]
410pub struct Rect {
411 left: FiniteF32,
412 top: FiniteF32,
413 right: FiniteF32,
414 bottom: FiniteF32,
415}
416
417impl core::fmt::Debug for Rect {
418 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
419 f.debug_struct("Rect")
420 .field("left", &self.left.get())
421 .field("top", &self.top.get())
422 .field("right", &self.right.get())
423 .field("bottom", &self.bottom.get())
424 .finish()
425 }
426}
427
428impl Rect {
429 pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
431 let left = FiniteF32::new(left)?;
432 let top = FiniteF32::new(top)?;
433 let right = FiniteF32::new(right)?;
434 let bottom = FiniteF32::new(bottom)?;
435
436 if left.get() <= right.get() && top.get() <= bottom.get() {
437 checked_f32_sub(right.get(), left.get())?;
439 checked_f32_sub(bottom.get(), top.get())?;
440
441 Some(Rect {
442 left,
443 top,
444 right,
445 bottom,
446 })
447 } else {
448 None
449 }
450 }
451
452 pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
454 Rect::from_ltrb(x, y, w + x, h + y)
455 }
456
457 pub fn left(&self) -> f32 {
459 self.left.get()
460 }
461
462 pub fn top(&self) -> f32 {
464 self.top.get()
465 }
466
467 pub fn right(&self) -> f32 {
469 self.right.get()
470 }
471
472 pub fn bottom(&self) -> f32 {
474 self.bottom.get()
475 }
476
477 pub fn x(&self) -> f32 {
479 self.left.get()
480 }
481
482 pub fn y(&self) -> f32 {
484 self.top.get()
485 }
486
487 #[inline]
489 pub fn width(&self) -> f32 {
490 self.right.get() - self.left.get()
491 }
492
493 #[inline]
495 pub fn height(&self) -> f32 {
496 self.bottom.get() - self.top.get()
497 }
498
499 pub fn round(&self) -> Option<IntRect> {
503 IntRect::from_xywh(
504 i32::saturate_round(self.x()),
505 i32::saturate_round(self.y()),
506 core::cmp::max(1, i32::saturate_round(self.width()) as u32),
507 core::cmp::max(1, i32::saturate_round(self.height()) as u32),
508 )
509 }
510
511 pub fn round_out(&self) -> Option<IntRect> {
515 IntRect::from_xywh(
516 i32::saturate_floor(self.x()),
517 i32::saturate_floor(self.y()),
518 core::cmp::max(1, i32::saturate_ceil(self.width()) as u32),
519 core::cmp::max(1, i32::saturate_ceil(self.height()) as u32),
520 )
521 }
522
523 pub fn intersect(&self, other: &Self) -> Option<Self> {
527 let left = self.x().max(other.x());
528 let top = self.y().max(other.y());
529
530 let right = self.right().min(other.right());
531 let bottom = self.bottom().min(other.bottom());
532
533 Rect::from_ltrb(left, top, right, bottom)
534 }
535
536 pub fn from_points(points: &[Point]) -> Option<Self> {
540 use crate::f32x4_t::f32x4;
541
542 if points.is_empty() {
543 return None;
544 }
545
546 let mut offset = 0;
547 let mut min;
548 let mut max;
549 if points.len() & 1 != 0 {
550 let pt = points[0];
551 min = f32x4([pt.x, pt.y, pt.x, pt.y]);
552 max = min;
553 offset += 1;
554 } else {
555 let pt0 = points[0];
556 let pt1 = points[1];
557 min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
558 max = min;
559 offset += 2;
560 }
561
562 let mut accum = f32x4::default();
563 while offset != points.len() {
564 let pt0 = points[offset + 0];
565 let pt1 = points[offset + 1];
566 let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
567
568 accum *= xy;
569 min = min.min(xy);
570 max = max.max(xy);
571 offset += 2;
572 }
573
574 let all_finite = accum * f32x4::default() == f32x4::default();
575 let min: [f32; 4] = min.0;
576 let max: [f32; 4] = max.0;
577 if all_finite {
578 Rect::from_ltrb(
579 min[0].min(min[2]),
580 min[1].min(min[3]),
581 max[0].max(max[2]),
582 max[1].max(max[3]),
583 )
584 } else {
585 None
586 }
587 }
588
589 pub fn inset(&mut self, dx: f32, dy: f32) -> Option<Self> {
591 Rect::from_ltrb(
592 self.left() + dx,
593 self.top() + dy,
594 self.right() - dx,
595 self.bottom() - dy,
596 )
597 }
598
599 pub fn outset(&mut self, dx: f32, dy: f32) -> Option<Self> {
601 self.inset(-dx, -dy)
602 }
603}
604
605fn checked_f32_sub(a: f32, b: f32) -> Option<f32> {
606 debug_assert!(a.is_finite());
607 debug_assert!(b.is_finite());
608
609 let n = a as f64 - b as f64;
610 if n > core::f32::MIN as f64 && n < core::f32::MAX as f64 {
612 Some(n as f32)
613 } else {
614 None
615 }
616}
617
618#[cfg(test)]
619mod rect_tests {
620 use super::*;
621
622 #[test]
623 fn tests() {
624 assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None);
625 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None);
626 assert_eq!(Rect::from_ltrb(core::f32::NAN, 10.0, 10.0, 10.0), None);
627 assert_eq!(Rect::from_ltrb(10.0, core::f32::NAN, 10.0, 10.0), None);
628 assert_eq!(Rect::from_ltrb(10.0, 10.0, core::f32::NAN, 10.0), None);
629 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, core::f32::NAN), None);
630 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, core::f32::INFINITY), None);
631
632 let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap();
633 assert_eq!(rect.left(), 10.0);
634 assert_eq!(rect.top(), 20.0);
635 assert_eq!(rect.right(), 30.0);
636 assert_eq!(rect.bottom(), 40.0);
637 assert_eq!(rect.width(), 20.0);
638 assert_eq!(rect.height(), 20.0);
639
640 let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap();
641 assert_eq!(rect.width(), 20.0);
642 assert_eq!(rect.height(), 20.0);
643 }
644
645 #[test]
646 fn round_overflow() {
647 let x = 128.0;
650 let width = i32::MAX as f32;
652
653 let rect = Rect::from_xywh(x, 0.0, width, 1.0).unwrap();
654 assert_eq!(rect.round(), None);
655 assert_eq!(rect.round_out(), None);
656 }
657}