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#[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 (
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 (
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#[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 "primary" => colors_sheet.primary,
298 "secondary" => colors_sheet.secondary,
299 "tertiary" => colors_sheet.tertiary,
300
301 "success" => colors_sheet.success,
303 "warning" => colors_sheet.warning,
304 "error" => colors_sheet.error,
305 "info" => colors_sheet.info,
306
307 "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 "border" => colors_sheet.border,
318 "border_focus" => colors_sheet.border_focus,
319 "border_disabled" => colors_sheet.border_disabled,
320
321 "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 "focus" => colors_sheet.focus,
330 "active" => colors_sheet.active,
331 "disabled" => colors_sheet.disabled,
332
333 "overlay" => colors_sheet.overlay,
335 "shadow" => colors_sheet.shadow,
336
337 _ => 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}