freya_hooks/theming/
mod.rs

1mod base;
2mod themes;
3
4#[doc(hidden)]
5pub use ::core::default::Default;
6#[doc(hidden)]
7pub use ::paste::paste;
8#[doc(hidden)]
9pub use ::std::borrow::Cow;
10pub use themes::*;
11
12/// Alias for `Cow::Borrowed`, because that's used a million times so shortening it is nice.
13/// Makes the code more readable.
14#[macro_export]
15macro_rules! cow_borrowed {
16    ($val:expr) => {
17        $crate::Cow::Borrowed($val)
18    };
19}
20
21/// Example usage:
22///
23/// ```rust
24/// # use crate::freya_hooks::define_theme;
25/// # use crate::freya_hooks::FontTheme;
26/// # use crate::freya_hooks::FontThemeWith;
27/// # #[derive(Clone, Debug, PartialEq, Eq)]
28/// # struct Bar;
29/// # #[derive(Clone, Debug, PartialEq, Eq)]
30/// # struct Foo;
31/// define_theme! {
32///     %[component]
33///     pub Test {
34///         %[cows]
35///         cow_string: str,
36///         %[subthemes]
37///         font_theme: FontTheme,
38///     }
39/// }
40/// ```
41#[macro_export]
42macro_rules! define_theme {
43    (NOTHING=) => {};
44
45    (
46        $(#[$attrs:meta])*
47        $(%[component$($component_attr_control:tt)?])?
48        $vis:vis $name:ident $(<$lifetime:lifetime>)? {
49            $(
50                %[cows$($cows_attr_control:tt)?]
51                $(
52                    $(#[$cow_field_attrs:meta])*
53                    $cow_field_name:ident: $cow_field_ty:ty,
54                )*
55            )?
56            $(
57                %[subthemes$($subthemes_attr_control:tt)?]
58                $(
59                    $(#[$subtheme_field_attrs:meta])*
60                    $subtheme_field_name:ident: $subtheme_field_ty_name:ident $(<$subtheme_field_ty_lifetime:lifetime>)?,
61                )*
62            )?
63    }) => {
64        $crate::define_theme!(NOTHING=$($($component_attr_control)?)?);
65        $crate::define_theme!(NOTHING=$($($cows_attr_control)?)?);
66        $crate::define_theme!(NOTHING=$($($subthemes_attr_control)?)?);
67        $crate::paste! {
68            #[derive(Default, Clone, Debug, PartialEq, Eq)]
69            #[doc = "You can use this to change a theme for only one component, with the `theme` property."]
70            $(#[$attrs])*
71            $vis struct [<$name ThemeWith>] $(<$lifetime>)? {
72                $($(
73                    $(#[$subtheme_field_attrs])*
74                    pub $subtheme_field_name: Option< [<$subtheme_field_ty_name With>] $(<$subtheme_field_ty_lifetime>)? >,
75                )*)?
76                $($(
77                    $(#[$cow_field_attrs])*
78                    pub $cow_field_name: Option<$crate::Cow<'static, $cow_field_ty>>,
79                )*)?
80            }
81
82            #[derive(Clone, Debug, PartialEq, Eq)]
83            $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
84            $(#[$attrs])*
85            $vis struct [<$name Theme>] $(<$lifetime>)? {
86                $($(
87                    $(#[$subtheme_field_attrs])*
88                    pub $subtheme_field_name: $subtheme_field_ty_name $(<$subtheme_field_ty_lifetime>)?,
89                )*)?
90                $($(
91                    $(#[$cow_field_attrs])*
92                    pub $cow_field_name: $crate::Cow<'static, $cow_field_ty>,
93                )*)?
94            }
95
96            impl $(<$lifetime>)? [<$name Theme>] $(<$lifetime>)? {
97
98                pub fn apply_colors(&mut self, colors: &$crate::ColorsSheet) {
99                    $($(
100                        self.$subtheme_field_name.apply_colors(colors);
101                    )*)?
102
103                    $($(
104                        self.$cow_field_name = colors.resolve(self.$cow_field_name.clone());
105                    )*)?
106                }
107
108                #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
109                pub fn apply_optional(&mut self, optional: & $($lifetime)? [<$name ThemeWith>]) {
110                    $($(
111                        if let Some($subtheme_field_name) = &optional.$subtheme_field_name {
112                            self.$subtheme_field_name.apply_optional($subtheme_field_name);
113                        }
114                    )*)?
115
116                    $($(
117                        if let Some($cow_field_name) = &optional.$cow_field_name {
118                            self.$cow_field_name = $cow_field_name.clone();
119                        }
120                    )*)?
121                }
122            }
123        }
124    };
125}
126
127/// Create `FooThemeWith` structs without having to deal with the verbose syntax.
128///
129/// # Examples
130///
131/// Without the macro:
132///
133/// ```no_run
134/// # use freya::prelude::*;
135/// # fn theme_with_example_no_macro() -> Element {
136/// rsx! {
137///     Button {
138///         theme: ButtonThemeWith {
139///             background: Some("blue".into()),
140///             font_theme: FontThemeWith {
141///                 color: Some("white".into()),
142///                 ..Default::default()
143///             }.into(),
144///             ..Default::default()
145///         }
146///     }
147/// }
148/// # }
149/// ```
150///
151/// With the macro:
152///
153/// ```no_run
154/// # use freya::prelude::*;
155/// # fn theme_with_example_no_macro() -> Element {
156/// rsx! {
157///     Button {
158///         theme: theme_with!(ButtonTheme {
159///             background: "blue".into(),
160///             font_theme: theme_with!(FontTheme {
161///                 color: "white".into(),
162///             }),
163///         })
164///     }
165/// }
166/// # }
167/// ```
168#[macro_export]
169macro_rules! theme_with {
170    ($theme_name:ident {
171        $(
172            $theme_field_name:ident: $theme_field_val:expr
173        ),* $(,)?
174    }) => {
175        $crate::paste! {
176            {
177                #[allow(clippy::needless_update)]
178                [<$theme_name With>] {
179                    $($theme_field_name: Some($theme_field_val),)*
180                    ..$crate::Default::default()
181                }
182            }
183        }
184    };
185}
186
187define_theme! {
188    %[component]
189    pub Dropdown {
190        %[cows]
191        width: str,
192        margin: str,
193        dropdown_background: str,
194        background_button: str,
195        hover_background: str,
196        border_fill: str,
197        focus_border_fill: str,
198        arrow_fill: str,
199        %[subthemes]
200        font_theme: FontTheme,
201    }
202}
203
204define_theme! {
205    %[component]
206    pub DropdownItem {
207        %[cows]
208        background: str,
209        select_background: str,
210        hover_background: str,
211        border_fill: str,
212        select_border_fill: str,
213        %[subthemes]
214        font_theme: FontTheme,
215    }
216}
217
218define_theme! {
219    %[component]
220    pub Button {
221        %[cows]
222        background: str,
223        hover_background: str,
224        border_fill: str,
225        focus_border_fill: str,
226        shadow: str,
227        margin: str,
228        corner_radius: str,
229        width: str,
230        height: str,
231        padding: str,
232        %[subthemes]
233        font_theme: FontTheme,
234    }
235}
236
237define_theme! {
238    %[component]
239    pub Input {
240        %[cows]
241        background: str,
242        hover_background: str,
243        border_fill: str,
244        focus_border_fill: str,
245        shadow: str,
246        margin: str,
247        corner_radius: str,
248        %[subthemes]
249        font_theme: FontTheme,
250        placeholder_font_theme: FontTheme,
251    }
252}
253
254define_theme! {
255    /// Theming properties for Fonts.
256    pub Font {
257        %[cows]
258        color: str,
259    }
260}
261
262define_theme! {
263    %[component]
264    pub Switch {
265        %[cows]
266        margin: str,
267        background: str,
268        thumb_background: str,
269        enabled_background: str,
270        enabled_thumb_background: str,
271        focus_border_fill: str,
272        enabled_focus_border_fill: str,
273    }
274}
275
276define_theme! {
277    %[component]
278    pub ScrollBar {
279        %[cows]
280        background: str,
281        thumb_background: str,
282        hover_thumb_background: str,
283        active_thumb_background: str,
284        size: str,
285    }
286}
287
288define_theme! {
289    %[component]
290    pub Body {
291        %[cows]
292        background: str,
293        color: str,
294    }
295}
296
297define_theme! {
298    %[component]
299    pub Slider {
300        %[cows]
301        background: str,
302        thumb_background: str,
303        thumb_inner_background: str,
304        border_fill: str,
305    }
306}
307
308define_theme! {
309    %[component]
310    pub Tooltip {
311        %[cows]
312        background: str,
313        color: str,
314        border_fill: str,
315    }
316}
317
318define_theme! {
319    %[component]
320    pub Accordion {
321        %[cows]
322        color: str,
323        background: str,
324        border_fill: str,
325    }
326}
327
328define_theme! {
329    %[component]
330    pub Loader {
331        %[cows]
332        primary_color: str,
333    }
334}
335
336define_theme! {
337    %[component]
338    pub Link {
339        %[cows]
340        highlight_color: str,
341    }
342}
343
344define_theme! {
345    %[component]
346    pub ProgressBar {
347        %[cows]
348        color: str,
349        background: str,
350        progress_background: str,
351        height: str,
352    }
353}
354
355define_theme! {
356    %[component]
357    pub Table {
358        %[cows]
359        background: str,
360        arrow_fill: str,
361        hover_row_background: str,
362        row_background: str,
363        divider_fill: str,
364        corner_radius: str,
365        %[subthemes]
366        font_theme: FontTheme,
367    }
368}
369
370define_theme! {
371    %[component]
372    pub Graph {
373        %[cows]
374        width: str,
375        height: str,
376    }
377}
378
379define_theme! {
380    %[component]
381    pub Icon {
382        %[cows]
383        margin: str,
384        width: str,
385        height: str,
386    }
387}
388
389define_theme! {
390    %[component]
391    pub Sidebar {
392        %[cows]
393        spacing: str,
394        background: str,
395        %[subthemes]
396        font_theme: FontTheme,
397    }
398}
399
400define_theme! {
401    %[component]
402    pub SidebarItem {
403        %[cows]
404        margin: str,
405        background: str,
406        hover_background: str,
407        %[subthemes]
408        font_theme: FontTheme,
409    }
410}
411
412define_theme! {
413    %[component]
414    pub Tile {
415        %[cows]
416        padding: str,
417    }
418}
419
420define_theme! {
421    %[component]
422    pub MenuItem {
423        %[cows]
424        hover_background: str,
425        corner_radius: str,
426        %[subthemes]
427        font_theme: FontTheme,
428    }
429}
430
431define_theme! {
432    %[component]
433    pub MenuContainer {
434        %[cows]
435        background: str,
436        padding: str,
437        shadow: str,
438        border_fill: str,
439        corner_radius: str,
440    }
441}
442
443define_theme! {
444    %[component]
445    pub SnackBar {
446        %[cows]
447        background: str,
448        color: str,
449     }
450}
451
452define_theme! {
453    %[component]
454    pub Radio {
455        %[cows]
456        unselected_fill: str,
457        selected_fill: str,
458        border_fill: str,
459    }
460}
461
462define_theme! {
463    %[component]
464    pub Checkbox {
465        %[cows]
466        unselected_fill: str,
467        selected_fill: str,
468        selected_icon_fill: str,
469        border_fill: str,
470    }
471}
472
473define_theme! {
474    %[component]
475    pub Popup {
476        %[cows]
477        background: str,
478        color: str,
479        cross_fill: str,
480        width: str,
481        height: str,
482    }
483}
484
485define_theme! {
486    %[component]
487    pub Tab {
488        %[cows]
489        background: str,
490        hover_background: str,
491        border_fill: str,
492        focus_border_fill: str,
493        width: str,
494        height: str,
495        padding: str,
496        %[subthemes]
497        font_theme: FontTheme,
498    }
499}
500
501define_theme! {
502    %[component]
503    pub BottomTab {
504        %[cows]
505        background: str,
506        hover_background: str,
507        width: str,
508        height: str,
509        padding: str,
510        %[subthemes]
511        font_theme: FontTheme,
512    }
513}
514
515define_theme! {
516    %[component]
517    pub ResizableHandle {
518        %[cows]
519        background: str,
520        hover_background: str,
521    }
522}
523
524#[derive(Clone, Debug, PartialEq, Eq)]
525pub struct ColorsSheet {
526    pub primary: Cow<'static, str>,
527    pub focused_primary_border: Cow<'static, str>,
528    pub secondary: Cow<'static, str>,
529    pub tertiary: Cow<'static, str>,
530    pub surface: Cow<'static, str>,
531    pub secondary_surface: Cow<'static, str>,
532    pub neutral_surface: Cow<'static, str>,
533    pub focused_surface: Cow<'static, str>,
534    pub opposite_surface: Cow<'static, str>,
535    pub secondary_opposite_surface: Cow<'static, str>,
536    pub tertiary_opposite_surface: Cow<'static, str>,
537    pub background: Cow<'static, str>,
538    pub focused_border: Cow<'static, str>,
539    pub solid: Cow<'static, str>,
540    pub color: Cow<'static, str>,
541    pub primary_color: Cow<'static, str>,
542    pub placeholder_color: Cow<'static, str>,
543    pub highlight_color: Cow<'static, str>,
544}
545
546impl ColorsSheet {
547    pub fn resolve(&self, val: Cow<'static, str>) -> Cow<'static, str> {
548        if val.starts_with("key") {
549            let key_val = val.replace("key(", "").replace(")", "");
550            match key_val.as_str() {
551                "primary" => self.primary.clone(),
552                "focused_primary_border" => self.focused_primary_border.clone(),
553                "secondary" => self.secondary.clone(),
554                "tertiary" => self.tertiary.clone(),
555                "surface" => self.surface.clone(),
556                "secondary_surface" => self.secondary_surface.clone(),
557                "neutral_surface" => self.neutral_surface.clone(),
558                "focused_surface" => self.focused_surface.clone(),
559                "opposite_surface" => self.opposite_surface.clone(),
560                "secondary_opposite_surface" => self.secondary_opposite_surface.clone(),
561                "tertiary_opposite_surface" => self.tertiary_opposite_surface.clone(),
562                "background" => self.background.clone(),
563                "focused_border" => self.focused_border.clone(),
564                "solid" => self.solid.clone(),
565                "color" => self.color.clone(),
566                "primary_color" => self.primary_color.clone(),
567                "placeholder_color" => self.placeholder_color.clone(),
568                "highlight_color" => self.highlight_color.clone(),
569                _ => self.primary.clone(),
570            }
571        } else {
572            val
573        }
574    }
575}
576
577#[derive(Clone, Debug, PartialEq, Eq)]
578pub struct Theme {
579    pub name: &'static str,
580    pub colors: ColorsSheet,
581    pub body: BodyTheme,
582    pub button: ButtonTheme,
583    pub filled_button: ButtonTheme,
584    pub outline_button: ButtonTheme,
585    pub switch: SwitchTheme,
586    pub scroll_bar: ScrollBarTheme,
587    pub slider: SliderTheme,
588    pub tooltip: TooltipTheme,
589    pub dropdown: DropdownTheme,
590    pub dropdown_item: DropdownItemTheme,
591    pub accordion: AccordionTheme,
592    pub loader: LoaderTheme,
593    pub link: LinkTheme,
594    pub progress_bar: ProgressBarTheme,
595    pub table: TableTheme,
596    pub input: InputTheme,
597    pub graph: GraphTheme,
598    pub icon: IconTheme,
599    pub sidebar: SidebarTheme,
600    pub sidebar_item: SidebarItemTheme,
601    pub tile: TileTheme,
602    pub radio: RadioTheme,
603    pub checkbox: CheckboxTheme,
604    pub menu_item: MenuItemTheme,
605    pub menu_container: MenuContainerTheme,
606    pub snackbar: SnackBarTheme,
607    pub popup: PopupTheme,
608    pub tab: TabTheme,
609    pub bottom_tab: BottomTabTheme,
610    pub resizable_handle: ResizableHandleTheme,
611}
612
613impl Default for Theme {
614    fn default() -> Self {
615        LIGHT_THEME
616    }
617}