use core::fmt;
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use euclid::approxord::{max, min};
use euclid::num::Zero;
use euclid::{Length, Scale};
use num_traits::NumCast;
#[repr(C)]
pub struct BorderRadius<T, U> {
pub top_left: T,
pub top_right: T,
pub bottom_right: T,
pub bottom_left: T,
#[doc(hidden)]
pub _unit: PhantomData<U>,
}
impl<T, U> Copy for BorderRadius<T, U> where T: Copy {}
impl<T, U> Clone for BorderRadius<T, U>
where
T: Clone,
{
fn clone(&self) -> Self {
BorderRadius {
top_left: self.top_left.clone(),
top_right: self.top_right.clone(),
bottom_right: self.bottom_right.clone(),
bottom_left: self.bottom_left.clone(),
_unit: PhantomData,
}
}
}
impl<T, U> Eq for BorderRadius<T, U> where T: Eq {}
impl<T, U> PartialEq for BorderRadius<T, U>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.top_left == other.top_left
&& self.top_right == other.top_right
&& self.bottom_right == other.bottom_right
&& self.bottom_left == other.bottom_left
}
}
impl<T, U> fmt::Debug for BorderRadius<T, U>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"BorderRadius({:?}, {:?}, {:?}, {:?})",
self.top_left, self.top_right, self.bottom_right, self.bottom_left
)
}
}
impl<T, U> Default for BorderRadius<T, U>
where
T: Default,
{
fn default() -> Self {
BorderRadius::new(T::default(), T::default(), T::default(), T::default())
}
}
impl<T, U> Zero for BorderRadius<T, U>
where
T: Zero,
{
fn zero() -> Self {
BorderRadius::new(T::zero(), T::zero(), T::zero(), T::zero())
}
}
impl<T, U> BorderRadius<T, U> {
pub const fn new(top_left: T, top_right: T, bottom_right: T, bottom_left: T) -> Self {
BorderRadius { top_left, top_right, bottom_right, bottom_left, _unit: PhantomData }
}
pub fn from_lengths(
top_left: Length<T, U>,
top_right: Length<T, U>,
bottom_right: Length<T, U>,
bottom_left: Length<T, U>,
) -> Self {
BorderRadius::new(top_left.0, top_right.0, bottom_right.0, bottom_left.0)
}
pub fn new_uniform(all: T) -> Self
where
T: Copy,
{
BorderRadius::new(all, all, all, all)
}
pub fn from_length(all: Length<T, U>) -> Self
where
T: Copy,
{
BorderRadius::new_uniform(all.0)
}
pub fn is_uniform(&self) -> bool
where
T: ApproxEq<T>,
{
self.top_left.approx_eq(&self.top_right)
&& self.top_left.approx_eq(&self.bottom_right)
&& self.top_left.approx_eq(&self.bottom_left)
}
pub fn as_uniform(&self) -> Option<T>
where
T: Copy + ApproxEq<T>,
{
if self.is_uniform() {
Some(self.top_left)
} else {
None
}
}
pub fn is_zero(&self) -> bool
where
T: ApproxEq<T> + Zero,
{
let zero = T::zero();
self.top_left.approx_eq(&zero)
&& self.top_right.approx_eq(&zero)
&& self.bottom_right.approx_eq(&zero)
&& self.bottom_left.approx_eq(&zero)
}
pub fn outer(&self, half_border_width: Length<T, U>) -> Self
where
T: Copy + PartialOrd + Zero,
{
let zero = T::zero();
BorderRadius::new(
if self.top_left > zero {
max(self.top_left, half_border_width.0)
} else {
self.top_left
},
if self.top_right > zero {
max(self.top_right, half_border_width.0)
} else {
self.top_right
},
if self.bottom_right > zero {
max(self.bottom_right, half_border_width.0)
} else {
self.bottom_right
},
if self.bottom_left > zero {
max(self.bottom_left, half_border_width.0)
} else {
self.bottom_left
},
)
}
pub fn inner(&self, half_border_width: Length<T, U>) -> Self
where
T: Copy + PartialOrd + Sub<T, Output = T> + Zero,
{
BorderRadius::new(
self.top_left - half_border_width.0,
self.top_right - half_border_width.0,
self.bottom_right - half_border_width.0,
self.bottom_left - half_border_width.0,
)
.max(Self::zero())
}
}
pub trait ApproxEq<Eps> {
fn approx_eq(&self, other: &Self) -> bool;
}
macro_rules! approx_eq {
($ty:ty, $eps:expr) => {
impl ApproxEq<$ty> for $ty {
#[inline]
fn approx_eq(&self, other: &$ty) -> bool {
num_traits::sign::abs(*self - *other) <= $eps
}
}
};
}
approx_eq!(i16, 0);
approx_eq!(i32, 0);
approx_eq!(f32, f32::EPSILON);
impl<T, U> Add for BorderRadius<T, U>
where
T: Add<T, Output = T>,
{
type Output = Self;
fn add(self, other: Self) -> Self {
BorderRadius::new(
self.top_left + other.top_left,
self.top_right + other.top_right,
self.bottom_right + other.bottom_right,
self.bottom_left + other.bottom_left,
)
}
}
impl<T, U> AddAssign<Self> for BorderRadius<T, U>
where
T: AddAssign<T>,
{
fn add_assign(&mut self, other: Self) {
self.top_left += other.top_left;
self.top_right += other.top_right;
self.bottom_right += other.bottom_right;
self.bottom_left += other.bottom_left;
}
}
impl<T, U> Sub for BorderRadius<T, U>
where
T: Sub<T, Output = T>,
{
type Output = Self;
fn sub(self, other: Self) -> Self {
BorderRadius::new(
self.top_left - other.top_left,
self.top_right - other.top_right,
self.bottom_right - other.bottom_right,
self.bottom_left - other.bottom_left,
)
}
}
impl<T, U> SubAssign<Self> for BorderRadius<T, U>
where
T: SubAssign<T>,
{
fn sub_assign(&mut self, other: Self) {
self.top_left -= other.top_left;
self.top_right -= other.top_right;
self.bottom_right -= other.bottom_right;
self.bottom_left -= other.bottom_left;
}
}
impl<T, U> Neg for BorderRadius<T, U>
where
T: Neg<Output = T>,
{
type Output = Self;
fn neg(self) -> Self {
BorderRadius {
top_left: -self.top_left,
top_right: -self.top_right,
bottom_right: -self.bottom_right,
bottom_left: -self.bottom_left,
_unit: PhantomData,
}
}
}
impl<T, U> Mul<T> for BorderRadius<T, U>
where
T: Copy + Mul,
{
type Output = BorderRadius<T::Output, U>;
#[inline]
fn mul(self, scale: T) -> Self::Output {
BorderRadius::new(
self.top_left * scale,
self.top_right * scale,
self.bottom_right * scale,
self.bottom_left * scale,
)
}
}
impl<T, U> MulAssign<T> for BorderRadius<T, U>
where
T: Copy + MulAssign,
{
#[inline]
fn mul_assign(&mut self, other: T) {
self.top_left *= other;
self.top_right *= other;
self.bottom_right *= other;
self.bottom_left *= other;
}
}
impl<T, U1, U2> Mul<Scale<T, U1, U2>> for BorderRadius<T, U1>
where
T: Copy + Mul,
{
type Output = BorderRadius<T::Output, U2>;
#[inline]
fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
BorderRadius::new(
self.top_left * scale.0,
self.top_right * scale.0,
self.bottom_right * scale.0,
self.bottom_left * scale.0,
)
}
}
impl<T, U> MulAssign<Scale<T, U, U>> for BorderRadius<T, U>
where
T: Copy + MulAssign,
{
#[inline]
fn mul_assign(&mut self, other: Scale<T, U, U>) {
*self *= other.0;
}
}
impl<T, U> Div<T> for BorderRadius<T, U>
where
T: Copy + Div,
{
type Output = BorderRadius<T::Output, U>;
#[inline]
fn div(self, scale: T) -> Self::Output {
BorderRadius::new(
self.top_left / scale,
self.top_right / scale,
self.bottom_right / scale,
self.bottom_left / scale,
)
}
}
impl<T, U> DivAssign<T> for BorderRadius<T, U>
where
T: Copy + DivAssign,
{
#[inline]
fn div_assign(&mut self, other: T) {
self.top_left /= other;
self.top_right /= other;
self.bottom_right /= other;
self.bottom_left /= other;
}
}
impl<T, U1, U2> Div<Scale<T, U1, U2>> for BorderRadius<T, U2>
where
T: Copy + Div,
{
type Output = BorderRadius<T::Output, U1>;
#[inline]
fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
BorderRadius::new(
self.top_left / scale.0,
self.top_right / scale.0,
self.bottom_right / scale.0,
self.bottom_left / scale.0,
)
}
}
impl<T, U> DivAssign<Scale<T, U, U>> for BorderRadius<T, U>
where
T: Copy + DivAssign,
{
fn div_assign(&mut self, other: Scale<T, U, U>) {
*self /= other.0;
}
}
impl<T, U> BorderRadius<T, U>
where
T: PartialOrd,
{
#[inline]
pub fn min(self, other: Self) -> Self {
BorderRadius::new(
min(self.top_left, other.top_left),
min(self.top_right, other.top_right),
min(self.bottom_right, other.bottom_right),
min(self.bottom_left, other.bottom_left),
)
}
#[inline]
pub fn max(self, other: Self) -> Self {
BorderRadius::new(
max(self.top_left, other.top_left),
max(self.top_right, other.top_right),
max(self.bottom_right, other.bottom_right),
max(self.bottom_left, other.bottom_left),
)
}
}
impl<T, U> BorderRadius<T, U>
where
T: NumCast + Copy,
{
#[inline]
pub fn cast<NewT: NumCast>(self) -> BorderRadius<NewT, U> {
self.try_cast().unwrap()
}
pub fn try_cast<NewT: NumCast>(self) -> Option<BorderRadius<NewT, U>> {
match (
NumCast::from(self.top_left),
NumCast::from(self.top_right),
NumCast::from(self.bottom_right),
NumCast::from(self.bottom_left),
) {
(Some(top_left), Some(top_right), Some(bottom_right), Some(bottom_left)) => {
Some(BorderRadius::new(top_left, top_right, bottom_right, bottom_left))
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::lengths::{LogicalBorderRadius, LogicalLength, PhysicalPx, ScaleFactor};
use euclid::UnknownUnit;
type BorderRadius = super::BorderRadius<f32, UnknownUnit>;
type IntBorderRadius = super::BorderRadius<i16, UnknownUnit>;
type PhysicalBorderRadius = super::BorderRadius<f32, PhysicalPx>;
#[test]
fn test_eq() {
let a = BorderRadius::new(1., 2., 3., 4.);
let b = BorderRadius::new(1., 2., 3., 4.);
let c = BorderRadius::new(4., 3., 2., 1.);
let d = BorderRadius::new(
c.top_left + f32::EPSILON / 2.,
c.top_right - f32::EPSILON / 2.,
c.bottom_right - f32::EPSILON / 2.,
c.bottom_left + f32::EPSILON / 2.,
);
assert_eq!(a, b);
assert_ne!(a, c);
assert_eq!(c, d);
}
#[test]
fn test_min_max() {
let a = BorderRadius::new(1., 2., 3., 4.);
let b = BorderRadius::new(4., 3., 2., 1.);
assert_eq!(a.min(b), BorderRadius::new(1., 2., 2., 1.));
assert_eq!(a.max(b), BorderRadius::new(4., 3., 3., 4.));
}
#[test]
fn test_scale() {
let scale = ScaleFactor::new(2.);
let logical_radius = LogicalBorderRadius::new(1., 2., 3., 4.);
let physical_radius = PhysicalBorderRadius::new(2., 4., 6., 8.);
assert_eq!(logical_radius * scale, physical_radius);
assert_eq!(physical_radius / scale, logical_radius);
}
#[test]
fn test_zero() {
assert!(BorderRadius::new_uniform(0.).is_zero());
assert!(BorderRadius::new_uniform(1.0e-9).is_zero());
assert!(!BorderRadius::new_uniform(1.0e-3).is_zero());
assert!(IntBorderRadius::new_uniform(0).is_zero());
assert!(!IntBorderRadius::new_uniform(1).is_zero());
}
#[test]
fn test_inner_outer() {
let radius = LogicalBorderRadius::new(0., 2.5, 5., 10.);
let half_border_width = LogicalLength::new(5.);
assert_eq!(radius.inner(half_border_width), LogicalBorderRadius::new(0., 0., 0., 5.));
assert_eq!(radius.outer(half_border_width), LogicalBorderRadius::new(0., 5., 5., 10.));
}
}