1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use crate::prelude::*;
use num_traits::cast::AsPrimitive;

pub type F32 = ordered_float::NotNan<f32>;

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, strum::Display, PartialOrd, Ord)]
pub enum Operator {
	#[strum(to_string = "+")] Plus,
	#[strum(to_string = "-")] Minus,
}

#[derive(Debug, PartialEq, Eq, Hash, Clone, SmartDefault, PartialOrd, Ord)]
pub enum Unit {
	#[default]
	Zero,
	Px(F32),
	Em(F32),
	Rem(F32),
	Vw(F32),
	Vh(F32),
	Vmin(F32),
	Vmax(F32),
	Fr(F32),
	Percent(F32),
	// probably a hack
	Duration(F32), // TODO: chrono duration?
	Calc(Box<Unit>, Operator, Box<Unit>),
}

impl Unit {
	#[inline] pub fn calc(left: Self, op: Operator, right: Self) -> Self { Self::Calc(Box::new(left), op, Box::new(right)) }
	#[inline] pub fn px<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Px(      F32::new(x.as_()).unwrap()) }
	#[inline] pub fn em<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Em(      F32::new(x.as_()).unwrap()) }
	#[inline] pub fn rem<T: AsPrimitive<f32>>(x: T)  -> Self { Self::Rem(     F32::new(x.as_()).unwrap()) }
	#[inline] pub fn vw<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Vw(      F32::new(x.as_()).unwrap()) }
	#[inline] pub fn vh<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Vh(      F32::new(x.as_()).unwrap()) }
	#[inline] pub fn vmin<T: AsPrimitive<f32>>(x: T) -> Self { Self::Vmin(    F32::new(x.as_()).unwrap()) }
	#[inline] pub fn vmax<T: AsPrimitive<f32>>(x: T) -> Self { Self::Vmax(    F32::new(x.as_()).unwrap()) }
	#[inline] pub fn fr<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Fr(      F32::new(x.as_()).unwrap()) }
	#[inline] pub fn pct<T: AsPrimitive<f32>>(x: T)  -> Self { Self::Percent( F32::new(x.as_()).unwrap()) }
	#[inline] pub fn dur<T: AsPrimitive<f32>>(x: T)  -> Self { Self::Duration(F32::new(x.as_()).unwrap()) }
}

#[rustfmt::skip]
impl std::fmt::Display for Unit {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			Self::Zero                  => "0".fmt(f),
			Self::Px(x)                 => write!(f, "{}px", x),
			Self::Em(x)                 => write!(f, "{}em", x),
			Self::Rem(x)                => write!(f, "{}rem", x),
			Self::Vw(x)                 => write!(f, "{}vw", x),
			Self::Vh(x)                 => write!(f, "{}vh", x),
			Self::Vmin(x)               => write!(f, "{}vmin", x),
			Self::Vmax(x)               => write!(f, "{}vmax", x),
			Self::Fr(x)                 => write!(f, "{}fr", x),
			Self::Percent(x)            => write!(f, "{}%", x),
			Self::Duration(x)           => write!(f, "{}ms", x),
			Self::Calc(left, op, right) => write!(f, "calc({} {} {})", left, op, right),
		}
	}
}

/// As pixels is the most common unit type, specifying `px` is optional
#[rustfmt::skip]
#[macro_export]
macro_rules! unit {
	(0)                                             => { $crate::units::Unit::Zero    };

	($e:literal $(px)?)                             => { $crate::units::Unit::px($e)        };
	($e:literal ms)                                 => { $crate::units::Unit::dur($e)       };
	($e:literal $frag:ident)                        => { $crate::units::Unit::$frag($e)     };
	($e:literal %)                                  => { $crate::units::Unit::pct($e)       };

	($e:ident $(px)?)                               => { $crate::units::Unit::px($e)        };
	($e:ident ms)                                   => { $crate::units::Unit::dur($e)       };
	($e:ident $frag:ident)                          => { $crate::units::Unit::$frag($e)     };
	($e:ident %)                                    => { $crate::units::Unit::pct($e)       };

	// this so you can use a more complex expression by wrapping it in parens
	(($($e:tt)+) $(px)?)                            => { $crate::units::Unit::px($($e)+)    };
	(($($e:tt)+) ms)                                => { $crate::units::Unit::dur($($e)+)   };
	(($($e:tt)+) $frag:ident)                       => { $crate::units::Unit::$frag($($e)+) };
	(($($e:tt)+) %)                                 => { $crate::units::Unit::pct($($e)+)   };

	// garbage
	($e1:literal $frag1:tt + $e2:literal $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1 $frag1), $crate::units::Operator::Plus,  $crate::unit!($e2 $frag2)) };
	($e1:ident   $frag1:tt + $e2:literal $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1 $frag1), $crate::units::Operator::Plus,  $crate::unit!($e2 $frag2)) };
	($e1:literal $frag1:tt + $e2:ident   $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1 $frag1), $crate::units::Operator::Plus,  $crate::unit!($e2 $frag2)) };
	($e1:ident   $frag1:tt + $e2:ident   $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1 $frag1), $crate::units::Operator::Plus,  $crate::unit!($e2 $frag2)) };

	($e1:literal $frag1:tt - $e2:literal $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1 $frag1), $crate::units::Operator::Minus, $crate::unit!($e2 $frag2)) };
	($e1:ident   $frag1:tt - $e2:literal $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1 $frag1), $crate::units::Operator::Minus, $crate::unit!($e2 $frag2)) };
	($e1:literal $frag1:tt - $e2:ident   $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1 $frag1), $crate::units::Operator::Minus, $crate::unit!($e2 $frag2)) };
	($e1:ident   $frag1:tt - $e2:ident   $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1 $frag1), $crate::units::Operator::Minus, $crate::unit!($e2 $frag2)) };
}