1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
13pub struct Rect {
14 pub x: u32,
16 pub y: u32,
18 pub width: u32,
20 pub height: u32,
22}
23
24impl Rect {
25 #[inline]
27 pub const fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
28 Self {
29 x,
30 y,
31 width,
32 height,
33 }
34 }
35
36 #[inline]
38 pub const fn area(&self) -> u32 {
39 self.width.saturating_mul(self.height)
40 }
41
42 #[inline]
44 pub const fn area_u64(&self) -> u64 {
45 self.width as u64 * self.height as u64
46 }
47
48 #[inline]
52 pub const fn right(&self) -> u32 {
53 self.x.saturating_add(self.width)
54 }
55
56 #[inline]
58 pub const fn checked_right(&self) -> Option<u32> {
59 self.x.checked_add(self.width)
60 }
61
62 #[inline]
66 pub const fn bottom(&self) -> u32 {
67 self.y.saturating_add(self.height)
68 }
69
70 #[inline]
72 pub const fn checked_bottom(&self) -> Option<u32> {
73 self.y.checked_add(self.height)
74 }
75
76 #[inline]
78 pub const fn has_valid_edges(&self) -> bool {
79 self.checked_right().is_some() && self.checked_bottom().is_some()
80 }
81
82 #[inline]
84 pub const fn is_empty(&self) -> bool {
85 self.width == 0 || self.height == 0
86 }
87
88 #[inline]
102 pub fn centered(&self, inner_w: u32, inner_h: u32) -> Rect {
103 let w = inner_w.min(self.width);
104 let h = inner_h.min(self.height);
105 let x = self.x.saturating_add((self.width.saturating_sub(w)) / 2);
106 let y = self.y.saturating_add((self.height.saturating_sub(h)) / 2);
107 Rect {
108 x,
109 y,
110 width: w,
111 height: h,
112 }
113 }
114
115 #[inline]
129 pub fn union(&self, other: Rect) -> Rect {
130 let x = self.x.min(other.x);
131 let y = self.y.min(other.y);
132 let right = self.right().max(other.right());
133 let bottom = self.bottom().max(other.bottom());
134 Rect {
135 x,
136 y,
137 width: right.saturating_sub(x),
138 height: bottom.saturating_sub(y),
139 }
140 }
141
142 #[inline]
156 pub fn intersection(&self, other: Rect) -> Option<Rect> {
157 let x = self.x.max(other.x);
158 let y = self.y.max(other.y);
159 let right = self.right().min(other.right());
160 let bottom = self.bottom().min(other.bottom());
161
162 if x < right && y < bottom {
163 Some(Rect {
164 x,
165 y,
166 width: right.saturating_sub(x),
167 height: bottom.saturating_sub(y),
168 })
169 } else {
170 None
171 }
172 }
173
174 #[inline]
188 pub fn contains(&self, x: u32, y: u32) -> bool {
189 x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
190 }
191
192 #[inline]
204 pub fn rows(&self) -> impl Iterator<Item = u32> + use<> {
205 self.y..self.bottom()
206 }
207
208 #[inline]
221 pub fn positions(&self) -> impl Iterator<Item = (u32, u32)> + use<> {
222 let x_start = self.x;
223 let x_end = self.right();
224 let y_start = self.y;
225 let y_end = self.bottom();
226
227 (y_start..y_end).flat_map(move |y| (x_start..x_end).map(move |x| (x, y)))
228 }
229
230 #[inline]
252 pub const fn center_in(self, parent: Rect) -> Rect {
253 let w = if self.width < parent.width {
254 self.width
255 } else {
256 parent.width
257 };
258 let h = if self.height < parent.height {
259 self.height
260 } else {
261 parent.height
262 };
263 let x = parent.x.saturating_add(parent.width.saturating_sub(w) / 2);
264 let y = parent.y.saturating_add(parent.height.saturating_sub(h) / 2);
265 Rect {
266 x,
267 y,
268 width: w,
269 height: h,
270 }
271 }
272
273 #[inline]
287 pub const fn center_horizontally_in(self, parent: Rect) -> Rect {
288 let w = if self.width < parent.width {
289 self.width
290 } else {
291 parent.width
292 };
293 let x = parent.x.saturating_add(parent.width.saturating_sub(w) / 2);
294 Rect {
295 x,
296 y: self.y,
297 width: w,
298 height: self.height,
299 }
300 }
301
302 #[inline]
316 pub const fn center_vertically_in(self, parent: Rect) -> Rect {
317 let h = if self.height < parent.height {
318 self.height
319 } else {
320 parent.height
321 };
322 let y = parent.y.saturating_add(parent.height.saturating_sub(h) / 2);
323 Rect {
324 x: self.x,
325 y,
326 width: self.width,
327 height: h,
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn test_centered_normal() {
338 let outer = Rect::new(0, 0, 10, 10);
339 let inner = outer.centered(4, 4);
340 assert_eq!(inner, Rect::new(3, 3, 4, 4));
341 }
342
343 #[test]
344 fn test_centered_larger_than_self() {
345 let outer = Rect::new(0, 0, 10, 10);
346 let inner = outer.centered(20, 20);
347 assert_eq!(inner, Rect::new(0, 0, 10, 10));
348 }
349
350 #[test]
351 fn test_centered_zero_size() {
352 let outer = Rect::new(5, 5, 10, 10);
353 let inner = outer.centered(0, 0);
354 assert_eq!(inner, Rect::new(10, 10, 0, 0));
355 }
356
357 #[test]
358 fn test_centered_offset() {
359 let outer = Rect::new(10, 20, 20, 20);
360 let inner = outer.centered(10, 10);
361 assert_eq!(inner, Rect::new(15, 25, 10, 10));
362 }
363
364 #[test]
365 fn test_union_overlapping() {
366 let r1 = Rect::new(0, 0, 5, 5);
367 let r2 = Rect::new(3, 3, 5, 5);
368 let union = r1.union(r2);
369 assert_eq!(union, Rect::new(0, 0, 8, 8));
370 }
371
372 #[test]
373 fn test_union_non_overlapping() {
374 let r1 = Rect::new(0, 0, 5, 5);
375 let r2 = Rect::new(10, 10, 5, 5);
376 let union = r1.union(r2);
377 assert_eq!(union, Rect::new(0, 0, 15, 15));
378 }
379
380 #[test]
381 fn test_union_same_rect() {
382 let r = Rect::new(5, 5, 10, 10);
383 let union = r.union(r);
384 assert_eq!(union, r);
385 }
386
387 #[test]
388 fn test_intersection_overlapping() {
389 let r1 = Rect::new(0, 0, 5, 5);
390 let r2 = Rect::new(3, 3, 5, 5);
391 let overlap = r1.intersection(r2);
392 assert_eq!(overlap, Some(Rect::new(3, 3, 2, 2)));
393 }
394
395 #[test]
396 fn test_intersection_non_overlapping() {
397 let r1 = Rect::new(0, 0, 5, 5);
398 let r2 = Rect::new(10, 10, 5, 5);
399 let overlap = r1.intersection(r2);
400 assert_eq!(overlap, None);
401 }
402
403 #[test]
404 fn test_intersection_adjacent() {
405 let r1 = Rect::new(0, 0, 5, 5);
406 let r2 = Rect::new(5, 0, 5, 5);
407 let overlap = r1.intersection(r2);
408 assert_eq!(overlap, None);
409 }
410
411 #[test]
412 fn test_intersection_same_rect() {
413 let r = Rect::new(5, 5, 10, 10);
414 let overlap = r.intersection(r);
415 assert_eq!(overlap, Some(r));
416 }
417
418 #[test]
419 fn test_contains_inside() {
420 let r = Rect::new(5, 5, 10, 10);
421 assert!(r.contains(5, 5));
422 assert!(r.contains(10, 10));
423 assert!(r.contains(14, 14));
424 }
425
426 #[test]
427 fn test_contains_outside() {
428 let r = Rect::new(5, 5, 10, 10);
429 assert!(!r.contains(4, 5));
430 assert!(!r.contains(5, 4));
431 assert!(!r.contains(15, 15));
432 assert!(!r.contains(15, 10));
433 }
434
435 #[test]
436 fn test_contains_on_edge() {
437 let r = Rect::new(5, 5, 10, 10);
438 assert!(r.contains(5, 5)); assert!(!r.contains(15, 5)); assert!(!r.contains(5, 15)); }
442
443 #[test]
444 fn test_rows_correct_range() {
445 let r = Rect::new(0, 2, 5, 3);
446 let rows: Vec<u32> = r.rows().collect();
447 assert_eq!(rows, vec![2, 3, 4]);
448 }
449
450 #[test]
451 fn test_rows_single_row() {
452 let r = Rect::new(0, 5, 10, 1);
453 let rows: Vec<u32> = r.rows().collect();
454 assert_eq!(rows, vec![5]);
455 }
456
457 #[test]
458 fn test_rows_empty() {
459 let r = Rect::new(0, 5, 10, 0);
460 let rows: Vec<u32> = r.rows().collect();
461 assert!(rows.is_empty());
462 }
463
464 #[test]
465 fn test_positions_correct_count() {
466 let r = Rect::new(0, 0, 3, 2);
467 let positions: Vec<(u32, u32)> = r.positions().collect();
468 assert_eq!(positions.len(), 6);
469 }
470
471 #[test]
472 fn test_positions_order() {
473 let r = Rect::new(0, 0, 2, 2);
474 let positions: Vec<(u32, u32)> = r.positions().collect();
475 assert_eq!(positions, vec![(0, 0), (1, 0), (0, 1), (1, 1)]);
476 }
477
478 #[test]
479 fn test_positions_offset() {
480 let r = Rect::new(5, 3, 2, 2);
481 let positions: Vec<(u32, u32)> = r.positions().collect();
482 assert_eq!(positions, vec![(5, 3), (6, 3), (5, 4), (6, 4)]);
483 }
484
485 #[test]
486 fn test_positions_empty() {
487 let r = Rect::new(0, 0, 0, 5);
488 let positions: Vec<(u32, u32)> = r.positions().collect();
489 assert!(positions.is_empty());
490 }
491
492 #[test]
493 fn rect_area_no_overflow() {
494 let r = Rect::new(0, 0, u32::MAX, u32::MAX);
496 assert_eq!(r.area(), u32::MAX);
497 let r2 = Rect::new(0, 0, 65536, 65536);
499 assert_eq!(r2.area(), u32::MAX);
500 assert_eq!(r2.area_u64(), 4_294_967_296);
501 }
502
503 #[test]
504 fn checked_edges_report_unrepresentable_origins() {
505 let invalid = Rect::new(u32::MAX, u32::MAX, 1, 1);
506 assert_eq!(invalid.checked_right(), None);
507 assert_eq!(invalid.checked_bottom(), None);
508 assert!(!invalid.has_valid_edges());
509
510 let valid = Rect::new(u32::MAX - 1, u32::MAX - 1, 1, 1);
511 assert_eq!(valid.checked_right(), Some(u32::MAX));
512 assert_eq!(valid.checked_bottom(), Some(u32::MAX));
513 assert!(valid.has_valid_edges());
514 }
515
516 #[test]
517 fn rect_edges_saturate_instead_of_wrapping() {
518 let r = Rect::new(u32::MAX, u32::MAX - 1, 10, 10);
519 assert_eq!(r.right(), u32::MAX);
520 assert_eq!(r.bottom(), u32::MAX);
521 assert!(!r.contains(0, 0), "saturated edge must not wrap to origin");
522 }
523
524 #[test]
525 fn rect_union_and_intersection_do_not_wrap_at_u32_max() {
526 let edge = Rect::new(u32::MAX - 1, u32::MAX - 1, 10, 10);
527 let origin = Rect::new(0, 0, 1, 1);
528
529 assert_eq!(edge.union(origin), Rect::new(0, 0, u32::MAX, u32::MAX));
530 assert_eq!(edge.intersection(origin), None);
531 }
532
533 #[test]
534 fn rect_centering_saturates_large_offsets() {
535 let parent = Rect::new(u32::MAX - 2, u32::MAX - 2, 10, 10);
536 let child = Rect::new(0, 0, 2, 2).center_in(parent);
537 assert_eq!(child.x, u32::MAX);
538 assert_eq!(child.y, u32::MAX);
539 }
540
541 #[test]
542 fn test_center_in_basic() {
543 let dialog = Rect::new(0, 0, 40, 10);
544 let screen = Rect::new(0, 0, 120, 40);
545 assert_eq!(dialog.center_in(screen), Rect::new(40, 15, 40, 10));
546 }
547
548 #[test]
549 fn test_center_in_self_bigger_clamps() {
550 let oversize = Rect::new(0, 0, 200, 80);
552 let screen = Rect::new(0, 0, 120, 40);
553 assert_eq!(oversize.center_in(screen), Rect::new(0, 0, 120, 40));
554 }
555
556 #[test]
557 fn test_center_in_offset_parent() {
558 let dialog = Rect::new(999, 999, 40, 10); let parent = Rect::new(10, 5, 100, 30);
562 assert_eq!(dialog.center_in(parent), Rect::new(40, 15, 40, 10));
563 }
564
565 #[test]
566 fn test_center_in_self_position_ignored() {
567 let a = Rect::new(0, 0, 10, 4).center_in(Rect::new(0, 0, 20, 10));
569 let b = Rect::new(99, 99, 10, 4).center_in(Rect::new(0, 0, 20, 10));
570 assert_eq!(a, b);
571 }
572
573 #[test]
574 fn test_center_horizontally_in_preserves_y_height() {
575 let banner = Rect::new(0, 5, 30, 3);
576 let screen = Rect::new(0, 0, 120, 40);
577 assert_eq!(
578 banner.center_horizontally_in(screen),
579 Rect::new(45, 5, 30, 3)
580 );
581 }
582
583 #[test]
584 fn test_center_horizontally_in_clamps_width() {
585 let wide = Rect::new(0, 4, 200, 3);
586 let screen = Rect::new(0, 0, 120, 40);
587 assert_eq!(wide.center_horizontally_in(screen), Rect::new(0, 4, 120, 3));
589 }
590
591 #[test]
592 fn test_center_vertically_in_preserves_x_width() {
593 let sidebar = Rect::new(2, 0, 20, 10);
594 let screen = Rect::new(0, 0, 120, 40);
595 assert_eq!(
596 sidebar.center_vertically_in(screen),
597 Rect::new(2, 15, 20, 10)
598 );
599 }
600
601 #[test]
602 fn test_center_vertically_in_clamps_height() {
603 let tall = Rect::new(3, 0, 8, 200);
604 let screen = Rect::new(0, 0, 120, 40);
605 assert_eq!(tall.center_vertically_in(screen), Rect::new(3, 0, 8, 40));
606 }
607}