embedded_graphics/primitives/rounded_rectangle/
corner_radii.rs

1//! Rounded rectangle corner radii configuration
2
3use 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))]
31pub 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
45impl 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))]
114pub struct CornerRadiiBuilder {
115    corners: CornerRadii,
116}
117
118impl 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
398impl From<&CornerRadii> for CornerRadiiBuilder {
399    fn from(corners: &CornerRadii) -> Self {
400        Self { corners: *corners }
401    }
402}
403
404#[cfg(test)]
405mod 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}