hobo_css/
units.rs

1use num_traits::cast::AsPrimitive;
2
3pub type F32 = ordered_float::NotNan<f32>;
4
5#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, strum::Display, PartialOrd, Ord)]
6pub enum Operator {
7	#[strum(to_string = "+")] Plus,
8	#[strum(to_string = "-")] Minus,
9}
10
11#[derive(Debug, PartialEq, Eq, Hash, Clone, Default, PartialOrd, Ord)]
12pub enum Unit {
13	#[default]
14	Zero,
15	Px(F32),
16	Em(F32),
17	Rem(F32),
18	Vw(F32),
19	Vh(F32),
20	Vmin(F32),
21	Vmax(F32),
22	Fr(F32),
23	Percent(F32),
24	// probably a hack
25	Duration(F32), // TODO: chrono duration?
26	Calc(Box<Unit>, Operator, Box<Unit>),
27}
28
29impl Unit {
30	#[inline] pub fn calc(left: Self, op: Operator, right: Self) -> Self { Self::Calc(Box::new(left), op, Box::new(right)) }
31	#[inline] pub fn px<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Px(      F32::new(x.as_()).unwrap()) }
32	#[inline] pub fn em<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Em(      F32::new(x.as_()).unwrap()) }
33	#[inline] pub fn rem<T: AsPrimitive<f32>>(x: T)  -> Self { Self::Rem(     F32::new(x.as_()).unwrap()) }
34	#[inline] pub fn vw<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Vw(      F32::new(x.as_()).unwrap()) }
35	#[inline] pub fn vh<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Vh(      F32::new(x.as_()).unwrap()) }
36	#[inline] pub fn vmin<T: AsPrimitive<f32>>(x: T) -> Self { Self::Vmin(    F32::new(x.as_()).unwrap()) }
37	#[inline] pub fn vmax<T: AsPrimitive<f32>>(x: T) -> Self { Self::Vmax(    F32::new(x.as_()).unwrap()) }
38	#[inline] pub fn fr<T: AsPrimitive<f32>>(x: T)   -> Self { Self::Fr(      F32::new(x.as_()).unwrap()) }
39	#[inline] pub fn pct<T: AsPrimitive<f32>>(x: T)  -> Self { Self::Percent( F32::new(x.as_()).unwrap()) }
40	#[inline] pub fn dur<T: AsPrimitive<f32>>(x: T)  -> Self { Self::Duration(F32::new(x.as_()).unwrap()) }
41}
42
43#[rustfmt::skip]
44impl std::fmt::Display for Unit {
45	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46		match self {
47			Self::Zero                  => "0".fmt(f),
48			Self::Px(x)                 => write!(f, "{}px", x),
49			Self::Em(x)                 => write!(f, "{}em", x),
50			Self::Rem(x)                => write!(f, "{}rem", x),
51			Self::Vw(x)                 => write!(f, "{}vw", x),
52			Self::Vh(x)                 => write!(f, "{}vh", x),
53			Self::Vmin(x)               => write!(f, "{}vmin", x),
54			Self::Vmax(x)               => write!(f, "{}vmax", x),
55			Self::Fr(x)                 => write!(f, "{}fr", x),
56			Self::Percent(x)            => write!(f, "{}%", x),
57			Self::Duration(x)           => write!(f, "{}ms", x),
58			Self::Calc(left, op, right) => write!(f, "calc({} {} {})", left, op, right),
59		}
60	}
61}
62
63impl std::ops::Add for Unit {
64	type Output = Self;
65
66	fn add(self, rhs: Self) -> Self {
67		match (self, rhs) {
68			(Self::Zero, Self::Zero) => Self::Zero,
69			(Self::Px(a), Self::Px(b)) => Self::Px(a + b),
70			(Self::Em(a), Self::Em(b)) => Self::Em(a + b),
71			(Self::Rem(a), Self::Rem(b)) => Self::Rem(a + b),
72			(Self::Vw(a), Self::Vw(b)) => Self::Vw(a + b),
73			(Self::Vh(a), Self::Vh(b)) => Self::Vh(a + b),
74			(Self::Vmin(a), Self::Vmin(b)) => Self::Vmin(a + b),
75			(Self::Vmax(a), Self::Vmax(b)) => Self::Vmax(a + b),
76			(Self::Fr(a), Self::Fr(b)) => Self::Fr(a + b),
77			(Self::Percent(a), Self::Percent(b)) => Self::Percent(a + b),
78			(Self::Duration(a), Self::Duration(b)) => Self::Duration(a + b),
79			(a, b) => Self::Calc(Box::new(a), Operator::Plus, Box::new(b)),
80		}
81	}
82}
83
84impl std::ops::Sub for Unit {
85	type Output = Self;
86
87	fn sub(self, rhs: Self) -> Self {
88		match (self, rhs) {
89			(Self::Zero, Self::Zero) => Self::Zero,
90			(Self::Px(a), Self::Px(b)) => Self::Px(a - b),
91			(Self::Em(a), Self::Em(b)) => Self::Em(a - b),
92			(Self::Rem(a), Self::Rem(b)) => Self::Rem(a - b),
93			(Self::Vw(a), Self::Vw(b)) => Self::Vw(a - b),
94			(Self::Vh(a), Self::Vh(b)) => Self::Vh(a - b),
95			(Self::Vmin(a), Self::Vmin(b)) => Self::Vmin(a - b),
96			(Self::Vmax(a), Self::Vmax(b)) => Self::Vmax(a - b),
97			(Self::Fr(a), Self::Fr(b)) => Self::Fr(a - b),
98			(Self::Percent(a), Self::Percent(b)) => Self::Percent(a - b),
99			(Self::Duration(a), Self::Duration(b)) => Self::Duration(a - b),
100			(a, b) => Self::Calc(Box::new(a), Operator::Minus, Box::new(b)),
101		}
102	}
103}
104
105/// As pixels is the most common unit type, specifying `px` is optional
106#[rustfmt::skip]
107#[macro_export]
108macro_rules! unit {
109	(0)                                             => { $crate::units::Unit::Zero          };
110
111	($e:literal $(px)?)                             => { $crate::units::Unit::px($e)        };
112	($e:literal ms)                                 => { $crate::units::Unit::dur($e)       };
113	($e:literal $frag:ident)                        => { $crate::units::Unit::$frag($e)     };
114	($e:literal %)                                  => { $crate::units::Unit::pct($e)       };
115
116	($e:ident $(px)?)                               => { $crate::units::Unit::px($e)        };
117	($e:ident ms)                                   => { $crate::units::Unit::dur($e)       };
118	($e:ident $frag:ident)                          => { $crate::units::Unit::$frag($e)     };
119	($e:ident %)                                    => { $crate::units::Unit::pct($e)       };
120
121	// this so you can use a more complex expression by wrapping it in parens
122	(($($e:tt)+) $(px)?)                            => { $crate::units::Unit::px($($e)+)    };
123	(($($e:tt)+) ms)                                => { $crate::units::Unit::dur($($e)+)   };
124	(($($e:tt)+) $frag:ident)                       => { $crate::units::Unit::$frag($($e)+) };
125	(($($e:tt)+) %)                                 => { $crate::units::Unit::pct($($e)+)   };
126
127	// garbage
128	($e1:literal  $frag1:tt + $e2:literal  $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Plus, $crate::unit!($e2       $frag2)) };
129	($e1:ident    $frag1:tt + $e2:literal  $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Plus, $crate::unit!($e2       $frag2)) };
130	(($($e1:tt)+) $frag1:tt + $e2:literal  $frag2:tt) => { $crate::units::Unit::calc($crate::unit!(($($e1)+) $frag1), $crate::units::Operator::Plus, $crate::unit!($e2       $frag2)) };
131	($e1:literal  $frag1:tt + $e2:ident    $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Plus, $crate::unit!($e2       $frag2)) };
132	($e1:ident    $frag1:tt + $e2:ident    $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Plus, $crate::unit!($e2       $frag2)) };
133	(($($e1:tt)+) $frag1:tt + $e2:ident    $frag2:tt) => { $crate::units::Unit::calc($crate::unit!(($($e1)+) $frag1), $crate::units::Operator::Plus, $crate::unit!($e2       $frag2)) };
134	($e1:literal  $frag1:tt + ($($e2:tt)+) $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Plus, $crate::unit!(($($e2)+) $frag2)) };
135	($e1:ident    $frag1:tt + ($($e2:tt)+) $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Plus, $crate::unit!(($($e2)+) $frag2)) };
136	(($($e1:tt)+) $frag1:tt + ($($e2:tt)+) $frag2:tt) => { $crate::units::Unit::calc($crate::unit!(($($e1)+) $frag1), $crate::units::Operator::Plus, $crate::unit!(($($e2)+) $frag2)) };
137
138	($e1:literal  $frag1:tt - $e2:literal  $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Minus, $crate::unit!($e2       $frag2)) };
139	($e1:ident    $frag1:tt - $e2:literal  $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Minus, $crate::unit!($e2       $frag2)) };
140	(($($e1:tt)+) $frag1:tt - $e2:literal  $frag2:tt) => { $crate::units::Unit::calc($crate::unit!(($($e1)+) $frag1), $crate::units::Operator::Minus, $crate::unit!($e2       $frag2)) };
141	($e1:literal  $frag1:tt - $e2:ident    $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Minus, $crate::unit!($e2       $frag2)) };
142	($e1:ident    $frag1:tt - $e2:ident    $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Minus, $crate::unit!($e2       $frag2)) };
143	(($($e1:tt)+) $frag1:tt - $e2:ident    $frag2:tt) => { $crate::units::Unit::calc($crate::unit!(($($e1)+) $frag1), $crate::units::Operator::Minus, $crate::unit!($e2       $frag2)) };
144	($e1:literal  $frag1:tt - ($($e2:tt)+) $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Minus, $crate::unit!(($($e2)+) $frag2)) };
145	($e1:ident    $frag1:tt - ($($e2:tt)+) $frag2:tt) => { $crate::units::Unit::calc($crate::unit!($e1       $frag1), $crate::units::Operator::Minus, $crate::unit!(($($e2)+) $frag2)) };
146	(($($e1:tt)+) $frag1:tt - ($($e2:tt)+) $frag2:tt) => { $crate::units::Unit::calc($crate::unit!(($($e1)+) $frag1), $crate::units::Operator::Minus, $crate::unit!(($($e2)+) $frag2)) };
147}