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 theme = theme.read();
224 let mut requested_theme = theme.$theme_name.clone();
225
226 if let Some(theme_override) = $theme_prop {
227 requested_theme.apply_optional(&theme_override);
228 }
229
230 requested_theme.resolve(&theme.colors)
231 }};
232}
233
234#[derive(Clone, Debug, PartialEq, Eq)]
235pub enum Preference<T> {
236 Specific(T),
237 Reference(&'static str),
238}
239
240impl<T> From<T> for Preference<T> {
241 fn from(value: T) -> Self {
242 Preference::Specific(value)
243 }
244}
245
246pub trait ResolvablePreference<T: Clone> {
247 fn resolve(&self, colors_sheet: &ColorsSheet) -> T;
248}
249
250impl ResolvablePreference<Color> for Preference<Color> {
251 fn resolve(&self, colors_sheet: &ColorsSheet) -> Color {
252 match self {
253 Self::Reference(reference) => match *reference {
254 "primary" => colors_sheet.primary,
256 "secondary" => colors_sheet.secondary,
257 "tertiary" => colors_sheet.tertiary,
258
259 "success" => colors_sheet.success,
261 "warning" => colors_sheet.warning,
262 "error" => colors_sheet.error,
263 "info" => colors_sheet.info,
264
265 "background" => colors_sheet.background,
267 "surface_primary" => colors_sheet.surface_primary,
268 "surface_secondary" => colors_sheet.surface_secondary,
269 "surface_tertiary" => colors_sheet.surface_tertiary,
270 "surface_inverse" => colors_sheet.surface_inverse,
271 "surface_inverse_secondary" => colors_sheet.surface_inverse_secondary,
272 "surface_inverse_tertiary" => colors_sheet.surface_inverse_tertiary,
273
274 "border" => colors_sheet.border,
276 "border_focus" => colors_sheet.border_focus,
277 "border_disabled" => colors_sheet.border_disabled,
278
279 "text_primary" => colors_sheet.text_primary,
281 "text_secondary" => colors_sheet.text_secondary,
282 "text_placeholder" => colors_sheet.text_placeholder,
283 "text_inverse" => colors_sheet.text_inverse,
284 "text_highlight" => colors_sheet.text_highlight,
285
286 "hover" => colors_sheet.hover,
288 "focus" => colors_sheet.focus,
289 "active" => colors_sheet.active,
290 "disabled" => colors_sheet.disabled,
291
292 "overlay" => colors_sheet.overlay,
294 "shadow" => colors_sheet.shadow,
295
296 _ => colors_sheet.primary,
298 },
299
300 Self::Specific(value) => *value,
301 }
302 }
303}
304
305impl ResolvablePreference<Size> for Preference<Size> {
306 fn resolve(&self, _colors_sheet: &ColorsSheet) -> Size {
307 match self {
308 Self::Reference(_) => {
309 panic!("Only Colors support references.")
310 }
311 Self::Specific(value) => value.clone(),
312 }
313 }
314}
315
316impl ResolvablePreference<Gaps> for Preference<Gaps> {
317 fn resolve(&self, _colors_sheet: &ColorsSheet) -> Gaps {
318 match self {
319 Self::Reference(_) => {
320 panic!("Only Colors support references.")
321 }
322 Self::Specific(value) => *value,
323 }
324 }
325}
326
327impl ResolvablePreference<CornerRadius> for Preference<CornerRadius> {
328 fn resolve(&self, _colors_sheet: &ColorsSheet) -> CornerRadius {
329 match self {
330 Self::Reference(_) => {
331 panic!("Only Colors support references.")
332 }
333 Self::Specific(value) => *value,
334 }
335 }
336}
337
338impl ResolvablePreference<f32> for Preference<f32> {
339 fn resolve(&self, _colors_sheet: &ColorsSheet) -> f32 {
340 match self {
341 Self::Reference(_) => {
342 panic!("Only Colors support references.")
343 }
344 Self::Specific(value) => *value,
345 }
346 }
347}