freya_components/theming/
macros.rs1use 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 "primary" => colors_sheet.primary,
219 "secondary" => colors_sheet.secondary,
220 "tertiary" => colors_sheet.tertiary,
221
222 "success" => colors_sheet.success,
224 "warning" => colors_sheet.warning,
225 "error" => colors_sheet.error,
226 "info" => colors_sheet.info,
227
228 "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 "border" => colors_sheet.border,
239 "border_focus" => colors_sheet.border_focus,
240 "border_disabled" => colors_sheet.border_disabled,
241
242 "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 "focus" => colors_sheet.focus,
251 "active" => colors_sheet.active,
252 "disabled" => colors_sheet.disabled,
253
254 "overlay" => colors_sheet.overlay,
256 "shadow" => colors_sheet.shadow,
257
258 _ => 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}