1use std::f32::consts::SQRT_2;
2
3use freya_engine::prelude::*;
4use torin::scaled::Scaled;
5
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[derive(PartialEq, Clone, Debug, Default, Copy)]
18pub struct CornerRadius {
19 pub top_left: f32,
20 pub top_right: f32,
21 pub bottom_right: f32,
22 pub bottom_left: f32,
23 pub smoothing: f32,
24}
25
26impl From<f32> for CornerRadius {
27 fn from(value: f32) -> Self {
28 CornerRadius::new_all(value)
29 }
30}
31
32impl CornerRadius {
33 pub const fn new_all(radius: f32) -> Self {
35 Self {
36 top_left: radius,
37 top_right: radius,
38 bottom_right: radius,
39 bottom_left: radius,
40 smoothing: 0.,
41 }
42 }
43
44 pub fn fill_top(&mut self, value: f32) {
45 self.top_left = value;
46 self.top_right = value;
47 }
48
49 pub fn fill_bottom(&mut self, value: f32) {
50 self.bottom_left = value;
51 self.bottom_right = value;
52 }
53
54 pub fn fill_all(&mut self, value: f32) {
55 self.fill_bottom(value);
56 self.fill_top(value);
57 }
58
59 pub fn with_smoothing(mut self, smoothing: f32) -> Self {
61 self.smoothing = smoothing.clamp(0.0, 1.0);
62 self
63 }
64
65 pub fn smoothed_path(&self, rect: RRect) -> Path {
67 let mut path = PathBuilder::new();
68
69 let left = rect.rect().left();
70 let top = rect.rect().top();
71 let width = rect.width();
72 let height = rect.height();
73
74 let top_right = rect.radii(SkCorner::UpperRight).x;
75 if top_right > 0.0 {
76 let (a, b, c, d, l, p, radius) =
77 compute_smooth_corner(top_right, self.smoothing, width, height);
78
79 path.move_to((f32::max(width / 2.0, width - p), 0.0))
80 .cubic_to(
81 (width - (p - a), 0.0),
82 (width - (p - a - b), 0.0),
83 (width - (p - a - b - c), d),
84 )
85 .r_arc_to(
86 (radius, radius),
87 0.0,
88 ArcSize::Small,
89 PathDirection::CW,
90 (l, l),
91 )
92 .cubic_to(
93 (width, p - a - b),
94 (width, p - a),
95 (width, f32::min(height / 2.0, p)),
96 );
97 } else {
98 path.move_to((width / 2.0, 0.0))
99 .line_to((width, 0.0))
100 .line_to((width, height / 2.0));
101 }
102
103 let bottom_right = rect.radii(SkCorner::LowerRight).x;
104 if bottom_right > 0.0 {
105 let (a, b, c, d, l, p, radius) =
106 compute_smooth_corner(bottom_right, self.smoothing, width, height);
107
108 path.line_to((width, f32::max(height / 2.0, height - p)))
109 .cubic_to(
110 (width, height - (p - a)),
111 (width, height - (p - a - b)),
112 (width - d, height - (p - a - b - c)),
113 )
114 .r_arc_to(
115 (radius, radius),
116 0.0,
117 ArcSize::Small,
118 PathDirection::CW,
119 (-l, l),
120 )
121 .cubic_to(
122 (width - (p - a - b), height),
123 (width - (p - a), height),
124 (f32::max(width / 2.0, width - p), height),
125 );
126 } else {
127 path.line_to((width, height)).line_to((width / 2.0, height));
128 }
129
130 let bottom_left = rect.radii(SkCorner::LowerLeft).x;
131 if bottom_left > 0.0 {
132 let (a, b, c, d, l, p, radius) =
133 compute_smooth_corner(bottom_left, self.smoothing, width, height);
134
135 path.line_to((f32::min(width / 2.0, p), height))
136 .cubic_to(
137 (p - a, height),
138 (p - a - b, height),
139 (p - a - b - c, height - d),
140 )
141 .r_arc_to(
142 (radius, radius),
143 0.0,
144 ArcSize::Small,
145 PathDirection::CW,
146 (-l, -l),
147 )
148 .cubic_to(
149 (0.0, height - (p - a - b)),
150 (0.0, height - (p - a)),
151 (0.0, f32::max(height / 2.0, height - p)),
152 );
153 } else {
154 path.line_to((0.0, height)).line_to((0.0, height / 2.0));
155 }
156
157 let top_left = rect.radii(SkCorner::UpperLeft).x;
158 if top_left > 0.0 {
159 let (a, b, c, d, l, p, radius) =
160 compute_smooth_corner(top_left, self.smoothing, width, height);
161
162 path.line_to((0.0, f32::min(height / 2.0, p)))
163 .cubic_to((0.0, p - a), (0.0, p - a - b), (d, p - a - b - c))
164 .r_arc_to(
165 (radius, radius),
166 0.0,
167 ArcSize::Small,
168 PathDirection::CW,
169 (l, -l),
170 )
171 .cubic_to(
172 (p - a - b, 0.0),
173 (p - a, 0.0),
174 (f32::min(width / 2.0, p), 0.0),
175 );
176 } else {
177 path.line_to((0.0, 0.0));
178 }
179
180 path.detach()
181 .make_transform(&Matrix::translate((left, top)))
182 }
183
184 pub fn pretty(&self) -> String {
185 format!(
186 "({}, {}, {}, {})",
187 self.top_left, self.top_right, self.bottom_right, self.bottom_left
188 )
189 }
190
191 pub fn is_round(&self) -> bool {
192 self.top_left > 0. || self.top_right > 0. || self.bottom_right > 0. || self.bottom_left > 0.
193 }
194}
195
196fn compute_smooth_corner(
198 corner_radius: f32,
199 smoothing: f32,
200 width: f32,
201 height: f32,
202) -> (f32, f32, f32, f32, f32, f32, f32) {
203 let max_p = f32::min(width, height) / 2.0;
204 let corner_radius = f32::min(corner_radius, max_p);
205
206 let p = f32::min((1.0 + smoothing) * corner_radius, max_p);
207
208 let angle_alpha: f32;
209 let angle_beta: f32;
210
211 if corner_radius <= max_p / 2.0 {
212 angle_alpha = 45.0 * smoothing;
213 angle_beta = 90.0 * (1.0 - smoothing);
214 } else {
215 let diff_ratio = (corner_radius - max_p / 2.0) / (max_p / 2.0);
216
217 angle_alpha = 45.0 * smoothing * (1.0 - diff_ratio);
218 angle_beta = 90.0 * (1.0 - smoothing * (1.0 - diff_ratio));
219 }
220
221 let angle_theta = (90.0 - angle_beta) / 2.0;
222 let dist_p3_p4 = corner_radius * (angle_theta / 2.0).to_radians().tan();
223
224 let l = (angle_beta / 2.0).to_radians().sin() * corner_radius * SQRT_2;
225 let c = dist_p3_p4 * angle_alpha.to_radians().cos();
226 let d = c * angle_alpha.to_radians().tan();
227 let b = (p - l - c - d) / 3.0;
228 let a = 2.0 * b;
229
230 (a, b, c, d, l, p, corner_radius)
231}
232
233impl Scaled for CornerRadius {
234 fn scale(&mut self, scale: f32) {
235 self.top_left *= scale;
236 self.top_right *= scale;
237 self.bottom_left *= scale;
238 self.bottom_right *= scale;
239 }
240}