Skip to main content

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_type:ty, $theme_key:expr) => {{
222        let theme = $crate::theming::hooks::get_theme_or_default();
223        let theme = theme.read();
224        let mut requested_theme = theme
225            .get::<$theme_type>($theme_key)
226            .cloned()
227            .expect(concat!("Theme key not found: ", $theme_key));
228
229        if let Some(theme_override) = $theme_prop {
230            requested_theme.apply_optional(&theme_override);
231        }
232
233        requested_theme.resolve(&theme.colors)
234    }};
235}
236
237#[derive(Clone, Debug, PartialEq, Eq)]
238pub enum Preference<T> {
239    Specific(T),
240    Reference(&'static str),
241}
242
243impl<T> From<T> for Preference<T> {
244    fn from(value: T) -> Self {
245        Preference::Specific(value)
246    }
247}
248
249pub trait ResolvablePreference<T: Clone> {
250    fn resolve(&self, colors_sheet: &ColorsSheet) -> T;
251}
252
253impl ResolvablePreference<Color> for Preference<Color> {
254    fn resolve(&self, colors_sheet: &ColorsSheet) -> Color {
255        match self {
256            Self::Reference(reference) => match *reference {
257                // Brand & Accent
258                "primary" => colors_sheet.primary,
259                "secondary" => colors_sheet.secondary,
260                "tertiary" => colors_sheet.tertiary,
261
262                // Status
263                "success" => colors_sheet.success,
264                "warning" => colors_sheet.warning,
265                "error" => colors_sheet.error,
266                "info" => colors_sheet.info,
267
268                // Surfaces
269                "background" => colors_sheet.background,
270                "surface_primary" => colors_sheet.surface_primary,
271                "surface_secondary" => colors_sheet.surface_secondary,
272                "surface_tertiary" => colors_sheet.surface_tertiary,
273                "surface_inverse" => colors_sheet.surface_inverse,
274                "surface_inverse_secondary" => colors_sheet.surface_inverse_secondary,
275                "surface_inverse_tertiary" => colors_sheet.surface_inverse_tertiary,
276
277                // Borders
278                "border" => colors_sheet.border,
279                "border_focus" => colors_sheet.border_focus,
280                "border_disabled" => colors_sheet.border_disabled,
281
282                // Text
283                "text_primary" => colors_sheet.text_primary,
284                "text_secondary" => colors_sheet.text_secondary,
285                "text_placeholder" => colors_sheet.text_placeholder,
286                "text_inverse" => colors_sheet.text_inverse,
287                "text_highlight" => colors_sheet.text_highlight,
288
289                // States
290                "hover" => colors_sheet.hover,
291                "focus" => colors_sheet.focus,
292                "active" => colors_sheet.active,
293                "disabled" => colors_sheet.disabled,
294
295                // Utility
296                "overlay" => colors_sheet.overlay,
297                "shadow" => colors_sheet.shadow,
298
299                // Fallback
300                _ => colors_sheet.primary,
301            },
302
303            Self::Specific(value) => *value,
304        }
305    }
306}
307
308impl ResolvablePreference<Size> for Preference<Size> {
309    fn resolve(&self, _colors_sheet: &ColorsSheet) -> Size {
310        match self {
311            Self::Reference(_) => {
312                panic!("Only Colors support references.")
313            }
314            Self::Specific(value) => value.clone(),
315        }
316    }
317}
318
319impl ResolvablePreference<Gaps> for Preference<Gaps> {
320    fn resolve(&self, _colors_sheet: &ColorsSheet) -> Gaps {
321        match self {
322            Self::Reference(_) => {
323                panic!("Only Colors support references.")
324            }
325            Self::Specific(value) => *value,
326        }
327    }
328}
329
330impl ResolvablePreference<CornerRadius> for Preference<CornerRadius> {
331    fn resolve(&self, _colors_sheet: &ColorsSheet) -> CornerRadius {
332        match self {
333            Self::Reference(_) => {
334                panic!("Only Colors support references.")
335            }
336            Self::Specific(value) => *value,
337        }
338    }
339}
340
341impl ResolvablePreference<f32> for Preference<f32> {
342    fn resolve(&self, _colors_sheet: &ColorsSheet) -> f32 {
343        match self {
344            Self::Reference(_) => {
345                panic!("Only Colors support references.")
346            }
347            Self::Specific(value) => *value,
348        }
349    }
350}