1mod base;
2mod themes;
3
4#[doc(hidden)]
5pub use ::core::default::Default;
6#[doc(hidden)]
7pub use ::paste::paste;
8#[doc(hidden)]
9pub use ::std::borrow::Cow;
10pub use themes::*;
11
12#[macro_export]
15macro_rules! cow_borrowed {
16 ($val:expr) => {
17 $crate::Cow::Borrowed($val)
18 };
19}
20
21#[macro_export]
42macro_rules! define_theme {
43 (NOTHING=) => {};
44
45 (
46 $(#[$attrs:meta])*
47 $(%[component$($component_attr_control:tt)?])?
48 $vis:vis $name:ident $(<$lifetime:lifetime>)? {
49 $(
50 %[cows$($cows_attr_control:tt)?]
51 $(
52 $(#[$cow_field_attrs:meta])*
53 $cow_field_name:ident: $cow_field_ty:ty,
54 )*
55 )?
56 $(
57 %[subthemes$($subthemes_attr_control:tt)?]
58 $(
59 $(#[$subtheme_field_attrs:meta])*
60 $subtheme_field_name:ident: $subtheme_field_ty_name:ident $(<$subtheme_field_ty_lifetime:lifetime>)?,
61 )*
62 )?
63 }) => {
64 $crate::define_theme!(NOTHING=$($($component_attr_control)?)?);
65 $crate::define_theme!(NOTHING=$($($cows_attr_control)?)?);
66 $crate::define_theme!(NOTHING=$($($subthemes_attr_control)?)?);
67 $crate::paste! {
68 #[derive(Default, Clone, Debug, PartialEq, Eq)]
69 #[doc = "You can use this to change a theme for only one component, with the `theme` property."]
70 $(#[$attrs])*
71 $vis struct [<$name ThemeWith>] $(<$lifetime>)? {
72 $($(
73 $(#[$subtheme_field_attrs])*
74 pub $subtheme_field_name: Option< [<$subtheme_field_ty_name With>] $(<$subtheme_field_ty_lifetime>)? >,
75 )*)?
76 $($(
77 $(#[$cow_field_attrs])*
78 pub $cow_field_name: Option<$crate::Cow<'static, $cow_field_ty>>,
79 )*)?
80 }
81
82 #[derive(Clone, Debug, PartialEq, Eq)]
83 $(#[doc = "Theming properties for the `" $name "` component."] $($component_attr_control)?)?
84 $(#[$attrs])*
85 $vis struct [<$name Theme>] $(<$lifetime>)? {
86 $($(
87 $(#[$subtheme_field_attrs])*
88 pub $subtheme_field_name: $subtheme_field_ty_name $(<$subtheme_field_ty_lifetime>)?,
89 )*)?
90 $($(
91 $(#[$cow_field_attrs])*
92 pub $cow_field_name: $crate::Cow<'static, $cow_field_ty>,
93 )*)?
94 }
95
96 impl $(<$lifetime>)? [<$name Theme>] $(<$lifetime>)? {
97
98 pub fn apply_colors(&mut self, colors: &$crate::ColorsSheet) {
99 $($(
100 self.$subtheme_field_name.apply_colors(colors);
101 )*)?
102
103 $($(
104 self.$cow_field_name = colors.resolve(self.$cow_field_name.clone());
105 )*)?
106 }
107
108 #[doc = "Checks each field in `optional` and if it's `Some`, it overwrites the corresponding `self` field."]
109 pub fn apply_optional(&mut self, optional: & $($lifetime)? [<$name ThemeWith>]) {
110 $($(
111 if let Some($subtheme_field_name) = &optional.$subtheme_field_name {
112 self.$subtheme_field_name.apply_optional($subtheme_field_name);
113 }
114 )*)?
115
116 $($(
117 if let Some($cow_field_name) = &optional.$cow_field_name {
118 self.$cow_field_name = $cow_field_name.clone();
119 }
120 )*)?
121 }
122 }
123 }
124 };
125}
126
127#[macro_export]
169macro_rules! theme_with {
170 ($theme_name:ident {
171 $(
172 $theme_field_name:ident: $theme_field_val:expr
173 ),* $(,)?
174 }) => {
175 $crate::paste! {
176 {
177 #[allow(clippy::needless_update)]
178 [<$theme_name With>] {
179 $($theme_field_name: Some($theme_field_val),)*
180 ..$crate::Default::default()
181 }
182 }
183 }
184 };
185}
186
187define_theme! {
188 %[component]
189 pub Dropdown {
190 %[cows]
191 width: str,
192 margin: str,
193 dropdown_background: str,
194 background_button: str,
195 hover_background: str,
196 border_fill: str,
197 focus_border_fill: str,
198 arrow_fill: str,
199 %[subthemes]
200 font_theme: FontTheme,
201 }
202}
203
204define_theme! {
205 %[component]
206 pub DropdownItem {
207 %[cows]
208 background: str,
209 select_background: str,
210 hover_background: str,
211 border_fill: str,
212 select_border_fill: str,
213 %[subthemes]
214 font_theme: FontTheme,
215 }
216}
217
218define_theme! {
219 %[component]
220 pub Button {
221 %[cows]
222 background: str,
223 hover_background: str,
224 border_fill: str,
225 focus_border_fill: str,
226 shadow: str,
227 margin: str,
228 corner_radius: str,
229 width: str,
230 height: str,
231 padding: str,
232 %[subthemes]
233 font_theme: FontTheme,
234 }
235}
236
237define_theme! {
238 %[component]
239 pub Input {
240 %[cows]
241 background: str,
242 hover_background: str,
243 border_fill: str,
244 focus_border_fill: str,
245 shadow: str,
246 margin: str,
247 corner_radius: str,
248 %[subthemes]
249 font_theme: FontTheme,
250 placeholder_font_theme: FontTheme,
251 }
252}
253
254define_theme! {
255 pub Font {
257 %[cows]
258 color: str,
259 }
260}
261
262define_theme! {
263 %[component]
264 pub Switch {
265 %[cows]
266 margin: str,
267 background: str,
268 thumb_background: str,
269 enabled_background: str,
270 enabled_thumb_background: str,
271 focus_border_fill: str,
272 enabled_focus_border_fill: str,
273 }
274}
275
276define_theme! {
277 %[component]
278 pub ScrollBar {
279 %[cows]
280 background: str,
281 thumb_background: str,
282 hover_thumb_background: str,
283 active_thumb_background: str,
284 size: str,
285 }
286}
287
288define_theme! {
289 %[component]
290 pub Body {
291 %[cows]
292 background: str,
293 color: str,
294 }
295}
296
297define_theme! {
298 %[component]
299 pub Slider {
300 %[cows]
301 background: str,
302 thumb_background: str,
303 thumb_inner_background: str,
304 border_fill: str,
305 }
306}
307
308define_theme! {
309 %[component]
310 pub Tooltip {
311 %[cows]
312 background: str,
313 color: str,
314 border_fill: str,
315 }
316}
317
318define_theme! {
319 %[component]
320 pub Accordion {
321 %[cows]
322 color: str,
323 background: str,
324 border_fill: str,
325 }
326}
327
328define_theme! {
329 %[component]
330 pub Loader {
331 %[cows]
332 primary_color: str,
333 }
334}
335
336define_theme! {
337 %[component]
338 pub Link {
339 %[cows]
340 highlight_color: str,
341 }
342}
343
344define_theme! {
345 %[component]
346 pub ProgressBar {
347 %[cows]
348 color: str,
349 background: str,
350 progress_background: str,
351 height: str,
352 }
353}
354
355define_theme! {
356 %[component]
357 pub Table {
358 %[cows]
359 background: str,
360 arrow_fill: str,
361 hover_row_background: str,
362 row_background: str,
363 divider_fill: str,
364 corner_radius: str,
365 %[subthemes]
366 font_theme: FontTheme,
367 }
368}
369
370define_theme! {
371 %[component]
372 pub Graph {
373 %[cows]
374 width: str,
375 height: str,
376 }
377}
378
379define_theme! {
380 %[component]
381 pub Icon {
382 %[cows]
383 margin: str,
384 width: str,
385 height: str,
386 }
387}
388
389define_theme! {
390 %[component]
391 pub Sidebar {
392 %[cows]
393 spacing: str,
394 background: str,
395 %[subthemes]
396 font_theme: FontTheme,
397 }
398}
399
400define_theme! {
401 %[component]
402 pub SidebarItem {
403 %[cows]
404 margin: str,
405 background: str,
406 hover_background: str,
407 %[subthemes]
408 font_theme: FontTheme,
409 }
410}
411
412define_theme! {
413 %[component]
414 pub Tile {
415 %[cows]
416 padding: str,
417 }
418}
419
420define_theme! {
421 %[component]
422 pub MenuItem {
423 %[cows]
424 hover_background: str,
425 corner_radius: str,
426 %[subthemes]
427 font_theme: FontTheme,
428 }
429}
430
431define_theme! {
432 %[component]
433 pub MenuContainer {
434 %[cows]
435 background: str,
436 padding: str,
437 shadow: str,
438 border_fill: str,
439 corner_radius: str,
440 }
441}
442
443define_theme! {
444 %[component]
445 pub SnackBar {
446 %[cows]
447 background: str,
448 color: str,
449 }
450}
451
452define_theme! {
453 %[component]
454 pub Radio {
455 %[cows]
456 unselected_fill: str,
457 selected_fill: str,
458 border_fill: str,
459 }
460}
461
462define_theme! {
463 %[component]
464 pub Checkbox {
465 %[cows]
466 unselected_fill: str,
467 selected_fill: str,
468 selected_icon_fill: str,
469 border_fill: str,
470 }
471}
472
473define_theme! {
474 %[component]
475 pub Popup {
476 %[cows]
477 background: str,
478 color: str,
479 cross_fill: str,
480 width: str,
481 height: str,
482 }
483}
484
485define_theme! {
486 %[component]
487 pub Tab {
488 %[cows]
489 background: str,
490 hover_background: str,
491 border_fill: str,
492 focus_border_fill: str,
493 width: str,
494 height: str,
495 padding: str,
496 %[subthemes]
497 font_theme: FontTheme,
498 }
499}
500
501define_theme! {
502 %[component]
503 pub BottomTab {
504 %[cows]
505 background: str,
506 hover_background: str,
507 width: str,
508 height: str,
509 padding: str,
510 %[subthemes]
511 font_theme: FontTheme,
512 }
513}
514
515define_theme! {
516 %[component]
517 pub ResizableHandle {
518 %[cows]
519 background: str,
520 hover_background: str,
521 }
522}
523
524#[derive(Clone, Debug, PartialEq, Eq)]
525pub struct ColorsSheet {
526 pub primary: Cow<'static, str>,
527 pub focused_primary_border: Cow<'static, str>,
528 pub secondary: Cow<'static, str>,
529 pub tertiary: Cow<'static, str>,
530 pub surface: Cow<'static, str>,
531 pub secondary_surface: Cow<'static, str>,
532 pub neutral_surface: Cow<'static, str>,
533 pub focused_surface: Cow<'static, str>,
534 pub opposite_surface: Cow<'static, str>,
535 pub secondary_opposite_surface: Cow<'static, str>,
536 pub tertiary_opposite_surface: Cow<'static, str>,
537 pub background: Cow<'static, str>,
538 pub focused_border: Cow<'static, str>,
539 pub solid: Cow<'static, str>,
540 pub color: Cow<'static, str>,
541 pub primary_color: Cow<'static, str>,
542 pub placeholder_color: Cow<'static, str>,
543 pub highlight_color: Cow<'static, str>,
544}
545
546impl ColorsSheet {
547 pub fn resolve(&self, val: Cow<'static, str>) -> Cow<'static, str> {
548 if val.starts_with("key") {
549 let key_val = val.replace("key(", "").replace(")", "");
550 match key_val.as_str() {
551 "primary" => self.primary.clone(),
552 "focused_primary_border" => self.focused_primary_border.clone(),
553 "secondary" => self.secondary.clone(),
554 "tertiary" => self.tertiary.clone(),
555 "surface" => self.surface.clone(),
556 "secondary_surface" => self.secondary_surface.clone(),
557 "neutral_surface" => self.neutral_surface.clone(),
558 "focused_surface" => self.focused_surface.clone(),
559 "opposite_surface" => self.opposite_surface.clone(),
560 "secondary_opposite_surface" => self.secondary_opposite_surface.clone(),
561 "tertiary_opposite_surface" => self.tertiary_opposite_surface.clone(),
562 "background" => self.background.clone(),
563 "focused_border" => self.focused_border.clone(),
564 "solid" => self.solid.clone(),
565 "color" => self.color.clone(),
566 "primary_color" => self.primary_color.clone(),
567 "placeholder_color" => self.placeholder_color.clone(),
568 "highlight_color" => self.highlight_color.clone(),
569 _ => self.primary.clone(),
570 }
571 } else {
572 val
573 }
574 }
575}
576
577#[derive(Clone, Debug, PartialEq, Eq)]
578pub struct Theme {
579 pub name: &'static str,
580 pub colors: ColorsSheet,
581 pub body: BodyTheme,
582 pub button: ButtonTheme,
583 pub filled_button: ButtonTheme,
584 pub outline_button: ButtonTheme,
585 pub switch: SwitchTheme,
586 pub scroll_bar: ScrollBarTheme,
587 pub slider: SliderTheme,
588 pub tooltip: TooltipTheme,
589 pub dropdown: DropdownTheme,
590 pub dropdown_item: DropdownItemTheme,
591 pub accordion: AccordionTheme,
592 pub loader: LoaderTheme,
593 pub link: LinkTheme,
594 pub progress_bar: ProgressBarTheme,
595 pub table: TableTheme,
596 pub input: InputTheme,
597 pub graph: GraphTheme,
598 pub icon: IconTheme,
599 pub sidebar: SidebarTheme,
600 pub sidebar_item: SidebarItemTheme,
601 pub tile: TileTheme,
602 pub radio: RadioTheme,
603 pub checkbox: CheckboxTheme,
604 pub menu_item: MenuItemTheme,
605 pub menu_container: MenuContainerTheme,
606 pub snackbar: SnackBarTheme,
607 pub popup: PopupTheme,
608 pub tab: TabTheme,
609 pub bottom_tab: BottomTabTheme,
610 pub resizable_handle: ResizableHandleTheme,
611}
612
613impl Default for Theme {
614 fn default() -> Self {
615 LIGHT_THEME
616 }
617}