css-style 0.14.1

Typed CSS Style
Documentation
//! This module provide a `calc` CSS function.
//!
//! # Usage
//!
//! Most usecases doesn't need to use `Calc` type or `calc` function, because
//! all unit types impl `Add`, `Sub`, `Mul`, `Div` and the output is `Calc`
//! type.
//!
//! You really don't need to think much about this, because all you need to do to
//! use the `calc` function is to use `+`, `-`, `*`, `/` just like normal Rust code
//!
//! # Examples
//! ```rust
//! use css_style::unit::{px, vh, vw, percent};
//!
//! let height = vh(20) * 2.0;
//! assert_eq!("calc(20.00vh * 2)", height.to_string());
//!
//! let width = 7.5 * vw(50) + px(80) / 2.5;
//! assert_eq!("calc((7.5 * 50.00vw) + (80.00px / 2.5))", width.to_string());
//!
//! // you can control the ordering using parentheses
//! let width = 7.5 * (vw(50) + px(80)) / 2.5;
//! assert_eq!("calc((7.5 * (50.00vw + 80.00px)) / 2.5)", width.to_string());
//!
//! // not Div and Mul only accept {number}s
//! // This doesn't compile:
//! // let x = px(50) / px(10);
//! // OR
//! // let x = px(50) * px(10);
//! ```

use std::{fmt, ops};

use crate::{
    margin, padding, position, size,
    unit::{Length, Percent},
};
use paste::paste;

/// A type that represent a `calc` CSS function
///
/// Note: I think current implementation doesn't cover every use case, but it's
/// good enough for most cases.
#[derive(Debug, PartialOrd, PartialEq, Clone)]
pub enum Calc {
    // Possible values
    Length(Box<Length>),
    Percent(Percent),
    CssVariable(String),

    // Possible operations, these op can be used as value too

    // Add Calc into another Calc
    Sum(Box<Calc>, Box<Calc>),

    // Subtract Calc with another Calc
    Sub(Box<Calc>, Box<Calc>),

    // Multiply Calc with f32
    Mul(Box<Calc>, f32),

    // Just like Mul but diffrent order, this doesn't make the result diffrent
    // but I put it just in case :D
    Mul2(f32, Box<Calc>),

    // Divide Calc with f32
    Div(Box<Calc>, f32),
    // This is not needed because one can do this div using normal Rust code
    // (e.g. 2.0 / 3.0), and to be honest it would make the enum more
    // complicated :P
    //
    // Div2(f32, f32),
}

impl From<Length> for Calc {
    fn from(source: Length) -> Self {
        Calc::Length(Box::new(source))
    }
}

impl TryFrom<padding::Length> for Calc {
    type Error = &'static str;

    fn try_from(value: padding::Length) -> Result<Self, Self::Error> {
        match value {
            padding::Length::Length(len) => Ok(len.into()),
            padding::Length::Percent(per) => Ok(per.into()),
            padding::Length::Inherit => Err("Calc cannot accept inherit as value"),
        }
    }
}

impl TryFrom<margin::Length> for Calc {
    type Error = &'static str;

    fn try_from(value: margin::Length) -> Result<Self, Self::Error> {
        match value {
            margin::Length::Length(len) => Ok(len.into()),
            margin::Length::Percent(per) => Ok(per.into()),
            _ => Err("Calc cannot accept values such as inherit, auto ..etc"),
        }
    }
}

impl TryFrom<size::Length> for Calc {
    type Error = &'static str;

    fn try_from(value: size::Length) -> Result<Self, Self::Error> {
        match value {
            size::Length::Length(len) => Ok(len.into()),
            size::Length::Percent(per) => Ok(per.into()),
            _ => Err("Calc cannot accept values such as inherit, auto ..etc"),
        }
    }
}

impl TryFrom<position::PostionLength> for Calc {
    type Error = &'static str;

    fn try_from(value: position::PostionLength) -> Result<Self, Self::Error> {
        match value {
            position::PostionLength::Length(len) => Ok(len.into()),
            position::PostionLength::Percent(per) => Ok(per.into()),
            _ => Err("Calc cannot accept values such as inherit, auto ..etc"),
        }
    }
}

impl From<String> for Calc {
    fn from(source: String) -> Self {
        Calc::CssVariable(source)
    }
}

impl From<&str> for Calc {
    fn from(source: &str) -> Self {
        Calc::CssVariable(source.to_string())
    }
}

impl fmt::Display for Calc {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fn fmt_calc(calc: &Calc) -> String {
            match calc {
                Calc::Length(val) => val.to_string(),
                Calc::Percent(val) => val.to_string(),
                Calc::CssVariable(val) => format!("var({})", val.to_string()),
                Calc::Sum(c, val) => format!("({} + {})", fmt_calc(c), fmt_calc(val)),
                Calc::Sub(c, val) => format!("({} - {})", fmt_calc(c), fmt_calc(val)),
                Calc::Mul(c, val) => format!("({} * {})", fmt_calc(c), val),
                Calc::Mul2(val, c) => format!("({} * {})", val, fmt_calc(c)),
                Calc::Div(c, val) => format!("({} / {})", fmt_calc(c), val),
            }
        }
        match self {
            Self::Length(val) => write!(f, "{}", val),
            Self::Percent(val) => write!(f, "{}", val),
            Self::CssVariable(val) => write!(f, "calc(var({}))", val),
            _ => write!(f, "calc{}", fmt_calc(self)),
        }
    }
}

