css_style/
style.rs

1use super::*;
2use indexmap::IndexMap;
3use paste::paste;
4use std::{borrow::Cow, fmt};
5
6/// This is the main struct used to build and manipulate css properties, it
7/// provieds many methods to do that.
8///
9/// ```
10/// use css_style::{prelude::*, color, unit::{ms, px}};
11///
12/// style()
13///     .and_transition(|conf| {
14///         conf
15///             .insert("opacity", |conf| conf.duration(ms(150.)).ease())
16///             .insert("transform", |conf| conf.duration(ms(150.)).ease())
17///             .insert("visibility", |conf| conf.duration(ms(150.)).ease())
18///     })
19///     .and_position(|conf| conf.absolute())
20///     .and_background(|conf| conf.color(color::named::WHITE))
21///     .and_border(|conf| {
22///         conf.none()
23///             .width(px(0))
24///             .radius(px(4))
25///     })
26///     .and_padding(|conf| conf.x(px(4)).y(px(2)))
27///     .and_margin(|conf| conf.top(px(2)))
28///     .insert("box-shadow", "0 2px 8px rgba(0, 35, 11, 0.15)");
29/// ```
30#[derive(Default, PartialEq, Debug, Clone)]
31pub struct Style {
32    values: IndexMap<Cow<'static, str>, String>,
33}
34
35impl fmt::Display for Style {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        self.values
38            .iter()
39            .map(|(k, v)| format!("{}: {};", k, v))
40            .collect::<String>()
41            .fmt(f)
42    }
43}
44
45macro_rules! setter_functions {
46    ( @more_fns $prop_ty:ident and ) => {
47        paste! {
48            /// Setter for `$prop_ty` that takes a closure which returns `$prop_ty`
49            pub fn [<and_ $prop_ty:snake>](mut self, val: impl FnOnce($prop_ty) -> $prop_ty) -> Self
50            where
51                $prop_ty: Default + StyleUpdater,
52            {
53                self = val($prop_ty::default()).update_style(self);
54                self
55            }
56
57        }
58    };
59    ( $( $prop_ty:ident $( +$ext:ident )? $(,)? )+ ) => {
60        $(
61            paste! {
62                /// Setter for `$prop_ty`
63                pub fn [<$prop_ty:snake>](mut self, val: impl Into<$prop_ty>) -> Self
64                where
65                    $prop_ty: StyleUpdater,
66                {
67                    self = val.into().update_style(self);
68                    self
69                }
70
71                /// Setter for `$prop_ty` that takes `Option<$prop_ty`
72                pub fn [<try_ $prop_ty:snake>](self, val: Option<impl Into<$prop_ty>>) -> Self {
73                    if let Some(val) = val {
74                        self.[<$prop_ty:snake>](val)
75                    } else {
76                        self
77                    }
78                }
79            }
80            $( setter_functions!(@more_fns $prop_ty $ext); )?
81        )+
82    }
83}
84
85impl Style {
86    pub fn new() -> Self {
87        Self::default()
88    }
89
90    /// This method convert this style to html style value
91    pub fn to_css(&self) -> Option<String> {
92        self.values
93            .iter()
94            .fold(Option::None, |mut css, (key, value)| {
95                *css.get_or_insert(String::default()) += &format!("{}: {};", key, value);
96                css
97            })
98    }
99
100    /// Insert a new css key value pair, or overwrite an existing one.
101    pub fn insert(mut self, key: impl Into<Cow<'static, str>>, value: impl ToString) -> Self {
102        self.values.insert(key.into(), value.to_string());
103        self
104    }
105
106    /// Same as `insert` but take `Option`
107    pub fn try_insert(
108        self,
109        key: impl Into<Cow<'static, str>>,
110        value: Option<impl ToString>,
111    ) -> Self {
112        if let Some(val) = value {
113            self.insert(key, val)
114        } else {
115            self
116        }
117    }
118
119    /// Merge two style
120    pub fn merge(mut self, other: impl StyleUpdater) -> Self {
121        self = other.update_style(self);
122        self
123    }
124
125    /// Same as `merge` but take `Option`
126    pub fn try_merge(self, other: Option<impl StyleUpdater>) -> Self {
127        if let Some(other) = other {
128            self.merge(other)
129        } else {
130            self
131        }
132    }
133
134    setter_functions! {
135        Opacity,
136        Gap,
137        AlignContent,
138        AlignItems,
139        JustifyContent,
140        JustifySelf,
141        AlignSelf,
142        FlexWrap,
143        FlexBasis,
144        FlexDirection,
145        FlexOrder,
146        FlexGrow,
147        FlexShrink,
148        Display,
149        Visibility,
150        Cursor,
151        Background +and,
152        Border +and,
153        Margin +and,
154        Padding +and,
155        Size +and,
156        Transition +and,
157        BoxShadow +and,
158        Position +and,
159        Text +and,
160        Font +and,
161    }
162}
163
164pub trait StyleUpdater {
165    fn update_style(self, style: Style) -> Style;
166}
167
168impl StyleUpdater for Style {
169    fn update_style(self, mut style: Style) -> Style {
170        style.values.extend(self.values);
171        style
172    }
173}
174
175pub fn style() -> Style {
176    Style::default()
177}