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}