1use std::{
2 f32::consts::SQRT_2,
3 fmt,
4};
5
6use freya_engine::prelude::*;
7use torin::scaled::Scaled;
8
9use crate::parsing::{
10 Parse,
11 ParseError,
12};
13
14#[derive(PartialEq, Clone, Debug, Default, Copy)]
15pub struct CornerRadius {
16 pub top_left: f32,
17 pub top_right: f32,
18 pub bottom_right: f32,
19 pub bottom_left: f32,
20 pub smoothing: f32,
21}
22
23fn compute_smooth_corner(
25 corner_radius: f32,
26 smoothing: f32,
27 width: f32,
28 height: f32,
29) -> (f32, f32, f32, f32, f32, f32, f32) {
30 let max_p = f32::min(width, height) / 2.0;
31 let corner_radius = f32::min(corner_radius, max_p);
32
33 let p = f32::min((1.0 + smoothing) * corner_radius, max_p);
34
35 let angle_alpha: f32;
36 let angle_beta: f32;
37
38 if corner_radius <= max_p / 2.0 {
39 angle_alpha = 45.0 * smoothing;
40 angle_beta = 90.0 * (1.0 - smoothing);
41 } else {
42 let diff_ratio = (corner_radius - max_p / 2.0) / (max_p / 2.0);
43
44 angle_alpha = 45.0 * smoothing * (1.0 - diff_ratio);
45 angle_beta = 90.0 * (1.0 - smoothing * (1.0 - diff_ratio));
46 }
47
48 let angle_theta = (90.0 - angle_beta) / 2.0;
49 let dist_p3_p4 = corner_radius * (angle_theta / 2.0).to_radians().tan();
50
51 let l = (angle_beta / 2.0).to_radians().sin() * corner_radius * SQRT_2;
52 let c = dist_p3_p4 * angle_alpha.to_radians().cos();
53 let d = c * angle_alpha.to_radians().tan();
54 let b = (p - l - c - d) / 3.0;
55 let a = 2.0 * b;
56
57 (a, b, c, d, l, p, corner_radius)
58}
59
60impl CornerRadius {
61 pub fn fill_top(&mut self, value: f32) {
62 self.top_left = value;
63 self.top_right = value;
64 }
65
66 pub fn fill_bottom(&mut self, value: f32) {
67 self.bottom_left = value;
68 self.bottom_right = value;
69 }
70
71 pub fn fill_all(&mut self, value: f32) {
72 self.fill_bottom(value);
73 self.fill_top(value);
74 }
75
76 pub fn smoothed_path(&self, rect: RRect) -> Path {
78 let mut path = Path::new();
79
80 let width = rect.width();
81 let height = rect.height();
82
83 let top_right = rect.radii(Corner::UpperRight).x;
84 if top_right > 0.0 {
85 let (a, b, c, d, l, p, radius) =
86 compute_smooth_corner(top_right, self.smoothing, width, height);
87
88 path.move_to((f32::max(width / 2.0, width - p), 0.0))
89 .cubic_to(
90 (width - (p - a), 0.0),
91 (width - (p - a - b), 0.0),
92 (width - (p - a - b - c), d),
93 )
94 .r_arc_to_rotated(
95 (radius, radius),
96 0.0,
97 ArcSize::Small,
98 PathDirection::CW,
99 (l, l),
100 )
101 .cubic_to(
102 (width, p - a - b),
103 (width, p - a),
104 (width, f32::min(height / 2.0, p)),
105 );
106 } else {
107 path.move_to((width / 2.0, 0.0))
108 .line_to((width, 0.0))
109 .line_to((width, height / 2.0));
110 }
111
112 let bottom_right = rect.radii(Corner::LowerRight).x;
113 if bottom_right > 0.0 {
114 let (a, b, c, d, l, p, radius) =
115 compute_smooth_corner(bottom_right, self.smoothing, width, height);
116
117 path.line_to((width, f32::max(height / 2.0, height - p)))
118 .cubic_to(
119 (width, height - (p - a)),
120 (width, height - (p - a - b)),
121 (width - d, height - (p - a - b - c)),
122 )
123 .r_arc_to_rotated(
124 (radius, radius),
125 0.0,
126 ArcSize::Small,
127 PathDirection::CW,
128 (-l, l),
129 )
130 .cubic_to(
131 (width - (p - a - b), height),
132 (width - (p - a), height),
133 (f32::max(width / 2.0, width - p), height),
134 );
135 } else {
136 path.line_to((width, height)).line_to((width / 2.0, height));
137 }
138
139 let bottom_left = rect.radii(Corner::LowerLeft).x;
140 if bottom_left > 0.0 {
141 let (a, b, c, d, l, p, radius) =
142 compute_smooth_corner(bottom_left, self.smoothing, width, height);
143
144 path.line_to((f32::min(width / 2.0, p), height))
145 .cubic_to(
146 (p - a, height),
147 (p - a - b, height),
148 (p - a - b - c, height - d),
149 )
150 .r_arc_to_rotated(
151 (radius, radius),
152 0.0,
153 ArcSize::Small,
154 PathDirection::CW,
155 (-l, -l),
156 )
157 .cubic_to(
158 (0.0, height - (p - a - b)),
159 (0.0, height - (p - a)),
160 (0.0, f32::max(height / 2.0, height - p)),
161 );
162 } else {
163 path.line_to((0.0, height)).line_to((0.0, height / 2.0));
164 }
165
166 let top_left = rect.radii(Corner::UpperLeft).x;
167 if top_left > 0.0 {
168 let (a, b, c, d, l, p, radius) =
169 compute_smooth_corner(top_left, self.smoothing, width, height);
170
171 path.line_to((0.0, f32::min(height / 2.0, p)))
172 .cubic_to((0.0, p - a), (0.0, p - a - b), (d, p - a - b - c))
173 .r_arc_to_rotated(
174 (radius, radius),
175 0.0,
176 ArcSize::Small,
177 PathDirection::CW,
178 (l, -l),
179 )
180 .cubic_to(
181 (p - a - b, 0.0),
182 (p - a, 0.0),
183 (f32::min(width / 2.0, p), 0.0),
184 );
185 } else {
186 path.line_to((0.0, 0.0));
187 }
188
189 path.close();
190 path
191 }
192
193 pub fn pretty(&self) -> String {
194 format!(
195 "({}, {}, {}, {})",
196 self.top_left, self.top_right, self.bottom_right, self.bottom_left
197 )
198 }
199}
200
201impl Parse for CornerRadius {
202 fn parse(value: &str) -> Result<Self, ParseError> {
203 let mut radius = CornerRadius::default();
204 let mut values = value.split_ascii_whitespace();
205
206 match values.clone().count() {
207 1 => {
209 radius.fill_all(
210 values
211 .next()
212 .ok_or(ParseError)?
213 .parse::<f32>()
214 .map_err(|_| ParseError)?,
215 );
216 }
217 2 => {
219 radius.fill_top(
221 values
222 .next()
223 .ok_or(ParseError)?
224 .parse::<f32>()
225 .map_err(|_| ParseError)?,
226 );
227
228 radius.fill_bottom(
230 values
231 .next()
232 .ok_or(ParseError)?
233 .parse::<f32>()
234 .map_err(|_| ParseError)?,
235 )
236 }
237 4 => {
239 radius = CornerRadius {
240 top_left: values
241 .next()
242 .ok_or(ParseError)?
243 .parse::<f32>()
244 .map_err(|_| ParseError)?,
245 top_right: values
246 .next()
247 .ok_or(ParseError)?
248 .parse::<f32>()
249 .map_err(|_| ParseError)?,
250 bottom_right: values
251 .next()
252 .ok_or(ParseError)?
253 .parse::<f32>()
254 .map_err(|_| ParseError)?,
255 bottom_left: values
256 .next()
257 .ok_or(ParseError)?
258 .parse::<f32>()
259 .map_err(|_| ParseError)?,
260 ..Default::default()
261 }
262 }
263 _ => return Err(ParseError),
264 }
265
266 Ok(radius)
267 }
268}
269
270impl fmt::Display for CornerRadius {
271 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
272 write!(
273 f,
274 "{} {} {} {}",
275 self.top_left, self.top_right, self.bottom_left, self.bottom_right
276 )
277 }
278}
279
280impl Scaled for CornerRadius {
281 fn scale(&mut self, scale: f32) {
282 self.top_left *= scale;
283 self.top_right *= scale;
284 self.bottom_left *= scale;
285 self.bottom_right *= scale;
286 }
287}