macro_rules! ops_impl {
    ($($op: ident<$($t: ty),*> for $target: ty => { $e: expr }),* $(,)?) => {
        $(
            $(
                paste! {
                    impl ops::$op<$t> for $target {
                        type Output = Calc;

                        fn [<$op:snake>](self, rhs: $t) -> Calc {
                            $e(self, rhs)
                        }
                    }
                }
            )*
        )*
    };

    ($($op: ident<$($t: ty),*> and Optional for $target: ty => { $e: expr }),* $(,)?) => {
        $(
            $(
                paste! {
                    impl ops::$op<$t> for $target {
                        type Output = Calc;

                        fn [<$op:snake>](self, rhs: $t) -> Calc {
                            $e(self, rhs)
                        }
                    }
                }

            )*

            impl<T> ops::$op<Option<T>> for $target
            where
                $target: ops::$op<T, Output = Calc>,
            {
                type Output = Calc;

                paste! {
                    fn [<$op:snake>](self, rhs: Option<T>) -> Self::Output {
                        if let Some(length) = rhs {
                            self.[<$op:snake>](length)
                        } else {
                            self.into()
                        }
                    }
                }
            }
        )*
    };
}

// base impl
ops_impl!(
    Add<Calc, Percent, Length> and Optional for Calc                         => { |c, rhs| Self::Sum(Box::new(c), Box::new(Calc::from(rhs))) },
    Sub<Calc, Percent, Length> and Optional for Calc                         => { |c, rhs| Self::Sub(Box::new(c), Box::new(Calc::from(rhs))) },
    Mul<f32, i32, i16, i8, u32, u16, u8, isize, usize> and Optional for Calc => { |c, val| Self::Mul(Box::new(c), val as f32) },
    Div<f32, i32, i16, i8, u32, u16, u8, isize, usize> and Optional for Calc => { |c, val| Self::Div(Box::new(c), val as f32) },
);

ops_impl!(
    Mul<Calc> for f32   => { |val, c| Calc::Mul2(val, Box::new(c)) },
    Mul<Calc> for i32   => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
    Mul<Calc> for i16   => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
    Mul<Calc> for i8    => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
    Mul<Calc> for u32   => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
    Mul<Calc> for u16   => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
    Mul<Calc> for u8    => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
    Mul<Calc> for isize => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },
    Mul<Calc> for usize => { |val, c| Calc::Mul2(val as f32, Box::new(c)) },

    Mul<Length> for f32   => { |val, length| Calc::Mul2(val, Box::new(Calc::from(length))) },
    Mul<Length> for i32   => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
    Mul<Length> for i16   => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
    Mul<Length> for i8    => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
    Mul<Length> for u32   => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
    Mul<Length> for u16   => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
    Mul<Length> for u8    => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
    Mul<Length> for isize => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },
    Mul<Length> for usize => { |val, length| Calc::Mul2(val as f32, Box::new(Calc::from(length))) },

    Mul<Percent> for f32   => { |val, pct| Calc::Mul2(val, Box::new(Calc::from(pct))) },
    Mul<Percent> for i32   => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
    Mul<Percent> for i16   => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
    Mul<Percent> for i8    => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
    Mul<Percent> for u32   => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
    Mul<Percent> for u16   => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
    Mul<Percent> for u8    => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
    Mul<Percent> for isize => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },
    Mul<Percent> for usize => { |val, pct| Calc::Mul2(val as f32, Box::new(Calc::from(pct))) },

    Add<Calc, Percent, Length> for Length                         => { |length, rhs| Calc::from(length).add(rhs) },
    Sub<Calc, Percent, Length> for Length                         => { |length, rhs| Calc::from(length).sub(rhs) },
    Mul<f32, i32, i16, i8, u32, u16, u8, isize, usize> for Length => { |length, val| Calc::from(length).mul(val as f32) },
    Div<f32, i32, i16, i8, u32, u16, u8, isize, usize> for Length => { |length, val| Calc::from(length).div(val as f32) },
);

#[derive(Clone, Debug, PartialEq, Display, From)]
pub enum CalcArgument {
    Length(Length),
    Percent(Percent),
    CssVariable(String),
}

impl From<&str> for CalcArgument {
    fn from(source: &str) -> Self {
        CalcArgument::CssVariable(source.to_string())
    }
}

pub fn calc(value: impl Into<CalcArgument>) -> Calc {
    match value.into() {
        CalcArgument::Length(len) => Calc::Length(Box::new(len)),
        CalcArgument::Percent(per) => Calc::Percent(per),
        CalcArgument::CssVariable(var) => Calc::CssVariable(var),
    }
}