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#[cfg(not(feature = "std"))]
12use crate::common::FloatFuncs;
13
14#[derive(Clone, Copy, Default, Debug, PartialEq)]
34#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub struct RoundedRect {
37 rect: Rect,
39 radii: RoundedRectRadii,
41}
42
43impl RoundedRect {
44 #[inline]
48 pub fn new(
49 x0: f64,
50 y0: f64,
51 x1: f64,
52 y1: f64,
53 radii: impl Into<RoundedRectRadii>,
54 ) -> RoundedRect {
55 RoundedRect::from_rect(Rect::new(x0, y0, x1, y1), radii)
56 }
57
58 #[inline]
64 pub fn from_rect(rect: Rect, radii: impl Into<RoundedRectRadii>) -> RoundedRect {
65 let rect = rect.abs();
66 let shortest_side_length = (rect.width()).min(rect.height());
67 let radii = radii.into().abs().clamp(shortest_side_length / 2.0);
68
69 RoundedRect { rect, radii }
70 }
71
72 #[inline]
76 pub fn from_points(
77 p0: impl Into<Point>,
78 p1: impl Into<Point>,
79 radii: impl Into<RoundedRectRadii>,
80 ) -> RoundedRect {
81 Rect::from_points(p0, p1).to_rounded_rect(radii)
82 }
83
84 #[inline]
88 pub fn from_origin_size(
89 origin: impl Into<Point>,
90 size: impl Into<Size>,
91 radii: impl Into<RoundedRectRadii>,
92 ) -> RoundedRect {
93 Rect::from_origin_size(origin, size).to_rounded_rect(radii)
94 }
95
96 #[inline]
98 pub fn width(&self) -> f64 {
99 self.rect.width()
100 }
101
102 #[inline]
104 pub fn height(&self) -> f64 {
105 self.rect.height()
106 }
107
108 #[inline]
110 pub fn radii(&self) -> RoundedRectRadii {
111 self.radii
112 }
113
114 pub fn rect(&self) -> Rect {
116 self.rect
117 }
118
119 #[inline]
123 pub fn origin(&self) -> Point {
124 self.rect.origin()
125 }
126
127 #[inline]
129 pub fn center(&self) -> Point {
130 self.rect.center()
131 }
132
133 #[inline]
135 pub fn is_finite(&self) -> bool {
136 self.rect.is_finite() && self.radii.is_finite()
137 }
138
139 #[inline]
141 pub fn is_nan(&self) -> bool {
142 self.rect.is_nan() || self.radii.is_nan()
143 }
144}
145
146#[doc(hidden)]
147pub struct RoundedRectPathIter {
148 idx: usize,
149 rect: RectPathIter,
150 arcs: [ArcAppendIter; 4],
151}
152
153impl Shape for RoundedRect {
154 type PathElementsIter<'iter> = RoundedRectPathIter;
155
156 fn path_elements(&self, tolerance: f64) -> RoundedRectPathIter {
157 let radii = self.radii();
158
159 let build_arc_iter = |i, center, ellipse_radii| {
160 let arc = Arc {
161 center,
162 radii: ellipse_radii,
163 start_angle: FRAC_PI_2 * i as f64,
164 sweep_angle: FRAC_PI_2,
165 x_rotation: 0.0,
166 };
167 arc.append_iter(tolerance)
168 };
169
170 let arcs = [
172 build_arc_iter(
173 2,
174 Point {
175 x: self.rect.x0 + radii.top_left,
176 y: self.rect.y0 + radii.top_left,
177 },
178 Vec2 {
179 x: radii.top_left,
180 y: radii.top_left,
181 },
182 ),
183 build_arc_iter(
184 3,
185 Point {
186 x: self.rect.x1 - radii.top_right,
187 y: self.rect.y0 + radii.top_right,
188 },
189 Vec2 {
190 x: radii.top_right,
191 y: radii.top_right,
192 },
193 ),
194 build_arc_iter(
195 0,
196 Point {
197 x: self.rect.x1 - radii.bottom_right,
198 y: self.rect.y1 - radii.bottom_right,
199 },
200 Vec2 {
201 x: radii.bottom_right,
202 y: radii.bottom_right,
203 },
204 ),
205 build_arc_iter(
206 1,
207 Point {
208 x: self.rect.x0 + radii.bottom_left,
209 y: self.rect.y1 - radii.bottom_left,
210 },
211 Vec2 {
212 x: radii.bottom_left,
213 y: radii.bottom_left,
214 },
215 ),
216 ];
217
218 let rect = RectPathIter {
219 rect: self.rect,
220 ix: 0,
221 radii,
222 };
223
224 RoundedRectPathIter { idx: 0, rect, arcs }
225 }
226
227 #[inline]
228 fn area(&self) -> f64 {
229 let radii = self.radii();
242
243 self.rect.area()
247 + [
248 radii.top_left,
249 radii.top_right,
250 radii.bottom_right,
251 radii.bottom_left,
252 ]
253 .iter()
254 .map(|radius| (FRAC_PI_4 - 1.0) * radius * radius)
255 .sum::<f64>()
256 }
257
258 #[inline]
259 fn perimeter(&self, _accuracy: f64) -> f64 {
260 let radii = self.radii();
275
276 self.rect.perimeter(1.0)
280 + ([
281 radii.top_left,
282 radii.top_right,
283 radii.bottom_right,
284 radii.bottom_left,
285 ])
286 .iter()
287 .map(|radius| (-2.0 + FRAC_PI_2) * radius)
288 .sum::<f64>()
289 }
290
291 #[inline]
292 fn winding(&self, mut pt: Point) -> i32 {
293 let center = self.center();
294
295 pt.x -= center.x;
297 pt.y -= center.y;
298
299 let radii = self.radii();
302 let radius = match pt {
303 pt if pt.x < 0.0 && pt.y < 0.0 => radii.top_left,
304 pt if pt.x >= 0.0 && pt.y < 0.0 => radii.top_right,
305 pt if pt.x >= 0.0 && pt.y >= 0.0 => radii.bottom_right,
306 pt if pt.x < 0.0 && pt.y >= 0.0 => radii.bottom_left,
307 _ => 0.0,
308 };
309
310 let inside_half_width = (self.width() / 2.0 - radius).max(0.0);
314 let inside_half_height = (self.height() / 2.0 - radius).max(0.0);
315
316 let px = (pt.x.abs() - inside_half_width).max(0.0);
327 let py = (pt.y.abs() - inside_half_height).max(0.0);
328
329 let inside = px * px + py * py <= radius * radius;
334 if inside {
335 1
336 } else {
337 0
338 }
339 }
340
341 #[inline]
342 fn bounding_box(&self) -> Rect {
343 self.rect.bounding_box()
344 }
345
346 #[inline]
347 fn as_rounded_rect(&self) -> Option<RoundedRect> {
348 Some(*self)
349 }
350}
351
352struct RectPathIter {
353 rect: Rect,
354 radii: RoundedRectRadii,
355 ix: usize,
356}
357
358impl Iterator for RectPathIter {
360 type Item = PathEl;
361
362 fn next(&mut self) -> Option<PathEl> {
363 self.ix += 1;
364 match self.ix {
365 1 => Some(PathEl::MoveTo(Point::new(
366 self.rect.x0,
367 self.rect.y0 + self.radii.top_left,
368 ))),
369 2 => Some(PathEl::LineTo(Point::new(
370 self.rect.x1 - self.radii.top_right,
371 self.rect.y0,
372 ))),
373 3 => Some(PathEl::LineTo(Point::new(
374 self.rect.x1,
375 self.rect.y1 - self.radii.bottom_right,
376 ))),
377 4 => Some(PathEl::LineTo(Point::new(
378 self.rect.x0 + self.radii.bottom_left,
379 self.rect.y1,
380 ))),
381 5 => Some(PathEl::ClosePath),
382 _ => None,
383 }
384 }
385}
386
387impl Iterator for RoundedRectPathIter {
389 type Item = PathEl;
390
391 fn next(&mut self) -> Option<PathEl> {
392 if self.idx > 4 {
393 return None;
394 }
395
396 if self.idx == 0 {
401 self.idx += 1;
402 return self.rect.next();
403 }
404
405 match self.arcs[self.idx - 1].next() {
408 Some(elem) => Some(elem),
409 None => {
410 self.idx += 1;
411 self.rect.next()
412 }
413 }
414 }
415}
416
417impl Add<Vec2> for RoundedRect {
418 type Output = RoundedRect;
419
420 #[inline]
421 fn add(self, v: Vec2) -> RoundedRect {
422 RoundedRect::from_rect(self.rect + v, self.radii)
423 }
424}
425
426impl Sub<Vec2> for RoundedRect {
427 type Output = RoundedRect;
428
429 #[inline]
430 fn sub(self, v: Vec2) -> RoundedRect {
431 RoundedRect::from_rect(self.rect - v, self.radii)
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use crate::{Circle, Point, Rect, RoundedRect, Shape};
438
439 #[test]
440 fn area() {
441 let epsilon = 1e-9;
442
443 let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
445 let rounded_rect = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 0.0);
446 assert!((rect.area() - rounded_rect.area()).abs() < epsilon);
447
448 let circle = Circle::new((0.0, 0.0), 50.0);
450 let rounded_rect = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 50.0);
451 assert!((circle.area() - rounded_rect.area()).abs() < epsilon);
452 }
453
454 #[test]
455 fn winding() {
456 let rect = RoundedRect::new(-5.0, -5.0, 10.0, 20.0, (5.0, 5.0, 5.0, 0.0));
457 assert_eq!(rect.winding(Point::new(0.0, 0.0)), 1);
458 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);
463
464 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); }
467
468 #[test]
469 fn bez_conversion() {
470 let rect = RoundedRect::new(-5.0, -5.0, 10.0, 20.0, 5.0);
471 let p = rect.to_path(1e-9);
472 let epsilon = 1e-7;
474 assert!((rect.area() - p.area()).abs() < epsilon);
475 assert_eq!(p.winding(Point::new(0.0, 0.0)), 1);
476 }
477}