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