1use crate::utilities::math_utilities;
4use crate::{CornerRadius, Point, Rect, Thickness, Vector};
5
6#[derive(Clone, Copy, Debug, PartialEq, Default)]
8pub struct RoundedRect {
9 pub rect: Rect,
10 pub radii_top_left: Vector,
11 pub radii_top_right: Vector,
12 pub radii_bottom_left: Vector,
13 pub radii_bottom_right: Vector,
14}
15
16impl RoundedRect {
17 #[inline]
19 pub const fn new(
20 rect: Rect,
21 radii_top_left: Vector,
22 radii_top_right: Vector,
23 radii_bottom_right: Vector,
24 radii_bottom_left: Vector,
25 ) -> Self {
26 Self {
27 rect,
28 radii_top_left,
29 radii_top_right,
30 radii_bottom_left,
31 radii_bottom_right,
32 }
33 }
34
35 #[inline]
37 pub const fn from_radii(
38 rect: Rect,
39 radius_top_left: f64,
40 radius_top_right: f64,
41 radius_bottom_right: f64,
42 radius_bottom_left: f64,
43 ) -> Self {
44 Self::new(
45 rect,
46 Vector::new(radius_top_left, radius_top_left),
47 Vector::new(radius_top_right, radius_top_right),
48 Vector::new(radius_bottom_right, radius_bottom_right),
49 Vector::new(radius_bottom_left, radius_bottom_left),
50 )
51 }
52
53 #[inline]
55 pub const fn from_uniform_radii(rect: Rect, radii: Vector) -> Self {
56 Self::new(rect, radii, radii, radii, radii)
57 }
58
59 #[inline]
61 pub const fn from_radius_xy(rect: Rect, radius_x: f64, radius_y: f64) -> Self {
62 Self::from_uniform_radii(rect, Vector::new(radius_x, radius_y))
63 }
64
65 #[inline]
67 pub const fn from_radius(rect: Rect, radius: f64) -> Self {
68 Self::from_radius_xy(rect, radius, radius)
69 }
70
71 #[inline]
73 pub const fn from_rect(rect: Rect) -> Self {
74 Self::from_radius(rect, 0.0)
75 }
76
77 #[inline]
79 pub const fn from_corner_radius(bounds: Rect, radius: CornerRadius) -> Self {
80 Self::from_radii(
81 bounds,
82 radius.top_left,
83 radius.top_right,
84 radius.bottom_right,
85 radius.bottom_left,
86 )
87 }
88
89 #[inline]
90 pub fn equals(&self, other: RoundedRect) -> bool {
91 *self == other
92 }
93
94 #[inline]
96 pub fn is_rounded(&self) -> bool {
97 self.radii_top_left != Vector::ZERO
98 || self.radii_top_right != Vector::ZERO
99 || self.radii_bottom_right != Vector::ZERO
100 || self.radii_bottom_left != Vector::ZERO
101 }
102
103 #[inline]
105 pub fn is_uniform(&self) -> bool {
106 self.radii_top_left.equals(self.radii_top_right)
107 && self.radii_top_left.equals(self.radii_bottom_right)
108 && self.radii_top_left.equals(self.radii_bottom_left)
109 }
110
111 #[inline]
112 pub fn inflate(&self, dx: f64, dy: f64) -> RoundedRect {
113 self.deflate(-dx, -dy)
114 }
115
116 pub fn deflate(&self, dx: f64, dy: f64) -> RoundedRect {
117 if !self.is_rounded() {
118 return RoundedRect::from_rect(
119 self.rect.deflate_thickness(Thickness::symmetric(dx, dy)),
120 );
121 }
122
123 let mut left = self.rect.x + dx;
125 let mut top = self.rect.y + dy;
126 let mut right = left + self.rect.width - dx * 2.0;
127 let mut bottom = top + self.rect.height - dy * 2.0;
128 let mut radii = [
129 self.radii_top_left,
130 self.radii_top_right,
131 self.radii_bottom_right,
132 self.radii_bottom_left,
133 ];
134
135 let mut degenerate = false;
136 if right <= left {
137 degenerate = true;
138 left = (left + right) * 0.5;
139 right = left;
140 }
141 if bottom <= top {
142 degenerate = true;
143 top = (top + bottom) * 0.5;
144 bottom = top;
145 }
146 if degenerate {
147 return RoundedRect::from_rect(Rect::new(left, top, right - left, bottom - top));
148 }
149
150 for radius in &mut radii {
151 let rx = math_utilities::max(0.0, radius.x - dx);
152 let ry = math_utilities::max(0.0, radius.y - dy);
153 *radius = if rx == 0.0 || ry == 0.0 {
154 Vector::default()
155 } else {
156 Vector::new(rx, ry)
157 };
158 }
159
160 RoundedRect::new(
161 Rect::new(left, top, right - left, bottom - top),
162 radii[0],
163 radii[1],
164 radii[2],
165 radii[3],
166 )
167 }
168
169 #[inline]
172 pub fn is_empty(&self) -> bool {
173 *self == RoundedRect::default()
174 }
175
176 #[inline]
177 fn is_outside_corner(dx: f64, dy: f64, radius: f64) -> bool {
178 (dx < 0.0) && (dy < 0.0) && (dx * dx + dy * dy > radius * radius)
179 }
180
181 pub fn contains_exclusive(&self, p: Point) -> bool {
185 if !self.rect.contains_exclusive(p) {
187 return false;
188 }
189
190 let rect = self.rect;
191
192 let mut scale_factor = 1.0;
194 if rect.width > 0.0 {
195 let radii_width = math_utilities::max(
196 self.radii_top_left.x + self.radii_top_right.x,
197 self.radii_bottom_left.x + self.radii_bottom_right.x,
198 );
199 if radii_width > rect.width {
200 scale_factor = math_utilities::min(scale_factor, rect.width / radii_width);
201 }
202 }
203 if rect.height > 0.0 {
204 let radii_height = math_utilities::max(
205 self.radii_top_left.y + self.radii_bottom_left.y,
206 self.radii_top_right.y + self.radii_bottom_right.y,
207 );
208 if radii_height > rect.height {
209 scale_factor = math_utilities::min(scale_factor, rect.height / radii_height);
210 }
211 }
212
213 let p = Point::new(p.x - rect.x, p.y - rect.y);
215
216 let radius =
218 math_utilities::min(self.radii_top_left.x, self.radii_top_left.y) * scale_factor;
219 if Self::is_outside_corner(p.x - radius, p.y - radius, radius) {
220 return false;
221 }
222
223 let radius =
225 math_utilities::min(self.radii_top_right.x, self.radii_top_right.y) * scale_factor;
226 if Self::is_outside_corner(rect.width - radius - p.x, p.y - radius, radius) {
227 return false;
228 }
229
230 let radius = math_utilities::min(self.radii_bottom_right.x, self.radii_bottom_right.y)
232 * scale_factor;
233 if Self::is_outside_corner(
234 rect.width - radius - p.x,
235 rect.height - radius - p.y,
236 radius,
237 ) {
238 return false;
239 }
240
241 let radius =
243 math_utilities::min(self.radii_bottom_left.x, self.radii_bottom_left.y) * scale_factor;
244 if Self::is_outside_corner(p.x - radius, rect.height - radius - p.y, radius) {
245 return false;
246 }
247
248 true
249 }
250}
251
252impl From<Rect> for RoundedRect {
253 #[inline]
254 fn from(r: Rect) -> RoundedRect {
255 RoundedRect::from_rect(r)
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn contains_exclusive_should_return_expected_result_for_point() {
265 let cases: [(f64, f64, bool); 13] = [
266 (0.0, 0.0, false),
268 (100.0, 0.0, false),
269 (100.0, 100.0, false),
270 (0.0, 100.0, false),
271 (10.0, 10.0, false),
273 (90.0, 10.0, true),
274 (90.0, 90.0, false),
275 (10.0, 90.0, true),
276 (17.0, 17.0, false),
278 (83.0, 17.0, true),
279 (83.0, 83.0, true),
280 (17.0, 83.0, true),
281 (50.0, 50.0, true),
283 ];
284
285 for (x, y, expected_result) in cases {
286 let rrect = RoundedRect::from_corner_radius(
287 Rect::new(0.0, 0.0, 100.0, 100.0),
288 CornerRadius::new(60.0, 10.0, 50.0, 30.0),
289 );
290
291 assert_eq!(
292 expected_result,
293 rrect.contains_exclusive(Point::new(x, y)),
294 "point ({x}, {y})"
295 );
296 }
297 }
298
299 #[test]
302 fn rounded_uniform_and_deflate() {
303 let plain: RoundedRect = Rect::new(0.0, 0.0, 10.0, 10.0).into();
304 assert!(!plain.is_rounded());
305 assert!(plain.is_uniform());
306 assert!(RoundedRect::default().is_empty());
307 assert_eq!(
308 plain.deflate(1.0, 2.0),
309 RoundedRect::from_rect(Rect::new(1.0, 2.0, 8.0, 6.0))
310 );
311
312 let rr = RoundedRect::from_radius(Rect::new(0.0, 0.0, 100.0, 100.0), 10.0);
313 assert!(rr.is_rounded());
314 assert_eq!(
315 rr.deflate(4.0, 4.0),
316 RoundedRect::from_radius(Rect::new(4.0, 4.0, 92.0, 92.0), 6.0)
317 );
318 assert_eq!(
320 rr.deflate(10.0, 10.0),
321 RoundedRect::from_rect(Rect::new(10.0, 10.0, 80.0, 80.0))
322 );
323 assert_eq!(
325 rr.deflate(60.0, 10.0),
326 RoundedRect::from_rect(Rect::new(50.0, 10.0, 0.0, 80.0))
327 );
328 assert_eq!(
329 rr.inflate(5.0, 5.0),
330 RoundedRect::from_radius(Rect::new(-5.0, -5.0, 110.0, 110.0), 15.0)
331 );
332 }
333}