css-style 0.14.1

Typed CSS Style
Documentation
use crate::calc::Calc;
use paste::paste;
use std::{fmt, ops};

pub trait Unit {
    fn zero() -> Self;
    fn half() -> Self;
    fn full() -> Self;
}

macro_rules! units {
    (@one $name: ident: $css: literal) => {
        #[derive(Clone, Debug, Copy, PartialEq, PartialOrd, From)]
        pub struct $name(f32);

        impl fmt::Display for $name {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                write!(f, "{:.2}", self.0)?;
                write!(f, $css)
            }
        }
    };

    (@two $name: ident: $css: literal -> $fn_ty: ty) => {
        paste! {
            pub fn [<$name:snake>](value: impl Into<$name>) -> $fn_ty {
                (value.into()).into()
            }
        }
    };

    (@three $name: ident: $css: literal -> $target: ty) => {
        // Add
        impl ops::Add<Length> for $name {
            type Output = Calc;

            fn add(self, rhs: Length) -> Self::Output {
                Calc::from(<$target>::from(self)).add(rhs)
            }
        }

        impl ops::Add<$name> for $name {
            type Output = Calc;

            fn add(self, rhs: $name) -> Self::Output {
                Calc::from(<$target>::from(self)).add(<$target>::from(rhs))
            }
        }

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

            fn add(self, rhs: Option<T>) -> Self::Output {
                if let Some(length) = rhs {
                    self.add(length)
                } else {
                    self.into()
                }
            }
        }

        // Sub
        impl ops::Sub<Length> for $name {
            type Output = Calc;

            fn sub(self, rhs: Length) -> Self::Output {
                Calc::from(<$target>::from(self)).sub(rhs)
            }
        }

        impl ops::Sub<$name> for $name {
            type Output = Calc;

            fn sub(self, rhs: $name) -> Self::Output {
                Calc::from(<$target>::from(self)).sub(<$target>::from(rhs))
            }
        }

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

            fn sub(self, rhs: Option<T>) -> Self::Output {
                if let Some(length) = rhs {
                    self.sub(length)
                } else {
                    self.into()
                }
            }
        }

        // Mul
        impl ops::Mul<f32> for $name {
            type Output = Calc;

            fn mul(self, rhs: f32) -> Self::Output {
                Calc::from(<$target>::from(self)).mul(rhs)
            }
        }

        impl ops::Mul<i32> for $name {
            type Output = Calc;

            fn mul(self, rhs: i32) -> Self::Output {
                Calc::from(<$target>::from(self)).mul(rhs as f32)
            }
        }

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

            fn mul(self, rhs: Option<T>) -> Self::Output {
                if let Some(length) = rhs {
                    self.mul(length)
                } else {
                    self.into()
                }
            }
        }

        // Div
        impl ops::Div<f32> for $name {
            type Output = Calc;

            fn div(self, rhs: f32) -> Self::Output {
                Calc::from(<$target>::from(self)).div(rhs)
            }
        }

        impl ops::Div<i32> for $name {
            type Output = Calc;

            fn div(self, rhs: i32) -> Self::Output {
                Calc::from(<$target>::from(self)).div(rhs as f32)
            }
        }

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

            fn div(self, rhs: Option<T>) -> Self::Output {
                if let Some(length) = rhs {
                    self.div(length)
                } else {
                    self.into()
                }
            }
        }
    };


    ($($name: ident: $css: literal),* $(,)?) => {
        $(
            units!(@one $name: $css);
        )*
    };

    ($(+fn -> Length { $($name: ident: $css: literal),* $(,)? }),* $(,)?) => {
        $(
            $(
                units!(@one $name: $css);
                units!(@two $name: $css -> Length);
                units!(@three $name: $css -> Length);

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

                impl From<i32> for $name {
                    fn from(source: i32) -> Self {
                        $name(source as f32)
                    }
                }
            )*
        )*
    };

    ($(+fn -> Percent { $($name: ident: $css: literal),* $(,)? }),* $(,)?) => {
        $(
            $(
                #[derive(Clone, Debug, Copy, PartialEq, PartialOrd, From)]
                pub struct $name(f32);

                impl fmt::Display for $name {
                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                        write!(f, "{:.2}", self.0 * 100.0)?;
                        write!(f, $css)
                    }
                }

                units!(@two $name: $css -> Percent);
                units!(@three $name: $css -> Percent);

                impl From<Percent> for Calc {
                    fn from(source: Percent) -> Self {
                        Calc::Percent(source)
                    }
                }
            )*
        )*
    };

    ($(+fn -> $fn_ty: ty { $($name: ident: $css: literal),* $(,)? }),* $(,)?) => {
        $(
            $(
                units!(@one $name: $css);
                units!(@two $name: $css -> $fn_ty);
            )*
        )*
    };
}

