1use core::fmt;
9use core::marker::PhantomData;
10use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
11use euclid::approxord::{max, min};
12use euclid::num::Zero;
13use euclid::{Length, Scale};
14use num_traits::NumCast;
15
16#[repr(C)]
19pub struct BorderRadius<T, U> {
20 pub top_left: T,
22 pub top_right: T,
24 pub bottom_right: T,
26 pub bottom_left: T,
28 #[doc(hidden)]
29 pub _unit: PhantomData<U>,
30}
31
32impl<T, U> Copy for BorderRadius<T, U> where T: Copy {}
33
34impl<T, U> Clone for BorderRadius<T, U>
35where
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
49impl<T, U> Eq for BorderRadius<T, U> where T: Eq {}
50
51impl<T, U> PartialEq for BorderRadius<T, U>
52where
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
63impl<T, U> fmt::Debug for BorderRadius<T, U>
64where
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
76impl<T, U> Default for BorderRadius<T, U>
77where
78 T: Default,
79{
80 fn default() -> Self {
81 BorderRadius::new(T::default(), T::default(), T::default(), T::default())
82 }
83}
84
85impl<T, U> Zero for BorderRadius<T, U>
86where
87 T: Zero,
88{
89 fn zero() -> Self {
90 BorderRadius::new(T::zero(), T::zero(), T::zero(), T::zero())
91 }
92}
93
94impl<T, U> BorderRadius<T, U> {
95 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 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 pub fn new_uniform(all: T) -> Self
118 where
119 T: Copy,
120 {
121 BorderRadius::new(all, all, all, all)
122 }
123
124 pub fn from_length(all: Length<T, U>) -> Self
126 where
127 T: Copy,
128 {
129 BorderRadius::new_uniform(all.0)
130 }
131
132 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 pub fn as_uniform(&self) -> Option<T>
144 where
145 T: Copy + ApproxEq<T>,
146 {
147 if self.is_uniform() { Some(self.top_left) } else { None }
148 }
149
150 pub fn is_zero(&self) -> bool
152 where
153 T: ApproxEq<T> + Zero,
154 {
155 let zero = T::zero();
156 self.top_left.approx_eq(&zero)
157 && self.top_right.approx_eq(&zero)
158 && self.bottom_right.approx_eq(&zero)
159 && self.bottom_left.approx_eq(&zero)
160 }
161
162 pub fn outer(&self, half_border_width: Length<T, U>) -> Self
167 where
168 T: Copy + PartialOrd + Zero,
169 {
170 let zero = T::zero();
171 BorderRadius::new(
172 if self.top_left > zero {
173 max(self.top_left, half_border_width.0)
174 } else {
175 self.top_left
176 },
177 if self.top_right > zero {
178 max(self.top_right, half_border_width.0)
179 } else {
180 self.top_right
181 },
182 if self.bottom_right > zero {
183 max(self.bottom_right, half_border_width.0)
184 } else {
185 self.bottom_right
186 },
187 if self.bottom_left > zero {
188 max(self.bottom_left, half_border_width.0)
189 } else {
190 self.bottom_left
191 },
192 )
193 }
194
195 pub fn inner(&self, half_border_width: Length<T, U>) -> Self
200 where
201 T: Copy + PartialOrd + Sub<T, Output = T> + Zero,
202 {
203 BorderRadius::new(
204 self.top_left - half_border_width.0,
205 self.top_right - half_border_width.0,
206 self.bottom_right - half_border_width.0,
207 self.bottom_left - half_border_width.0,
208 )
209 .max(Self::zero())
210 }
211}
212
213pub trait ApproxEq<Eps> {
215 fn approx_eq(&self, other: &Self) -> bool;
217}
218
219macro_rules! approx_eq {
220 ($ty:ty, $eps:expr) => {
221 impl ApproxEq<$ty> for $ty {
222 #[inline]
223 fn approx_eq(&self, other: &$ty) -> bool {
224 num_traits::sign::abs(*self - *other) <= $eps
225 }
226 }
227 };
228}
229
230approx_eq!(i16, 0);
231approx_eq!(i32, 0);
232approx_eq!(f32, f32::EPSILON);
233
234impl<T, U> Add for BorderRadius<T, U>
235where
236 T: Add<T, Output = T>,
237{
238 type Output = Self;
239 fn add(self, other: Self) -> Self {
240 BorderRadius::new(
241 self.top_left + other.top_left,
242 self.top_right + other.top_right,
243 self.bottom_right + other.bottom_right,
244 self.bottom_left + other.bottom_left,
245 )
246 }
247}
248
249impl<T, U> AddAssign<Self> for BorderRadius<T, U>
250where
251 T: AddAssign<T>,
252{
253 fn add_assign(&mut self, other: Self) {
254 self.top_left += other.top_left;
255 self.top_right += other.top_right;
256 self.bottom_right += other.bottom_right;
257 self.bottom_left += other.bottom_left;
258 }
259}
260
261impl<T, U> Sub for BorderRadius<T, U>
262where
263 T: Sub<T, Output = T>,
264{
265 type Output = Self;
266 fn sub(self, other: Self) -> Self {
267 BorderRadius::new(
268 self.top_left - other.top_left,
269 self.top_right - other.top_right,
270 self.bottom_right - other.bottom_right,
271 self.bottom_left - other.bottom_left,
272 )
273 }
274}
275
276impl<T, U> SubAssign<Self> for BorderRadius<T, U>
277where
278 T: SubAssign<T>,
279{
280 fn sub_assign(&mut self, other: Self) {
281 self.top_left -= other.top_left;
282 self.top_right -= other.top_right;
283 self.bottom_right -= other.bottom_right;
284 self.bottom_left -= other.bottom_left;
285 }
286}
287
288impl<T, U> Neg for BorderRadius<T, U>
289where
290 T: Neg<Output = T>,
291{
292 type Output = Self;
293 fn neg(self) -> Self {
294 BorderRadius {
295 top_left: -self.top_left,
296 top_right: -self.top_right,
297 bottom_right: -self.bottom_right,
298 bottom_left: -self.bottom_left,
299 _unit: PhantomData,
300 }
301 }
302}
303
304impl<T, U> Mul<T> for BorderRadius<T, U>
305where
306 T: Copy + Mul,
307{
308 type Output = BorderRadius<T::Output, U>;
309
310 #[inline]
311 fn mul(self, scale: T) -> Self::Output {
312 BorderRadius::new(
313 self.top_left * scale,
314 self.top_right * scale,
315 self.bottom_right * scale,
316 self.bottom_left * scale,
317 )
318 }
319}
320
321impl<T, U> MulAssign<T> for BorderRadius<T, U>
322where
323 T: Copy + MulAssign,
324{
325 #[inline]
326 fn mul_assign(&mut self, other: T) {
327 self.top_left *= other;
328 self.top_right *= other;
329 self.bottom_right *= other;
330 self.bottom_left *= other;
331 }
332}
333
334impl<T, U1, U2> Mul<Scale<T, U1, U2>> for BorderRadius<T, U1>
335where
336 T: Copy + Mul,
337{
338 type Output = BorderRadius<T::Output, U2>;
339
340 #[inline]
341 fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
342 BorderRadius::new(
343 self.top_left * scale.0,
344 self.top_right * scale.0,
345 self.bottom_right * scale.0,
346 self.bottom_left * scale.0,
347 )
348 }
349}
350
351impl<T, U> MulAssign<Scale<T, U, U>> for BorderRadius<T, U>
352where
353 T: Copy + MulAssign,
354{
355 #[inline]
356 fn mul_assign(&mut self, other: Scale<T, U, U>) {
357 *self *= other.0;
358 }
359}
360
361impl<T, U> Div<T> for BorderRadius<T, U>
362where
363 T: Copy + Div,
364{
365 type Output = BorderRadius<T::Output, U>;
366
367 #[inline]
368 fn div(self, scale: T) -> Self::Output {
369 BorderRadius::new(
370 self.top_left / scale,
371 self.top_right / scale,
372 self.bottom_right / scale,
373 self.bottom_left / scale,
374 )
375 }
376}
377
378impl<T, U> DivAssign<T> for BorderRadius<T, U>
379where
380 T: Copy + DivAssign,
381{
382 #[inline]
383 fn div_assign(&mut self, other: T) {
384 self.top_left /= other;
385 self.top_right /= other;
386 self.bottom_right /= other;
387 self.bottom_left /= other;
388 }
389}
390
391impl<T, U1, U2> Div<Scale<T, U1, U2>> for BorderRadius<T, U2>
392where
393 T: Copy + Div,
394{
395 type Output = BorderRadius<T::Output, U1>;
396
397 #[inline]
398 fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
399 BorderRadius::new(
400 self.top_left / scale.0,
401 self.top_right / scale.0,
402 self.bottom_right / scale.0,
403 self.bottom_left / scale.0,
404 )
405 }
406}
407
408impl<T, U> DivAssign<Scale<T, U, U>> for BorderRadius<T, U>
409where
410 T: Copy + DivAssign,
411{
412 fn div_assign(&mut self, other: Scale<T, U, U>) {
413 *self /= other.0;
414 }
415}
416
417impl<T, U> BorderRadius<T, U>
418where
419 T: PartialOrd,
420{
421 #[inline]
423 pub fn min(self, other: Self) -> Self {
424 BorderRadius::new(
425 min(self.top_left, other.top_left),
426 min(self.top_right, other.top_right),
427 min(self.bottom_right, other.bottom_right),
428 min(self.bottom_left, other.bottom_left),
429 )
430 }
431
432 #[inline]
434 pub fn max(self, other: Self) -> Self {
435 BorderRadius::new(
436 max(self.top_left, other.top_left),
437 max(self.top_right, other.top_right),
438 max(self.bottom_right, other.bottom_right),
439 max(self.bottom_left, other.bottom_left),
440 )
441 }
442}
443
444impl<T, U> BorderRadius<T, U>
445where
446 T: NumCast + Copy,
447{
448 #[inline]
450 pub fn cast<NewT: NumCast>(self) -> BorderRadius<NewT, U> {
451 self.try_cast().unwrap()
452 }
453
454 pub fn try_cast<NewT: NumCast>(self) -> Option<BorderRadius<NewT, U>> {
456 match (
457 NumCast::from(self.top_left),
458 NumCast::from(self.top_right),
459 NumCast::from(self.bottom_right),
460 NumCast::from(self.bottom_left),
461 ) {
462 (Some(top_left), Some(top_right), Some(bottom_right), Some(bottom_left)) => {
463 Some(BorderRadius::new(top_left, top_right, bottom_right, bottom_left))
464 }
465 _ => None,
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use crate::lengths::{LogicalBorderRadius, LogicalLength, PhysicalPx, ScaleFactor};
473 use euclid::UnknownUnit;
474
475 type BorderRadius = super::BorderRadius<f32, UnknownUnit>;
476 type IntBorderRadius = super::BorderRadius<i16, UnknownUnit>;
477 type PhysicalBorderRadius = super::BorderRadius<f32, PhysicalPx>;
478
479 #[test]
480 fn test_eq() {
481 let a = BorderRadius::new(1., 2., 3., 4.);
482 let b = BorderRadius::new(1., 2., 3., 4.);
483 let c = BorderRadius::new(4., 3., 2., 1.);
484 let d = BorderRadius::new(
485 c.top_left + f32::EPSILON / 2.,
486 c.top_right - f32::EPSILON / 2.,
487 c.bottom_right - f32::EPSILON / 2.,
488 c.bottom_left + f32::EPSILON / 2.,
489 );
490 assert_eq!(a, b);
491 assert_ne!(a, c);
492 assert_eq!(c, d);
493 }
494
495 #[test]
496 fn test_min_max() {
497 let a = BorderRadius::new(1., 2., 3., 4.);
498 let b = BorderRadius::new(4., 3., 2., 1.);
499 assert_eq!(a.min(b), BorderRadius::new(1., 2., 2., 1.));
500 assert_eq!(a.max(b), BorderRadius::new(4., 3., 3., 4.));
501 }
502
503 #[test]
504 fn test_scale() {
505 let scale = ScaleFactor::new(2.);
506 let logical_radius = LogicalBorderRadius::new(1., 2., 3., 4.);
507 let physical_radius = PhysicalBorderRadius::new(2., 4., 6., 8.);
508 assert_eq!(logical_radius * scale, physical_radius);
509 assert_eq!(physical_radius / scale, logical_radius);
510 }
511
512 #[test]
513 fn test_zero() {
514 assert!(BorderRadius::new_uniform(0.).is_zero());
515 assert!(BorderRadius::new_uniform(1.0e-9).is_zero());
516 assert!(!BorderRadius::new_uniform(1.0e-3).is_zero());
517 assert!(IntBorderRadius::new_uniform(0).is_zero());
518 assert!(!IntBorderRadius::new_uniform(1).is_zero());
519 }
520
521 #[test]
522 fn test_inner_outer() {
523 let radius = LogicalBorderRadius::new(0., 2.5, 5., 10.);
524 let half_border_width = LogicalLength::new(5.);
525 assert_eq!(radius.inner(half_border_width), LogicalBorderRadius::new(0., 0., 0., 5.));
526 assert_eq!(radius.outer(half_border_width), LogicalBorderRadius::new(0., 5., 5., 10.));
527 }
528}