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 + self.width
48 }
49
50 #[inline]
54 pub const fn bottom(&self) -> u32 {
55 self.y + 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 + (self.width.saturating_sub(w)) / 2;
82 let y = self.y + (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 - x,
114 height: bottom - 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 - x,
143 height: bottom - 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> {
181 self.y..self.bottom()
182 }
183
184 #[inline]
197 pub fn positions(&self) -> impl Iterator<Item = (u32, u32)> {
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 + parent.width.saturating_sub(w) / 2;
240 let y = parent.y + 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 + 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 + 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 test_center_in_basic() {
480 let dialog = Rect::new(0, 0, 40, 10);
481 let screen = Rect::new(0, 0, 120, 40);
482 assert_eq!(dialog.center_in(screen), Rect::new(40, 15, 40, 10));
483 }
484
485 #[test]
486 fn test_center_in_self_bigger_clamps() {
487 let oversize = Rect::new(0, 0, 200, 80);
489 let screen = Rect::new(0, 0, 120, 40);
490 assert_eq!(oversize.center_in(screen), Rect::new(0, 0, 120, 40));
491 }
492
493 #[test]
494 fn test_center_in_offset_parent() {
495 let dialog = Rect::new(999, 999, 40, 10); let parent = Rect::new(10, 5, 100, 30);
499 assert_eq!(dialog.center_in(parent), Rect::new(40, 15, 40, 10));
500 }
501
502 #[test]
503 fn test_center_in_self_position_ignored() {
504 let a = Rect::new(0, 0, 10, 4).center_in(Rect::new(0, 0, 20, 10));
506 let b = Rect::new(99, 99, 10, 4).center_in(Rect::new(0, 0, 20, 10));
507 assert_eq!(a, b);
508 }
509
510 #[test]
511 fn test_center_horizontally_in_preserves_y_height() {
512 let banner = Rect::new(0, 5, 30, 3);
513 let screen = Rect::new(0, 0, 120, 40);
514 assert_eq!(
515 banner.center_horizontally_in(screen),
516 Rect::new(45, 5, 30, 3)
517 );
518 }
519
520 #[test]
521 fn test_center_horizontally_in_clamps_width() {
522 let wide = Rect::new(0, 4, 200, 3);
523 let screen = Rect::new(0, 0, 120, 40);
524 assert_eq!(wide.center_horizontally_in(screen), Rect::new(0, 4, 120, 3));
526 }
527
528 #[test]
529 fn test_center_vertically_in_preserves_x_width() {
530 let sidebar = Rect::new(2, 0, 20, 10);
531 let screen = Rect::new(0, 0, 120, 40);
532 assert_eq!(
533 sidebar.center_vertically_in(screen),
534 Rect::new(2, 15, 20, 10)
535 );
536 }
537
538 #[test]
539 fn test_center_vertically_in_clamps_height() {
540 let tall = Rect::new(3, 0, 8, 200);
541 let screen = Rect::new(0, 0, 120, 40);
542 assert_eq!(tall.center_vertically_in(screen), Rect::new(3, 0, 8, 40));
543 }
544}