1#![forbid(unsafe_code)]
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
9pub struct Size {
10 pub width: u16,
12 pub height: u16,
14}
15
16impl Size {
17 pub const ZERO: Self = Self {
19 width: 0,
20 height: 0,
21 };
22
23 pub const MAX: Self = Self {
27 width: u16::MAX,
28 height: u16::MAX,
29 };
30
31 #[inline]
33 pub const fn new(width: u16, height: u16) -> Self {
34 Self { width, height }
35 }
36
37 #[inline]
39 pub const fn is_empty(&self) -> bool {
40 self.width == 0 || self.height == 0
41 }
42
43 #[inline]
45 pub const fn area(&self) -> u32 {
46 self.width as u32 * self.height as u32
47 }
48
49 #[inline]
51 pub const fn clamp_max(&self, max: Size) -> Size {
52 Size {
53 width: if self.width > max.width {
54 max.width
55 } else {
56 self.width
57 },
58 height: if self.height > max.height {
59 max.height
60 } else {
61 self.height
62 },
63 }
64 }
65
66 #[inline]
68 pub const fn clamp_min(&self, min: Size) -> Size {
69 Size {
70 width: if self.width < min.width {
71 min.width
72 } else {
73 self.width
74 },
75 height: if self.height < min.height {
76 min.height
77 } else {
78 self.height
79 },
80 }
81 }
82}
83
84impl From<(u16, u16)> for Size {
85 fn from((width, height): (u16, u16)) -> Self {
86 Self { width, height }
87 }
88}
89
90impl From<Rect> for Size {
91 fn from(rect: Rect) -> Self {
92 Self {
93 width: rect.width,
94 height: rect.height,
95 }
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
103pub struct Rect {
104 pub x: u16,
106 pub y: u16,
108 pub width: u16,
110 pub height: u16,
112}
113
114impl Rect {
115 #[inline]
117 pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
118 Self {
119 x,
120 y,
121 width,
122 height,
123 }
124 }
125
126 #[inline]
128 pub const fn from_size(width: u16, height: u16) -> Self {
129 Self::new(0, 0, width, height)
130 }
131
132 #[inline]
134 pub const fn left(&self) -> u16 {
135 self.x
136 }
137
138 #[inline]
140 pub const fn top(&self) -> u16 {
141 self.y
142 }
143
144 #[inline]
146 pub const fn right(&self) -> u16 {
147 self.x.saturating_add(self.width)
148 }
149
150 #[inline]
152 pub const fn bottom(&self) -> u16 {
153 self.y.saturating_add(self.height)
154 }
155
156 #[inline]
158 pub const fn area(&self) -> u32 {
159 self.width as u32 * self.height as u32
160 }
161
162 #[inline]
164 pub const fn is_empty(&self) -> bool {
165 self.width == 0 || self.height == 0
166 }
167
168 #[inline]
170 pub const fn contains(&self, x: u16, y: u16) -> bool {
171 x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
172 }
173
174 #[inline]
178 pub fn intersection(&self, other: &Rect) -> Rect {
179 self.intersection_opt(other).unwrap_or_default()
180 }
181
182 pub fn inner(&self, margin: Sides) -> Rect {
184 let x = self.x.saturating_add(margin.left);
185 let y = self.y.saturating_add(margin.top);
186 let width = self
187 .width
188 .saturating_sub(margin.left)
189 .saturating_sub(margin.right);
190 let height = self
191 .height
192 .saturating_sub(margin.top)
193 .saturating_sub(margin.bottom);
194
195 Rect {
196 x,
197 y,
198 width,
199 height,
200 }
201 }
202
203 pub fn union(&self, other: &Rect) -> Rect {
207 let x = self.x.min(other.x);
208 let y = self.y.min(other.y);
209 let right = self.right().max(other.right());
210 let bottom = self.bottom().max(other.bottom());
211
212 Rect {
213 x,
214 y,
215 width: right.saturating_sub(x),
216 height: bottom.saturating_sub(y),
217 }
218 }
219
220 #[inline]
222 pub fn intersection_opt(&self, other: &Rect) -> Option<Rect> {
223 let x = self.x.max(other.x);
224 let y = self.y.max(other.y);
225 let right = self.right().min(other.right());
226 let bottom = self.bottom().min(other.bottom());
227
228 if x < right && y < bottom {
229 Some(Rect::new(
230 x,
231 y,
232 right.saturating_sub(x),
233 bottom.saturating_sub(y),
234 ))
235 } else {
236 None
237 }
238 }
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
243pub struct Sides {
244 pub top: u16,
246 pub right: u16,
248 pub bottom: u16,
250 pub left: u16,
252}
253
254impl Sides {
255 pub const fn all(val: u16) -> Self {
257 Self {
258 top: val,
259 right: val,
260 bottom: val,
261 left: val,
262 }
263 }
264
265 pub const fn horizontal(val: u16) -> Self {
267 Self {
268 top: 0,
269 right: val,
270 bottom: 0,
271 left: val,
272 }
273 }
274
275 pub const fn vertical(val: u16) -> Self {
277 Self {
278 top: val,
279 right: 0,
280 bottom: val,
281 left: 0,
282 }
283 }
284
285 pub const fn new(top: u16, right: u16, bottom: u16, left: u16) -> Self {
287 Self {
288 top,
289 right,
290 bottom,
291 left,
292 }
293 }
294
295 #[inline]
297 pub const fn horizontal_sum(&self) -> u16 {
298 self.left.saturating_add(self.right)
299 }
300
301 #[inline]
303 pub const fn vertical_sum(&self) -> u16 {
304 self.top.saturating_add(self.bottom)
305 }
306}
307
308impl From<u16> for Sides {
309 fn from(val: u16) -> Self {
310 Self::all(val)
311 }
312}
313
314impl From<(u16, u16)> for Sides {
315 fn from((vertical, horizontal): (u16, u16)) -> Self {
316 Self {
317 top: vertical,
318 right: horizontal,
319 bottom: vertical,
320 left: horizontal,
321 }
322 }
323}
324
325impl From<(u16, u16, u16, u16)> for Sides {
326 fn from((top, right, bottom, left): (u16, u16, u16, u16)) -> Self {
327 Self {
328 top,
329 right,
330 bottom,
331 left,
332 }
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::{Rect, Sides, Size};
339
340 #[test]
341 fn rect_contains_edges() {
342 let rect = Rect::new(2, 3, 4, 5);
343 assert!(rect.contains(2, 3));
344 assert!(rect.contains(5, 7));
345 assert!(!rect.contains(6, 3));
346 assert!(!rect.contains(2, 8));
347 }
348
349 #[test]
350 fn rect_intersection_overlaps() {
351 let a = Rect::new(0, 0, 4, 4);
352 let b = Rect::new(2, 2, 4, 4);
353 assert_eq!(a.intersection(&b), Rect::new(2, 2, 2, 2));
354 }
355
356 #[test]
357 fn rect_intersection_no_overlap_is_empty() {
358 let a = Rect::new(0, 0, 2, 2);
359 let b = Rect::new(3, 3, 2, 2);
360 assert_eq!(a.intersection(&b), Rect::default());
361 }
362
363 #[test]
364 fn rect_inner_reduces() {
365 let rect = Rect::new(0, 0, 10, 10);
366 let inner = rect.inner(Sides {
367 top: 1,
368 right: 2,
369 bottom: 3,
370 left: 4,
371 });
372 assert_eq!(inner, Rect::new(4, 1, 4, 6));
373 }
374
375 #[test]
376 fn sides_constructors_and_conversions() {
377 assert_eq!(Sides::all(3), Sides::from(3));
378 assert_eq!(
379 Sides::horizontal(2),
380 Sides {
381 top: 0,
382 right: 2,
383 bottom: 0,
384 left: 2,
385 }
386 );
387 assert_eq!(
388 Sides::vertical(4),
389 Sides {
390 top: 4,
391 right: 0,
392 bottom: 4,
393 left: 0,
394 }
395 );
396 assert_eq!(
397 Sides::from((1, 2)),
398 Sides {
399 top: 1,
400 right: 2,
401 bottom: 1,
402 left: 2,
403 }
404 );
405 assert_eq!(
406 Sides::from((1, 2, 3, 4)),
407 Sides {
408 top: 1,
409 right: 2,
410 bottom: 3,
411 left: 4,
412 }
413 );
414 }
415
416 #[test]
417 fn sides_sums() {
418 let sides = Sides {
419 top: 1,
420 right: 2,
421 bottom: 3,
422 left: 4,
423 };
424 assert_eq!(sides.horizontal_sum(), 6);
425 assert_eq!(sides.vertical_sum(), 4);
426 }
427
428 #[test]
431 fn rect_new_and_default() {
432 let r = Rect::new(5, 10, 20, 15);
433 assert_eq!(r.x, 5);
434 assert_eq!(r.y, 10);
435 assert_eq!(r.width, 20);
436 assert_eq!(r.height, 15);
437
438 let d = Rect::default();
439 assert_eq!(d, Rect::new(0, 0, 0, 0));
440 }
441
442 #[test]
443 fn rect_from_size() {
444 let r = Rect::from_size(80, 24);
445 assert_eq!(r.x, 0);
446 assert_eq!(r.y, 0);
447 assert_eq!(r.width, 80);
448 assert_eq!(r.height, 24);
449 }
450
451 #[test]
454 fn rect_left_top_right_bottom() {
455 let r = Rect::new(10, 20, 30, 40);
456 assert_eq!(r.left(), 10);
457 assert_eq!(r.top(), 20);
458 assert_eq!(r.right(), 40);
459 assert_eq!(r.bottom(), 60);
460 }
461
462 #[test]
463 fn rect_right_bottom_saturating() {
464 let r = Rect::new(u16::MAX - 5, u16::MAX - 3, 100, 100);
466 assert_eq!(r.right(), u16::MAX);
467 assert_eq!(r.bottom(), u16::MAX);
468 }
469
470 #[test]
473 fn rect_area() {
474 assert_eq!(Rect::new(0, 0, 10, 20).area(), 200);
475 assert_eq!(Rect::new(5, 5, 0, 10).area(), 0);
476 assert_eq!(Rect::new(0, 0, 1, 1).area(), 1);
477 }
478
479 #[test]
480 fn rect_is_empty() {
481 assert!(Rect::new(0, 0, 0, 0).is_empty());
482 assert!(Rect::new(5, 5, 0, 10).is_empty());
483 assert!(Rect::new(5, 5, 10, 0).is_empty());
484 assert!(!Rect::new(0, 0, 1, 1).is_empty());
485 }
486
487 #[test]
490 fn rect_contains_boundary_conditions() {
491 let r = Rect::new(0, 0, 5, 5);
492 assert!(r.contains(0, 0));
494 assert!(r.contains(4, 4));
496 assert!(!r.contains(5, 0));
498 assert!(!r.contains(0, 5));
500 }
501
502 #[test]
503 fn rect_contains_empty_rect() {
504 let r = Rect::new(5, 5, 0, 0);
505 assert!(!r.contains(5, 5));
507 }
508
509 #[test]
512 fn rect_union_basic() {
513 let a = Rect::new(0, 0, 5, 5);
514 let b = Rect::new(3, 3, 5, 5);
515 let u = a.union(&b);
516 assert_eq!(u, Rect::new(0, 0, 8, 8));
517 }
518
519 #[test]
520 fn rect_union_disjoint() {
521 let a = Rect::new(0, 0, 2, 2);
522 let b = Rect::new(10, 10, 3, 3);
523 let u = a.union(&b);
524 assert_eq!(u, Rect::new(0, 0, 13, 13));
525 }
526
527 #[test]
528 fn rect_union_contained() {
529 let outer = Rect::new(0, 0, 10, 10);
530 let inner = Rect::new(2, 2, 3, 3);
531 assert_eq!(outer.union(&inner), outer);
532 assert_eq!(inner.union(&outer), outer);
533 }
534
535 #[test]
536 fn rect_union_self() {
537 let r = Rect::new(5, 10, 20, 15);
538 assert_eq!(r.union(&r), r);
539 }
540
541 #[test]
544 fn rect_intersection_self() {
545 let r = Rect::new(5, 5, 10, 10);
546 assert_eq!(r.intersection(&r), r);
547 }
548
549 #[test]
550 fn rect_intersection_contained() {
551 let outer = Rect::new(0, 0, 20, 20);
552 let inner = Rect::new(5, 5, 5, 5);
553 assert_eq!(outer.intersection(&inner), inner);
554 assert_eq!(inner.intersection(&outer), inner);
555 }
556
557 #[test]
558 fn rect_intersection_adjacent_no_overlap() {
559 let a = Rect::new(0, 0, 5, 5);
561 let b = Rect::new(5, 0, 5, 5);
562 assert!(a.intersection(&b).is_empty());
563 }
564
565 #[test]
566 fn rect_intersection_opt_returns_none_for_no_overlap() {
567 let a = Rect::new(0, 0, 2, 2);
568 let b = Rect::new(5, 5, 2, 2);
569 assert_eq!(a.intersection_opt(&b), None);
570 }
571
572 #[test]
573 fn rect_intersection_opt_returns_some_for_overlap() {
574 let a = Rect::new(0, 0, 5, 5);
575 let b = Rect::new(3, 3, 5, 5);
576 assert_eq!(a.intersection_opt(&b), Some(Rect::new(3, 3, 2, 2)));
577 }
578
579 #[test]
582 fn rect_inner_large_margin_clamps_to_zero() {
583 let r = Rect::new(0, 0, 10, 10);
584 let inner = r.inner(Sides::all(20));
585 assert_eq!(inner.width, 0);
587 assert_eq!(inner.height, 0);
588 }
589
590 #[test]
591 fn rect_inner_zero_margin() {
592 let r = Rect::new(5, 10, 20, 30);
593 let inner = r.inner(Sides::all(0));
594 assert_eq!(inner, r);
595 }
596
597 #[test]
598 fn rect_inner_asymmetric_margin() {
599 let r = Rect::new(0, 0, 20, 20);
600 let inner = r.inner(Sides::new(2, 3, 4, 5));
601 assert_eq!(inner.x, 5);
602 assert_eq!(inner.y, 2);
603 assert_eq!(inner.width, 12); assert_eq!(inner.height, 14); }
606
607 #[test]
610 fn sides_new_explicit() {
611 let s = Sides::new(1, 2, 3, 4);
612 assert_eq!(s.top, 1);
613 assert_eq!(s.right, 2);
614 assert_eq!(s.bottom, 3);
615 assert_eq!(s.left, 4);
616 }
617
618 #[test]
619 fn sides_default_is_zero() {
620 let s = Sides::default();
621 assert_eq!(s, Sides::new(0, 0, 0, 0));
622 }
623
624 #[test]
625 fn sides_sums_saturating() {
626 let s = Sides::new(u16::MAX, 0, u16::MAX, 0);
627 assert_eq!(s.vertical_sum(), u16::MAX);
628 }
629
630 #[test]
633 fn size_new_and_constants() {
634 let s = Size::new(80, 24);
635 assert_eq!(s.width, 80);
636 assert_eq!(s.height, 24);
637
638 assert_eq!(Size::ZERO, Size::new(0, 0));
639 assert_eq!(Size::MAX, Size::new(u16::MAX, u16::MAX));
640 }
641
642 #[test]
643 fn size_default_is_zero() {
644 assert_eq!(Size::default(), Size::ZERO);
645 }
646
647 #[test]
648 fn size_is_empty() {
649 assert!(Size::ZERO.is_empty());
650 assert!(Size::new(0, 10).is_empty());
651 assert!(Size::new(10, 0).is_empty());
652 assert!(!Size::new(1, 1).is_empty());
653 }
654
655 #[test]
656 fn size_area() {
657 assert_eq!(Size::new(10, 20).area(), 200);
658 assert_eq!(Size::ZERO.area(), 0);
659 assert_eq!(Size::new(1, 1).area(), 1);
660 }
661
662 #[test]
663 fn size_clamp_max() {
664 let s = Size::new(100, 50);
665 assert_eq!(s.clamp_max(Size::new(80, 40)), Size::new(80, 40));
666 assert_eq!(s.clamp_max(Size::new(200, 200)), s);
667 assert_eq!(s.clamp_max(Size::new(80, 100)), Size::new(80, 50));
668 }
669
670 #[test]
671 fn size_clamp_min() {
672 let s = Size::new(10, 5);
673 assert_eq!(s.clamp_min(Size::new(20, 10)), Size::new(20, 10));
674 assert_eq!(s.clamp_min(Size::new(5, 3)), s);
675 assert_eq!(s.clamp_min(Size::new(15, 3)), Size::new(15, 5));
676 }
677
678 #[test]
679 fn size_from_tuple() {
680 let s: Size = (80, 24).into();
681 assert_eq!(s, Size::new(80, 24));
682 }
683
684 #[test]
685 fn size_from_rect() {
686 let r = Rect::new(5, 10, 80, 24);
687 let s: Size = r.into();
688 assert_eq!(s, Size::new(80, 24));
689 }
690}