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