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]
46 pub const fn right(&self) -> u32 {
47 self.x.saturating_add(self.width)
48 }
49
50 #[inline]
54 pub const fn bottom(&self) -> u32 {
55 self.y.saturating_add(self.height)
56 }
57
58 #[inline]
60 pub const fn is_empty(&self) -> bool {
61 self.width == 0 || self.height == 0
62 }
63
64 #[inline]
78 pub fn centered(&self, inner_w: u32, inner_h: u32) -> Rect {
79 let w = inner_w.min(self.width);
80 let h = inner_h.min(self.height);
81 let x = self.x.saturating_add((self.width.saturating_sub(w)) / 2);
82 let y = self.y.saturating_add((self.height.saturating_sub(h)) / 2);
83 Rect {
84 x,
85 y,
86 width: w,
87 height: h,
88 }
89 }
90
91 #[inline]
105 pub fn union(&self, other: Rect) -> Rect {
106 let x = self.x.min(other.x);
107 let y = self.y.min(other.y);
108 let right = self.right().max(other.right());
109 let bottom = self.bottom().max(other.bottom());
110 Rect {
111 x,
112 y,
113 width: right.saturating_sub(x),
114 height: bottom.saturating_sub(y),
115 }
116 }
117
118 #[inline]
132 pub fn intersection(&self, other: Rect) -> Option<Rect> {
133 let x = self.x.max(other.x);
134 let y = self.y.max(other.y);
135 let right = self.right().min(other.right());
136 let bottom = self.bottom().min(other.bottom());
137
138 if x < right && y < bottom {
139 Some(Rect {
140 x,
141 y,
142 width: right.saturating_sub(x),
143 height: bottom.saturating_sub(y),
144 })
145 } else {
146 None
147 }
148 }
149
150 #[inline]
164 pub fn contains(&self, x: u32, y: u32) -> bool {
165 x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
166 }
167
168 #[inline]
180 pub fn rows(&self) -> impl Iterator<Item = u32> + use<> {
181 self.y..self.bottom()
182 }
183
184 #[inline]
197 pub fn positions(&self) -> impl Iterator<Item = (u32, u32)> + use<> {
198 let x_start = self.x;
199 let x_end = self.right();
200 let y_start = self.y;
201 let y_end = self.bottom();
202
203 (y_start..y_end).flat_map(move |y| (x_start..x_end).map(move |x| (x, y)))
204 }
205
206 #[inline]
228 pub const fn center_in(self, parent: Rect) -> Rect {
229 let w = if self.width < parent.width {
230 self.width
231 } else {
232 parent.width
233 };
234 let h = if self.height < parent.height {
235 self.height
236 } else {
237 parent.height
238 };
239 let x = parent.x.saturating_add(parent.width.saturating_sub(w) / 2);
240 let y = parent.y.saturating_add(parent.height.saturating_sub(h) / 2);
241 Rect {
242 x,
243 y,
244 width: w,
245 height: h,
246 }
247 }
248
249 #[inline]
263 pub const fn center_horizontally_in(self, parent: Rect) -> Rect {
264 let w = if self.width < parent.width {
265 self.width
266 } else {
267 parent.width
268 };
269 let x = parent.x.saturating_add(parent.width.saturating_sub(w) / 2);
270 Rect {
271 x,
272 y: self.y,
273 width: w,
274 height: self.height,
275 }
276 }
277
278 #[inline]
292 pub const fn center_vertically_in(self, parent: Rect) -> Rect {
293 let h = if self.height < parent.height {
294 self.height
295 } else {
296 parent.height
297 };
298 let y = parent.y.saturating_add(parent.height.saturating_sub(h) / 2);
299 Rect {
300 x: self.x,
301 y,
302 width: self.width,
303 height: h,
304 }
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_centered_normal() {
314 let outer = Rect::new(0, 0, 10, 10);
315 let inner = outer.centered(4, 4);
316 assert_eq!(inner, Rect::new(3, 3, 4, 4));
317 }
318
319 #[test]
320 fn test_centered_larger_than_self() {
321 let outer = Rect::new(0, 0, 10, 10);
322 let inner = outer.centered(20, 20);
323 assert_eq!(inner, Rect::new(0, 0, 10, 10));
324 }
325
326 #[test]
327 fn test_centered_zero_size() {
328 let outer = Rect::new(5, 5, 10, 10);
329 let inner = outer.centered(0, 0);
330 assert_eq!(inner, Rect::new(10, 10, 0, 0));
331 }
332
333 #[test]
334 fn test_centered_offset() {
335 let outer = Rect::new(10, 20, 20, 20);
336 let inner = outer.centered(10, 10);
337 assert_eq!(inner, Rect::new(15, 25, 10, 10));
338 }
339
340 #[test]
341 fn test_union_overlapping() {
342 let r1 = Rect::new(0, 0, 5, 5);
343 let r2 = Rect::new(3, 3, 5, 5);
344 let union = r1.union(r2);
345 assert_eq!(union, Rect::new(0, 0, 8, 8));
346 }
347
348 #[test]
349 fn test_union_non_overlapping() {
350 let r1 = Rect::new(0, 0, 5, 5);
351 let r2 = Rect::new(10, 10, 5, 5);
352 let union = r1.union(r2);
353 assert_eq!(union, Rect::new(0, 0, 15, 15));
354 }
355
356 #[test]
357 fn test_union_same_rect() {
358 let r = Rect::new(5, 5, 10, 10);
359 let union = r.union(r);
360 assert_eq!(union, r);
361 }
362
363 #[test]
364 fn test_intersection_overlapping() {
365 let r1 = Rect::new(0, 0, 5, 5);
366 let r2 = Rect::new(3, 3, 5, 5);
367 let overlap = r1.intersection(r2);
368 assert_eq!(overlap, Some(Rect::new(3, 3, 2, 2)));
369 }
370
371 #[test]
372 fn test_intersection_non_overlapping() {
373 let r1 = Rect::new(0, 0, 5, 5);
374 let r2 = Rect::new(10, 10, 5, 5);
375 let overlap = r1.intersection(r2);
376 assert_eq!(overlap, None);
377 }
378
379 #[test]
380 fn test_intersection_adjacent() {
381 let r1 = Rect::new(0, 0, 5, 5);
382 let r2 = Rect::new(5, 0, 5, 5);
383 let overlap = r1.intersection(r2);
384 assert_eq!(overlap, None);
385 }
386
387 #[test]
388 fn test_intersection_same_rect() {
389 let r = Rect::new(5, 5, 10, 10);
390 let overlap = r.intersection(r);
391 assert_eq!(overlap, Some(r));
392 }
393
394 #[test]
395 fn test_contains_inside() {
396 let r = Rect::new(5, 5, 10, 10);
397 assert!(r.contains(5, 5));
398 assert!(r.contains(10, 10));
399 assert!(r.contains(14, 14));
400 }
401
402 #[test]
403 fn test_contains_outside() {
404 let r = Rect::new(5, 5, 10, 10);
405 assert!(!r.contains(4, 5));
406 assert!(!r.contains(5, 4));
407 assert!(!r.contains(15, 15));
408 assert!(!r.contains(15, 10));
409 }
410
411 #[test]
412 fn test_contains_on_edge() {
413 let r = Rect::new(5, 5, 10, 10);
414 assert!(r.contains(5, 5)); assert!(!r.contains(15, 5)); assert!(!r.contains(5, 15)); }
418
419 #[test]
420 fn test_rows_correct_range() {
421 let r = Rect::new(0, 2, 5, 3);
422 let rows: Vec<u32> = r.rows().collect();
423 assert_eq!(rows, vec![2, 3, 4]);
424 }
425
426 #[test]
427 fn test_rows_single_row() {
428 let r = Rect::new(0, 5, 10, 1);
429 let rows: Vec<u32> = r.rows().collect();
430 assert_eq!(rows, vec![5]);
431 }
432
433 #[test]
434 fn test_rows_empty() {
435 let r = Rect::new(0, 5, 10, 0);
436 let rows: Vec<u32> = r.rows().collect();
437 assert!(rows.is_empty());
438 }
439
440 #[test]
441 fn test_positions_correct_count() {
442 let r = Rect::new(0, 0, 3, 2);
443 let positions: Vec<(u32, u32)> = r.positions().collect();
444 assert_eq!(positions.len(), 6);
445 }
446
447 #[test]
448 fn test_positions_order() {
449 let r = Rect::new(0, 0, 2, 2);
450 let positions: Vec<(u32, u32)> = r.positions().collect();
451 assert_eq!(positions, vec![(0, 0), (1, 0), (0, 1), (1, 1)]);
452 }
453
454 #[test]
455 fn test_positions_offset() {
456 let r = Rect::new(5, 3, 2, 2);
457 let positions: Vec<(u32, u32)> = r.positions().collect();
458 assert_eq!(positions, vec![(5, 3), (6, 3), (5, 4), (6, 4)]);
459 }
460
461 #[test]
462 fn test_positions_empty() {
463 let r = Rect::new(0, 0, 0, 5);
464 let positions: Vec<(u32, u32)> = r.positions().collect();
465 assert!(positions.is_empty());
466 }
467
468 #[test]
469 fn rect_area_no_overflow() {
470 let r = Rect::new(0, 0, u32::MAX, u32::MAX);
472 assert_eq!(r.area(), u32::MAX);
473 let r2 = Rect::new(0, 0, 65536, 65536);
475 assert_eq!(r2.area(), u32::MAX);
476 }
477
478 #[test]
479 fn rect_edges_saturate_instead_of_wrapping() {
480 let r = Rect::new(u32::MAX, u32::MAX - 1, 10, 10);
481 assert_eq!(r.right(), u32::MAX);
482 assert_eq!(r.bottom(), u32::MAX);
483 assert!(!r.contains(0, 0), "saturated edge must not wrap to origin");
484 }
485
486 #[test]
487 fn rect_union_and_intersection_do_not_wrap_at_u32_max() {
488 let edge = Rect::new(u32::MAX - 1, u32::MAX - 1, 10, 10);
489 let origin = Rect::new(0, 0, 1, 1);
490
491 assert_eq!(edge.union(origin), Rect::new(0, 0, u32::MAX, u32::MAX));
492 assert_eq!(edge.intersection(origin), None);
493 }
494
495 #[test]
496 fn rect_centering_saturates_large_offsets() {
497 let parent = Rect::new(u32::MAX - 2, u32::MAX - 2, 10, 10);
498 let child = Rect::new(0, 0, 2, 2).center_in(parent);
499 assert_eq!(child.x, u32::MAX);
500 assert_eq!(child.y, u32::MAX);
501 }
502
503 #[test]
504 fn test_center_in_basic() {
505 let dialog = Rect::new(0, 0, 40, 10);
506 let screen = Rect::new(0, 0, 120, 40);
507 assert_eq!(dialog.center_in(screen), Rect::new(40, 15, 40, 10));
508 }
509
510 #[test]
511 fn test_center_in_self_bigger_clamps() {
512 let oversize = Rect::new(0, 0, 200, 80);
514 let screen = Rect::new(0, 0, 120, 40);
515 assert_eq!(oversize.center_in(screen), Rect::new(0, 0, 120, 40));
516 }
517
518 #[test]
519 fn test_center_in_offset_parent() {
520 let dialog = Rect::new(999, 999, 40, 10); let parent = Rect::new(10, 5, 100, 30);
524 assert_eq!(dialog.center_in(parent), Rect::new(40, 15, 40, 10));
525 }
526
527 #[test]
528 fn test_center_in_self_position_ignored() {
529 let a = Rect::new(0, 0, 10, 4).center_in(Rect::new(0, 0, 20, 10));
531 let b = Rect::new(99, 99, 10, 4).center_in(Rect::new(0, 0, 20, 10));
532 assert_eq!(a, b);
533 }
534
535 #[test]
536 fn test_center_horizontally_in_preserves_y_height() {
537 let banner = Rect::new(0, 5, 30, 3);
538 let screen = Rect::new(0, 0, 120, 40);
539 assert_eq!(
540 banner.center_horizontally_in(screen),
541 Rect::new(45, 5, 30, 3)
542 );
543 }
544
545 #[test]
546 fn test_center_horizontally_in_clamps_width() {
547 let wide = Rect::new(0, 4, 200, 3);
548 let screen = Rect::new(0, 0, 120, 40);
549 assert_eq!(wide.center_horizontally_in(screen), Rect::new(0, 4, 120, 3));
551 }
552
553 #[test]
554 fn test_center_vertically_in_preserves_x_width() {
555 let sidebar = Rect::new(2, 0, 20, 10);
556 let screen = Rect::new(0, 0, 120, 40);
557 assert_eq!(
558 sidebar.center_vertically_in(screen),
559 Rect::new(2, 15, 20, 10)
560 );
561 }
562
563 #[test]
564 fn test_center_vertically_in_clamps_height() {
565 let tall = Rect::new(3, 0, 8, 200);
566 let screen = Rect::new(0, 0, 120, 40);
567 assert_eq!(tall.center_vertically_in(screen), Rect::new(3, 0, 8, 40));
568 }
569}