css-style 0.14.1

Typed CSS Style
Documentation
use super::*;
use indexmap::IndexMap;
use paste::paste;
use std::{borrow::Cow, fmt};

/// This is the main struct used to build and manipulate css properties, it
/// provieds many methods to do that.
///
/// ```
/// use css_style::{prelude::*, color, unit::{ms, px}};
///
/// style()
///     .and_transition(|conf| {
///         conf
///             .insert("opacity", |conf| conf.duration(ms(150.)).ease())
///             .insert("transform", |conf| conf.duration(ms(150.)).ease())
///             .insert("visibility", |conf| conf.duration(ms(150.)).ease())
///     })
///     .and_position(|conf| conf.absolute())
///     .and_background(|conf| conf.color(color::named::WHITE))
///     .and_border(|conf| {
///         conf.none()
///             .width(px(0))
///             .radius(px(4))
///     })
///     .and_padding(|conf| conf.x(px(4)).y(px(2)))
///     .and_margin(|conf| conf.top(px(2)))
///     .insert("box-shadow", "0 2px 8px rgba(0, 35, 11, 0.15)");
/// ```
#[derive(Default, PartialEq, Debug, Clone)]
pub struct Style {
    values: IndexMap<Cow<'static, str>, String>,
}

impl fmt::Display for Style {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.values
            .iter()
            .map(|(k, v)| format!("{}: {};", k, v))
            .collect::<String>()
            .fmt(f)
    }
}

macro_rules! setter_functions {
    ( @more_fns $prop_ty:ident and ) => {
        paste! {
            /// Setter for `$prop_ty` that takes a closure which returns `$prop_ty`
            pub fn [<and_ $prop_ty:snake>](mut self, val: impl FnOnce($prop_ty) -> $prop_ty) -> Self
            where
                $prop_ty: Default + StyleUpdater,
            {
                self = val($prop_ty::default()).update_style(self);
                self
            }

        }
    };
    ( $( $prop_ty:ident $( +$ext:ident )? $(,)? )+ ) => {
        $(
            paste! {
                /// Setter for `$prop_ty`
                pub fn [<$prop_ty:snake>](mut self, val: impl Into<$prop_ty>) -> Self
                where
                    $prop_ty: StyleUpdater,
                {
                    self = val.into().update_style(self);
                    self
                }

                /// Setter for `$prop_ty` that takes `Option<$prop_ty`
                pub fn [<try_ $prop_ty:snake>](self, val: Option<impl Into<$prop_ty>>) -> Self {
                    if let Some(val) = val {
                        self.[<$prop_ty:snake>](val)
                    } else {
                        self
                    }
                }
            }
            $( setter_functions!(@more_fns $prop_ty $ext); )?
        )+
    }
}

impl Style {
    pub fn new() -> Self {
        Self::default()
    }

    /// This method convert this style to html style value
    pub fn to_css(&self) -> Option<String> {
        self.values
            .iter()
            .fold(Option::None, |mut css, (key, value)| {
                *css.get_or_insert(String::default()) += &format!("{}: {};", key, value);
                css
            })
    }

    /// Insert a new css key value pair, or overwrite an existing one.
    pub fn insert(mut self, key: impl Into<Cow<'static, str>>, value: impl ToString) -> Self {
        self.values.insert(key.into(), value.to_string());
        self
    }

    /// Same as `insert` but take `Option`
    pub fn try_insert(
        self,
        key: impl Into<Cow<'static, str>>,
        value: Option<impl ToString>,
    ) -> Self {
        if let Some(val) = value {
            self.insert(key, val)
        } else {
            self
        }
    }

    /// Merge two style
    pub fn merge(mut self, other: impl StyleUpdater) -> Self {
        self = other.update_style(self);
        self
    }

    /// Same as `merge` but take `Option`
    pub fn try_merge(self, other: Option<impl StyleUpdater>) -> Self {
        if let Some(other) = other {
            self.merge(other)
        } else {
            self
        }
    }

    setter_functions! {
        Opacity,
        Gap,
        AlignContent,
        AlignItems,
        JustifyContent,
        JustifySelf,
        AlignSelf,
        FlexWrap,
        FlexBasis,
        FlexDirection,
        FlexOrder,
        FlexGrow,
        FlexShrink,
        Display,
        Visibility,
        Cursor,
        Background +and,
        Border +and,
        Margin +and,
        Padding +and,
        Size +and,
        Transition +and,
        BoxShadow +and,
        Position +and,
        Text +and,
        Font +and,
    }
}

pub trait StyleUpdater {
    fn update_style(self, style: Style) -> Style;
}

impl StyleUpdater for Style {
    fn update_style(self, mut style: Style) -> Style {
        style.values.extend(self.values);
        style
    }
}

pub fn style() -> Style {
    Style::default()
}