freya_components/theming/
macros.rs1#[doc(hidden)]
2pub use ::paste::paste;
3use freya_core::prelude::*;
4use torin::{
5 gaps::Gaps,
6 size::Size,
7};
8
9use crate::theming::component_themes::ColorsSheet;
10
11#[macro_export]
12macro_rules! define_theme {
13 (NOTHING=) => {};
14
15 (
16 $(#[$attrs:meta])*
17 for = $for_ty:ident ;
18 theme_field = $theme_field:ident ;
19 $(%[component$($component_attr_control:tt)?])?
20 $vis:vis $name:ident $(<$lifetime:lifetime>)? {
21 $(
22 %[fields$($cows_attr_control:tt)?]
23 $(
24 $(#[$field_attrs:meta])*
25 $field_name:ident: $field_ty:ty,
26 )*
27 )?
28 }) => {
29 $crate::define_theme!(NOTHING=$($($component_attr_control)?)?);
30 $crate::theming::macros::paste! {
31 #[derive(Default, Clone, Debug, PartialEq)]
32 #[doc = "You can use this to change a theme for only one component, with the `theme` property."]
33 $(#[$attrs])*
34 $vis struct [<$name ThemePartial>] $(<$lifetime>)? {
35 $($(
36 $(#[$field_attrs])*
37 pub $field_name: Option<$crate::theming::macros::Preference<$field_ty>>,
38 )*)?
39 }
40
41 #[derive(Clone, Debug, PartialEq)]
42 $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
43 $(#[$attrs])*
44 $vis struct [<$name ThemePreference>] $(<$lifetime>)? {
45 $($(
46 $(#[$field_attrs])*
47 pub $field_name: $crate::theming::macros::Preference<$field_ty>,
48 )*)?
49 }
50
51 #[derive(Clone, Debug, PartialEq)]
52 $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
53 $(#[$attrs])*
54 $vis struct [<$name Theme>] $(<$lifetime>)? {
55 $($(
56 $(#[$field_attrs])*
57 pub $field_name: $field_ty,
58 )*)?
59 }
60
61 impl $(<$lifetime>)? [<$name ThemePreference>] $(<$lifetime>)? {
62 #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
63 pub fn apply_optional(&mut self, optional: & $($lifetime)? [<$name ThemePartial>]) {
64
65 $($(
66 if let Some($field_name) = &optional.$field_name {
67 self.$field_name = $field_name.clone();
68 }
69 )*)?
70 }
71
72 #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
73 pub fn resolve(&mut self, colors_sheet: &$crate::theming::component_themes::ColorsSheet) -> [<$name Theme>] {
74 use $crate::theming::macros::ResolvablePreference;
75 [<$name Theme>] {
76 $(
77 $(
78 $field_name: self.$field_name.resolve(colors_sheet),
79 )*
80 )?
81 }
82 }
83 }
84
85 impl $(<$lifetime>)? [<$name ThemePartial>] $(<$lifetime>)? {
86 pub fn new() -> Self {
87 Self::default()
88 }
89
90 $($(
91 $(#[$field_attrs])*
92 pub fn $field_name(mut self, $field_name: impl Into<$field_ty>) -> Self {
93 self.$field_name = Some($crate::theming::macros::Preference::Specific($field_name.into()));
94 self
95 }
96 )*)?
97 }
98
99 pub trait [<$name ThemePartialExt>] {
100 $($(
101 $(#[$field_attrs])*
102 fn $field_name(self, $field_name: impl Into<$field_ty>) -> Self;
103 )*)?
104 }
105
106 impl $(<$lifetime>)? [<$name ThemePartialExt>] for $for_ty $(<$lifetime>)? {
107 $($(
108 $(#[$field_attrs])*
109 fn $field_name(mut self, $field_name: impl Into<$field_ty>) -> Self {
110 self.$theme_field = Some(self.$theme_field.unwrap_or_default().$field_name($field_name));
111 self
112 }
113 )*)?
114 }
115 }
116 };
117
118 (
119 $(#[$attrs:meta])*
120 $(%[component$($component_attr_control:tt)?])?
121 $vis:vis $name:ident $(<$lifetime:lifetime>)? {
122 $(
123 %[fields$($cows_attr_control:tt)?]
124 $(
125 $(#[$field_attrs:meta])*
126 $field_name:ident: $field_ty:ty,
127 )*
128 )?
129 }) => {
130 $crate::define_theme!(NOTHING=$($($component_attr_control)?)?);
131 $crate::theming::macros::paste! {
132 #[derive(Default, Clone, Debug, PartialEq)]
133 #[doc = "You can use this to change a theme for only one component, with the `theme` property."]
134 $(#[$attrs])*
135 $vis struct [<$name ThemePartial>] $(<$lifetime>)? {
136 $($(
137 $(#[$field_attrs])*
138 pub $field_name: Option<$crate::theming::macros::Preference<$field_ty>>,
139 )*)?
140 }
141
142 #[derive(Clone, Debug, PartialEq)]
143 $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
144 $(#[$attrs])*
145 $vis struct [<$name ThemePreference>] $(<$lifetime>)? {
146 $($(
147 $(#[$field_attrs])*
148 pub $field_name: $crate::theming::macros::Preference<$field_ty>,
149 )*)?
150 }
151
152 #[derive(Clone, Debug, PartialEq)]
153 $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
154 $(#[$attrs])*
155 $vis struct [<$name Theme>] $(<$lifetime>)? {
156 $($(
157 $(#[$field_attrs])*
158 pub $field_name: $field_ty,
159 )*)?
160 }
161
162 impl $(<$lifetime>)? [<$name ThemePreference>] $(<$lifetime>)? {
163 #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
164 pub fn apply_optional(&mut self, optional: & $($lifetime)? [<$name ThemePartial>]) {
165 $($(
166 if let Some($field_name) = &optional.$field_name {
167 self.$field_name = $field_name.clone();
168 }
169 )*)?
170 }
171
172 #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
173 pub fn resolve(&mut self, colors_sheet: &$crate::theming::component_themes::ColorsSheet) -> [<$name Theme>] {
174 use $crate::theming::macros::ResolvablePreference;
175 [<$name Theme>] {
176 $(
177 $(
178 $field_name: self.$field_name.resolve(colors_sheet),
179 )*
180 )?
181 }
182 }
183 }
184
185 impl $(<$lifetime>)? [<$name ThemePartial>] $(<$lifetime>)? {
186 pub fn new() -> Self {
187 Self::default()
188 }
189
190 $($(
191 $(#[$field_attrs])*
192 pub fn $field_name(mut self, $field_name: impl Into<$field_ty>) -> Self {
193 self.$field_name = Some($crate::theming::macros::Preference::Specific($field_name.into()));
194 self
195 }
196 )*)?
197 }
198
199 pub trait [<$name ThemePartialExt>] {
200 $($(
201 $(#[$field_attrs])*
202 fn $field_name(self, $field_name: impl Into<$field_ty>) -> Self;
203 )*)?
204 }
205
206 impl $(<$lifetime>)? [<$name ThemePartialExt>] for $name $(<$lifetime>)? {
207 $($(
208 $(#[$field_attrs])*
209 fn $field_name(mut self, $field_name: impl Into<$field_ty>) -> Self {
210 self.theme = Some(self.theme.unwrap_or_default().$field_name($field_name));
211 self
212 }
213 )*)?
214 }
215 }
216 };
217}
218
219#[macro_export]
220macro_rules! get_theme {
221 ($theme_prop:expr, $theme_name:ident) => {{
222 let theme = $crate::theming::hooks::get_theme_or_default();
223 let mut requested_theme = theme.$theme_name;
224
225 if let Some(theme_override) = $theme_prop {
226 requested_theme.apply_optional(&theme_override);
227 }
228
229 requested_theme.resolve(&theme.colors)
230 }};
231}
232
233#[derive(Clone, Debug, PartialEq, Eq)]
234pub enum Preference<T> {
235 Specific(T),
236 Reference(&'static str),
237}
238
239impl<T> From<T> for Preference<T> {
240 fn from(value: T) -> Self {
241 Preference::Specific(value)
242 }
243}
244
245pub trait ResolvablePreference<T: Clone> {
246 fn resolve(&self, colors_sheet: &ColorsSheet) -> T;
247}
248
249impl ResolvablePreference<Color> for Preference<Color> {
250 fn resolve(&self, colors_sheet: &ColorsSheet) -> Color {
251 match self {
252 Self::Reference(reference) => match *reference {
253 "primary" => colors_sheet.primary,
255 "secondary" => colors_sheet.secondary,
256 "tertiary" => colors_sheet.tertiary,
257
258 "success" => colors_sheet.success,
260 "warning" => colors_sheet.warning,
261 "error" => colors_sheet.error,
262 "info" => colors_sheet.info,
263
264 "background" => colors_sheet.background,
266 "surface_primary" => colors_sheet.surface_primary,
267 "surface_secondary" => colors_sheet.surface_secondary,
268 "surface_tertiary" => colors_sheet.surface_tertiary,
269 "surface_inverse" => colors_sheet.surface_inverse,
270 "surface_inverse_secondary" => colors_sheet.surface_inverse_secondary,
271 "surface_inverse_tertiary" => colors_sheet.surface_inverse_tertiary,
272
273 "border" => colors_sheet.border,
275 "border_focus" => colors_sheet.border_focus,
276 "border_disabled" => colors_sheet.border_disabled,
277
278 "text_primary" => colors_sheet.text_primary,
280 "text_secondary" => colors_sheet.text_secondary,
281 "text_placeholder" => colors_sheet.text_placeholder,
282 "text_inverse" => colors_sheet.text_inverse,
283 "text_highlight" => colors_sheet.text_highlight,
284
285 "hover" => colors_sheet.hover,
287 "focus" => colors_sheet.focus,
288 "active" => colors_sheet.active,
289 "disabled" => colors_sheet.disabled,
290
291 "overlay" => colors_sheet.overlay,
293 "shadow" => colors_sheet.shadow,
294
295 _ => colors_sheet.primary,
297 },
298
299 Self::Specific(value) => *value,
300 }
301 }
302}
303
304impl ResolvablePreference<Size> for Preference<Size> {
305 fn resolve(&self, _colors_sheet: &ColorsSheet) -> Size {
306 match self {
307 Self::Reference(_) => {
308 panic!("Only Colors support references.")
309 }
310 Self::Specific(value) => value.clone(),
311 }
312 }
313}
314
315impl ResolvablePreference<Gaps> for Preference<Gaps> {
316 fn resolve(&self, _colors_sheet: &ColorsSheet) -> Gaps {
317 match self {
318 Self::Reference(_) => {
319 panic!("Only Colors support references.")
320 }
321 Self::Specific(value) => *value,
322 }
323 }
324}
325
326impl ResolvablePreference<CornerRadius> for Preference<CornerRadius> {
327 fn resolve(&self, _colors_sheet: &ColorsSheet) -> CornerRadius {
328 match self {
329 Self::Reference(_) => {
330 panic!("Only Colors support references.")
331 }
332 Self::Specific(value) => *value,
333 }
334 }
335}
336
337impl ResolvablePreference<f32> for Preference<f32> {
338 fn resolve(&self, _colors_sheet: &ColorsSheet) -> f32 {
339 match self {
340 Self::Reference(_) => {
341 panic!("Only Colors support references.")
342 }
343 Self::Specific(value) => *value,
344 }
345 }
346}