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