freya_components/theming/
macros.rs

1#[doc(hidden)]
2pub use ::paste::paste;
3use freya_core::prelude::*;
4use torin::{
5    gaps::Gaps,
6    size::Size,
7};
8
9use crate::theming::component_themes::ColorsSheet;
10
11#[macro_export]
12macro_rules! define_theme {
13    (NOTHING=) => {};
14
15    (
16        $(#[$attrs:meta])*
17        for = $for_ty:ident ;
18        theme_field = $theme_field:ident ;
19        $(%[component$($component_attr_control:tt)?])?
20        $vis:vis $name:ident $(<$lifetime:lifetime>)? {
21            $(
22                %[fields$($cows_attr_control:tt)?]
23                $(
24                    $(#[$field_attrs:meta])*
25                    $field_name:ident: $field_ty:ty,
26                )*
27            )?
28    }) => {
29        $crate::define_theme!(NOTHING=$($($component_attr_control)?)?);
30        $crate::theming::macros::paste! {
31            #[derive(Default, Clone, Debug, PartialEq)]
32            #[doc = "You can use this to change a theme for only one component, with the `theme` property."]
33            $(#[$attrs])*
34            $vis struct [<$name ThemePartial>] $(<$lifetime>)? {
35                $($(
36                    $(#[$field_attrs])*
37                    pub $field_name: Option<$crate::theming::macros::Preference<$field_ty>>,
38                )*)?
39            }
40
41            #[derive(Clone, Debug, PartialEq)]
42            $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
43            $(#[$attrs])*
44            $vis struct [<$name ThemePreference>] $(<$lifetime>)? {
45                $($(
46                    $(#[$field_attrs])*
47                    pub $field_name: $crate::theming::macros::Preference<$field_ty>,
48                )*)?
49            }
50
51            #[derive(Clone, Debug, PartialEq)]
52            $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
53            $(#[$attrs])*
54            $vis struct [<$name Theme>] $(<$lifetime>)? {
55                $($(
56                    $(#[$field_attrs])*
57                    pub $field_name: $field_ty,
58                )*)?
59            }
60
61            impl $(<$lifetime>)? [<$name ThemePreference>] $(<$lifetime>)? {
62                #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
63                pub fn apply_optional(&mut self, optional: & $($lifetime)? [<$name ThemePartial>]) {
64
65                    $($(
66                        if let Some($field_name) = &optional.$field_name {
67                            self.$field_name = $field_name.clone();
68                        }
69                    )*)?
70                }
71
72                #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
73                pub fn resolve(&mut self, colors_sheet: &$crate::theming::component_themes::ColorsSheet) -> [<$name Theme>] {
74                    use $crate::theming::macros::ResolvablePreference;
75                    [<$name Theme>] {
76                        $(
77                            $(
78                                $field_name: self.$field_name.resolve(colors_sheet),
79                            )*
80                        )?
81                    }
82                }
83            }
84
85            impl $(<$lifetime>)? [<$name ThemePartial>] $(<$lifetime>)? {
86                pub fn new() -> Self {
87                    Self::default()
88                }
89
90                $($(
91                    $(#[$field_attrs])*
92                    pub fn $field_name(mut self, $field_name: impl Into<$field_ty>) -> Self {
93                        self.$field_name = Some($crate::theming::macros::Preference::Specific($field_name.into()));
94                        self
95                    }
96                )*)?
97            }
98
99            pub trait [<$name ThemePartialExt>] {
100                $($(
101                    $(#[$field_attrs])*
102                    fn $field_name(self, $field_name: impl Into<$field_ty>) -> Self;
103                )*)?
104            }
105
106            impl $(<$lifetime>)? [<$name ThemePartialExt>] for $for_ty $(<$lifetime>)? {
107                $($(
108                    $(#[$field_attrs])*
109                    fn $field_name(mut self, $field_name: impl Into<$field_ty>) -> Self {
110                        self.$theme_field = Some(self.$theme_field.unwrap_or_default().$field_name($field_name));
111                        self
112                    }
113                )*)?
114            }
115        }
116    };
117
118    (
119        $(#[$attrs:meta])*
120        $(%[component$($component_attr_control:tt)?])?
121        $vis:vis $name:ident $(<$lifetime:lifetime>)? {
122            $(
123                %[fields$($cows_attr_control:tt)?]
124                $(
125                    $(#[$field_attrs:meta])*
126                    $field_name:ident: $field_ty:ty,
127                )*
128            )?
129    }) => {
130        $crate::define_theme!(NOTHING=$($($component_attr_control)?)?);
131        $crate::theming::macros::paste! {
132            #[derive(Default, Clone, Debug, PartialEq)]
133            #[doc = "You can use this to change a theme for only one component, with the `theme` property."]
134            $(#[$attrs])*
135            $vis struct [<$name ThemePartial>] $(<$lifetime>)? {
136                $($(
137                    $(#[$field_attrs])*
138                    pub $field_name: Option<$crate::theming::macros::Preference<$field_ty>>,
139                )*)?
140            }
141
142            #[derive(Clone, Debug, PartialEq)]
143            $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
144            $(#[$attrs])*
145            $vis struct [<$name ThemePreference>] $(<$lifetime>)? {
146                $($(
147                    $(#[$field_attrs])*
148                    pub $field_name: $crate::theming::macros::Preference<$field_ty>,
149                )*)?
150            }
151
152            #[derive(Clone, Debug, PartialEq)]
153            $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
154            $(#[$attrs])*
155            $vis struct [<$name Theme>] $(<$lifetime>)? {
156                $($(
157                    $(#[$field_attrs])*
158                    pub $field_name: $field_ty,
159                )*)?
160            }
161
162            impl $(<$lifetime>)? [<$name ThemePreference>] $(<$lifetime>)? {
163                #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
164                pub fn apply_optional(&mut self, optional: & $($lifetime)? [<$name ThemePartial>]) {
165                    $($(
166                        if let Some($field_name) = &optional.$field_name {
167                            self.$field_name = $field_name.clone();
168                        }
169                    )*)?
170                }
171
172                #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
173                pub fn resolve(&mut self, colors_sheet: &$crate::theming::component_themes::ColorsSheet) -> [<$name Theme>] {
174                    use $crate::theming::macros::ResolvablePreference;
175                    [<$name Theme>] {
176                        $(
177                            $(
178                                $field_name: self.$field_name.resolve(colors_sheet),
179                            )*
180                        )?
181                    }
182                }
183            }
184
185            impl $(<$lifetime>)? [<$name ThemePartial>] $(<$lifetime>)? {
186                pub fn new() -> Self {
187                    Self::default()
188                }
189
190                $($(
191                    $(#[$field_attrs])*
192                    pub fn $field_name(mut self, $field_name: impl Into<$field_ty>) -> Self {
193                        self.$field_name = Some($crate::theming::macros::Preference::Specific($field_name.into()));
194                        self
195                    }
196                )*)?
197            }
198
199            pub trait [<$name ThemePartialExt>] {
200                $($(
201                    $(#[$field_attrs])*
202                    fn $field_name(self, $field_name: impl Into<$field_ty>) -> Self;
203                )*)?
204            }
205
206            impl $(<$lifetime>)? [<$name ThemePartialExt>] for $name $(<$lifetime>)? {
207                $($(
208                    $(#[$field_attrs])*
209                    fn $field_name(mut self, $field_name: impl Into<$field_ty>) -> Self {
210                        self.theme = Some(self.theme.unwrap_or_default().$field_name($field_name));
211                        self
212                    }
213                )*)?
214            }
215        }
216    };
217}
218
219#[macro_export]
220macro_rules! get_theme {
221    ($theme_prop:expr, $theme_name:ident) => {{
222        let theme = $crate::theming::hooks::get_theme_or_default();
223        let mut requested_theme = theme.$theme_name;
224
225        if let Some(theme_override) = $theme_prop {
226            requested_theme.apply_optional(&theme_override);
227        }
228
229        requested_theme.resolve(&theme.colors)
230    }};
231}
232
233#[derive(Clone, Debug, PartialEq, Eq)]
234pub enum Preference<T> {
235    Specific(T),
236    Reference(&'static str),
237}
238
239impl<T> From<T> for Preference<T> {
240    fn from(value: T) -> Self {
241        Preference::Specific(value)
242    }
243}
244
245pub trait ResolvablePreference<T: Clone> {
246    fn resolve(&self, colors_sheet: &ColorsSheet) -> T;
247}
248
249impl ResolvablePreference<Color> for Preference<Color> {
250    fn resolve(&self, colors_sheet: &ColorsSheet) -> Color {
251        match self {
252            Self::Reference(reference) => match *reference {
253                // Brand & Accent
254                "primary" => colors_sheet.primary,
255                "secondary" => colors_sheet.secondary,
256                "tertiary" => colors_sheet.tertiary,
257
258                // Status
259                "success" => colors_sheet.success,
260                "warning" => colors_sheet.warning,
261                "error" => colors_sheet.error,
262                "info" => colors_sheet.info,
263
264                // Surfaces
265                "background" => colors_sheet.background,
266                "surface_primary" => colors_sheet.surface_primary,
267                "surface_secondary" => colors_sheet.surface_secondary,
268                "surface_tertiary" => colors_sheet.surface_tertiary,
269                "surface_inverse" => colors_sheet.surface_inverse,
270                "surface_inverse_secondary" => colors_sheet.surface_inverse_secondary,
271                "surface_inverse_tertiary" => colors_sheet.surface_inverse_tertiary,
272
273                // Borders
274                "border" => colors_sheet.border,
275                "border_focus" => colors_sheet.border_focus,
276                "border_disabled" => colors_sheet.border_disabled,
277
278                // Text
279                "text_primary" => colors_sheet.text_primary,
280                "text_secondary" => colors_sheet.text_secondary,
281                "text_placeholder" => colors_sheet.text_placeholder,
282                "text_inverse" => colors_sheet.text_inverse,
283                "text_highlight" => colors_sheet.text_highlight,
284
285                // States
286                "hover" => colors_sheet.hover,
287                "focus" => colors_sheet.focus,
288                "active" => colors_sheet.active,
289                "disabled" => colors_sheet.disabled,
290
291                // Utility
292                "overlay" => colors_sheet.overlay,
293                "shadow" => colors_sheet.shadow,
294
295                // Fallback
296                _ => colors_sheet.primary,
297            },
298
299            Self::Specific(value) => *value,
300        }
301    }
302}
303
304impl ResolvablePreference<Size> for Preference<Size> {
305    fn resolve(&self, _colors_sheet: &ColorsSheet) -> Size {
306        match self {
307            Self::Reference(_) => {
308                panic!("Only Colors support references.")
309            }
310            Self::Specific(value) => value.clone(),
311        }
312    }
313}
314
315impl ResolvablePreference<Gaps> for Preference<Gaps> {
316    fn resolve(&self, _colors_sheet: &ColorsSheet) -> Gaps {
317        match self {
318            Self::Reference(_) => {
319                panic!("Only Colors support references.")
320            }
321            Self::Specific(value) => *value,
322        }
323    }
324}
325
326impl ResolvablePreference<CornerRadius> for Preference<CornerRadius> {
327    fn resolve(&self, _colors_sheet: &ColorsSheet) -> CornerRadius {
328        match self {
329            Self::Reference(_) => {
330                panic!("Only Colors support references.")
331            }
332            Self::Specific(value) => *value,
333        }
334    }
335}
336
337impl ResolvablePreference<f32> for Preference<f32> {
338    fn resolve(&self, _colors_sheet: &ColorsSheet) -> f32 {
339        match self {
340            Self::Reference(_) => {
341                panic!("Only Colors support references.")
342            }
343            Self::Specific(value) => *value,
344        }
345    }
346}