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 smoothed_path(&self, rect: RRect) -> Path {
61 let mut path = PathBuilder::new();
62
63 let width = rect.width();
64 let height = rect.height();
65
66 let top_right = rect.radii(SkCorner::UpperRight).x;
67 if top_right > 0.0 {
68 let (a, b, c, d, l, p, radius) =
69 compute_smooth_corner(top_right, self.smoothing, width, height);
70
71 path.move_to((f32::max(width / 2.0, width - p), 0.0))
72 .cubic_to(
73 (width - (p - a), 0.0),
74 (width - (p - a - b), 0.0),
75 (width - (p - a - b - c), d),
76 )
77 .r_arc_to(
78 (radius, radius),
79 0.0,
80 ArcSize::Small,
81 PathDirection::CW,
82 (l, l),
83 )
84 .cubic_to(
85 (width, p - a - b),
86 (width, p - a),
87 (width, f32::min(height / 2.0, p)),
88 );
89 } else {
90 path.move_to((width / 2.0, 0.0))
91 .line_to((width, 0.0))
92 .line_to((width, height / 2.0));
93 }
94
95 let bottom_right = rect.radii(SkCorner::LowerRight).x;
96 if bottom_right > 0.0 {
97 let (a, b, c, d, l, p, radius) =
98 compute_smooth_corner(bottom_right, self.smoothing, width, height);
99
100 path.line_to((width, f32::max(height / 2.0, height - p)))
101 .cubic_to(
102 (width, height - (p - a)),
103 (width, height - (p - a - b)),
104 (width - d, height - (p - a - b - c)),
105 )
106 .r_arc_to(
107 (radius, radius),
108 0.0,
109 ArcSize::Small,
110 PathDirection::CW,
111 (-l, l),
112 )
113 .cubic_to(
114 (width - (p - a - b), height),
115 (width - (p - a), height),
116 (f32::max(width / 2.0, width - p), height),
117 );
118 } else {
119 path.line_to((width, height)).line_to((width / 2.0, height));
120 }
121
122 let bottom_left = rect.radii(SkCorner::LowerLeft).x;
123 if bottom_left > 0.0 {
124 let (a, b, c, d, l, p, radius) =
125 compute_smooth_corner(bottom_left, self.smoothing, width, height);
126
127 path.line_to((f32::min(width / 2.0, p), height))
128 .cubic_to(
129 (p - a, height),
130 (p - a - b, height),
131 (p - a - b - c, height - d),
132 )
133 .r_arc_to(
134 (radius, radius),
135 0.0,
136 ArcSize::Small,
137 PathDirection::CW,
138 (-l, -l),
139 )
140 .cubic_to(
141 (0.0, height - (p - a - b)),
142 (0.0, height - (p - a)),
143 (0.0, f32::max(height / 2.0, height - p)),
144 );
145 } else {
146 path.line_to((0.0, height)).line_to((0.0, height / 2.0));
147 }
148
149 let top_left = rect.radii(SkCorner::UpperLeft).x;
150 if top_left > 0.0 {
151 let (a, b, c, d, l, p, radius) =
152 compute_smooth_corner(top_left, self.smoothing, width, height);
153
154 path.line_to((0.0, f32::min(height / 2.0, p)))
155 .cubic_to((0.0, p - a), (0.0, p - a - b), (d, p - a - b - c))
156 .r_arc_to(
157 (radius, radius),
158 0.0,
159 ArcSize::Small,
160 PathDirection::CW,
161 (l, -l),
162 )
163 .cubic_to(
164 (p - a - b, 0.0),
165 (p - a, 0.0),
166 (f32::min(width / 2.0, p), 0.0),
167 );
168 } else {
169 path.line_to((0.0, 0.0));
170 }
171
172 path.detach()
173 }
174
175 pub fn pretty(&self) -> String {
176 format!(
177 "({}, {}, {}, {})",
178 self.top_left, self.top_right, self.bottom_right, self.bottom_left
179 )
180 }
181
182 pub fn is_round(&self) -> bool {
183 self.top_left > 0. || self.top_right > 0. || self.bottom_right > 0. || self.bottom_left > 0.
184 }
185}
186
187fn compute_smooth_corner(
189 corner_radius: f32,
190 smoothing: f32,
191 width: f32,
192 height: f32,
193) -> (f32, f32, f32, f32, f32, f32, f32) {
194 let max_p = f32::min(width, height) / 2.0;
195 let corner_radius = f32::min(corner_radius, max_p);
196
197 let p = f32::min((1.0 + smoothing) * corner_radius, max_p);
198
199 let angle_alpha: f32;
200 let angle_beta: f32;
201
202 if corner_radius <= max_p / 2.0 {
203 angle_alpha = 45.0 * smoothing;
204 angle_beta = 90.0 * (1.0 - smoothing);
205 } else {
206 let diff_ratio = (corner_radius - max_p / 2.0) / (max_p / 2.0);
207
208 angle_alpha = 45.0 * smoothing * (1.0 - diff_ratio);
209 angle_beta = 90.0 * (1.0 - smoothing * (1.0 - diff_ratio));
210 }
211
212 let angle_theta = (90.0 - angle_beta) / 2.0;
213 let dist_p3_p4 = corner_radius * (angle_theta / 2.0).to_radians().tan();
214
215 let l = (angle_beta / 2.0).to_radians().sin() * corner_radius * SQRT_2;
216 let c = dist_p3_p4 * angle_alpha.to_radians().cos();
217 let d = c * angle_alpha.to_radians().tan();
218 let b = (p - l - c - d) / 3.0;
219 let a = 2.0 * b;
220
221 (a, b, c, d, l, p, corner_radius)
222}
223
224impl Scaled for CornerRadius {
225 fn scale(&mut self, scale: f32) {
226 self.top_left *= scale;
227 self.top_right *= scale;
228 self.bottom_left *= scale;
229 self.bottom_right *= scale;
230 }
231}