1use std::f64::consts::{FRAC_PI_2, PI};
2use std::ops::{Add, Sub};
3
4use kurbo::{Arc, ArcAppendIter, Ellipse, PathEl, Point, Rect, Shape, Size, Vec2};
5
6#[derive(Debug, Clone, Copy)]
9pub struct RoundRect {
10 rect: Rect,
11 radii: Vec2,
12}
13
14impl RoundRect {
15 #[inline]
17 #[must_use]
18 pub fn new(x0: f64, y0: f64, x1: f64, y1: f64, rx: f64, ry: f64) -> Self {
19 Self::from_rect(Rect::new(x0, y0, x1, y1), Vec2::new(rx, ry))
20 }
21
22 #[inline]
24 #[must_use]
25 pub fn from_rect(rect: Rect, radii: impl Into<Vec2>) -> Self {
26 let rect = rect.abs();
27 let radii = radii.into();
28 let radii = Vec2::new(
29 radii.x.min(rect.width() / 2.0),
30 radii.y.min(rect.height() / 2.0),
31 );
32
33 Self { rect, radii }
34 }
35
36 #[inline]
38 #[must_use]
39 pub fn from_points(p0: impl Into<Point>, p1: impl Into<Point>, radii: impl Into<Vec2>) -> Self {
40 Self::from_rect(Rect::from_points(p0, p1), radii)
41 }
42
43 #[inline]
45 #[must_use]
46 pub fn from_origin_size(
47 origin: impl Into<Point>,
48 size: impl Into<Size>,
49 radii: impl Into<Vec2>,
50 ) -> Self {
51 Self::from_rect(Rect::from_origin_size(origin, size), radii)
52 }
53
54 #[inline]
56 #[must_use]
57 pub fn from_center_size(
58 origin: impl Into<Point>,
59 size: impl Into<Size>,
60 radii: impl Into<Vec2>,
61 ) -> Self {
62 Self::from_rect(Rect::from_center_size(origin, size), radii)
63 }
64
65 #[inline]
67 #[must_use]
68 pub fn width(&self) -> f64 {
69 self.rect.width()
70 }
71
72 #[inline]
74 #[must_use]
75 pub fn height(&self) -> f64 {
76 self.rect.height()
77 }
78
79 #[inline]
81 #[must_use]
82 pub const fn radii(&self) -> Vec2 {
83 self.radii
84 }
85
86 #[inline]
88 #[must_use]
89 pub const fn rect(&self) -> Rect {
90 self.rect
91 }
92
93 #[inline]
95 #[must_use]
96 pub fn origin(&self) -> Point {
97 self.rect.origin()
98 }
99
100 #[inline]
102 #[must_use]
103 pub fn center(&self) -> Point {
104 self.rect.center()
105 }
106
107 #[inline]
109 #[must_use]
110 pub fn size(&self) -> Size {
111 self.rect().size()
112 }
113
114 #[inline]
116 #[must_use]
117 pub fn with_origin(self, origin: impl Into<Point>) -> Self {
118 Self::from_origin_size(origin, self.size(), self.radii())
119 }
120
121 #[inline]
123 #[must_use]
124 pub fn with_size(self, size: impl Into<Size>) -> Self {
125 Self::from_origin_size(self.origin(), size, self.radii())
126 }
127
128 #[inline]
130 #[must_use]
131 pub fn with_radii(self, radii: impl Into<Vec2>) -> Self {
132 Self::from_origin_size(self.origin(), self.size(), radii)
133 }
134}
135
136#[doc(hidden)]
137#[allow(clippy::module_name_repetitions)]
138pub struct RoundRectPathIter {
139 idx: usize,
140 rect: RectPathIter,
141 arcs: [ArcAppendIter; 4],
142}
143
144impl Shape for RoundRect {
145 type PathElementsIter<'iter> = RoundRectPathIter;
146
147 fn path_elements(&self, tolerance: f64) -> RoundRectPathIter {
148 let radii = self.radii();
149
150 let build_arc_iter = |i, center, ellipse_radii| {
151 let arc = Arc {
152 center,
153 radii: ellipse_radii,
154 start_angle: FRAC_PI_2 * f64::from(i),
155 sweep_angle: FRAC_PI_2,
156 x_rotation: 0.0,
157 };
158 arc.append_iter(tolerance)
159 };
160
161 let arcs = [
163 build_arc_iter(
164 2,
165 Point {
166 x: self.rect.x0 + radii.x,
167 y: self.rect.y0 + radii.y,
168 },
169 radii,
170 ),
171 build_arc_iter(
172 3,
173 Point {
174 x: self.rect.x1 - radii.x,
175 y: self.rect.y0 + radii.y,
176 },
177 radii,
178 ),
179 build_arc_iter(
180 0,
181 Point {
182 x: self.rect.x1 - radii.x,
183 y: self.rect.y1 - radii.y,
184 },
185 radii,
186 ),
187 build_arc_iter(
188 1,
189 Point {
190 x: self.rect.x0 + radii.x,
191 y: self.rect.y1 - radii.y,
192 },
193 radii,
194 ),
195 ];
196
197 let rect = RectPathIter {
198 rect: self.rect,
199 ix: 0,
200 radii,
201 };
202
203 RoundRectPathIter { idx: 0, rect, arcs }
204 }
205
206 #[inline]
207 fn area(&self) -> f64 {
208 self.rect.area() - (4.0 - PI) * self.radii().x * self.radii().y
209 }
210
211 #[inline]
212 fn perimeter(&self, accuracy: f64) -> f64 {
213 self.rect.perimeter(accuracy) - 4.0 * (self.radii.x + self.radii.y)
214 + Ellipse::new(Point::ORIGIN, self.radii, 0.0).perimeter(accuracy)
215 }
216
217 #[inline]
218 fn winding(&self, mut pt: Point) -> i32 {
219 let center = self.center();
220 let Vec2 { x: rx, y: ry } = self.radii;
221
222 if rx <= 0.0 || ry <= 0.0 {
223 return self.bounding_box().winding(pt);
224 }
225
226 pt.x -= center.x;
228 pt.y -= center.y;
229
230 let inside_half_width = (self.width() / 2.0 - rx).max(0.0);
234 let inside_half_height = (self.height() / 2.0 - ry).max(0.0);
235
236 let px = (pt.x.abs() - inside_half_width).max(0.0);
246 let py = (pt.y.abs() - inside_half_height).max(0.0);
247
248 let inside = (px * px) / (rx * rx) + (py * py) / (ry * ry) <= 1.0;
253 i32::from(inside)
254 }
255
256 #[inline]
257 fn bounding_box(&self) -> Rect {
258 self.rect.bounding_box()
259 }
260}
261
262struct RectPathIter {
263 rect: Rect,
264 radii: Vec2,
265 ix: usize,
266}
267
268impl Iterator for RectPathIter {
270 type Item = PathEl;
271
272 fn next(&mut self) -> Option<PathEl> {
273 self.ix += 1;
274 match self.ix {
275 1 => Some(PathEl::MoveTo(Point::new(
276 self.rect.x0,
277 self.rect.y0 + self.radii.y,
278 ))),
279 2 => Some(PathEl::LineTo(Point::new(
280 self.rect.x1 - self.radii.x,
281 self.rect.y0,
282 ))),
283 3 => Some(PathEl::LineTo(Point::new(
284 self.rect.x1,
285 self.rect.y1 - self.radii.y,
286 ))),
287 4 => Some(PathEl::LineTo(Point::new(
288 self.rect.x0 + self.radii.x,
289 self.rect.y1,
290 ))),
291 5 => Some(PathEl::ClosePath),
292 _ => None, }
294 }
295}
296
297impl Iterator for RoundRectPathIter {
299 type Item = PathEl;
300
301 fn next(&mut self) -> Option<PathEl> {
302 if self.idx > 4 {
303 return None;
304 }
305
306 if self.idx == 0 {
311 self.idx += 1;
312 return self.rect.next();
313 }
314
315 if let Some(elem) = self.arcs[self.idx - 1].next() {
318 Some(elem)
319 } else {
320 self.idx += 1;
321 self.rect.next()
322 }
323 }
324}
325
326impl Add<Vec2> for RoundRect {
327 type Output = Self;
328
329 #[inline]
330 fn add(self, v: Vec2) -> Self::Output {
331 Self::from_rect(self.rect + v, self.radii)
332 }
333}
334
335impl Sub<Vec2> for RoundRect {
336 type Output = Self;
337
338 #[inline]
339 fn sub(self, v: Vec2) -> Self::Output {
340 Self::from_rect(self.rect - v, self.radii)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use assert_approx_eq::assert_approx_eq;
347 use kurbo::{Circle, Point, Rect, Shape};
348
349 use super::*;
350
351 #[test]
352 fn test_round_rect_new() {
353 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
354
355 assert_eq!(rect.rect.x0, 1.0);
356 assert_eq!(rect.rect.y0, 2.0);
357 assert_eq!(rect.rect.x1, 3.0);
358 assert_eq!(rect.rect.y1, 5.0);
359 assert_eq!(rect.radii.x, 0.25);
360 assert_eq!(rect.radii.y, 0.75);
361 }
362
363 #[test]
364 fn test_round_rect_from_rect() {
365 let rect = RoundRect::from_rect(Rect::new(1.0, 2.0, 3.0, 5.0), (0.25, 0.75));
366
367 assert_eq!(rect.rect.x0, 1.0);
368 assert_eq!(rect.rect.y0, 2.0);
369 assert_eq!(rect.rect.x1, 3.0);
370 assert_eq!(rect.rect.y1, 5.0);
371 assert_eq!(rect.radii.x, 0.25);
372 assert_eq!(rect.radii.y, 0.75);
373 }
374
375 #[test]
376 fn test_round_rect_from_points() {
377 let rect = RoundRect::from_points((1.0, 2.0), (3.0, 5.0), (0.25, 0.75));
378
379 assert_eq!(rect.rect.x0, 1.0);
380 assert_eq!(rect.rect.y0, 2.0);
381 assert_eq!(rect.rect.x1, 3.0);
382 assert_eq!(rect.rect.y1, 5.0);
383 assert_eq!(rect.radii.x, 0.25);
384 assert_eq!(rect.radii.y, 0.75);
385 }
386
387 #[test]
388 fn test_round_rect_from_origin_size() {
389 let rect = RoundRect::from_origin_size((1.0, 2.0), (2.0, 3.0), (0.25, 0.75));
390
391 assert_eq!(rect.rect.x0, 1.0);
392 assert_eq!(rect.rect.y0, 2.0);
393 assert_eq!(rect.rect.x1, 3.0);
394 assert_eq!(rect.rect.y1, 5.0);
395 assert_eq!(rect.radii.x, 0.25);
396 assert_eq!(rect.radii.y, 0.75);
397 }
398
399 #[test]
400 fn test_round_rect_from_center_size() {
401 let rect = RoundRect::from_center_size((2.0, 3.5), (2.0, 3.0), (0.25, 0.75));
402
403 assert_eq!(rect.rect.x0, 1.0);
404 assert_eq!(rect.rect.y0, 2.0);
405 assert_eq!(rect.rect.x1, 3.0);
406 assert_eq!(rect.rect.y1, 5.0);
407 assert_eq!(rect.radii.x, 0.25);
408 assert_eq!(rect.radii.y, 0.75);
409 }
410
411 #[test]
412 fn test_round_rect_width() {
413 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
414
415 assert_eq!(rect.width(), 2.0);
416 }
417
418 #[test]
419 fn test_round_rect_height() {
420 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
421
422 assert_eq!(rect.height(), 3.0);
423 }
424
425 #[test]
426 fn test_round_rect_radii() {
427 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
428
429 assert_eq!(rect.radii(), Vec2::new(0.25, 0.75));
430 }
431
432 #[test]
433 fn test_round_rect_rect() {
434 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
435
436 assert_eq!(rect.rect(), Rect::new(1.0, 2.0, 3.0, 5.0));
437 }
438
439 #[test]
440 fn test_round_rect_origin() {
441 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
442
443 assert_eq!(rect.origin(), Point::new(1.0, 2.0));
444 }
445
446 #[test]
447 fn test_round_rect_center() {
448 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
449
450 assert_eq!(rect.center(), Point::new(2.0, 3.5));
451 }
452
453 #[test]
454 fn test_round_rect_size() {
455 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
456
457 assert_eq!(rect.size(), Size::new(2.0, 3.0));
458 }
459
460 #[test]
461 fn test_round_rect_with_origin() {
462 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75).with_origin((2.0, 4.0));
463
464 assert_eq!(rect.origin(), Point::new(2.0, 4.0));
465 }
466
467 #[test]
468 fn test_round_rect_with_size() {
469 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75).with_size((3.0, 1.0));
470
471 assert_eq!(rect.size(), Size::new(3.0, 1.0));
472 }
473
474 #[test]
475 fn test_round_rect_with_radii() {
476 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75).with_radii((0.5, 0.25));
477
478 assert_eq!(rect.radii(), Vec2::new(0.5, 0.25));
479 }
480
481 #[test]
482 fn test_round_rect_path_elements() {
483 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75);
484 let path = rect.to_path(1e-9);
485
486 assert_approx_eq!(rect.area(), path.area());
487 assert_eq!(path.winding(Point::new(2.0, 3.0)), 1);
488 }
489
490 #[test]
491 fn test_round_rect_area() {
492 let ref_rect = Rect::new(0.0, 0.0, 10.0, 10.0);
494 let rect = RoundRect::new(0.0, 0.0, 10.0, 10.0, 0.0, 0.0);
495 assert_approx_eq!(ref_rect.area(), rect.area());
496
497 let circle = Circle::new((0.0, 0.0), 5.0);
499 let rect = RoundRect::new(0.0, 0.0, 10.0, 10.0, 5.0, 5.0);
500 assert_approx_eq!(circle.area(), rect.area());
501 }
502
503 #[test]
504 fn test_round_rect_perimeter() {
505 let rect = RoundRect::new(0.0, 0.0, 10.0, 10.0, 0.0, 0.0);
507 assert_approx_eq!(rect.perimeter(1.0), 40.0);
508
509 let rect = RoundRect::new(0.0, 0.0, 10.0, 10.0, 5.0, 5.0);
511 assert_approx_eq!(rect.perimeter(1.0), 10. * PI, 0.01);
512 }
513
514 #[test]
515 fn test_round_rect_winding() {
516 let rect = RoundRect::new(-5.0, -5.0, 10.0, 20.0, 5.0, 5.0);
517 assert_eq!(rect.winding(Point::new(0.0, 0.0)), 1);
518 assert_eq!(rect.winding(Point::new(-5.0, 0.0)), 1); assert_eq!(rect.winding(Point::new(0.0, 20.0)), 1); assert_eq!(rect.winding(Point::new(10.0, 20.0)), 0); assert_eq!(rect.winding(Point::new(-5.0, 20.0)), 0); assert_eq!(rect.winding(Point::new(-10.0, 0.0)), 0);
523
524 let rect = RoundRect::new(-10.0, -20.0, 10.0, 20.0, 0.0, 0.0); assert_eq!(rect.winding(Point::new(10.0, 20.0)), 0); }
527
528 #[test]
529 fn test_round_rect_add_sub() {
530 let rect = RoundRect::new(1.0, 2.0, 3.0, 5.0, 0.25, 0.75) + Vec2::new(2.0, 3.0);
531
532 assert_eq!(rect.rect, Rect::new(3.0, 5.0, 5.0, 8.0));
533 assert_eq!(rect.radii, Vec2::new(0.25, 0.75));
534
535 let rect = rect - Vec2::new(1.0, 4.0);
536
537 assert_eq!(rect.rect, Rect::new(2.0, 1.0, 4.0, 4.0));
538 assert_eq!(rect.radii, Vec2::new(0.25, 0.75));
539 }
540}