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