1// Copyright 2021 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A description of the radii for each corner of a rounded rectangle.
5
6use core::convert::From;
7
8#[cfg(not(feature = "std"))]
9use crate::common::FloatFuncs;
10
11/// Radii for each corner of a rounded rectangle.
12///
13/// The use of `top` as in `top_left` assumes a y-down coordinate space. Piet
14/// (and Druid by extension) uses a y-down coordinate space, but Kurbo also
15/// supports a y-up coordinate space, in which case `top_left` would actually
16/// refer to the bottom-left corner, and vice versa. Top may not always
17/// actually be the top, but `top` corners will always have a smaller y-value
18/// than `bottom` corners.
19#[derive(Clone, Copy, Default, Debug, PartialEq)]
20#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct RoundedRectRadii {
23 /// The radius of the top-left corner.
24 pub top_left: f64,
25 /// The radius of the top-right corner.
26 pub top_right: f64,
27 /// The radius of the bottom-right corner.
28 pub bottom_right: f64,
29 /// The radius of the bottom-left corner.
30 pub bottom_left: f64,
31}
32
33impl RoundedRectRadii {
34 /// Create a new RoundedRectRadii. This function takes radius values for
35 /// the four corners. The argument order is "top_left, top_right,
36 /// bottom_right, bottom_left", or clockwise starting from top_left.
37 pub const fn new(top_left: f64, top_right: f64, bottom_right: f64, bottom_left: f64) -> Self {
38 RoundedRectRadii {
39 top_left,
40 top_right,
41 bottom_right,
42 bottom_left,
43 }
44 }
45
46 /// Create a new RoundedRectRadii from a single radius. The `radius`
47 /// argument will be set as the radius for all four corners.
48 pub const fn from_single_radius(radius: f64) -> Self {
49 RoundedRectRadii {
50 top_left: radius,
51 top_right: radius,
52 bottom_right: radius,
53 bottom_left: radius,
54 }
55 }
56
57 /// Takes the absolute value of all corner radii.
58 pub fn abs(&self) -> Self {
59 RoundedRectRadii::new(
60 self.top_left.abs(),
61 self.top_right.abs(),
62 self.bottom_right.abs(),
63 self.bottom_left.abs(),
64 )
65 }
66
67 /// For each corner, takes the min of that corner's radius and `max`.
68 pub fn clamp(&self, max: f64) -> Self {
69 RoundedRectRadii::new(
70 self.top_left.min(max),
71 self.top_right.min(max),
72 self.bottom_right.min(max),
73 self.bottom_left.min(max),
74 )
75 }
76
77 /// Returns true if all radius values are finite.
78 pub fn is_finite(&self) -> bool {
79 self.top_left.is_finite()
80 && self.top_right.is_finite()
81 && self.bottom_right.is_finite()
82 && self.bottom_left.is_finite()
83 }
84
85 /// Returns true if any corner radius value is NaN.
86 pub fn is_nan(&self) -> bool {
87 self.top_left.is_nan()
88 || self.top_right.is_nan()
89 || self.bottom_right.is_nan()
90 || self.bottom_left.is_nan()
91 }
92
93 /// If all radii are equal, returns the value of the radii. Otherwise,
94 /// returns `None`.
95 pub fn as_single_radius(&self) -> Option<f64> {
96 let epsilon = 1e-9;
97
98 if (self.top_left - self.top_right).abs() < epsilon
99 && (self.top_right - self.bottom_right).abs() < epsilon
100 && (self.bottom_right - self.bottom_left).abs() < epsilon
101 {
102 Some(self.top_left)
103 } else {
104 None
105 }
106 }
107}
108
109impl From<f64> for RoundedRectRadii {
110 fn from(radius: f64) -> Self {
111 RoundedRectRadii::from_single_radius(radius)
112 }
113}
114
115impl From<(f64, f64, f64, f64)> for RoundedRectRadii {
116 fn from(radii: (f64, f64, f64, f64)) -> Self {
117 RoundedRectRadii::new(top_left:radii.0, top_right:radii.1, bottom_right:radii.2, bottom_left:radii.3)
118 }
119}
120