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