units! {
    +fn -> Length {
        // https://www.w3.org/TR/css-values-4/#font-relative-lengths Em: "em",
        Rem: "rem", Em: "em", Ex: "ex", Rex: "rex", Cap: "cap", Rcap: "rcap", Ch: "ch",
        Rch: "rch", Ic: "ic", Ric: "ric", Lh: "lh", Rlh: "rlh",

        // https://www.w3.org/TR/css-values-4/#viewport-relative-lengths
        Vw: "vw", Vh: "vh", Vi: "vi", Vb: "vb",
        Vmin: "vmin", Vmax: "vmax",

        // https://www.w3.org/TR/css-values-4/#absolute-lengths
        Cm: "cm", Mm: "mm", Q: "q", Inch: "in",
        Pc: "pc", Pt: "pt", Px: "px",
    },
}

units! {
    // https://www.w3.org/TR/css-values-4/#percentages
    +fn -> Percent { Percent: "%" },
}

units! {
    // https://www.w3.org/TR/css-values-4/#time
    +fn -> Ms { Ms: "ms" },
    +fn -> Sec { Sec: "s" },
}

units! {
    // https://www.w3.org/TR/css-values-4/#angles
    Deg: "deg", Grad: "grad", Rad: "rad", Turn: "turn",

    // https://www.w3.org/TR/css-values-4/#frequency
    Hz: "Hz", KHz: "kHz",

    // https://www.w3.org/TR/css-values-4/#resolution
    Dpi: "dpi", Dpcm: "dpcm", Dppx: "dppx",

}

// sum types

#[derive(Clone, Debug, PartialOrd, PartialEq, Display, From)]
pub enum Length {
    #[from]
    Em(Em),
    #[from]
    Rem(Rem),
    #[from]
    Ex(Ex),
    #[from]
    Rex(Rex),
    #[from]
    Cap(Cap),
    #[from]
    Rcap(Rcap),
    #[from]
    Ch(Ch),
    #[from]
    Rch(Rch),
    #[from]
    Ic(Ic),
    #[from]
    Ric(Ric),
    #[from]
    Lh(Lh),
    #[from]
    Rlh(Rlh),

    #[from]
    Vw(Vw),
    #[from]
    Vh(Vh),
    #[from]
    Vi(Vi),
    #[from]
    Vb(Vb),
    #[from]
    Vmin(Vmin),
    #[from]
    Vmax(Vmax),

    #[from]
    Cm(Cm),
    #[from]
    Mm(Mm),
    #[from]
    Q(Q),
    #[from]
    In(Inch),
    #[from]
    Pc(Pc),
    #[from]
    Pt(Pt),
    #[from]
    Px(Px),

    #[from]
    Calc(Calc),
}

#[derive(Clone, Debug, PartialEq, Display, From)]
pub enum LengthPercent {
    #[from]
    Length(Length),
    #[from(forward)]
    Percent(Percent),
    #[from]
    Calc(Calc),
}

impl Unit for LengthPercent {
    fn zero() -> Self {
        0.0.into()
    }

    fn half() -> Self {
        0.5.into()
    }

    fn full() -> Self {
        1.0.into()
    }
}