css_style/
calc.rs

1//! This module provide a `calc` CSS function.
2//!
3//! # Usage
4//!
5//! Most usecases doesn't need to use `Calc` type or `calc` function, because
6//! all unit types impl `Add`, `Sub`, `Mul`, `Div` and the output is `Calc`
7//! type.
8//!
9//! You really don't need to think much about this, because all you need to do to
10//! use the `calc` function is to use `+`, `-`, `*`, `/` just like normal Rust code
11//!
12//! # Examples
13//! ```rust
14//! use css_style::unit::{px, vh, vw, percent};
15//!
16//! let height = vh(20) * 2.0;
17//! assert_eq!("calc(20.00vh * 2)", height.to_string());
18//!
19//! let width = 7.5 * vw(50) + px(80) / 2.5;
20//! assert_eq!("calc((7.5 * 50.00vw) + (80.00px / 2.5))", width.to_string());
21//!
22//! // you can control the ordering using parentheses
23//! let width = 7.5 * (vw(50) + px(80)) / 2.5;
24//! assert_eq!("calc((7.5 * (50.00vw + 80.00px)) / 2.5)", width.to_string());
25//!
26//! // not Div and Mul only accept {number}s
27//! // This doesn't compile:
28//! // let x = px(50) / px(10);
29//! // OR
30//! // let x = px(50) * px(10);
31//! ```
32
33use std::{fmt, ops};
34
35use crate::{
36    margin, padding, position, size,
37    unit::{Length, Percent},
38};
39use paste::paste;
40
41/// A type that represent a `calc` CSS function
42///
43/// Note: I think current implementation doesn't cover every use case, but it's
44/// good enough for most cases.
45#[derive(Debug, PartialOrd, PartialEq, Clone)]
46pub enum Calc {
47    // Possible values
48    Length(Box<Length>),
49    Percent(Percent),
50    CssVariable(String),
51
52    // Possible operations, these op can be used as value too
53
54    // Add Calc into another Calc
55    Sum(Box<Calc>, Box<Calc>),
56
57    // Subtract Calc with another Calc
58    Sub(Box<Calc>, Box<Calc>),
59
60    // Multiply Calc with f32
61    Mul(Box<Calc>, f32),
62
63    // Just like Mul but diffrent order, this doesn't make the result diffrent
64    // but I put it just in case :D
65    Mul2(f32, Box<Calc>),
66
67    // Divide Calc with f32
68    Div(Box<Calc>, f32),
69    // This is not needed because one can do this div using normal Rust code
70    // (e.g. 2.0 / 3.0), and to be honest it would make the enum more
71    // complicated :P
72    //
73    // Div2(f32, f32),
74}
75
76impl From<Length> for Calc {
77    fn from(source: Length) -> Self {
78        Calc::Length(Box::new(source))
79    }
80}
81
82impl TryFrom<padding::Length> for Calc {
83    type Error = &'static str;
84
85    fn try_from(value: padding::Length) -> Result<Self, Self::Error> {
86        match value {
87            padding::Length::Length(len) => Ok(len.into()),
88            padding::Length::Percent(per) => Ok(per.into()),
89            padding::Length::Inherit => Err("Calc cannot accept inherit as value"),
90        }
91    }
92}
93
94impl TryFrom<margin::Length> for Calc {
95    type Error = &'static str;
96
97    fn try_from(value: margin::Length) -> Result<Self, Self::Error> {
98        match value {
99            margin::Length::Length(len) => Ok(len.into()),
100            margin::Length::Percent(per) => Ok(per.into()),
101            _ => Err("Calc cannot accept values such as inherit, auto ..etc"),
102        }
103    }
104}
105
106impl TryFrom<size::Length> for Calc {
107    type Error = &'static str;
108
109    fn try_from(value: size::Length) -> Result<Self, Self::Error> {
110        match value {
111            size::Length::Length(len) => Ok(len.into()),
112            size::Length::Percent(per) => Ok(per.into()),
113            _ => Err("Calc cannot accept values such as inherit, auto ..etc"),
114        }
115    }
116}
117
118impl TryFrom<position::PostionLength> for Calc {
119    type Error = &'static str;
120
121    fn try_from(value: position::PostionLength) -> Result<Self, Self::Error> {
122        match value {
123            position::PostionLength::Length(len) => Ok(len.into()),
124            position::PostionLength::Percent(per) => Ok(per.into()),
125            _ => Err("Calc cannot accept values such as inherit, auto ..etc"),
126        }
127    }
128}
129
130impl From<String> for Calc {
131    fn from(source: String) -> Self {
132        Calc::CssVariable(source)
133    }
134}
135
136impl From<&str> for Calc {
137    fn from(source: &str) -> Self {
138        Calc::CssVariable(source.to_string())
139    }
140}
141
142impl fmt::Display for Calc {
143    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144        fn fmt_calc(calc: &Calc) -> String {
145            match calc {
146                Calc::Length(val) => val.to_string(),
147                Calc::Percent(val) => val.to_string(),
148                Calc::CssVariable(val) => format!("var({})", val.to_string()),
149                Calc::Sum(c, val) => format!("({} + {})", fmt_calc(c), fmt_calc(val)),
150                Calc::Sub(c, val) => format!("({} - {})", fmt_calc(c), fmt_calc(val)),
151                Calc::Mul(c, val) => format!("({} * {})", fmt_calc(c), val),
152                Calc::Mul2(val, c) => format!("({} * {})", val, fmt_calc(c)),
153                Calc::Div(c, val) => format!("({} / {})", fmt_calc(c), val),
154            }
155        }
156        match self {
157            Self::Length(val) => write!(f, "{}", val),
158            Self::Percent(val) => write!(f, "{}", val),
159            Self::CssVariable(val) => write!(f, "calc(var({}))", val),
160            _ => write!(f, "calc{}", fmt_calc(self)),
161        }
162    }
163}
164
165macro_rules! ops_impl {
166    ($($op: ident<$($t: ty),*> for $target: ty => { $e: expr }),* $(,)?) => {
167        $(
168            $(
169                paste! {
170                    impl ops::$op<$t> for $target {
171                        type Output = Calc;
172
173                        fn [<$op:snake>](self, rhs: $t) -> Calc {
174                            $e(self, rhs)
175                        }
176                    }
177                }
178            )*
179        )*
180    };
181
182    ($($op: ident<$($t: ty),*> and Optional for $target: ty => { $e: expr }),* $(,)?) => {
183        $(
184            $(
185                paste! {
186                    impl ops::$op<$t> for $target {
187                        type Output = Calc;
188
189                        fn [<$op:snake>](self, rhs: $t) -> Calc {
190                            $e(self, rhs)
191                        }
192                    }
193                }
194
195            )*
196
197            impl<T> ops::$op<Option<T>> for $target
198            where
199                $target: ops::$op<T, Output = Calc>,
200            {
201                type Output = Calc;
202
203                paste! {
204                    fn [<$op:snake>](self, rhs: Option<T>) -> Self::Output {
205                        if let Some(length) = rhs {
206                            self.[<$op:snake>](length)
207                        } else {
208                            self.into()
209                        }
210                    }
211                }
212            }
213        )*
214    };
215}
216
217// base impl
218ops_impl!(
219    Add<Calc, Percent, Length> and Optional for Calc                         => { |c, rhs| Self::Sum(Box::new(c), Box::new(Calc::from(rhs))) },
220    Sub<Calc, Percent, Length> and Optional for Calc                         => { |c, rhs| Self::Sub(Box::new(c), Box::new(Calc::from(rhs))) },
221    Mul<f32, i32, i16, i8, u32, u16, u8, isize, usize> and Optional for Calc => { |c, val| Self::Mul(Box::new(c), val as f32) },
222    Div<f32, i32, i16, i8, u32, u16, u8, isize, usize> and Optional for Calc => { |c, val| Self::Div(Box::new(c), val as f32) },
223);
224
225ops_impl!(
226    Mul<Calc> for f32   => { |val, c| Calc::Mul2(val, Box::new(c)) },
227    Mul<Calc> for i32   => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
228    Mul<Calc> for i16   => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
229    Mul<Calc> for i8    => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
230    Mul<Calc> for u32   => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
231    Mul<Calc> for u16   => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
232    Mul<Calc> for u8    => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
233    Mul<Calc> for isize => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
234    Mul<Calc> for usize => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
235
236    Mul<Length> for f32   => { |val, length| Calc::Mul2(val, Box::new(Calc::from(length))) },
237    Mul<Length> for i32   => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
238    Mul<Length> for i16   => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
239    Mul<Length> for i8    => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
240    Mul<Length> for u32   => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
241    Mul<Length> for u16   => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
242    Mul<Length> for u8    => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
243    Mul<Length> for isize => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
244    Mul<Length> for usize => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
245
246    Mul<Percent> for f32   => { |val, pct| Calc::Mul2(val, Box::new(Calc::from(pct))) },
247    Mul<Percent> for i32   => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
248    Mul<Percent> for i16   => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
249    Mul<Percent> for i8    => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
250    Mul<Percent> for u32   => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
251    Mul<Percent> for u16   => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
252    Mul<Percent> for u8    => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
253    Mul<Percent> for isize => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
254    Mul<Percent> for usize => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
255
256    Add<Calc, Percent, Length> for Length                         => { |length, rhs| Calc::from(length).add(rhs) },
257    Sub<Calc, Percent, Length> for Length                         => { |length, rhs| Calc::from(length).sub(rhs) },
258    Mul<f32, i32, i16, i8, u32, u16, u8, isize, usize> for Length => { |length, val| Calc::from(length).mul(val as f32) },
259    Div<f32, i32, i16, i8, u32, u16, u8, isize, usize> for Length => { |length, val| Calc::from(length).div(val as f32) },
260);
261
262#[derive(Clone, Debug, PartialEq, Display, From)]
263pub enum CalcArgument {
264    Length(Length),
265    Percent(Percent),
266    CssVariable(String),
267}
268
269impl From<&str> for CalcArgument {
270    fn from(source: &str) -> Self {
271        CalcArgument::CssVariable(source.to_string())
272    }
273}
274
275pub fn calc(value: impl Into<CalcArgument>) -> Calc {
276    match value.into() {
277        CalcArgument::Length(len) => Calc::Length(Box::new(len)),
278        CalcArgument::Percent(per) => Calc::Percent(per),
279        CalcArgument::CssVariable(var) => Calc::CssVariable(var),
280    }
281}