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 "hover" => colors_sheet.hover,
251 "focus" => colors_sheet.focus,
252 "active" => colors_sheet.active,
253 "disabled" => colors_sheet.disabled,
254
255 "overlay" => colors_sheet.overlay,
257 "shadow" => colors_sheet.shadow,
258
259 _ => colors_sheet.primary,
261 },
262
263 Self::Specific(value) => *value,
264 }
265 }
266}
267
268impl ResolvablePreference<Size> for Preference<Size> {
269 fn resolve(&self, _colors_sheet: &ColorsSheet) -> Size {
270 match self {
271 Self::Reference(_) => {
272 panic!("Only Colors support references.")
273 }
274 Self::Specific(value) => value.clone(),
275 }
276 }
277}
278
279impl ResolvablePreference<Gaps> for Preference<Gaps> {
280 fn resolve(&self, _colors_sheet: &ColorsSheet) -> Gaps {
281 match self {
282 Self::Reference(_) => {
283 panic!("Only Colors support references.")
284 }
285 Self::Specific(value) => *value,
286 }
287 }
288}
289
290impl ResolvablePreference<CornerRadius> for Preference<CornerRadius> {
291 fn resolve(&self, _colors_sheet: &ColorsSheet) -> CornerRadius {
292 match self {
293 Self::Reference(_) => {
294 panic!("Only Colors support references.")
295 }
296 Self::Specific(value) => *value,
297 }
298 }
299}
300
301impl ResolvablePreference<f32> for Preference<f32> {
302 fn resolve(&self, _colors_sheet: &ColorsSheet) -> f32 {
303 match self {
304 Self::Reference(_) => {
305 panic!("Only Colors support references.")
306 }
307 Self::Specific(value) => *value,
308 }
309 }
310}
311
312impl ResolvablePreference<Duration> for Preference<Duration> {
313 fn resolve(&self, _colors_sheet: &ColorsSheet) -> Duration {
314 match self {
315 Self::Reference(_) => {
316 panic!("Only Colors support references.")
317 }
318 Self::Specific(value) => *value,
319 }
320 }
321}