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 #[inline]
184 pub fn inner(&self, margin: Sides) -> Rect {
185 let x = self.x.saturating_add(margin.left);
186 let y = self.y.saturating_add(margin.top);
187 let width = self
188 .width
189 .saturating_sub(margin.left)
190 .saturating_sub(margin.right);
191 let height = self
192 .height
193 .saturating_sub(margin.top)
194 .saturating_sub(margin.bottom);
195
196 Rect {
197 x,
198 y,
199 width,
200 height,
201 }
202 }
203
204 #[inline]
208 pub fn union(&self, other: &Rect) -> Rect {
209 let x = self.x.min(other.x);
210 let y = self.y.min(other.y);
211 let right = self.right().max(other.right());
212 let bottom = self.bottom().max(other.bottom());
213
214 Rect {
215 x,
216 y,
217 width: right.saturating_sub(x),
218 height: bottom.saturating_sub(y),
219 }
220 }
221
222 #[inline]
224 pub fn intersection_opt(&self, other: &Rect) -> Option<Rect> {
225 let x = self.x.max(other.x);
226 let y = self.y.max(other.y);
227 let right = self.right().min(other.right());
228 let bottom = self.bottom().min(other.bottom());
229
230 if x < right && y < bottom {
231 Some(Rect::new(
232 x,
233 y,
234 right.saturating_sub(x),
235 bottom.saturating_sub(y),
236 ))
237 } else {
238 None
239 }
240 }
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
245pub struct Sides {
246 pub top: u16,
248 pub right: u16,
250 pub bottom: u16,
252 pub left: u16,
254}
255
256impl Sides {
257 pub const fn all(val: u16) -> Self {
259 Self {
260 top: val,
261 right: val,
262 bottom: val,
263 left: val,
264 }
265 }
266
267 pub const fn horizontal(val: u16) -> Self {
269 Self {
270 top: 0,
271 right: val,
272 bottom: 0,
273 left: val,
274 }
275 }
276
277 pub const fn vertical(val: u16) -> Self {
279 Self {
280 top: val,
281 right: 0,
282 bottom: val,
283 left: 0,
284 }
285 }
286
287 pub const fn new(top: u16, right: u16, bottom: u16, left: u16) -> Self {
289 Self {
290 top,
291 right,
292 bottom,
293 left,
294 }
295 }
296
297 #[inline]
299 pub const fn horizontal_sum(&self) -> u16 {
300 self.left.saturating_add(self.right)
301 }
302
303 #[inline]
305 pub const fn vertical_sum(&self) -> u16 {
306 self.top.saturating_add(self.bottom)
307 }
308}
309
310impl From<u16> for Sides {
311 fn from(val: u16) -> Self {
312 Self::all(val)
313 }
314}
315
316impl From<(u16, u16)> for Sides {
317 fn from((vertical, horizontal): (u16, u16)) -> Self {
318 Self {
319 top: vertical,
320 right: horizontal,
321 bottom: vertical,
322 left: horizontal,
323 }
324 }
325}
326
327impl From<(u16, u16, u16, u16)> for Sides {
328 fn from((top, right, bottom, left): (u16, u16, u16, u16)) -> Self {
329 Self {
330 top,
331 right,
332 bottom,
333 left,
334 }
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::{Rect, Sides, Size};
341
342 #[test]
343 fn rect_contains_edges() {
344 let rect = Rect::new(2, 3, 4, 5);
345 assert!(rect.contains(2, 3));
346 assert!(rect.contains(5, 7));
347 assert!(!rect.contains(6, 3));
348 assert!(!rect.contains(2, 8));
349 }
350
351 #[test]
352 fn rect_intersection_overlaps() {
353 let a = Rect::new(0, 0, 4, 4);
354 let b = Rect::new(2, 2, 4, 4);
355 assert_eq!(a.intersection(&b), Rect::new(2, 2, 2, 2));
356 }
357
358 #[test]
359 fn rect_intersection_no_overlap_is_empty() {
360 let a = Rect::new(0, 0, 2, 2);
361 let b = Rect::new(3, 3, 2, 2);
362 assert_eq!(a.intersection(&b), Rect::default());
363 }
364
365 #[test]
366 fn rect_inner_reduces() {
367 let rect = Rect::new(0, 0, 10, 10);
368 let inner = rect.inner(Sides {
369 top: 1,
370 right: 2,
371 bottom: 3,
372 left: 4,
373 });
374 assert_eq!(inner, Rect::new(4, 1, 4, 6));
375 }
376
377 #[test]
378 fn sides_constructors_and_conversions() {
379 assert_eq!(Sides::all(3), Sides::from(3));
380 assert_eq!(
381 Sides::horizontal(2),
382 Sides {
383 top: 0,
384 right: 2,
385 bottom: 0,
386 left: 2,
387 }
388 );
389 assert_eq!(
390 Sides::vertical(4),
391 Sides {
392 top: 4,
393 right: 0,
394 bottom: 4,
395 left: 0,
396 }
397 );
398 assert_eq!(
399 Sides::from((1, 2)),
400 Sides {
401 top: 1,
402 right: 2,
403 bottom: 1,
404 left: 2,
405 }
406 );
407 assert_eq!(
408 Sides::from((1, 2, 3, 4)),
409 Sides {
410 top: 1,
411 right: 2,
412 bottom: 3,
413 left: 4,
414 }
415 );
416 }
417
418 #[test]
419 fn sides_sums() {
420 let sides = Sides {
421 top: 1,
422 right: 2,
423 bottom: 3,
424 left: 4,
425 };
426 assert_eq!(sides.horizontal_sum(), 6);
427 assert_eq!(sides.vertical_sum(), 4);
428 }
429
430 #[test]
433 fn rect_new_and_default() {
434 let r = Rect::new(5, 10, 20, 15);
435 assert_eq!(r.x, 5);
436 assert_eq!(r.y, 10);
437 assert_eq!(r.width, 20);
438 assert_eq!(r.height, 15);
439
440 let d = Rect::default();
441 assert_eq!(d, Rect::new(0, 0, 0, 0));
442 }
443
444 #[test]
445 fn rect_from_size() {
446 let r = Rect::from_size(80, 24);
447 assert_eq!(r.x, 0);
448 assert_eq!(r.y, 0);
449 assert_eq!(r.width, 80);
450 assert_eq!(r.height, 24);
451 }
452
453 #[test]
456 fn rect_left_top_right_bottom() {
457 let r = Rect::new(10, 20, 30, 40);
458 assert_eq!(r.left(), 10);
459 assert_eq!(r.top(), 20);
460 assert_eq!(r.right(), 40);
461 assert_eq!(r.bottom(), 60);
462 }
463
464 #[test]
465 fn rect_right_bottom_saturating() {
466 let r = Rect::new(u16::MAX - 5, u16::MAX - 3, 100, 100);
468 assert_eq!(r.right(), u16::MAX);
469 assert_eq!(r.bottom(), u16::MAX);
470 }
471
472 #[test]
475 fn rect_area() {
476 assert_eq!(Rect::new(0, 0, 10, 20).area(), 200);
477 assert_eq!(Rect::new(5, 5, 0, 10).area(), 0);
478 assert_eq!(Rect::new(0, 0, 1, 1).area(), 1);
479 }
480
481 #[test]
482 fn rect_is_empty() {
483 assert!(Rect::new(0, 0, 0, 0).is_empty());
484 assert!(Rect::new(5, 5, 0, 10).is_empty());
485 assert!(Rect::new(5, 5, 10, 0).is_empty());
486 assert!(!Rect::new(0, 0, 1, 1).is_empty());
487 }
488
489 #[test]
492 fn rect_contains_boundary_conditions() {
493 let r = Rect::new(0, 0, 5, 5);
494 assert!(r.contains(0, 0));
496 assert!(r.contains(4, 4));
498 assert!(!r.contains(5, 0));
500 assert!(!r.contains(0, 5));
502 }
503
504 #[test]
505 fn rect_contains_empty_rect() {
506 let r = Rect::new(5, 5, 0, 0);
507 assert!(!r.contains(5, 5));
509 }
510
511 #[test]
514 fn rect_union_basic() {
515 let a = Rect::new(0, 0, 5, 5);
516 let b = Rect::new(3, 3, 5, 5);
517 let u = a.union(&b);
518 assert_eq!(u, Rect::new(0, 0, 8, 8));
519 }
520
521 #[test]
522 fn rect_union_disjoint() {
523 let a = Rect::new(0, 0, 2, 2);
524 let b = Rect::new(10, 10, 3, 3);
525 let u = a.union(&b);
526 assert_eq!(u, Rect::new(0, 0, 13, 13));
527 }
528
529 #[test]
530 fn rect_union_contained() {
531 let outer = Rect::new(0, 0, 10, 10);
532 let inner = Rect::new(2, 2, 3, 3);
533 assert_eq!(outer.union(&inner), outer);
534 assert_eq!(inner.union(&outer), outer);
535 }
536
537 #[test]
538 fn rect_union_self() {
539 let r = Rect::new(5, 10, 20, 15);
540 assert_eq!(r.union(&r), r);
541 }
542
543 #[test]
546 fn rect_intersection_self() {
547 let r = Rect::new(5, 5, 10, 10);
548 assert_eq!(r.intersection(&r), r);
549 }
550
551 #[test]
552 fn rect_intersection_contained() {
553 let outer = Rect::new(0, 0, 20, 20);
554 let inner = Rect::new(5, 5, 5, 5);
555 assert_eq!(outer.intersection(&inner), inner);
556 assert_eq!(inner.intersection(&outer), inner);
557 }
558
559 #[test]
560 fn rect_intersection_adjacent_no_overlap() {
561 let a = Rect::new(0, 0, 5, 5);
563 let b = Rect::new(5, 0, 5, 5);
564 assert!(a.intersection(&b).is_empty());
565 }
566
567 #[test]
568 fn rect_intersection_opt_returns_none_for_no_overlap() {
569 let a = Rect::new(0, 0, 2, 2);
570 let b = Rect::new(5, 5, 2, 2);
571 assert_eq!(a.intersection_opt(&b), None);
572 }
573
574 #[test]
575 fn rect_intersection_opt_returns_some_for_overlap() {
576 let a = Rect::new(0, 0, 5, 5);
577 let b = Rect::new(3, 3, 5, 5);
578 assert_eq!(a.intersection_opt(&b), Some(Rect::new(3, 3, 2, 2)));
579 }
580
581 #[test]
584 fn rect_inner_large_margin_clamps_to_zero() {
585 let r = Rect::new(0, 0, 10, 10);
586 let inner = r.inner(Sides::all(20));
587 assert_eq!(inner.width, 0);
589 assert_eq!(inner.height, 0);
590 }
591
592 #[test]
593 fn rect_inner_zero_margin() {
594 let r = Rect::new(5, 10, 20, 30);
595 let inner = r.inner(Sides::all(0));
596 assert_eq!(inner, r);
597 }
598
599 #[test]
600 fn rect_inner_asymmetric_margin() {
601 let r = Rect::new(0, 0, 20, 20);
602 let inner = r.inner(Sides::new(2, 3, 4, 5));
603 assert_eq!(inner.x, 5);
604 assert_eq!(inner.y, 2);
605 assert_eq!(inner.width, 12); assert_eq!(inner.height, 14); }
608
609 #[test]
612 fn sides_new_explicit() {
613 let s = Sides::new(1, 2, 3, 4);
614 assert_eq!(s.top, 1);
615 assert_eq!(s.right, 2);
616 assert_eq!(s.bottom, 3);
617 assert_eq!(s.left, 4);
618 }
619
620 #[test]
621 fn sides_default_is_zero() {
622 let s = Sides::default();
623 assert_eq!(s, Sides::new(0, 0, 0, 0));
624 }
625
626 #[test]
627 fn sides_sums_saturating() {
628 let s = Sides::new(u16::MAX, 0, u16::MAX, 0);
629 assert_eq!(s.vertical_sum(), u16::MAX);
630 }
631
632 #[test]
635 fn size_new_and_constants() {
636 let s = Size::new(80, 24);
637 assert_eq!(s.width, 80);
638 assert_eq!(s.height, 24);
639
640 assert_eq!(Size::ZERO, Size::new(0, 0));
641 assert_eq!(Size::MAX, Size::new(u16::MAX, u16::MAX));
642 }
643
644 #[test]
645 fn size_default_is_zero() {
646 assert_eq!(Size::default(), Size::ZERO);
647 }
648
649 #[test]
650 fn size_is_empty() {
651 assert!(Size::ZERO.is_empty());
652 assert!(Size::new(0, 10).is_empty());
653 assert!(Size::new(10, 0).is_empty());
654 assert!(!Size::new(1, 1).is_empty());
655 }
656
657 #[test]
658 fn size_area() {
659 assert_eq!(Size::new(10, 20).area(), 200);
660 assert_eq!(Size::ZERO.area(), 0);
661 assert_eq!(Size::new(1, 1).area(), 1);
662 }
663
664 #[test]
665 fn size_clamp_max() {
666 let s = Size::new(100, 50);
667 assert_eq!(s.clamp_max(Size::new(80, 40)), Size::new(80, 40));
668 assert_eq!(s.clamp_max(Size::new(200, 200)), s);
669 assert_eq!(s.clamp_max(Size::new(80, 100)), Size::new(80, 50));
670 }
671
672 #[test]
673 fn size_clamp_min() {
674 let s = Size::new(10, 5);
675 assert_eq!(s.clamp_min(Size::new(20, 10)), Size::new(20, 10));
676 assert_eq!(s.clamp_min(Size::new(5, 3)), s);
677 assert_eq!(s.clamp_min(Size::new(15, 3)), Size::new(15, 5));
678 }
679
680 #[test]
681 fn size_from_tuple() {
682 let s: Size = (80, 24).into();
683 assert_eq!(s, Size::new(80, 24));
684 }
685
686 #[test]
687 fn size_from_rect() {
688 let r = Rect::new(5, 10, 80, 24);
689 let s: Size = r.into();
690 assert_eq!(s, Size::new(80, 24));
691 }
692}