1#![allow(clippy::too_many_arguments)]
4
5use std::fmt::{self, Debug};
6
7use super::geometry::{Offset, Radius, Rect};
8use super::lerp::lerp_double_non_null;
9
10#[derive(Clone, Copy, PartialEq)]
12struct Rounded {
13 left: f64,
14 top: f64,
15 right: f64,
16 bottom: f64,
17 tl_radius_x: f64,
18 tl_radius_y: f64,
19 tr_radius_x: f64,
20 tr_radius_y: f64,
21 br_radius_x: f64,
22 br_radius_y: f64,
23 bl_radius_x: f64,
24 bl_radius_y: f64,
25}
26
27impl Rounded {
28 fn raw(
29 left: f64,
30 top: f64,
31 right: f64,
32 bottom: f64,
33 tl_radius_x: f64,
34 tl_radius_y: f64,
35 tr_radius_x: f64,
36 tr_radius_y: f64,
37 br_radius_x: f64,
38 br_radius_y: f64,
39 bl_radius_x: f64,
40 bl_radius_y: f64,
41 ) -> Rounded {
42 debug_assert!(tl_radius_x >= 0.0);
43 debug_assert!(tl_radius_y >= 0.0);
44 debug_assert!(tr_radius_x >= 0.0);
45 debug_assert!(tr_radius_y >= 0.0);
46 debug_assert!(br_radius_x >= 0.0);
47 debug_assert!(br_radius_y >= 0.0);
48 debug_assert!(bl_radius_x >= 0.0);
49 debug_assert!(bl_radius_y >= 0.0);
50 Rounded {
51 left,
52 top,
53 right,
54 bottom,
55 tl_radius_x,
56 tl_radius_y,
57 tr_radius_x,
58 tr_radius_y,
59 br_radius_x,
60 br_radius_y,
61 bl_radius_x,
62 bl_radius_y,
63 }
64 }
65
66 fn uniform(
67 left: f64,
68 top: f64,
69 right: f64,
70 bottom: f64,
71 radius_x: f64,
72 radius_y: f64,
73 ) -> Rounded {
74 Rounded::raw(
75 left, top, right, bottom, radius_x, radius_y, radius_x, radius_y, radius_x, radius_y,
76 radius_x, radius_y,
77 )
78 }
79
80 fn corners(
81 left: f64,
82 top: f64,
83 right: f64,
84 bottom: f64,
85 top_left: Radius,
86 top_right: Radius,
87 bottom_right: Radius,
88 bottom_left: Radius,
89 ) -> Rounded {
90 Rounded::raw(
91 left,
92 top,
93 right,
94 bottom,
95 top_left.x,
96 top_left.y,
97 top_right.x,
98 top_right.y,
99 bottom_right.x,
100 bottom_right.y,
101 bottom_left.x,
102 bottom_left.y,
103 )
104 }
105
106 fn shift(self, offset: Offset) -> Rounded {
107 Rounded::raw(
108 self.left + offset.dx(),
109 self.top + offset.dy(),
110 self.right + offset.dx(),
111 self.bottom + offset.dy(),
112 self.tl_radius_x,
113 self.tl_radius_y,
114 self.tr_radius_x,
115 self.tr_radius_y,
116 self.br_radius_x,
117 self.br_radius_y,
118 self.bl_radius_x,
119 self.bl_radius_y,
120 )
121 }
122
123 fn inflate(self, delta: f64) -> Rounded {
124 Rounded::raw(
125 self.left - delta,
126 self.top - delta,
127 self.right + delta,
128 self.bottom + delta,
129 (self.tl_radius_x + delta).max(0.0),
130 (self.tl_radius_y + delta).max(0.0),
131 (self.tr_radius_x + delta).max(0.0),
132 (self.tr_radius_y + delta).max(0.0),
133 (self.br_radius_x + delta).max(0.0),
134 (self.br_radius_y + delta).max(0.0),
135 (self.bl_radius_x + delta).max(0.0),
136 (self.bl_radius_y + delta).max(0.0),
137 )
138 }
139
140 fn width(self) -> f64 {
141 self.right - self.left
142 }
143
144 fn height(self) -> f64 {
145 self.bottom - self.top
146 }
147
148 fn get_min(min: f64, radius1: f64, radius2: f64, limit: f64) -> f64 {
149 let sum = radius1 + radius2;
150 if sum > limit && sum != 0.0 {
151 return min.min(limit / sum);
152 }
153 min
154 }
155
156 fn scale_radii(self) -> Rounded {
157 let mut scale = 1.0;
158 scale = Rounded::get_min(scale, self.bl_radius_y, self.tl_radius_y, self.height());
159 scale = Rounded::get_min(scale, self.tl_radius_x, self.tr_radius_x, self.width());
160 scale = Rounded::get_min(scale, self.tr_radius_y, self.br_radius_y, self.height());
161 scale = Rounded::get_min(scale, self.br_radius_x, self.bl_radius_x, self.width());
162 debug_assert!(scale >= 0.0);
163 if scale < 1.0 {
164 Rounded::raw(
165 self.left,
166 self.top,
167 self.right,
168 self.bottom,
169 self.tl_radius_x * scale,
170 self.tl_radius_y * scale,
171 self.tr_radius_x * scale,
172 self.tr_radius_y * scale,
173 self.br_radius_x * scale,
174 self.br_radius_y * scale,
175 self.bl_radius_x * scale,
176 self.bl_radius_y * scale,
177 )
178 } else {
179 self
180 }
181 }
182
183 fn lerp_to(self, b: Option<Rounded>, t: f64) -> Rounded {
184 match b {
185 None => {
186 let k = 1.0 - t;
187 Rounded::raw(
188 self.left * k,
189 self.top * k,
190 self.right * k,
191 self.bottom * k,
192 (self.tl_radius_x * k).max(0.0),
193 (self.tl_radius_y * k).max(0.0),
194 (self.tr_radius_x * k).max(0.0),
195 (self.tr_radius_y * k).max(0.0),
196 (self.br_radius_x * k).max(0.0),
197 (self.br_radius_y * k).max(0.0),
198 (self.bl_radius_x * k).max(0.0),
199 (self.bl_radius_y * k).max(0.0),
200 )
201 }
202 Some(b) => Rounded::raw(
203 lerp_double_non_null(self.left, b.left, t),
204 lerp_double_non_null(self.top, b.top, t),
205 lerp_double_non_null(self.right, b.right, t),
206 lerp_double_non_null(self.bottom, b.bottom, t),
207 lerp_double_non_null(self.tl_radius_x, b.tl_radius_x, t).max(0.0),
208 lerp_double_non_null(self.tl_radius_y, b.tl_radius_y, t).max(0.0),
209 lerp_double_non_null(self.tr_radius_x, b.tr_radius_x, t).max(0.0),
210 lerp_double_non_null(self.tr_radius_y, b.tr_radius_y, t).max(0.0),
211 lerp_double_non_null(self.br_radius_x, b.br_radius_x, t).max(0.0),
212 lerp_double_non_null(self.br_radius_y, b.br_radius_y, t).max(0.0),
213 lerp_double_non_null(self.bl_radius_x, b.bl_radius_x, t).max(0.0),
214 lerp_double_non_null(self.bl_radius_y, b.bl_radius_y, t).max(0.0),
215 ),
216 }
217 }
218
219 fn fmt_debug(self, class_name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 let rect = format!(
221 "{:.1}, {:.1}, {:.1}, {:.1}",
222 self.left, self.top, self.right, self.bottom
223 );
224 let tl = Radius::elliptical(self.tl_radius_x, self.tl_radius_y);
225 let tr = Radius::elliptical(self.tr_radius_x, self.tr_radius_y);
226 let br = Radius::elliptical(self.br_radius_x, self.br_radius_y);
227 let bl = Radius::elliptical(self.bl_radius_x, self.bl_radius_y);
228 if tl == tr && tr == br && br == bl {
229 if tl.x == tl.y {
230 write!(f, "{class_name}.fromLTRBR({rect}, {:.1})", tl.x)
231 } else {
232 write!(
233 f,
234 "{class_name}.fromLTRBXY({rect}, {:.1}, {:.1})",
235 tl.x, tl.y
236 )
237 }
238 } else {
239 write!(
240 f,
241 "{class_name}.fromLTRBAndCorners({rect}, topLeft: {tl:?}, topRight: {tr:?}, bottomRight: {br:?}, bottomLeft: {bl:?})"
242 )
243 }
244 }
245}
246
247macro_rules! rounded_type {
248 ($name:ident) => {
249 impl $name {
250 fn from_inner(inner: Rounded) -> $name {
251 $name {
252 left: inner.left,
253 top: inner.top,
254 right: inner.right,
255 bottom: inner.bottom,
256 tl_radius_x: inner.tl_radius_x,
257 tl_radius_y: inner.tl_radius_y,
258 tr_radius_x: inner.tr_radius_x,
259 tr_radius_y: inner.tr_radius_y,
260 br_radius_x: inner.br_radius_x,
261 br_radius_y: inner.br_radius_y,
262 bl_radius_x: inner.bl_radius_x,
263 bl_radius_y: inner.bl_radius_y,
264 }
265 }
266
267 fn inner(self) -> Rounded {
268 Rounded {
269 left: self.left,
270 top: self.top,
271 right: self.right,
272 bottom: self.bottom,
273 tl_radius_x: self.tl_radius_x,
274 tl_radius_y: self.tl_radius_y,
275 tr_radius_x: self.tr_radius_x,
276 tr_radius_y: self.tr_radius_y,
277 br_radius_x: self.br_radius_x,
278 br_radius_y: self.br_radius_y,
279 bl_radius_x: self.bl_radius_x,
280 bl_radius_y: self.bl_radius_y,
281 }
282 }
283
284 pub fn from_ltrbxy(
287 left: f64,
288 top: f64,
289 right: f64,
290 bottom: f64,
291 radius_x: f64,
292 radius_y: f64,
293 ) -> $name {
294 $name::from_inner(Rounded::uniform(
295 left, top, right, bottom, radius_x, radius_y,
296 ))
297 }
298
299 pub fn from_ltrbr(
302 left: f64,
303 top: f64,
304 right: f64,
305 bottom: f64,
306 radius: Radius,
307 ) -> $name {
308 $name::from_ltrbxy(left, top, right, bottom, radius.x, radius.y)
309 }
310
311 pub fn from_rect_xy(rect: Rect, radius_x: f64, radius_y: f64) -> $name {
313 $name::from_ltrbxy(
314 rect.left,
315 rect.top,
316 rect.right,
317 rect.bottom,
318 radius_x,
319 radius_y,
320 )
321 }
322
323 pub fn from_rect_and_radius(rect: Rect, radius: Radius) -> $name {
326 $name::from_rect_xy(rect, radius.x, radius.y)
327 }
328
329 pub fn from_ltrb_and_corners(
331 left: f64,
332 top: f64,
333 right: f64,
334 bottom: f64,
335 top_left: Radius,
336 top_right: Radius,
337 bottom_right: Radius,
338 bottom_left: Radius,
339 ) -> $name {
340 $name::from_inner(Rounded::corners(
341 left,
342 top,
343 right,
344 bottom,
345 top_left,
346 top_right,
347 bottom_right,
348 bottom_left,
349 ))
350 }
351
352 pub fn from_rect_and_corners(
354 rect: Rect,
355 top_left: Radius,
356 top_right: Radius,
357 bottom_right: Radius,
358 bottom_left: Radius,
359 ) -> $name {
360 $name::from_ltrb_and_corners(
361 rect.left,
362 rect.top,
363 rect.right,
364 rect.bottom,
365 top_left,
366 top_right,
367 bottom_right,
368 bottom_left,
369 )
370 }
371
372 pub const ZERO: $name = $name {
374 left: 0.0,
375 top: 0.0,
376 right: 0.0,
377 bottom: 0.0,
378 tl_radius_x: 0.0,
379 tl_radius_y: 0.0,
380 tr_radius_x: 0.0,
381 tr_radius_y: 0.0,
382 br_radius_x: 0.0,
383 br_radius_y: 0.0,
384 bl_radius_x: 0.0,
385 bl_radius_y: 0.0,
386 };
387
388 pub fn tl_radius(self) -> Radius {
390 Radius::elliptical(self.tl_radius_x, self.tl_radius_y)
391 }
392
393 pub fn tr_radius(self) -> Radius {
395 Radius::elliptical(self.tr_radius_x, self.tr_radius_y)
396 }
397
398 pub fn br_radius(self) -> Radius {
400 Radius::elliptical(self.br_radius_x, self.br_radius_y)
401 }
402
403 pub fn bl_radius(self) -> Radius {
405 Radius::elliptical(self.bl_radius_x, self.bl_radius_y)
406 }
407
408 pub fn shift(self, offset: Offset) -> $name {
410 $name::from_inner(self.inner().shift(offset))
411 }
412
413 pub fn inflate(self, delta: f64) -> $name {
415 $name::from_inner(self.inner().inflate(delta))
416 }
417
418 pub fn deflate(self, delta: f64) -> $name {
420 self.inflate(-delta)
421 }
422
423 pub fn width(self) -> f64 {
425 self.inner().width()
426 }
427
428 pub fn height(self) -> f64 {
430 self.inner().height()
431 }
432
433 pub fn outer_rect(self) -> Rect {
435 Rect::from_ltrb(self.left, self.top, self.right, self.bottom)
436 }
437
438 pub fn safe_inner_rect(self) -> Rect {
441 const K_INSET_FACTOR: f64 = 0.29289321881; let left_radius = self.bl_radius_x.max(self.tl_radius_x);
443 let top_radius = self.tl_radius_y.max(self.tr_radius_y);
444 let right_radius = self.tr_radius_x.max(self.br_radius_x);
445 let bottom_radius = self.br_radius_y.max(self.bl_radius_y);
446 Rect::from_ltrb(
447 self.left + left_radius * K_INSET_FACTOR,
448 self.top + top_radius * K_INSET_FACTOR,
449 self.right - right_radius * K_INSET_FACTOR,
450 self.bottom - bottom_radius * K_INSET_FACTOR,
451 )
452 }
453
454 pub fn middle_rect(self) -> Rect {
457 let left_radius = self.bl_radius_x.max(self.tl_radius_x);
458 let top_radius = self.tl_radius_y.max(self.tr_radius_y);
459 let right_radius = self.tr_radius_x.max(self.br_radius_x);
460 let bottom_radius = self.br_radius_y.max(self.bl_radius_y);
461 Rect::from_ltrb(
462 self.left + left_radius,
463 self.top + top_radius,
464 self.right - right_radius,
465 self.bottom - bottom_radius,
466 )
467 }
468
469 pub fn wide_middle_rect(self) -> Rect {
471 let top_radius = self.tl_radius_y.max(self.tr_radius_y);
472 let bottom_radius = self.br_radius_y.max(self.bl_radius_y);
473 Rect::from_ltrb(
474 self.left,
475 self.top + top_radius,
476 self.right,
477 self.bottom - bottom_radius,
478 )
479 }
480
481 pub fn tall_middle_rect(self) -> Rect {
483 let left_radius = self.bl_radius_x.max(self.tl_radius_x);
484 let right_radius = self.tr_radius_x.max(self.br_radius_x);
485 Rect::from_ltrb(
486 self.left + left_radius,
487 self.top,
488 self.right - right_radius,
489 self.bottom,
490 )
491 }
492
493 pub fn is_empty(self) -> bool {
495 self.left >= self.right || self.top >= self.bottom
496 }
497
498 pub fn is_finite(self) -> bool {
500 self.left.is_finite()
501 && self.top.is_finite()
502 && self.right.is_finite()
503 && self.bottom.is_finite()
504 }
505
506 pub fn is_rect(self) -> bool {
508 (self.tl_radius_x == 0.0 || self.tl_radius_y == 0.0)
509 && (self.tr_radius_x == 0.0 || self.tr_radius_y == 0.0)
510 && (self.bl_radius_x == 0.0 || self.bl_radius_y == 0.0)
511 && (self.br_radius_x == 0.0 || self.br_radius_y == 0.0)
512 }
513
514 pub fn is_stadium(self) -> bool {
516 self.tl_radius() == self.tr_radius()
517 && self.tr_radius() == self.br_radius()
518 && self.br_radius() == self.bl_radius()
519 && (self.width() <= 2.0 * self.tl_radius_x
520 || self.height() <= 2.0 * self.tl_radius_y)
521 }
522
523 pub fn is_ellipse(self) -> bool {
525 self.tl_radius() == self.tr_radius()
526 && self.tr_radius() == self.br_radius()
527 && self.br_radius() == self.bl_radius()
528 && self.width() <= 2.0 * self.tl_radius_x
529 && self.height() <= 2.0 * self.tl_radius_y
530 }
531
532 pub fn is_circle(self) -> bool {
534 self.width() == self.height() && self.is_ellipse()
535 }
536
537 pub fn shortest_side(self) -> f64 {
539 self.width().abs().min(self.height().abs())
540 }
541
542 pub fn longest_side(self) -> f64 {
544 self.width().abs().max(self.height().abs())
545 }
546
547 pub fn has_nan(self) -> bool {
549 self.left.is_nan()
550 || self.top.is_nan()
551 || self.right.is_nan()
552 || self.bottom.is_nan()
553 || self.tr_radius_x.is_nan()
554 || self.tr_radius_y.is_nan()
555 || self.tl_radius_x.is_nan()
556 || self.tl_radius_y.is_nan()
557 || self.br_radius_x.is_nan()
558 || self.br_radius_y.is_nan()
559 || self.bl_radius_x.is_nan()
560 || self.bl_radius_y.is_nan()
561 }
562
563 pub fn center(self) -> Offset {
566 Offset::new(
567 self.left + self.width() / 2.0,
568 self.top + self.height() / 2.0,
569 )
570 }
571
572 pub fn scale_radii(self) -> $name {
575 $name::from_inner(self.inner().scale_radii())
576 }
577
578 fn lerp_to(self, b: Option<$name>, t: f64) -> $name {
579 $name::from_inner(self.inner().lerp_to(b.map($name::inner), t))
580 }
581
582 pub fn lerp(a: Option<$name>, b: Option<$name>, t: f64) -> Option<$name> {
586 match a {
587 None => b.map(|b| b.lerp_to(None, 1.0 - t)),
588 Some(a) => Some(a.lerp_to(b, t)),
589 }
590 }
591 }
592 };
593}
594
595#[derive(Clone, Copy, PartialEq)]
597pub struct RRect {
598 pub left: f64,
600 pub top: f64,
602 pub right: f64,
604 pub bottom: f64,
606 pub tl_radius_x: f64,
608 pub tl_radius_y: f64,
610 pub tr_radius_x: f64,
612 pub tr_radius_y: f64,
614 pub br_radius_x: f64,
616 pub br_radius_y: f64,
618 pub bl_radius_x: f64,
620 pub bl_radius_y: f64,
622}
623
624rounded_type!(RRect);
625
626impl RRect {
627 pub fn contains(self, point: Offset) -> bool {
630 if point.dx() < self.left
631 || point.dx() >= self.right
632 || point.dy() < self.top
633 || point.dy() >= self.bottom
634 {
635 return false;
636 }
637 let scaled = self.scale_radii();
638 let (x, y, radius_x, radius_y);
639 if point.dx() < scaled.left + scaled.tl_radius_x
640 && point.dy() < scaled.top + scaled.tl_radius_y
641 {
642 x = point.dx() - scaled.left - scaled.tl_radius_x;
643 y = point.dy() - scaled.top - scaled.tl_radius_y;
644 radius_x = scaled.tl_radius_x;
645 radius_y = scaled.tl_radius_y;
646 } else if point.dx() > scaled.right - scaled.tr_radius_x
647 && point.dy() < scaled.top + scaled.tr_radius_y
648 {
649 x = point.dx() - scaled.right + scaled.tr_radius_x;
650 y = point.dy() - scaled.top - scaled.tr_radius_y;
651 radius_x = scaled.tr_radius_x;
652 radius_y = scaled.tr_radius_y;
653 } else if point.dx() > scaled.right - scaled.br_radius_x
654 && point.dy() > scaled.bottom - scaled.br_radius_y
655 {
656 x = point.dx() - scaled.right + scaled.br_radius_x;
657 y = point.dy() - scaled.bottom + scaled.br_radius_y;
658 radius_x = scaled.br_radius_x;
659 radius_y = scaled.br_radius_y;
660 } else if point.dx() < scaled.left + scaled.bl_radius_x
661 && point.dy() > scaled.bottom - scaled.bl_radius_y
662 {
663 x = point.dx() - scaled.left - scaled.bl_radius_x;
664 y = point.dy() - scaled.bottom + scaled.bl_radius_y;
665 radius_x = scaled.bl_radius_x;
666 radius_y = scaled.bl_radius_y;
667 } else {
668 return true;
669 }
670 let x = x / radius_x;
671 let y = y / radius_y;
672 x * x + y * y <= 1.0
673 }
674}
675
676impl Debug for RRect {
677 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
678 self.inner().fmt_debug("RRect", f)
679 }
680}
681
682#[derive(Clone, Copy, PartialEq)]
688pub struct RSuperellipse {
689 pub left: f64,
691 pub top: f64,
693 pub right: f64,
695 pub bottom: f64,
697 pub tl_radius_x: f64,
699 pub tl_radius_y: f64,
701 pub tr_radius_x: f64,
703 pub tr_radius_y: f64,
705 pub br_radius_x: f64,
707 pub br_radius_y: f64,
709 pub bl_radius_x: f64,
711 pub bl_radius_y: f64,
713}
714
715rounded_type!(RSuperellipse);
716
717impl RSuperellipse {
718 pub fn contains(self, point: Offset) -> bool {
724 valo_geometry::RoundSuperellipse::new(
725 self.outer_rect().into(),
726 super::rsuperellipse_radii_elliptical(self),
727 )
728 .contains(point.into())
729 }
730}
731
732impl Debug for RSuperellipse {
733 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734 self.inner().fmt_debug("RSuperellipse", f)
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741
742 #[test]
744 fn from_rect_xy() {
745 let base_rect = Rect::from_ltwh(1.0, 3.0, 5.0, 7.0);
746 let r = RRect::from_rect_xy(base_rect, 1.0, 1.0);
747 assert_eq!(r.left, 1.0);
748 assert_eq!(r.top, 3.0);
749 assert_eq!(r.right, 6.0);
750 assert_eq!(r.bottom, 10.0);
751 assert_eq!(r.shortest_side(), 5.0);
752 assert_eq!(r.longest_side(), 7.0);
753 }
754
755 fn sample_rrect() -> RRect {
756 RRect::from_rect_and_corners(
757 Rect::from_ltrb(1.0, 1.0, 2.0, 2.0),
758 Radius::circular(0.5),
759 Radius::circular(0.25),
760 Radius::elliptical(0.25, 0.75),
761 Radius::ZERO,
762 )
763 }
764
765 #[test]
767 fn contains_points() {
768 let rrect = sample_rrect();
769 assert!(!rrect.contains(Offset::new(1.0, 1.0)));
770 assert!(!rrect.contains(Offset::new(1.1, 1.1)));
771 assert!(rrect.contains(Offset::new(1.15, 1.15)));
772 assert!(!rrect.contains(Offset::new(2.0, 1.0)));
773 assert!(!rrect.contains(Offset::new(1.93, 1.07)));
774 assert!(!rrect.contains(Offset::new(1.97, 1.7)));
775 assert!(rrect.contains(Offset::new(1.7, 1.97)));
776 assert!(rrect.contains(Offset::new(1.0, 1.99)));
777 }
778
779 #[test]
781 fn contains_large_radii() {
782 let rrect = RRect::from_rect_and_corners(
783 Rect::from_ltrb(1.0, 1.0, 2.0, 2.0),
784 Radius::circular(5000.0),
785 Radius::circular(2500.0),
786 Radius::elliptical(2500.0, 7500.0),
787 Radius::ZERO,
788 );
789 assert!(!rrect.contains(Offset::new(1.0, 1.0)));
790 assert!(!rrect.contains(Offset::new(1.1, 1.1)));
791 assert!(rrect.contains(Offset::new(1.15, 1.15)));
792 assert!(!rrect.contains(Offset::new(2.0, 1.0)));
793 assert!(!rrect.contains(Offset::new(1.93, 1.07)));
794 assert!(!rrect.contains(Offset::new(1.97, 1.7)));
795 assert!(rrect.contains(Offset::new(1.7, 1.97)));
796 assert!(rrect.contains(Offset::new(1.0, 1.99)));
797 }
798
799 fn assert_scaled_corners(rrect: RRect) {
800 assert_eq!(rrect.left, 1.0);
801 assert_eq!(rrect.top, 1.0);
802 assert_eq!(rrect.right, 2.0);
803 assert_eq!(rrect.bottom, 2.0);
804 assert_eq!(rrect.tl_radius_x, 0.5);
805 assert_eq!(rrect.tl_radius_y, 0.5);
806 assert_eq!(rrect.tr_radius_x, 0.25);
807 assert_eq!(rrect.tr_radius_y, 0.25);
808 assert_eq!(rrect.bl_radius_x, 0.0);
809 assert_eq!(rrect.bl_radius_y, 0.0);
810 assert_eq!(rrect.br_radius_x, 0.25);
811 assert_eq!(rrect.br_radius_y, 0.75);
812 }
813
814 #[test]
816 fn scale_radii_leaves_valid_radii_unchanged() {
817 assert_scaled_corners(sample_rrect().scale_radii());
818 }
819
820 #[test]
822 fn scale_radii_when_sum_exceeds_side() {
823 let rrect = RRect::from_rect_and_corners(
824 Rect::from_ltrb(1.0, 1.0, 2.0, 2.0),
825 Radius::circular(5000.0),
826 Radius::circular(2500.0),
827 Radius::elliptical(2500.0, 7500.0),
828 Radius::ZERO,
829 )
830 .scale_radii();
831 assert_scaled_corners(rrect);
832 }
833
834 #[test]
836 #[cfg(debug_assertions)]
837 #[should_panic]
838 fn negative_corner_radii_panic_in_debug() {
839 let _ = RRect::from_rect_and_corners(
840 Rect::from_ltrb(10.0, 20.0, 30.0, 40.0),
841 Radius::circular(-1.0),
842 Radius::ZERO,
843 Radius::ZERO,
844 Radius::ZERO,
845 );
846 }
847
848 #[test]
850 fn inflate_clamps_when_deflating_past_zero() {
851 let mut rrect = RRect::from_rect_and_corners(
852 Rect::from_ltrb(10.0, 20.0, 30.0, 40.0),
853 Radius::circular(1.0),
854 Radius::circular(2.0),
855 Radius::circular(4.0),
856 Radius::circular(3.0),
857 )
858 .inflate(-1.0);
859 assert_eq!(rrect.tl_radius_x, 0.0);
860 assert_eq!(rrect.tl_radius_y, 0.0);
861 assert_eq!(rrect.tr_radius_x, 1.0);
862 assert_eq!(rrect.tr_radius_y, 1.0);
863 assert_eq!(rrect.bl_radius_x, 2.0);
864 assert_eq!(rrect.bl_radius_y, 2.0);
865 assert_eq!(rrect.br_radius_x, 3.0);
866 assert_eq!(rrect.br_radius_y, 3.0);
867
868 rrect = rrect.inflate(-1.0);
869 assert_eq!(rrect.tl_radius_x, 0.0);
870 assert_eq!(rrect.tr_radius_x, 0.0);
871 assert_eq!(rrect.bl_radius_x, 1.0);
872 assert_eq!(rrect.br_radius_x, 2.0);
873
874 rrect = rrect.inflate(-1.0);
875 assert_eq!(rrect.bl_radius_x, 0.0);
876 assert_eq!(rrect.br_radius_x, 1.0);
877
878 rrect = rrect.inflate(-1.0);
879 assert_eq!(rrect.br_radius_x, 0.0);
880 assert_eq!(rrect.br_radius_y, 0.0);
881 }
882
883 #[test]
884 fn rrect_and_rsuperellipse_are_unequal_at_the_same_floats() {
885 let rect = Rect::from_ltrb(0.0, 0.0, 10.0, 10.0);
886 let rrect = RRect::from_rect_and_radius(rect, Radius::circular(2.0));
887 let rse = RSuperellipse::from_rect_and_radius(rect, Radius::circular(2.0));
888 assert_eq!(rrect.left, rse.left);
889 assert!(format!("{rrect:?}").starts_with("RRect"));
890 assert!(format!("{rse:?}").starts_with("RSuperellipse"));
891 }
892
893 #[test]
894 fn rsuperellipse_contains_the_center_and_rejects_the_outside() {
895 let rse = RSuperellipse::from_rect_and_radius(
896 Rect::from_ltrb(0.0, 0.0, 10.0, 10.0),
897 Radius::circular(2.0),
898 );
899 assert!(rse.contains(Offset::new(5.0, 5.0)));
900 assert!(!rse.contains(Offset::new(-1.0, 5.0)));
901 }
902}