1use core::f64::consts::{FRAC_PI_2, FRAC_PI_4};
7use core::ops::{Add, Sub};
8
9use crate::{arc::ArcAppendIter, Arc, PathEl, Point, Rect, RoundedRectRadii, Shape, Size, Vec2};
10
11#[allow(unused_imports)] #[cfg(not(feature = "std"))]
13use crate::common::FloatFuncs;
14
15#[derive(Clone, Copy, Default, Debug, PartialEq)]
36#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct RoundedRect {
39 rect: Rect,
41 radii: RoundedRectRadii,
43}
44
45impl RoundedRect {
46 #[inline]
50 pub fn new(
51 x0: f64,
52 y0: f64,
53 x1: f64,
54 y1: f64,
55 radii: impl Into<RoundedRectRadii>,
56 ) -> RoundedRect {
57 RoundedRect::from_rect(Rect::new(x0, y0, x1, y1), radii)
58 }
59
60 #[inline]
66 pub fn from_rect(rect: Rect, radii: impl Into<RoundedRectRadii>) -> RoundedRect {
67 let rect = rect.abs();
68 let shortest_side_length = (rect.width()).min(rect.height());
69 let radii = radii.into().abs().clamp(shortest_side_length / 2.0);
70
71 RoundedRect { rect, radii }
72 }
73
74 #[inline]
78 pub fn from_points(
79 p0: impl Into<Point>,
80 p1: impl Into<Point>,
81 radii: impl Into<RoundedRectRadii>,
82 ) -> RoundedRect {
83 Rect::from_points(p0, p1).to_rounded_rect(radii)
84 }
85
86 #[inline]
90 pub fn from_origin_size(
91 origin: impl Into<Point>,
92 size: impl Into<Size>,
93 radii: impl Into<RoundedRectRadii>,
94 ) -> RoundedRect {
95 Rect::from_origin_size(origin, size).to_rounded_rect(radii)
96 }
97
98 #[inline]
100 pub fn width(&self) -> f64 {
101 self.rect.width()
102 }
103
104 #[inline]
106 pub fn height(&self) -> f64 {
107 self.rect.height()
108 }
109
110 #[inline(always)]
112 pub fn radii(&self) -> RoundedRectRadii {
113 self.radii
114 }
115
116 #[inline(always)]
118 pub fn rect(&self) -> Rect {
119 self.rect
120 }
121
122 #[inline(always)]
126 pub fn origin(&self) -> Point {
127 self.rect.origin()
128 }
129
130 #[inline]
132 pub fn center(&self) -> Point {
133 self.rect.center()
134 }
135
136 #[inline]
138 pub fn is_finite(&self) -> bool {
139 self.rect.is_finite() && self.radii.is_finite()
140 }
141
142 #[inline]
144 pub fn is_nan(&self) -> bool {
145 self.rect.is_nan() || self.radii.is_nan()
146 }
147}
148
149#[doc(hidden)]
150pub struct RoundedRectPathIter {
151 idx: usize,
152 rect: RectPathIter,
153 arcs: [ArcAppendIter; 4],
154}
155
156impl Shape for RoundedRect {
157 type PathElementsIter<'iter> = RoundedRectPathIter;
158
159 fn path_elements(&self, tolerance: f64) -> RoundedRectPathIter {
160 let radii = self.radii();
161
162 let build_arc_iter = |i, center, ellipse_radii| {
163 let arc = Arc {
164 center,
165 radii: ellipse_radii,
166 start_angle: FRAC_PI_2 * i as f64,
167 sweep_angle: FRAC_PI_2,
168 x_rotation: 0.0,
169 };
170 arc.append_iter(tolerance)
171 };
172
173 let arcs = [
175 build_arc_iter(
176 2,
177 Point {
178 x: self.rect.x0 + radii.top_left,
179 y: self.rect.y0 + radii.top_left,
180 },
181 Vec2 {
182 x: radii.top_left,
183 y: radii.top_left,
184 },
185 ),
186 build_arc_iter(
187 3,
188 Point {
189 x: self.rect.x1 - radii.top_right,
190 y: self.rect.y0 + radii.top_right,
191 },
192 Vec2 {
193 x: radii.top_right,
194 y: radii.top_right,
195 },
196 ),
197 build_arc_iter(
198 0,
199 Point {
200 x: self.rect.x1 - radii.bottom_right,
201 y: self.rect.y1 - radii.bottom_right,
202 },
203 Vec2 {
204 x: radii.bottom_right,
205 y: radii.bottom_right,
206 },
207 ),
208 build_arc_iter(
209 1,
210 Point {
211 x: self.rect.x0 + radii.bottom_left,
212 y: self.rect.y1 - radii.bottom_left,
213 },
214 Vec2 {
215 x: radii.bottom_left,
216 y: radii.bottom_left,
217 },
218 ),
219 ];
220
221 let rect = RectPathIter {
222 rect: self.rect,
223 ix: 0,
224 radii,
225 };
226
227 RoundedRectPathIter { idx: 0, rect, arcs }
228 }
229
230 #[inline]
231 fn area(&self) -> f64 {
232 let radii = self.radii();
245
246 self.rect.area()
250 + [
251 radii.top_left,
252 radii.top_right,
253 radii.bottom_right,
254 radii.bottom_left,
255 ]
256 .iter()
257 .map(|radius| (FRAC_PI_4 - 1.0) * radius * radius)
258 .sum::<f64>()
259 }
260
261 #[inline]
262 fn perimeter(&self, _accuracy: f64) -> f64 {
263 let radii = self.radii();
278
279 self.rect.perimeter(1.0)
283 + ([
284 radii.top_left,
285 radii.top_right,
286 radii.bottom_right,
287 radii.bottom_left,
288 ])
289 .iter()
290 .map(|radius| (-2.0 + FRAC_PI_2) * radius)
291 .sum::<f64>()
292 }
293
294 #[inline]
295 fn winding(&self, mut pt: Point) -> i32 {
296 let center = self.center();
297
298 pt.x -= center.x;
300 pt.y -= center.y;
301
302 let radii = self.radii();
305 let radius = match pt {
306 pt if pt.x < 0.0 && pt.y < 0.0 => radii.top_left,
307 pt if pt.x >= 0.0 && pt.y < 0.0 => radii.top_right,
308 pt if pt.x >= 0.0 && pt.y >= 0.0 => radii.bottom_right,
309 pt if pt.x < 0.0 && pt.y >= 0.0 => radii.bottom_left,
310 _ => 0.0,
311 };
312
313 let inside_half_width = (self.width() / 2.0 - radius).max(0.0);
317 let inside_half_height = (self.height() / 2.0 - radius).max(0.0);
318
319 let px = (pt.x.abs() - inside_half_width).max(0.0);
330 let py = (pt.y.abs() - inside_half_height).max(0.0);
331
332 let inside = px * px + py * py <= radius * radius;
337 if inside {
338 1
339 } else {
340 0
341 }
342 }
343
344 #[inline]
345 fn bounding_box(&self) -> Rect {
346 self.rect.bounding_box()
347 }
348
349 #[inline(always)]
350 fn as_rounded_rect(&self) -> Option<RoundedRect> {
351 Some(*self)
352 }
353}
354
355struct RectPathIter {
356 rect: Rect,
357 radii: RoundedRectRadii,
358 ix: usize,
359}
360
361impl Iterator for RectPathIter {
363 type Item = PathEl;
364
365 fn next(&mut self) -> Option<PathEl> {
366 self.ix += 1;
367 match self.ix {
368 1 => Some(PathEl::MoveTo(Point::new(
369 self.rect.x0,
370 self.rect.y0 + self.radii.top_left,
371 ))),
372 2 => Some(PathEl::LineTo(Point::new(
373 self.rect.x1 - self.radii.top_right,
374 self.rect.y0,
375 ))),
376 3 => Some(PathEl::LineTo(Point::new(
377 self.rect.x1,
378 self.rect.y1 - self.radii.bottom_right,
379 ))),
380 4 => Some(PathEl::LineTo(Point::new(
381 self.rect.x0 + self.radii.bottom_left,
382 self.rect.y1,
383 ))),
384 5 => Some(PathEl::ClosePath),
385 _ => None,
386 }
387 }
388}
389
390impl Iterator for RoundedRectPathIter {
392 type Item = PathEl;
393
394 fn next(&mut self) -> Option<PathEl> {
395 if self.idx > 4 {
396 return None;
397 }
398
399 if self.idx == 0 {
404 self.idx += 1;
405 return self.rect.next();
406 }
407
408 match self.arcs[self.idx - 1].next() {
411 Some(elem) => Some(elem),
412 None => {
413 self.idx += 1;
414 self.rect.next()
415 }
416 }
417 }
418}
419
420impl Add<Vec2> for RoundedRect {
421 type Output = RoundedRect;
422
423 #[inline]
424 fn add(self, v: Vec2) -> RoundedRect {
425 RoundedRect::from_rect(self.rect + v, self.radii)
426 }
427}
428
429impl Sub<Vec2> for RoundedRect {
430 type Output = RoundedRect;
431
432 #[inline]
433 fn sub(self, v: Vec2) -> RoundedRect {
434 RoundedRect::from_rect(self.rect - v, self.radii)
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use crate::{Circle, Point, Rect, RoundedRect, Shape};
441
442 #[test]
443 fn area() {
444 let epsilon = 1e-9;
445
446 let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
448 let rounded_rect = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 0.0);
449 assert!((rect.area() - rounded_rect.area()).abs() < epsilon);
450
451 let circle = Circle::new((0.0, 0.0), 50.0);
453 let rounded_rect = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 50.0);
454 assert!((circle.area() - rounded_rect.area()).abs() < epsilon);
455 }
456
457 #[test]
458 fn winding() {
459 let rect = RoundedRect::new(-5.0, -5.0, 10.0, 20.0, (5.0, 5.0, 5.0, 0.0));
460 assert_eq!(rect.winding(Point::new(0.0, 0.0)), 1);
461 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)), 1); assert_eq!(rect.winding(Point::new(-10.0, 0.0)), 0);
466
467 let rect = RoundedRect::new(-10.0, -20.0, 10.0, 20.0, 0.0); assert_eq!(rect.winding(Point::new(10.0, 20.0)), 1); }
470
471 #[test]
472 fn bez_conversion() {
473 let rect = RoundedRect::new(-5.0, -5.0, 10.0, 20.0, 5.0);
474 let p = rect.to_path(1e-9);
475 let epsilon = 1e-7;
477 assert!((rect.area() - p.area()).abs() < epsilon);
478 assert_eq!(p.winding(Point::new(0.0, 0.0)), 1);
479 }
480}