Skip to main content

freya_components/theming/
macros.rs

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