1 | //! Rounded rectangle corner radii configuration |
2 | |
3 | use crate::geometry::Size; |
4 | |
5 | /// The definition of each corner radius for a rounded rectangle. |
6 | /// |
7 | /// # Examples |
8 | /// |
9 | /// ## Create a radii configuration with equal corners |
10 | /// |
11 | /// This example create a `CornerRadii` instance where each corner has an equal, elliptical radius |
12 | /// of 10px x 8px. |
13 | /// |
14 | /// ```rust |
15 | /// use embedded_graphics::{geometry::Size, primitives::CornerRadii}; |
16 | /// |
17 | /// let radii = CornerRadii::new(Size::new(10, 8)); |
18 | /// |
19 | /// assert_eq!( |
20 | /// radii, |
21 | /// CornerRadii { |
22 | /// top_left: Size::new(10, 8), |
23 | /// top_right: Size::new(10, 8), |
24 | /// bottom_right: Size::new(10, 8), |
25 | /// bottom_left: Size::new(10, 8), |
26 | /// } |
27 | /// ); |
28 | /// ``` |
29 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] |
30 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
31 | pub struct CornerRadii { |
32 | /// Top left corner radius |
33 | pub top_left: Size, |
34 | |
35 | /// Top right corner radius |
36 | pub top_right: Size, |
37 | |
38 | /// Bottom right corner radius |
39 | pub bottom_right: Size, |
40 | |
41 | /// Bottom left corner radius |
42 | pub bottom_left: Size, |
43 | } |
44 | |
45 | impl CornerRadii { |
46 | /// Create a new set of corner radii with all corners having equal values. |
47 | /// |
48 | /// To create a `CornerRadii` instance with different radii for each corner, use the |
49 | /// [`CornerRadiiBuilder`] builder. |
50 | pub const fn new(radius: Size) -> Self { |
51 | Self { |
52 | top_left: radius, |
53 | top_right: radius, |
54 | bottom_right: radius, |
55 | bottom_left: radius, |
56 | } |
57 | } |
58 | |
59 | /// Confine corner radii that are too large to a given bounding rectangle |
60 | pub(in crate::primitives) fn confine(self, bounding_box: Size) -> Self { |
61 | let mut overlap = 0; |
62 | let mut size = 0; |
63 | let mut corner_size = 0; |
64 | |
65 | let top_radii = self.top_left.width + self.top_right.width; |
66 | let right_radii = self.top_right.height + self.bottom_right.height; |
67 | let bottom_radii = self.bottom_left.width + self.bottom_right.width; |
68 | let left_radii = self.top_left.height + self.bottom_left.height; |
69 | |
70 | let o = top_radii.saturating_sub(bounding_box.width); |
71 | if o > overlap { |
72 | size = bounding_box.width; |
73 | corner_size = top_radii; |
74 | overlap = o; |
75 | } |
76 | |
77 | let o = right_radii.saturating_sub(bounding_box.height); |
78 | if o > overlap { |
79 | size = bounding_box.height; |
80 | corner_size = right_radii; |
81 | overlap = o; |
82 | } |
83 | |
84 | let o = bottom_radii.saturating_sub(bounding_box.width); |
85 | if o > overlap { |
86 | size = bounding_box.width; |
87 | corner_size = bottom_radii; |
88 | overlap = o; |
89 | } |
90 | |
91 | let o = left_radii.saturating_sub(bounding_box.height); |
92 | if o > overlap { |
93 | size = bounding_box.height; |
94 | corner_size = left_radii; |
95 | overlap = o; |
96 | } |
97 | |
98 | if overlap > 0 && corner_size > 0 { |
99 | Self { |
100 | top_left: (self.top_left * size) / corner_size, |
101 | top_right: (self.top_right * size) / corner_size, |
102 | bottom_right: (self.bottom_right * size) / corner_size, |
103 | bottom_left: (self.bottom_left * size) / corner_size, |
104 | } |
105 | } else { |
106 | self |
107 | } |
108 | } |
109 | } |
110 | |
111 | /// [`CornerRadii`] builder. |
112 | #[derive (Copy, Clone, Debug, Default, Eq, PartialEq, Hash, PartialOrd, Ord)] |
113 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
114 | pub struct CornerRadiiBuilder { |
115 | corners: CornerRadii, |
116 | } |
117 | |
118 | impl CornerRadiiBuilder { |
119 | /// Create a new corner radii builder. |
120 | /// |
121 | /// All radii are defaulted to 0px x 0px. |
122 | pub const fn new() -> Self { |
123 | Self { |
124 | corners: CornerRadii::new(Size::zero()), |
125 | } |
126 | } |
127 | |
128 | /// Set all corner radii to the same value. |
129 | /// |
130 | /// # Examples |
131 | /// |
132 | /// ```rust |
133 | /// use embedded_graphics::{ |
134 | /// geometry::Size, |
135 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
136 | /// }; |
137 | /// |
138 | /// let corners = CornerRadiiBuilder::new().all(Size::new(10, 20)).build(); |
139 | /// |
140 | /// assert_eq!( |
141 | /// corners, |
142 | /// CornerRadii { |
143 | /// top_left: Size::new(10, 20), |
144 | /// top_right: Size::new(10, 20), |
145 | /// bottom_right: Size::new(10, 20), |
146 | /// bottom_left: Size::new(10, 20), |
147 | /// } |
148 | /// ); |
149 | /// ``` |
150 | pub const fn all(mut self, radius: Size) -> Self { |
151 | self.corners = CornerRadii::new(radius); |
152 | |
153 | self |
154 | } |
155 | |
156 | /// Set the top left and top right corner radii to the same value. |
157 | /// |
158 | /// # Examples |
159 | /// |
160 | /// ```rust |
161 | /// use embedded_graphics::{ |
162 | /// geometry::Size, |
163 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
164 | /// }; |
165 | /// |
166 | /// let corners = CornerRadiiBuilder::new().top(Size::new(10, 20)).build(); |
167 | /// |
168 | /// assert_eq!( |
169 | /// corners, |
170 | /// CornerRadii { |
171 | /// top_left: Size::new(10, 20), |
172 | /// top_right: Size::new(10, 20), |
173 | /// bottom_right: Size::zero(), |
174 | /// bottom_left: Size::zero(), |
175 | /// } |
176 | /// ); |
177 | /// ``` |
178 | pub const fn top(mut self, radius: Size) -> Self { |
179 | self.corners.top_left = radius; |
180 | self.corners.top_right = radius; |
181 | |
182 | self |
183 | } |
184 | |
185 | /// Set the top right and bottom right corner radii to the same value. |
186 | /// |
187 | /// # Examples |
188 | /// |
189 | /// ```rust |
190 | /// use embedded_graphics::{ |
191 | /// geometry::Size, |
192 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
193 | /// }; |
194 | /// |
195 | /// let corners = CornerRadiiBuilder::new().right(Size::new(10, 20)).build(); |
196 | /// |
197 | /// assert_eq!( |
198 | /// corners, |
199 | /// CornerRadii { |
200 | /// top_left: Size::zero(), |
201 | /// top_right: Size::new(10, 20), |
202 | /// bottom_right: Size::new(10, 20), |
203 | /// bottom_left: Size::zero(), |
204 | /// } |
205 | /// ); |
206 | /// ``` |
207 | pub const fn right(mut self, radius: Size) -> Self { |
208 | self.corners.top_right = radius; |
209 | self.corners.bottom_right = radius; |
210 | |
211 | self |
212 | } |
213 | |
214 | /// Set the bottom left and bottom right corner radii to the same value. |
215 | /// |
216 | /// # Examples |
217 | /// |
218 | /// ```rust |
219 | /// use embedded_graphics::{ |
220 | /// geometry::Size, |
221 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
222 | /// }; |
223 | /// |
224 | /// let corners = CornerRadiiBuilder::new().bottom(Size::new(10, 20)).build(); |
225 | /// |
226 | /// assert_eq!( |
227 | /// corners, |
228 | /// CornerRadii { |
229 | /// top_left: Size::zero(), |
230 | /// top_right: Size::zero(), |
231 | /// bottom_right: Size::new(10, 20), |
232 | /// bottom_left: Size::new(10, 20), |
233 | /// } |
234 | /// ); |
235 | /// ``` |
236 | pub const fn bottom(mut self, radius: Size) -> Self { |
237 | self.corners.bottom_left = radius; |
238 | self.corners.bottom_right = radius; |
239 | |
240 | self |
241 | } |
242 | |
243 | /// Set the top left and bottom left corner radii to the same value. |
244 | /// |
245 | /// # Examples |
246 | /// |
247 | /// ```rust |
248 | /// use embedded_graphics::{ |
249 | /// geometry::Size, |
250 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
251 | /// }; |
252 | /// |
253 | /// let corners = CornerRadiiBuilder::new().left(Size::new(10, 20)).build(); |
254 | /// |
255 | /// assert_eq!( |
256 | /// corners, |
257 | /// CornerRadii { |
258 | /// top_left: Size::new(10, 20), |
259 | /// top_right: Size::zero(), |
260 | /// bottom_right: Size::zero(), |
261 | /// bottom_left: Size::new(10, 20), |
262 | /// } |
263 | /// ); |
264 | /// ``` |
265 | pub const fn left(mut self, radius: Size) -> Self { |
266 | self.corners.top_left = radius; |
267 | self.corners.bottom_left = radius; |
268 | |
269 | self |
270 | } |
271 | |
272 | /// Set the top left corner radius. |
273 | /// |
274 | /// # Examples |
275 | /// |
276 | /// ```rust |
277 | /// use embedded_graphics::{ |
278 | /// geometry::Size, |
279 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
280 | /// }; |
281 | /// |
282 | /// let corners = CornerRadiiBuilder::new() |
283 | /// .top_left(Size::new(10, 20)) |
284 | /// .build(); |
285 | /// |
286 | /// assert_eq!( |
287 | /// corners, |
288 | /// CornerRadii { |
289 | /// top_left: Size::new(10, 20), |
290 | /// top_right: Size::zero(), |
291 | /// bottom_right: Size::zero(), |
292 | /// bottom_left: Size::zero(), |
293 | /// } |
294 | /// ); |
295 | /// ``` |
296 | pub const fn top_left(mut self, radius: Size) -> Self { |
297 | self.corners.top_left = radius; |
298 | |
299 | self |
300 | } |
301 | |
302 | /// Set the top right corner radius. |
303 | /// |
304 | /// # Examples |
305 | /// |
306 | /// ```rust |
307 | /// use embedded_graphics::{ |
308 | /// geometry::Size, |
309 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
310 | /// }; |
311 | /// |
312 | /// let corners = CornerRadiiBuilder::new() |
313 | /// .top_right(Size::new(10, 20)) |
314 | /// .build(); |
315 | /// |
316 | /// assert_eq!( |
317 | /// corners, |
318 | /// CornerRadii { |
319 | /// top_left: Size::zero(), |
320 | /// top_right: Size::new(10, 20), |
321 | /// bottom_right: Size::zero(), |
322 | /// bottom_left: Size::zero(), |
323 | /// } |
324 | /// ); |
325 | /// ``` |
326 | pub const fn top_right(mut self, radius: Size) -> Self { |
327 | self.corners.top_right = radius; |
328 | |
329 | self |
330 | } |
331 | |
332 | /// Set the bottom right corner radius. |
333 | /// |
334 | /// # Examples |
335 | /// |
336 | /// ```rust |
337 | /// use embedded_graphics::{ |
338 | /// geometry::Size, |
339 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
340 | /// }; |
341 | /// |
342 | /// let corners = CornerRadiiBuilder::new() |
343 | /// .bottom_right(Size::new(10, 20)) |
344 | /// .build(); |
345 | /// |
346 | /// assert_eq!( |
347 | /// corners, |
348 | /// CornerRadii { |
349 | /// top_left: Size::zero(), |
350 | /// top_right: Size::zero(), |
351 | /// bottom_right: Size::new(10, 20), |
352 | /// bottom_left: Size::zero(), |
353 | /// } |
354 | /// ); |
355 | /// ``` |
356 | pub const fn bottom_right(mut self, radius: Size) -> Self { |
357 | self.corners.bottom_right = radius; |
358 | |
359 | self |
360 | } |
361 | |
362 | /// Set the bottom left corner radius. |
363 | /// |
364 | /// # Examples |
365 | /// |
366 | /// ```rust |
367 | /// use embedded_graphics::{ |
368 | /// geometry::Size, |
369 | /// primitives::{CornerRadii, CornerRadiiBuilder}, |
370 | /// }; |
371 | /// |
372 | /// let corners = CornerRadiiBuilder::new() |
373 | /// .bottom_left(Size::new(10, 20)) |
374 | /// .build(); |
375 | /// |
376 | /// assert_eq!( |
377 | /// corners, |
378 | /// CornerRadii { |
379 | /// top_left: Size::zero(), |
380 | /// top_right: Size::zero(), |
381 | /// bottom_right: Size::zero(), |
382 | /// bottom_left: Size::new(10, 20), |
383 | /// } |
384 | /// ); |
385 | /// ``` |
386 | pub const fn bottom_left(mut self, radius: Size) -> Self { |
387 | self.corners.bottom_left = radius; |
388 | |
389 | self |
390 | } |
391 | |
392 | /// Consume the builder and produce a [`CornerRadii`] configuration. |
393 | pub const fn build(self) -> CornerRadii { |
394 | self.corners |
395 | } |
396 | } |
397 | |
398 | impl From<&CornerRadii> for CornerRadiiBuilder { |
399 | fn from(corners: &CornerRadii) -> Self { |
400 | Self { corners: *corners } |
401 | } |
402 | } |
403 | |
404 | #[cfg (test)] |
405 | mod tests { |
406 | use super::*; |
407 | |
408 | #[test ] |
409 | fn from_radii_to_builder() { |
410 | let radii = CornerRadii { |
411 | top_left: Size::new(1, 2), |
412 | top_right: Size::new(3, 4), |
413 | bottom_right: Size::new(5, 6), |
414 | bottom_left: Size::new(7, 8), |
415 | }; |
416 | |
417 | let builder: CornerRadiiBuilder = (&radii).into(); |
418 | |
419 | assert_eq!(builder.build(), radii); |
420 | } |
421 | |
422 | #[test ] |
423 | fn corner_radii_exact_size() { |
424 | let corners = CornerRadii { |
425 | top_left: Size::new(10, 15), |
426 | top_right: Size::new(10, 15), |
427 | bottom_right: Size::new(10, 15), |
428 | bottom_left: Size::new(10, 15), |
429 | }; |
430 | |
431 | assert_eq!(corners.confine(Size::new(20, 30)), corners); |
432 | } |
433 | |
434 | #[test ] |
435 | fn corner_radii_single_overlap() { |
436 | let corners = CornerRadii { |
437 | // Create an overlap of 5px in the Y direction |
438 | top_left: Size::new(10, 20), |
439 | top_right: Size::new(10, 15), |
440 | bottom_right: Size::new(10, 15), |
441 | bottom_left: Size::new(10, 15), |
442 | }; |
443 | |
444 | assert_eq!( |
445 | corners.confine(Size::new(20, 30)), |
446 | CornerRadii { |
447 | top_left: Size::new(8, 17), |
448 | top_right: Size::new(8, 12), |
449 | bottom_right: Size::new(8, 12), |
450 | bottom_left: Size::new(8, 12) |
451 | } |
452 | ); |
453 | } |
454 | |
455 | #[test ] |
456 | fn corner_radii_1px_overlap() { |
457 | let corners = CornerRadii { |
458 | // 1px overlap in Y |
459 | top_left: Size::new(10, 16), |
460 | // 1px overlap in X |
461 | top_right: Size::new(11, 15), |
462 | bottom_right: Size::new(10, 15), |
463 | bottom_left: Size::new(10, 15), |
464 | }; |
465 | |
466 | assert_eq!( |
467 | corners.confine(Size::new(20, 30)), |
468 | CornerRadii { |
469 | top_left: Size::new(9, 15), |
470 | top_right: Size::new(10, 14), |
471 | bottom_right: Size::new(9, 14), |
472 | bottom_left: Size::new(9, 14), |
473 | } |
474 | ); |
475 | } |
476 | |
477 | #[test ] |
478 | fn corner_radii_multiple_overlap() { |
479 | let corners = CornerRadii { |
480 | // Create an overlap of 5px in the Y direction |
481 | top_left: Size::new(10, 20), |
482 | top_right: Size::new(10, 15), |
483 | // Create an overlap of 8px in the X direction |
484 | bottom_right: Size::new(18, 15), |
485 | bottom_left: Size::new(10, 15), |
486 | }; |
487 | |
488 | assert_eq!( |
489 | corners.confine(Size::new(20, 30)), |
490 | CornerRadii { |
491 | top_left: Size::new(7, 14), |
492 | top_right: Size::new(7, 10), |
493 | bottom_right: Size::new(12, 10), |
494 | bottom_left: Size::new(7, 10), |
495 | } |
496 | ); |
497 | } |
498 | } |
499 | |