1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
3 | |
4 | /*! |
5 | This module contains border radius related types for the run-time library. |
6 | */ |
7 | |
8 | use core::fmt; |
9 | use core::marker::PhantomData; |
10 | use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; |
11 | use euclid::approxord::{max, min}; |
12 | use euclid::num::Zero; |
13 | use euclid::{Length, Scale}; |
14 | use num_traits::NumCast; |
15 | |
16 | /// Top-left, top-right, bottom-right, and bottom-left border radius, optionally |
17 | /// tagged with a unit. |
18 | #[repr (C)] |
19 | pub struct BorderRadius<T, U> { |
20 | /// The top-left radius. |
21 | pub top_left: T, |
22 | /// The top-right radius. |
23 | pub top_right: T, |
24 | /// The bottom-right radius. |
25 | pub bottom_right: T, |
26 | /// The bottom-left radius. |
27 | pub bottom_left: T, |
28 | #[doc (hidden)] |
29 | pub _unit: PhantomData<U>, |
30 | } |
31 | |
32 | impl<T, U> Copy for BorderRadius<T, U> where T: Copy {} |
33 | |
34 | impl<T, U> Clone for BorderRadius<T, U> |
35 | where |
36 | T: Clone, |
37 | { |
38 | fn clone(&self) -> Self { |
39 | BorderRadius { |
40 | top_left: self.top_left.clone(), |
41 | top_right: self.top_right.clone(), |
42 | bottom_right: self.bottom_right.clone(), |
43 | bottom_left: self.bottom_left.clone(), |
44 | _unit: PhantomData, |
45 | } |
46 | } |
47 | } |
48 | |
49 | impl<T, U> Eq for BorderRadius<T, U> where T: Eq {} |
50 | |
51 | impl<T, U> PartialEq for BorderRadius<T, U> |
52 | where |
53 | T: PartialEq, |
54 | { |
55 | fn eq(&self, other: &Self) -> bool { |
56 | self.top_left == other.top_left |
57 | && self.top_right == other.top_right |
58 | && self.bottom_right == other.bottom_right |
59 | && self.bottom_left == other.bottom_left |
60 | } |
61 | } |
62 | |
63 | impl<T, U> fmt::Debug for BorderRadius<T, U> |
64 | where |
65 | T: fmt::Debug, |
66 | { |
67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
68 | write!( |
69 | f, |
70 | "BorderRadius( {:?}, {:?}, {:?}, {:?})" , |
71 | self.top_left, self.top_right, self.bottom_right, self.bottom_left |
72 | ) |
73 | } |
74 | } |
75 | |
76 | impl<T, U> Default for BorderRadius<T, U> |
77 | where |
78 | T: Default, |
79 | { |
80 | fn default() -> Self { |
81 | BorderRadius::new(T::default(), T::default(), T::default(), T::default()) |
82 | } |
83 | } |
84 | |
85 | impl<T, U> Zero for BorderRadius<T, U> |
86 | where |
87 | T: Zero, |
88 | { |
89 | fn zero() -> Self { |
90 | BorderRadius::new(T::zero(), T::zero(), T::zero(), T::zero()) |
91 | } |
92 | } |
93 | |
94 | impl<T, U> BorderRadius<T, U> { |
95 | /// Constructor taking a scalar for each radius. |
96 | /// |
97 | /// Radii are specified in top-left, top-right, bottom-right, bottom-left |
98 | /// order following CSS's convention. |
99 | pub const fn new(top_left: T, top_right: T, bottom_right: T, bottom_left: T) -> Self { |
100 | BorderRadius { top_left, top_right, bottom_right, bottom_left, _unit: PhantomData } |
101 | } |
102 | |
103 | /// Constructor taking a typed Length for each radius. |
104 | /// |
105 | /// Radii are specified in top-left, top-right, bottom-right, bottom-left |
106 | /// order following CSS's convention. |
107 | pub fn from_lengths( |
108 | top_left: Length<T, U>, |
109 | top_right: Length<T, U>, |
110 | bottom_right: Length<T, U>, |
111 | bottom_left: Length<T, U>, |
112 | ) -> Self { |
113 | BorderRadius::new(top_left.0, top_right.0, bottom_right.0, bottom_left.0) |
114 | } |
115 | |
116 | /// Constructor taking the same scalar value for all radii. |
117 | pub fn new_uniform(all: T) -> Self |
118 | where |
119 | T: Copy, |
120 | { |
121 | BorderRadius::new(all, all, all, all) |
122 | } |
123 | |
124 | /// Constructor taking the same typed Length for all radii. |
125 | pub fn from_length(all: Length<T, U>) -> Self |
126 | where |
127 | T: Copy, |
128 | { |
129 | BorderRadius::new_uniform(all.0) |
130 | } |
131 | |
132 | /// Returns `true` if all radii are equal. |
133 | pub fn is_uniform(&self) -> bool |
134 | where |
135 | T: ApproxEq<T>, |
136 | { |
137 | self.top_left.approx_eq(&self.top_right) |
138 | && self.top_left.approx_eq(&self.bottom_right) |
139 | && self.top_left.approx_eq(&self.bottom_left) |
140 | } |
141 | |
142 | /// Returns the uniform radius if all are equal, or `None` otherwise. |
143 | pub fn as_uniform(&self) -> Option<T> |
144 | where |
145 | T: Copy + ApproxEq<T>, |
146 | { |
147 | if self.is_uniform() { |
148 | Some(self.top_left) |
149 | } else { |
150 | None |
151 | } |
152 | } |
153 | |
154 | /// Returns `true` if all radii are zero. |
155 | pub fn is_zero(&self) -> bool |
156 | where |
157 | T: ApproxEq<T> + Zero, |
158 | { |
159 | let zero = T::zero(); |
160 | self.top_left.approx_eq(&zero) |
161 | && self.top_right.approx_eq(&zero) |
162 | && self.bottom_right.approx_eq(&zero) |
163 | && self.bottom_left.approx_eq(&zero) |
164 | } |
165 | |
166 | /// Returns the outer radius. |
167 | /// |
168 | /// For any corner with a positive radius, the radius is ensured to be at |
169 | /// least `half_border_width`. |
170 | pub fn outer(&self, half_border_width: Length<T, U>) -> Self |
171 | where |
172 | T: Copy + PartialOrd + Zero, |
173 | { |
174 | let zero = T::zero(); |
175 | BorderRadius::new( |
176 | if self.top_left > zero { |
177 | max(self.top_left, half_border_width.0) |
178 | } else { |
179 | self.top_left |
180 | }, |
181 | if self.top_right > zero { |
182 | max(self.top_right, half_border_width.0) |
183 | } else { |
184 | self.top_right |
185 | }, |
186 | if self.bottom_right > zero { |
187 | max(self.bottom_right, half_border_width.0) |
188 | } else { |
189 | self.bottom_right |
190 | }, |
191 | if self.bottom_left > zero { |
192 | max(self.bottom_left, half_border_width.0) |
193 | } else { |
194 | self.bottom_left |
195 | }, |
196 | ) |
197 | } |
198 | |
199 | /// Returns the inner radius. |
200 | /// |
201 | /// A positive radius of each corner is subtracted by `half_border_width` |
202 | /// and min-clamped to zero. |
203 | pub fn inner(&self, half_border_width: Length<T, U>) -> Self |
204 | where |
205 | T: Copy + PartialOrd + Sub<T, Output = T> + Zero, |
206 | { |
207 | BorderRadius::new( |
208 | self.top_left - half_border_width.0, |
209 | self.top_right - half_border_width.0, |
210 | self.bottom_right - half_border_width.0, |
211 | self.bottom_left - half_border_width.0, |
212 | ) |
213 | .max(Self::zero()) |
214 | } |
215 | } |
216 | |
217 | /// Trait for testing approximate equality |
218 | pub trait ApproxEq<Eps> { |
219 | /// Returns `true` is this object is approximately equal to the other one. |
220 | fn approx_eq(&self, other: &Self) -> bool; |
221 | } |
222 | |
223 | macro_rules! approx_eq { |
224 | ($ty:ty, $eps:expr) => { |
225 | impl ApproxEq<$ty> for $ty { |
226 | #[inline] |
227 | fn approx_eq(&self, other: &$ty) -> bool { |
228 | num_traits::sign::abs(*self - *other) <= $eps |
229 | } |
230 | } |
231 | }; |
232 | } |
233 | |
234 | approx_eq!(i16, 0); |
235 | approx_eq!(i32, 0); |
236 | approx_eq!(f32, f32::EPSILON); |
237 | |
238 | impl<T, U> Add for BorderRadius<T, U> |
239 | where |
240 | T: Add<T, Output = T>, |
241 | { |
242 | type Output = Self; |
243 | fn add(self, other: Self) -> Self { |
244 | BorderRadius::new( |
245 | self.top_left + other.top_left, |
246 | self.top_right + other.top_right, |
247 | self.bottom_right + other.bottom_right, |
248 | self.bottom_left + other.bottom_left, |
249 | ) |
250 | } |
251 | } |
252 | |
253 | impl<T, U> AddAssign<Self> for BorderRadius<T, U> |
254 | where |
255 | T: AddAssign<T>, |
256 | { |
257 | fn add_assign(&mut self, other: Self) { |
258 | self.top_left += other.top_left; |
259 | self.top_right += other.top_right; |
260 | self.bottom_right += other.bottom_right; |
261 | self.bottom_left += other.bottom_left; |
262 | } |
263 | } |
264 | |
265 | impl<T, U> Sub for BorderRadius<T, U> |
266 | where |
267 | T: Sub<T, Output = T>, |
268 | { |
269 | type Output = Self; |
270 | fn sub(self, other: Self) -> Self { |
271 | BorderRadius::new( |
272 | self.top_left - other.top_left, |
273 | self.top_right - other.top_right, |
274 | self.bottom_right - other.bottom_right, |
275 | self.bottom_left - other.bottom_left, |
276 | ) |
277 | } |
278 | } |
279 | |
280 | impl<T, U> SubAssign<Self> for BorderRadius<T, U> |
281 | where |
282 | T: SubAssign<T>, |
283 | { |
284 | fn sub_assign(&mut self, other: Self) { |
285 | self.top_left -= other.top_left; |
286 | self.top_right -= other.top_right; |
287 | self.bottom_right -= other.bottom_right; |
288 | self.bottom_left -= other.bottom_left; |
289 | } |
290 | } |
291 | |
292 | impl<T, U> Neg for BorderRadius<T, U> |
293 | where |
294 | T: Neg<Output = T>, |
295 | { |
296 | type Output = Self; |
297 | fn neg(self) -> Self { |
298 | BorderRadius { |
299 | top_left: -self.top_left, |
300 | top_right: -self.top_right, |
301 | bottom_right: -self.bottom_right, |
302 | bottom_left: -self.bottom_left, |
303 | _unit: PhantomData, |
304 | } |
305 | } |
306 | } |
307 | |
308 | impl<T, U> Mul<T> for BorderRadius<T, U> |
309 | where |
310 | T: Copy + Mul, |
311 | { |
312 | type Output = BorderRadius<T::Output, U>; |
313 | |
314 | #[inline ] |
315 | fn mul(self, scale: T) -> Self::Output { |
316 | BorderRadius::new( |
317 | self.top_left * scale, |
318 | self.top_right * scale, |
319 | self.bottom_right * scale, |
320 | self.bottom_left * scale, |
321 | ) |
322 | } |
323 | } |
324 | |
325 | impl<T, U> MulAssign<T> for BorderRadius<T, U> |
326 | where |
327 | T: Copy + MulAssign, |
328 | { |
329 | #[inline ] |
330 | fn mul_assign(&mut self, other: T) { |
331 | self.top_left *= other; |
332 | self.top_right *= other; |
333 | self.bottom_right *= other; |
334 | self.bottom_left *= other; |
335 | } |
336 | } |
337 | |
338 | impl<T, U1, U2> Mul<Scale<T, U1, U2>> for BorderRadius<T, U1> |
339 | where |
340 | T: Copy + Mul, |
341 | { |
342 | type Output = BorderRadius<T::Output, U2>; |
343 | |
344 | #[inline ] |
345 | fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { |
346 | BorderRadius::new( |
347 | self.top_left * scale.0, |
348 | self.top_right * scale.0, |
349 | self.bottom_right * scale.0, |
350 | self.bottom_left * scale.0, |
351 | ) |
352 | } |
353 | } |
354 | |
355 | impl<T, U> MulAssign<Scale<T, U, U>> for BorderRadius<T, U> |
356 | where |
357 | T: Copy + MulAssign, |
358 | { |
359 | #[inline ] |
360 | fn mul_assign(&mut self, other: Scale<T, U, U>) { |
361 | *self *= other.0; |
362 | } |
363 | } |
364 | |
365 | impl<T, U> Div<T> for BorderRadius<T, U> |
366 | where |
367 | T: Copy + Div, |
368 | { |
369 | type Output = BorderRadius<T::Output, U>; |
370 | |
371 | #[inline ] |
372 | fn div(self, scale: T) -> Self::Output { |
373 | BorderRadius::new( |
374 | self.top_left / scale, |
375 | self.top_right / scale, |
376 | self.bottom_right / scale, |
377 | self.bottom_left / scale, |
378 | ) |
379 | } |
380 | } |
381 | |
382 | impl<T, U> DivAssign<T> for BorderRadius<T, U> |
383 | where |
384 | T: Copy + DivAssign, |
385 | { |
386 | #[inline ] |
387 | fn div_assign(&mut self, other: T) { |
388 | self.top_left /= other; |
389 | self.top_right /= other; |
390 | self.bottom_right /= other; |
391 | self.bottom_left /= other; |
392 | } |
393 | } |
394 | |
395 | impl<T, U1, U2> Div<Scale<T, U1, U2>> for BorderRadius<T, U2> |
396 | where |
397 | T: Copy + Div, |
398 | { |
399 | type Output = BorderRadius<T::Output, U1>; |
400 | |
401 | #[inline ] |
402 | fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { |
403 | BorderRadius::new( |
404 | self.top_left / scale.0, |
405 | self.top_right / scale.0, |
406 | self.bottom_right / scale.0, |
407 | self.bottom_left / scale.0, |
408 | ) |
409 | } |
410 | } |
411 | |
412 | impl<T, U> DivAssign<Scale<T, U, U>> for BorderRadius<T, U> |
413 | where |
414 | T: Copy + DivAssign, |
415 | { |
416 | fn div_assign(&mut self, other: Scale<T, U, U>) { |
417 | *self /= other.0; |
418 | } |
419 | } |
420 | |
421 | impl<T, U> BorderRadius<T, U> |
422 | where |
423 | T: PartialOrd, |
424 | { |
425 | /// Returns the minimum of the two radii. |
426 | #[inline ] |
427 | pub fn min(self, other: Self) -> Self { |
428 | BorderRadius::new( |
429 | top_left:min(self.top_left, other.top_left), |
430 | top_right:min(self.top_right, other.top_right), |
431 | bottom_right:min(self.bottom_right, other.bottom_right), |
432 | bottom_left:min(self.bottom_left, y:other.bottom_left), |
433 | ) |
434 | } |
435 | |
436 | /// Returns the maximum of the two radii. |
437 | #[inline ] |
438 | pub fn max(self, other: Self) -> Self { |
439 | BorderRadius::new( |
440 | top_left:max(self.top_left, other.top_left), |
441 | top_right:max(self.top_right, other.top_right), |
442 | bottom_right:max(self.bottom_right, other.bottom_right), |
443 | bottom_left:max(self.bottom_left, y:other.bottom_left), |
444 | ) |
445 | } |
446 | } |
447 | |
448 | impl<T, U> BorderRadius<T, U> |
449 | where |
450 | T: NumCast + Copy, |
451 | { |
452 | /// Cast from one numeric representation to another, preserving the units. |
453 | #[inline ] |
454 | pub fn cast<NewT: NumCast>(self) -> BorderRadius<NewT, U> { |
455 | self.try_cast().unwrap() |
456 | } |
457 | |
458 | /// Fallible cast from one numeric representation to another, preserving the units. |
459 | pub fn try_cast<NewT: NumCast>(self) -> Option<BorderRadius<NewT, U>> { |
460 | match ( |
461 | NumCast::from(self.top_left), |
462 | NumCast::from(self.top_right), |
463 | NumCast::from(self.bottom_right), |
464 | NumCast::from(self.bottom_left), |
465 | ) { |
466 | (Some(top_left: NewT), Some(top_right: NewT), Some(bottom_right: NewT), Some(bottom_left: NewT)) => { |
467 | Some(BorderRadius::new(top_left, top_right, bottom_right, bottom_left)) |
468 | } |
469 | _ => None, |
470 | } |
471 | } |
472 | } |
473 | |
474 | #[cfg (test)] |
475 | mod tests { |
476 | use crate::lengths::{LogicalBorderRadius, LogicalLength, PhysicalPx, ScaleFactor}; |
477 | use euclid::UnknownUnit; |
478 | |
479 | type BorderRadius = super::BorderRadius<f32, UnknownUnit>; |
480 | type IntBorderRadius = super::BorderRadius<i16, UnknownUnit>; |
481 | type PhysicalBorderRadius = super::BorderRadius<f32, PhysicalPx>; |
482 | |
483 | #[test ] |
484 | fn test_eq() { |
485 | let a = BorderRadius::new(1., 2., 3., 4.); |
486 | let b = BorderRadius::new(1., 2., 3., 4.); |
487 | let c = BorderRadius::new(4., 3., 2., 1.); |
488 | let d = BorderRadius::new( |
489 | c.top_left + f32::EPSILON / 2., |
490 | c.top_right - f32::EPSILON / 2., |
491 | c.bottom_right - f32::EPSILON / 2., |
492 | c.bottom_left + f32::EPSILON / 2., |
493 | ); |
494 | assert_eq!(a, b); |
495 | assert_ne!(a, c); |
496 | assert_eq!(c, d); |
497 | } |
498 | |
499 | #[test ] |
500 | fn test_min_max() { |
501 | let a = BorderRadius::new(1., 2., 3., 4.); |
502 | let b = BorderRadius::new(4., 3., 2., 1.); |
503 | assert_eq!(a.min(b), BorderRadius::new(1., 2., 2., 1.)); |
504 | assert_eq!(a.max(b), BorderRadius::new(4., 3., 3., 4.)); |
505 | } |
506 | |
507 | #[test ] |
508 | fn test_scale() { |
509 | let scale = ScaleFactor::new(2.); |
510 | let logical_radius = LogicalBorderRadius::new(1., 2., 3., 4.); |
511 | let physical_radius = PhysicalBorderRadius::new(2., 4., 6., 8.); |
512 | assert_eq!(logical_radius * scale, physical_radius); |
513 | assert_eq!(physical_radius / scale, logical_radius); |
514 | } |
515 | |
516 | #[test ] |
517 | fn test_zero() { |
518 | assert!(BorderRadius::new_uniform(0.).is_zero()); |
519 | assert!(BorderRadius::new_uniform(1.0e-9).is_zero()); |
520 | assert!(!BorderRadius::new_uniform(1.0e-3).is_zero()); |
521 | assert!(IntBorderRadius::new_uniform(0).is_zero()); |
522 | assert!(!IntBorderRadius::new_uniform(1).is_zero()); |
523 | } |
524 | |
525 | #[test ] |
526 | fn test_inner_outer() { |
527 | let radius = LogicalBorderRadius::new(0., 2.5, 5., 10.); |
528 | let half_border_width = LogicalLength::new(5.); |
529 | assert_eq!(radius.inner(half_border_width), LogicalBorderRadius::new(0., 0., 0., 5.)); |
530 | assert_eq!(radius.outer(half_border_width), LogicalBorderRadius::new(0., 5., 5., 10.)); |
531 | } |
532 | } |
533 | |