iced_style/
theme.rs

1//! Use the built-in theme and styles.
2pub mod palette;
3
4pub use palette::Palette;
5
6use crate::application;
7use crate::button;
8use crate::checkbox;
9use crate::container;
10use crate::core::widget::text;
11use crate::menu;
12use crate::pane_grid;
13use crate::pick_list;
14use crate::progress_bar;
15use crate::qr_code;
16use crate::radio;
17use crate::rule;
18use crate::scrollable;
19use crate::slider;
20use crate::svg;
21use crate::text_editor;
22use crate::text_input;
23use crate::toggler;
24
25use crate::core::{Background, Border, Color, Shadow, Vector};
26
27use std::fmt;
28use std::rc::Rc;
29use std::sync::Arc;
30
31/// A built-in theme.
32#[derive(Debug, Clone, PartialEq, Default)]
33pub enum Theme {
34    /// The built-in light variant.
35    #[default]
36    Light,
37    /// The built-in dark variant.
38    Dark,
39    /// The built-in Dracula variant.
40    Dracula,
41    /// The built-in Nord variant.
42    Nord,
43    /// The built-in Solarized Light variant.
44    SolarizedLight,
45    /// The built-in Solarized Dark variant.
46    SolarizedDark,
47    /// The built-in Gruvbox Light variant.
48    GruvboxLight,
49    /// The built-in Gruvbox Dark variant.
50    GruvboxDark,
51    /// The built-in Catppuccin Latte variant.
52    CatppuccinLatte,
53    /// The built-in Catppuccin Frappé variant.
54    CatppuccinFrappe,
55    /// The built-in Catppuccin Macchiato variant.
56    CatppuccinMacchiato,
57    /// The built-in Catppuccin Mocha variant.
58    CatppuccinMocha,
59    /// The built-in Tokyo Night variant.
60    TokyoNight,
61    /// The built-in Tokyo Night Storm variant.
62    TokyoNightStorm,
63    /// The built-in Tokyo Night Light variant.
64    TokyoNightLight,
65    /// The built-in Kanagawa Wave variant.
66    KanagawaWave,
67    /// The built-in Kanagawa Dragon variant.
68    KanagawaDragon,
69    /// The built-in Kanagawa Lotus variant.
70    KanagawaLotus,
71    /// The built-in Moonfly variant.
72    Moonfly,
73    /// The built-in Nightfly variant.
74    Nightfly,
75    /// The built-in Oxocarbon variant.
76    Oxocarbon,
77    /// A [`Theme`] that uses a [`Custom`] palette.
78    Custom(Arc<Custom>),
79}
80
81impl Theme {
82    /// A list with all the defined themes.
83    pub const ALL: &'static [Self] = &[
84        Self::Light,
85        Self::Dark,
86        Self::Dracula,
87        Self::Nord,
88        Self::SolarizedLight,
89        Self::SolarizedDark,
90        Self::GruvboxLight,
91        Self::GruvboxDark,
92        Self::CatppuccinLatte,
93        Self::CatppuccinFrappe,
94        Self::CatppuccinMacchiato,
95        Self::CatppuccinMocha,
96        Self::TokyoNight,
97        Self::TokyoNightStorm,
98        Self::TokyoNightLight,
99        Self::KanagawaWave,
100        Self::KanagawaDragon,
101        Self::KanagawaLotus,
102        Self::Moonfly,
103        Self::Nightfly,
104        Self::Oxocarbon,
105    ];
106
107    /// Creates a new custom [`Theme`] from the given [`Palette`].
108    pub fn custom(name: String, palette: Palette) -> Self {
109        Self::custom_with_fn(name, palette, palette::Extended::generate)
110    }
111
112    /// Creates a new custom [`Theme`] from the given [`Palette`], with
113    /// a custom generator of a [`palette::Extended`].
114    pub fn custom_with_fn(
115        name: String,
116        palette: Palette,
117        generate: impl FnOnce(Palette) -> palette::Extended,
118    ) -> Self {
119        Self::Custom(Arc::new(Custom::with_fn(name, palette, generate)))
120    }
121
122    /// Returns the [`Palette`] of the [`Theme`].
123    pub fn palette(&self) -> Palette {
124        match self {
125            Self::Light => Palette::LIGHT,
126            Self::Dark => Palette::DARK,
127            Self::Dracula => Palette::DRACULA,
128            Self::Nord => Palette::NORD,
129            Self::SolarizedLight => Palette::SOLARIZED_LIGHT,
130            Self::SolarizedDark => Palette::SOLARIZED_DARK,
131            Self::GruvboxLight => Palette::GRUVBOX_LIGHT,
132            Self::GruvboxDark => Palette::GRUVBOX_DARK,
133            Self::CatppuccinLatte => Palette::CATPPUCCIN_LATTE,
134            Self::CatppuccinFrappe => Palette::CATPPUCCIN_FRAPPE,
135            Self::CatppuccinMacchiato => Palette::CATPPUCCIN_MACCHIATO,
136            Self::CatppuccinMocha => Palette::CATPPUCCIN_MOCHA,
137            Self::TokyoNight => Palette::TOKYO_NIGHT,
138            Self::TokyoNightStorm => Palette::TOKYO_NIGHT_STORM,
139            Self::TokyoNightLight => Palette::TOKYO_NIGHT_LIGHT,
140            Self::KanagawaWave => Palette::KANAGAWA_WAVE,
141            Self::KanagawaDragon => Palette::KANAGAWA_DRAGON,
142            Self::KanagawaLotus => Palette::KANAGAWA_LOTUS,
143            Self::Moonfly => Palette::MOONFLY,
144            Self::Nightfly => Palette::NIGHTFLY,
145            Self::Oxocarbon => Palette::OXOCARBON,
146            Self::Custom(custom) => custom.palette,
147        }
148    }
149
150    /// Returns the [`palette::Extended`] of the [`Theme`].
151    pub fn extended_palette(&self) -> &palette::Extended {
152        match self {
153            Self::Light => &palette::EXTENDED_LIGHT,
154            Self::Dark => &palette::EXTENDED_DARK,
155            Self::Dracula => &palette::EXTENDED_DRACULA,
156            Self::Nord => &palette::EXTENDED_NORD,
157            Self::SolarizedLight => &palette::EXTENDED_SOLARIZED_LIGHT,
158            Self::SolarizedDark => &palette::EXTENDED_SOLARIZED_DARK,
159            Self::GruvboxLight => &palette::EXTENDED_GRUVBOX_LIGHT,
160            Self::GruvboxDark => &palette::EXTENDED_GRUVBOX_DARK,
161            Self::CatppuccinLatte => &palette::EXTENDED_CATPPUCCIN_LATTE,
162            Self::CatppuccinFrappe => &palette::EXTENDED_CATPPUCCIN_FRAPPE,
163            Self::CatppuccinMacchiato => {
164                &palette::EXTENDED_CATPPUCCIN_MACCHIATO
165            }
166            Self::CatppuccinMocha => &palette::EXTENDED_CATPPUCCIN_MOCHA,
167            Self::TokyoNight => &palette::EXTENDED_TOKYO_NIGHT,
168            Self::TokyoNightStorm => &palette::EXTENDED_TOKYO_NIGHT_STORM,
169            Self::TokyoNightLight => &palette::EXTENDED_TOKYO_NIGHT_LIGHT,
170            Self::KanagawaWave => &palette::EXTENDED_KANAGAWA_WAVE,
171            Self::KanagawaDragon => &palette::EXTENDED_KANAGAWA_DRAGON,
172            Self::KanagawaLotus => &palette::EXTENDED_KANAGAWA_LOTUS,
173            Self::Moonfly => &palette::EXTENDED_MOONFLY,
174            Self::Nightfly => &palette::EXTENDED_NIGHTFLY,
175            Self::Oxocarbon => &palette::EXTENDED_OXOCARBON,
176            Self::Custom(custom) => &custom.extended,
177        }
178    }
179}
180
181impl fmt::Display for Theme {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self {
184            Self::Light => write!(f, "Light"),
185            Self::Dark => write!(f, "Dark"),
186            Self::Dracula => write!(f, "Dracula"),
187            Self::Nord => write!(f, "Nord"),
188            Self::SolarizedLight => write!(f, "Solarized Light"),
189            Self::SolarizedDark => write!(f, "Solarized Dark"),
190            Self::GruvboxLight => write!(f, "Gruvbox Light"),
191            Self::GruvboxDark => write!(f, "Gruvbox Dark"),
192            Self::CatppuccinLatte => write!(f, "Catppuccin Latte"),
193            Self::CatppuccinFrappe => write!(f, "Catppuccin Frappé"),
194            Self::CatppuccinMacchiato => write!(f, "Catppuccin Macchiato"),
195            Self::CatppuccinMocha => write!(f, "Catppuccin Mocha"),
196            Self::TokyoNight => write!(f, "Tokyo Night"),
197            Self::TokyoNightStorm => write!(f, "Tokyo Night Storm"),
198            Self::TokyoNightLight => write!(f, "Tokyo Night Light"),
199            Self::KanagawaWave => write!(f, "Kanagawa Wave"),
200            Self::KanagawaDragon => write!(f, "Kanagawa Dragon"),
201            Self::KanagawaLotus => write!(f, "Kanagawa Lotus"),
202            Self::Moonfly => write!(f, "Moonfly"),
203            Self::Nightfly => write!(f, "Nightfly"),
204            Self::Oxocarbon => write!(f, "Oxocarbon"),
205            Self::Custom(custom) => custom.fmt(f),
206        }
207    }
208}
209
210/// A [`Theme`] with a customized [`Palette`].
211#[derive(Debug, Clone, PartialEq)]
212pub struct Custom {
213    name: String,
214    palette: Palette,
215    extended: palette::Extended,
216}
217
218impl Custom {
219    /// Creates a [`Custom`] theme from the given [`Palette`].
220    pub fn new(name: String, palette: Palette) -> Self {
221        Self::with_fn(name, palette, palette::Extended::generate)
222    }
223
224    /// Creates a [`Custom`] theme from the given [`Palette`] with
225    /// a custom generator of a [`palette::Extended`].
226    pub fn with_fn(
227        name: String,
228        palette: Palette,
229        generate: impl FnOnce(Palette) -> palette::Extended,
230    ) -> Self {
231        Self {
232            name,
233            palette,
234            extended: generate(palette),
235        }
236    }
237}
238
239impl fmt::Display for Custom {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        write!(f, "{}", self.name)
242    }
243}
244
245/// The style of an application.
246#[derive(Default)]
247pub enum Application {
248    /// The default style.
249    #[default]
250    Default,
251    /// A custom style.
252    Custom(Box<dyn application::StyleSheet<Style = Theme>>),
253}
254
255impl Application {
256    /// Creates a custom [`Application`] style.
257    pub fn custom(
258        custom: impl application::StyleSheet<Style = Theme> + 'static,
259    ) -> Self {
260        Self::Custom(Box::new(custom))
261    }
262}
263
264impl application::StyleSheet for Theme {
265    type Style = Application;
266
267    fn appearance(&self, style: &Self::Style) -> application::Appearance {
268        let palette = self.extended_palette();
269
270        match style {
271            Application::Default => application::Appearance {
272                background_color: palette.background.base.color,
273                text_color: palette.background.base.text,
274            },
275            Application::Custom(custom) => custom.appearance(self),
276        }
277    }
278}
279
280impl<T: Fn(&Theme) -> application::Appearance> application::StyleSheet for T {
281    type Style = Theme;
282
283    fn appearance(&self, style: &Self::Style) -> application::Appearance {
284        (self)(style)
285    }
286}
287
288/// The style of a button.
289#[derive(Default)]
290pub enum Button {
291    /// The primary style.
292    #[default]
293    Primary,
294    /// The secondary style.
295    Secondary,
296    /// The positive style.
297    Positive,
298    /// The destructive style.
299    Destructive,
300    /// The text style.
301    ///
302    /// Useful for links!
303    Text,
304    /// A custom style.
305    Custom(Box<dyn button::StyleSheet<Style = Theme>>),
306}
307
308impl Button {
309    /// Creates a custom [`Button`] style variant.
310    pub fn custom(
311        style_sheet: impl button::StyleSheet<Style = Theme> + 'static,
312    ) -> Self {
313        Self::Custom(Box::new(style_sheet))
314    }
315}
316
317impl button::StyleSheet for Theme {
318    type Style = Button;
319
320    fn active(&self, style: &Self::Style) -> button::Appearance {
321        let palette = self.extended_palette();
322
323        let appearance = button::Appearance {
324            border: Border::with_radius(2),
325            ..button::Appearance::default()
326        };
327
328        let from_pair = |pair: palette::Pair| button::Appearance {
329            background: Some(pair.color.into()),
330            text_color: pair.text,
331            ..appearance
332        };
333
334        match style {
335            Button::Primary => from_pair(palette.primary.strong),
336            Button::Secondary => from_pair(palette.secondary.base),
337            Button::Positive => from_pair(palette.success.base),
338            Button::Destructive => from_pair(palette.danger.base),
339            Button::Text => button::Appearance {
340                text_color: palette.background.base.text,
341                ..appearance
342            },
343            Button::Custom(custom) => custom.active(self),
344        }
345    }
346
347    fn hovered(&self, style: &Self::Style) -> button::Appearance {
348        let palette = self.extended_palette();
349
350        if let Button::Custom(custom) = style {
351            return custom.hovered(self);
352        }
353
354        let active = self.active(style);
355
356        let background = match style {
357            Button::Primary => Some(palette.primary.base.color),
358            Button::Secondary => Some(palette.background.strong.color),
359            Button::Positive => Some(palette.success.strong.color),
360            Button::Destructive => Some(palette.danger.strong.color),
361            Button::Text | Button::Custom(_) => None,
362        };
363
364        button::Appearance {
365            background: background.map(Background::from),
366            ..active
367        }
368    }
369
370    fn pressed(&self, style: &Self::Style) -> button::Appearance {
371        if let Button::Custom(custom) = style {
372            return custom.pressed(self);
373        }
374
375        button::Appearance {
376            shadow_offset: Vector::default(),
377            ..self.active(style)
378        }
379    }
380
381    fn disabled(&self, style: &Self::Style) -> button::Appearance {
382        if let Button::Custom(custom) = style {
383            return custom.disabled(self);
384        }
385
386        let active = self.active(style);
387
388        button::Appearance {
389            shadow_offset: Vector::default(),
390            background: active.background.map(|background| match background {
391                Background::Color(color) => Background::Color(Color {
392                    a: color.a * 0.5,
393                    ..color
394                }),
395                Background::Gradient(gradient) => {
396                    Background::Gradient(gradient.mul_alpha(0.5))
397                }
398            }),
399            text_color: Color {
400                a: active.text_color.a * 0.5,
401                ..active.text_color
402            },
403            ..active
404        }
405    }
406}
407
408/// The style of a checkbox.
409#[derive(Default)]
410pub enum Checkbox {
411    /// The primary style.
412    #[default]
413    Primary,
414    /// The secondary style.
415    Secondary,
416    /// The success style.
417    Success,
418    /// The danger style.
419    Danger,
420    /// A custom style.
421    Custom(Box<dyn checkbox::StyleSheet<Style = Theme>>),
422}
423
424impl checkbox::StyleSheet for Theme {
425    type Style = Checkbox;
426
427    fn active(
428        &self,
429        style: &Self::Style,
430        is_checked: bool,
431    ) -> checkbox::Appearance {
432        let palette = self.extended_palette();
433
434        match style {
435            Checkbox::Primary => checkbox_appearance(
436                palette.primary.strong.text,
437                palette.background.base,
438                palette.primary.strong,
439                is_checked,
440            ),
441            Checkbox::Secondary => checkbox_appearance(
442                palette.background.base.text,
443                palette.background.base,
444                palette.background.strong,
445                is_checked,
446            ),
447            Checkbox::Success => checkbox_appearance(
448                palette.success.base.text,
449                palette.background.base,
450                palette.success.base,
451                is_checked,
452            ),
453            Checkbox::Danger => checkbox_appearance(
454                palette.danger.base.text,
455                palette.background.base,
456                palette.danger.base,
457                is_checked,
458            ),
459            Checkbox::Custom(custom) => custom.active(self, is_checked),
460        }
461    }
462
463    fn hovered(
464        &self,
465        style: &Self::Style,
466        is_checked: bool,
467    ) -> checkbox::Appearance {
468        let palette = self.extended_palette();
469
470        match style {
471            Checkbox::Primary => checkbox_appearance(
472                palette.primary.strong.text,
473                palette.background.weak,
474                palette.primary.base,
475                is_checked,
476            ),
477            Checkbox::Secondary => checkbox_appearance(
478                palette.background.base.text,
479                palette.background.weak,
480                palette.background.strong,
481                is_checked,
482            ),
483            Checkbox::Success => checkbox_appearance(
484                palette.success.base.text,
485                palette.background.weak,
486                palette.success.base,
487                is_checked,
488            ),
489            Checkbox::Danger => checkbox_appearance(
490                palette.danger.base.text,
491                palette.background.weak,
492                palette.danger.base,
493                is_checked,
494            ),
495            Checkbox::Custom(custom) => custom.hovered(self, is_checked),
496        }
497    }
498
499    fn disabled(
500        &self,
501        style: &Self::Style,
502        is_checked: bool,
503    ) -> checkbox::Appearance {
504        let palette = self.extended_palette();
505
506        match style {
507            Checkbox::Primary => checkbox_appearance(
508                palette.primary.strong.text,
509                palette.background.weak,
510                palette.background.strong,
511                is_checked,
512            ),
513            Checkbox::Secondary => checkbox_appearance(
514                palette.background.strong.color,
515                palette.background.weak,
516                palette.background.weak,
517                is_checked,
518            ),
519            Checkbox::Success => checkbox_appearance(
520                palette.success.base.text,
521                palette.background.weak,
522                palette.success.weak,
523                is_checked,
524            ),
525            Checkbox::Danger => checkbox_appearance(
526                palette.danger.base.text,
527                palette.background.weak,
528                palette.danger.weak,
529                is_checked,
530            ),
531            Checkbox::Custom(custom) => custom.active(self, is_checked),
532        }
533    }
534}
535
536fn checkbox_appearance(
537    icon_color: Color,
538    base: palette::Pair,
539    accent: palette::Pair,
540    is_checked: bool,
541) -> checkbox::Appearance {
542    checkbox::Appearance {
543        background: Background::Color(if is_checked {
544            accent.color
545        } else {
546            base.color
547        }),
548        icon_color,
549        border: Border {
550            radius: 2.0.into(),
551            width: 1.0,
552            color: accent.color,
553        },
554        text_color: None,
555    }
556}
557
558/// The style of a container.
559#[derive(Default)]
560pub enum Container {
561    /// No style.
562    #[default]
563    Transparent,
564    /// A simple box.
565    Box,
566    /// A custom style.
567    Custom(Box<dyn container::StyleSheet<Style = Theme>>),
568}
569
570impl From<container::Appearance> for Container {
571    fn from(appearance: container::Appearance) -> Self {
572        Self::Custom(Box::new(move |_: &_| appearance))
573    }
574}
575
576impl<T: Fn(&Theme) -> container::Appearance + 'static> From<T> for Container {
577    fn from(f: T) -> Self {
578        Self::Custom(Box::new(f))
579    }
580}
581
582impl container::StyleSheet for Theme {
583    type Style = Container;
584
585    fn appearance(&self, style: &Self::Style) -> container::Appearance {
586        match style {
587            Container::Transparent => container::Appearance::default(),
588            Container::Box => {
589                let palette = self.extended_palette();
590
591                container::Appearance {
592                    text_color: None,
593                    background: Some(palette.background.weak.color.into()),
594                    border: Border::with_radius(2),
595                    shadow: Shadow::default(),
596                }
597            }
598            Container::Custom(custom) => custom.appearance(self),
599        }
600    }
601}
602
603impl<T: Fn(&Theme) -> container::Appearance> container::StyleSheet for T {
604    type Style = Theme;
605
606    fn appearance(&self, style: &Self::Style) -> container::Appearance {
607        (self)(style)
608    }
609}
610
611/// The style of a slider.
612#[derive(Default)]
613pub enum Slider {
614    /// The default style.
615    #[default]
616    Default,
617    /// A custom style.
618    Custom(Box<dyn slider::StyleSheet<Style = Theme>>),
619}
620
621impl slider::StyleSheet for Theme {
622    type Style = Slider;
623
624    fn active(&self, style: &Self::Style) -> slider::Appearance {
625        match style {
626            Slider::Default => {
627                let palette = self.extended_palette();
628
629                let handle = slider::Handle {
630                    shape: slider::HandleShape::Rectangle {
631                        width: 8,
632                        border_radius: 4.0.into(),
633                    },
634                    color: Color::WHITE,
635                    border_color: Color::WHITE,
636                    border_width: 1.0,
637                };
638
639                slider::Appearance {
640                    rail: slider::Rail {
641                        colors: (
642                            palette.primary.base.color,
643                            palette.secondary.base.color,
644                        ),
645                        width: 4.0,
646                        border_radius: 2.0.into(),
647                    },
648                    handle: slider::Handle {
649                        color: palette.background.base.color,
650                        border_color: palette.primary.base.color,
651                        ..handle
652                    },
653                }
654            }
655            Slider::Custom(custom) => custom.active(self),
656        }
657    }
658
659    fn hovered(&self, style: &Self::Style) -> slider::Appearance {
660        match style {
661            Slider::Default => {
662                let active = self.active(style);
663                let palette = self.extended_palette();
664
665                slider::Appearance {
666                    handle: slider::Handle {
667                        color: palette.primary.weak.color,
668                        ..active.handle
669                    },
670                    ..active
671                }
672            }
673            Slider::Custom(custom) => custom.hovered(self),
674        }
675    }
676
677    fn dragging(&self, style: &Self::Style) -> slider::Appearance {
678        match style {
679            Slider::Default => {
680                let active = self.active(style);
681                let palette = self.extended_palette();
682
683                slider::Appearance {
684                    handle: slider::Handle {
685                        color: palette.primary.base.color,
686                        ..active.handle
687                    },
688                    ..active
689                }
690            }
691            Slider::Custom(custom) => custom.dragging(self),
692        }
693    }
694}
695
696/// The style of a menu.
697#[derive(Clone, Default)]
698pub enum Menu {
699    /// The default style.
700    #[default]
701    Default,
702    /// A custom style.
703    Custom(Rc<dyn menu::StyleSheet<Style = Theme>>),
704}
705
706impl menu::StyleSheet for Theme {
707    type Style = Menu;
708
709    fn appearance(&self, style: &Self::Style) -> menu::Appearance {
710        match style {
711            Menu::Default => {
712                let palette = self.extended_palette();
713
714                menu::Appearance {
715                    text_color: palette.background.weak.text,
716                    background: palette.background.weak.color.into(),
717                    border: Border {
718                        width: 1.0,
719                        radius: 0.0.into(),
720                        color: palette.background.strong.color,
721                    },
722                    selected_text_color: palette.primary.strong.text,
723                    selected_background: palette.primary.strong.color.into(),
724                }
725            }
726            Menu::Custom(custom) => custom.appearance(self),
727        }
728    }
729}
730
731impl From<PickList> for Menu {
732    fn from(pick_list: PickList) -> Self {
733        match pick_list {
734            PickList::Default => Self::Default,
735            PickList::Custom(_, menu) => Self::Custom(menu),
736        }
737    }
738}
739
740/// The style of a pick list.
741#[derive(Clone, Default)]
742pub enum PickList {
743    /// The default style.
744    #[default]
745    Default,
746    /// A custom style.
747    Custom(
748        Rc<dyn pick_list::StyleSheet<Style = Theme>>,
749        Rc<dyn menu::StyleSheet<Style = Theme>>,
750    ),
751}
752
753impl pick_list::StyleSheet for Theme {
754    type Style = PickList;
755
756    fn active(&self, style: &Self::Style) -> pick_list::Appearance {
757        match style {
758            PickList::Default => {
759                let palette = self.extended_palette();
760
761                pick_list::Appearance {
762                    text_color: palette.background.weak.text,
763                    background: palette.background.weak.color.into(),
764                    placeholder_color: palette.background.strong.color,
765                    handle_color: palette.background.weak.text,
766                    border: Border {
767                        radius: 2.0.into(),
768                        width: 1.0,
769                        color: palette.background.strong.color,
770                    },
771                }
772            }
773            PickList::Custom(custom, _) => custom.active(self),
774        }
775    }
776
777    fn hovered(&self, style: &Self::Style) -> pick_list::Appearance {
778        match style {
779            PickList::Default => {
780                let palette = self.extended_palette();
781
782                pick_list::Appearance {
783                    text_color: palette.background.weak.text,
784                    background: palette.background.weak.color.into(),
785                    placeholder_color: palette.background.strong.color,
786                    handle_color: palette.background.weak.text,
787                    border: Border {
788                        radius: 2.0.into(),
789                        width: 1.0,
790                        color: palette.primary.strong.color,
791                    },
792                }
793            }
794            PickList::Custom(custom, _) => custom.hovered(self),
795        }
796    }
797}
798
799/// The style of a radio button.
800#[derive(Default)]
801pub enum Radio {
802    /// The default style.
803    #[default]
804    Default,
805    /// A custom style.
806    Custom(Box<dyn radio::StyleSheet<Style = Theme>>),
807}
808
809impl radio::StyleSheet for Theme {
810    type Style = Radio;
811
812    fn active(
813        &self,
814        style: &Self::Style,
815        is_selected: bool,
816    ) -> radio::Appearance {
817        match style {
818            Radio::Default => {
819                let palette = self.extended_palette();
820
821                radio::Appearance {
822                    background: Color::TRANSPARENT.into(),
823                    dot_color: palette.primary.strong.color,
824                    border_width: 1.0,
825                    border_color: palette.primary.strong.color,
826                    text_color: None,
827                }
828            }
829            Radio::Custom(custom) => custom.active(self, is_selected),
830        }
831    }
832
833    fn hovered(
834        &self,
835        style: &Self::Style,
836        is_selected: bool,
837    ) -> radio::Appearance {
838        match style {
839            Radio::Default => {
840                let active = self.active(style, is_selected);
841                let palette = self.extended_palette();
842
843                radio::Appearance {
844                    dot_color: palette.primary.strong.color,
845                    background: palette.primary.weak.color.into(),
846                    ..active
847                }
848            }
849            Radio::Custom(custom) => custom.hovered(self, is_selected),
850        }
851    }
852}
853
854/// The style of a toggler.
855#[derive(Default)]
856pub enum Toggler {
857    /// The default style.
858    #[default]
859    Default,
860    /// A custom style.
861    Custom(Box<dyn toggler::StyleSheet<Style = Theme>>),
862}
863
864impl toggler::StyleSheet for Theme {
865    type Style = Toggler;
866
867    fn active(
868        &self,
869        style: &Self::Style,
870        is_active: bool,
871    ) -> toggler::Appearance {
872        match style {
873            Toggler::Default => {
874                let palette = self.extended_palette();
875
876                toggler::Appearance {
877                    background: if is_active {
878                        palette.primary.strong.color
879                    } else {
880                        palette.background.strong.color
881                    },
882                    background_border_width: 0.0,
883                    background_border_color: Color::TRANSPARENT,
884                    foreground: if is_active {
885                        palette.primary.strong.text
886                    } else {
887                        palette.background.base.color
888                    },
889                    foreground_border_width: 0.0,
890                    foreground_border_color: Color::TRANSPARENT,
891                }
892            }
893            Toggler::Custom(custom) => custom.active(self, is_active),
894        }
895    }
896
897    fn hovered(
898        &self,
899        style: &Self::Style,
900        is_active: bool,
901    ) -> toggler::Appearance {
902        match style {
903            Toggler::Default => {
904                let palette = self.extended_palette();
905
906                toggler::Appearance {
907                    foreground: if is_active {
908                        Color {
909                            a: 0.5,
910                            ..palette.primary.strong.text
911                        }
912                    } else {
913                        palette.background.weak.color
914                    },
915                    ..self.active(style, is_active)
916                }
917            }
918            Toggler::Custom(custom) => custom.hovered(self, is_active),
919        }
920    }
921}
922
923/// The style of a pane grid.
924#[derive(Default)]
925pub enum PaneGrid {
926    /// The default style.
927    #[default]
928    Default,
929    /// A custom style.
930    Custom(Box<dyn pane_grid::StyleSheet<Style = Theme>>),
931}
932
933impl pane_grid::StyleSheet for Theme {
934    type Style = PaneGrid;
935
936    fn hovered_region(&self, style: &Self::Style) -> pane_grid::Appearance {
937        match style {
938            PaneGrid::Default => {
939                let palette = self.extended_palette();
940
941                pane_grid::Appearance {
942                    background: Background::Color(Color {
943                        a: 0.5,
944                        ..palette.primary.base.color
945                    }),
946                    border: Border {
947                        width: 2.0,
948                        color: palette.primary.strong.color,
949                        radius: 0.0.into(),
950                    },
951                }
952            }
953            PaneGrid::Custom(custom) => custom.hovered_region(self),
954        }
955    }
956
957    fn picked_split(&self, style: &Self::Style) -> Option<pane_grid::Line> {
958        match style {
959            PaneGrid::Default => {
960                let palette = self.extended_palette();
961
962                Some(pane_grid::Line {
963                    color: palette.primary.strong.color,
964                    width: 2.0,
965                })
966            }
967            PaneGrid::Custom(custom) => custom.picked_split(self),
968        }
969    }
970
971    fn hovered_split(&self, style: &Self::Style) -> Option<pane_grid::Line> {
972        match style {
973            PaneGrid::Default => {
974                let palette = self.extended_palette();
975
976                Some(pane_grid::Line {
977                    color: palette.primary.base.color,
978                    width: 2.0,
979                })
980            }
981            PaneGrid::Custom(custom) => custom.hovered_split(self),
982        }
983    }
984}
985
986/// The style of a progress bar.
987#[derive(Default)]
988pub enum ProgressBar {
989    /// The primary style.
990    #[default]
991    Primary,
992    /// The success style.
993    Success,
994    /// The danger style.
995    Danger,
996    /// A custom style.
997    Custom(Box<dyn progress_bar::StyleSheet<Style = Theme>>),
998}
999
1000impl<T: Fn(&Theme) -> progress_bar::Appearance + 'static> From<T>
1001    for ProgressBar
1002{
1003    fn from(f: T) -> Self {
1004        Self::Custom(Box::new(f))
1005    }
1006}
1007
1008impl progress_bar::StyleSheet for Theme {
1009    type Style = ProgressBar;
1010
1011    fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
1012        if let ProgressBar::Custom(custom) = style {
1013            return custom.appearance(self);
1014        }
1015
1016        let palette = self.extended_palette();
1017
1018        let from_palette = |bar: Color| progress_bar::Appearance {
1019            background: palette.background.strong.color.into(),
1020            bar: bar.into(),
1021            border_radius: 2.0.into(),
1022        };
1023
1024        match style {
1025            ProgressBar::Primary => from_palette(palette.primary.base.color),
1026            ProgressBar::Success => from_palette(palette.success.base.color),
1027            ProgressBar::Danger => from_palette(palette.danger.base.color),
1028            ProgressBar::Custom(custom) => custom.appearance(self),
1029        }
1030    }
1031}
1032
1033impl<T: Fn(&Theme) -> progress_bar::Appearance> progress_bar::StyleSheet for T {
1034    type Style = Theme;
1035
1036    fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
1037        (self)(style)
1038    }
1039}
1040
1041/// The style of a QR Code.
1042#[derive(Default)]
1043pub enum QRCode {
1044    /// The default style.
1045    #[default]
1046    Default,
1047    /// A custom style.
1048    Custom(Box<dyn qr_code::StyleSheet<Style = Theme>>),
1049}
1050
1051impl<T: Fn(&Theme) -> qr_code::Appearance + 'static> From<T> for QRCode {
1052    fn from(f: T) -> Self {
1053        Self::Custom(Box::new(f))
1054    }
1055}
1056
1057impl qr_code::StyleSheet for Theme {
1058    type Style = QRCode;
1059
1060    fn appearance(&self, style: &Self::Style) -> qr_code::Appearance {
1061        let palette = self.palette();
1062
1063        match style {
1064            QRCode::Default => qr_code::Appearance {
1065                cell: palette.text,
1066                background: palette.background,
1067            },
1068            QRCode::Custom(custom) => custom.appearance(self),
1069        }
1070    }
1071}
1072
1073impl<T: Fn(&Theme) -> qr_code::Appearance> qr_code::StyleSheet for T {
1074    type Style = Theme;
1075
1076    fn appearance(&self, style: &Self::Style) -> qr_code::Appearance {
1077        (self)(style)
1078    }
1079}
1080
1081/// The style of a rule.
1082#[derive(Default)]
1083pub enum Rule {
1084    /// The default style.
1085    #[default]
1086    Default,
1087    /// A custom style.
1088    Custom(Box<dyn rule::StyleSheet<Style = Theme>>),
1089}
1090
1091impl<T: Fn(&Theme) -> rule::Appearance + 'static> From<T> for Rule {
1092    fn from(f: T) -> Self {
1093        Self::Custom(Box::new(f))
1094    }
1095}
1096
1097impl rule::StyleSheet for Theme {
1098    type Style = Rule;
1099
1100    fn appearance(&self, style: &Self::Style) -> rule::Appearance {
1101        let palette = self.extended_palette();
1102
1103        match style {
1104            Rule::Default => rule::Appearance {
1105                color: palette.background.strong.color,
1106                width: 1,
1107                radius: 0.0.into(),
1108                fill_mode: rule::FillMode::Full,
1109            },
1110            Rule::Custom(custom) => custom.appearance(self),
1111        }
1112    }
1113}
1114
1115impl<T: Fn(&Theme) -> rule::Appearance> rule::StyleSheet for T {
1116    type Style = Theme;
1117
1118    fn appearance(&self, style: &Self::Style) -> rule::Appearance {
1119        (self)(style)
1120    }
1121}
1122
1123/**
1124 * Svg
1125 */
1126#[derive(Default)]
1127pub enum Svg {
1128    /// No filtering to the rendered SVG.
1129    #[default]
1130    Default,
1131    /// A custom style.
1132    Custom(Box<dyn svg::StyleSheet<Style = Theme>>),
1133}
1134
1135impl Svg {
1136    /// Creates a custom [`Svg`] style.
1137    pub fn custom_fn(f: fn(&Theme) -> svg::Appearance) -> Self {
1138        Self::Custom(Box::new(f))
1139    }
1140}
1141
1142impl svg::StyleSheet for Theme {
1143    type Style = Svg;
1144
1145    fn appearance(&self, style: &Self::Style) -> svg::Appearance {
1146        match style {
1147            Svg::Default => svg::Appearance::default(),
1148            Svg::Custom(custom) => custom.appearance(self),
1149        }
1150    }
1151
1152    fn hovered(&self, style: &Self::Style) -> svg::Appearance {
1153        self.appearance(style)
1154    }
1155}
1156
1157impl svg::StyleSheet for fn(&Theme) -> svg::Appearance {
1158    type Style = Theme;
1159
1160    fn appearance(&self, style: &Self::Style) -> svg::Appearance {
1161        (self)(style)
1162    }
1163
1164    fn hovered(&self, style: &Self::Style) -> svg::Appearance {
1165        self.appearance(style)
1166    }
1167}
1168
1169/// The style of a scrollable.
1170#[derive(Default)]
1171pub enum Scrollable {
1172    /// The default style.
1173    #[default]
1174    Default,
1175    /// A custom style.
1176    Custom(Box<dyn scrollable::StyleSheet<Style = Theme>>),
1177}
1178
1179impl Scrollable {
1180    /// Creates a custom [`Scrollable`] theme.
1181    pub fn custom<T: scrollable::StyleSheet<Style = Theme> + 'static>(
1182        style: T,
1183    ) -> Self {
1184        Self::Custom(Box::new(style))
1185    }
1186}
1187
1188impl scrollable::StyleSheet for Theme {
1189    type Style = Scrollable;
1190
1191    fn active(&self, style: &Self::Style) -> scrollable::Appearance {
1192        match style {
1193            Scrollable::Default => {
1194                let palette = self.extended_palette();
1195
1196                scrollable::Appearance {
1197                    container: container::Appearance::default(),
1198                    scrollbar: scrollable::Scrollbar {
1199                        background: Some(palette.background.weak.color.into()),
1200                        border: Border::with_radius(2),
1201                        scroller: scrollable::Scroller {
1202                            color: palette.background.strong.color,
1203                            border: Border::with_radius(2),
1204                        },
1205                    },
1206                    gap: None,
1207                }
1208            }
1209            Scrollable::Custom(custom) => custom.active(self),
1210        }
1211    }
1212
1213    fn hovered(
1214        &self,
1215        style: &Self::Style,
1216        is_mouse_over_scrollbar: bool,
1217    ) -> scrollable::Appearance {
1218        match style {
1219            Scrollable::Default => {
1220                if is_mouse_over_scrollbar {
1221                    let palette = self.extended_palette();
1222
1223                    scrollable::Appearance {
1224                        scrollbar: scrollable::Scrollbar {
1225                            background: Some(
1226                                palette.background.weak.color.into(),
1227                            ),
1228                            border: Border::with_radius(2),
1229                            scroller: scrollable::Scroller {
1230                                color: palette.primary.strong.color,
1231                                border: Border::with_radius(2),
1232                            },
1233                        },
1234                        ..self.active(style)
1235                    }
1236                } else {
1237                    self.active(style)
1238                }
1239            }
1240            Scrollable::Custom(custom) => {
1241                custom.hovered(self, is_mouse_over_scrollbar)
1242            }
1243        }
1244    }
1245
1246    fn dragging(&self, style: &Self::Style) -> scrollable::Appearance {
1247        match style {
1248            Scrollable::Default => self.hovered(style, true),
1249            Scrollable::Custom(custom) => custom.dragging(self),
1250        }
1251    }
1252}
1253
1254/// The style of text.
1255#[derive(Clone, Copy, Default)]
1256pub enum Text {
1257    /// The default style.
1258    #[default]
1259    Default,
1260    /// Colored text.
1261    Color(Color),
1262}
1263
1264impl From<Color> for Text {
1265    fn from(color: Color) -> Self {
1266        Text::Color(color)
1267    }
1268}
1269
1270impl text::StyleSheet for Theme {
1271    type Style = Text;
1272
1273    fn appearance(&self, style: Self::Style) -> text::Appearance {
1274        match style {
1275            Text::Default => text::Appearance::default(),
1276            Text::Color(c) => text::Appearance { color: Some(c) },
1277        }
1278    }
1279}
1280
1281/// The style of a text input.
1282#[derive(Default)]
1283pub enum TextInput {
1284    /// The default style.
1285    #[default]
1286    Default,
1287    /// A custom style.
1288    Custom(Box<dyn text_input::StyleSheet<Style = Theme>>),
1289}
1290
1291impl text_input::StyleSheet for Theme {
1292    type Style = TextInput;
1293
1294    fn active(&self, style: &Self::Style) -> text_input::Appearance {
1295        if let TextInput::Custom(custom) = style {
1296            return custom.active(self);
1297        }
1298
1299        let palette = self.extended_palette();
1300
1301        text_input::Appearance {
1302            background: palette.background.base.color.into(),
1303            border: Border {
1304                radius: 2.0.into(),
1305                width: 1.0,
1306                color: palette.background.strong.color,
1307            },
1308            icon_color: palette.background.weak.text,
1309        }
1310    }
1311
1312    fn hovered(&self, style: &Self::Style) -> text_input::Appearance {
1313        if let TextInput::Custom(custom) = style {
1314            return custom.hovered(self);
1315        }
1316
1317        let palette = self.extended_palette();
1318
1319        text_input::Appearance {
1320            background: palette.background.base.color.into(),
1321            border: Border {
1322                radius: 2.0.into(),
1323                width: 1.0,
1324                color: palette.background.base.text,
1325            },
1326            icon_color: palette.background.weak.text,
1327        }
1328    }
1329
1330    fn focused(&self, style: &Self::Style) -> text_input::Appearance {
1331        if let TextInput::Custom(custom) = style {
1332            return custom.focused(self);
1333        }
1334
1335        let palette = self.extended_palette();
1336
1337        text_input::Appearance {
1338            background: palette.background.base.color.into(),
1339            border: Border {
1340                radius: 2.0.into(),
1341                width: 1.0,
1342                color: palette.primary.strong.color,
1343            },
1344            icon_color: palette.background.weak.text,
1345        }
1346    }
1347
1348    fn placeholder_color(&self, style: &Self::Style) -> Color {
1349        if let TextInput::Custom(custom) = style {
1350            return custom.placeholder_color(self);
1351        }
1352
1353        let palette = self.extended_palette();
1354
1355        palette.background.strong.color
1356    }
1357
1358    fn value_color(&self, style: &Self::Style) -> Color {
1359        if let TextInput::Custom(custom) = style {
1360            return custom.value_color(self);
1361        }
1362
1363        let palette = self.extended_palette();
1364
1365        palette.background.base.text
1366    }
1367
1368    fn selection_color(&self, style: &Self::Style) -> Color {
1369        if let TextInput::Custom(custom) = style {
1370            return custom.selection_color(self);
1371        }
1372
1373        let palette = self.extended_palette();
1374
1375        palette.primary.weak.color
1376    }
1377
1378    fn disabled(&self, style: &Self::Style) -> text_input::Appearance {
1379        if let TextInput::Custom(custom) = style {
1380            return custom.disabled(self);
1381        }
1382
1383        let palette = self.extended_palette();
1384
1385        text_input::Appearance {
1386            background: palette.background.weak.color.into(),
1387            border: Border {
1388                radius: 2.0.into(),
1389                width: 1.0,
1390                color: palette.background.strong.color,
1391            },
1392            icon_color: palette.background.strong.color,
1393        }
1394    }
1395
1396    fn disabled_color(&self, style: &Self::Style) -> Color {
1397        if let TextInput::Custom(custom) = style {
1398            return custom.disabled_color(self);
1399        }
1400
1401        self.placeholder_color(style)
1402    }
1403}
1404
1405/// The style of a text input.
1406#[derive(Default)]
1407pub enum TextEditor {
1408    /// The default style.
1409    #[default]
1410    Default,
1411    /// A custom style.
1412    Custom(Box<dyn text_editor::StyleSheet<Style = Theme>>),
1413}
1414
1415impl text_editor::StyleSheet for Theme {
1416    type Style = TextEditor;
1417
1418    fn active(&self, style: &Self::Style) -> text_editor::Appearance {
1419        if let TextEditor::Custom(custom) = style {
1420            return custom.active(self);
1421        }
1422
1423        let palette = self.extended_palette();
1424
1425        text_editor::Appearance {
1426            background: palette.background.base.color.into(),
1427            border: Border {
1428                radius: 2.0.into(),
1429                width: 1.0,
1430                color: palette.background.strong.color,
1431            },
1432        }
1433    }
1434
1435    fn hovered(&self, style: &Self::Style) -> text_editor::Appearance {
1436        if let TextEditor::Custom(custom) = style {
1437            return custom.hovered(self);
1438        }
1439
1440        let palette = self.extended_palette();
1441
1442        text_editor::Appearance {
1443            background: palette.background.base.color.into(),
1444            border: Border {
1445                radius: 2.0.into(),
1446                width: 1.0,
1447                color: palette.background.base.text,
1448            },
1449        }
1450    }
1451
1452    fn focused(&self, style: &Self::Style) -> text_editor::Appearance {
1453        if let TextEditor::Custom(custom) = style {
1454            return custom.focused(self);
1455        }
1456
1457        let palette = self.extended_palette();
1458
1459        text_editor::Appearance {
1460            background: palette.background.base.color.into(),
1461            border: Border {
1462                radius: 2.0.into(),
1463                width: 1.0,
1464                color: palette.primary.strong.color,
1465            },
1466        }
1467    }
1468
1469    fn placeholder_color(&self, style: &Self::Style) -> Color {
1470        if let TextEditor::Custom(custom) = style {
1471            return custom.placeholder_color(self);
1472        }
1473
1474        let palette = self.extended_palette();
1475
1476        palette.background.strong.color
1477    }
1478
1479    fn value_color(&self, style: &Self::Style) -> Color {
1480        if let TextEditor::Custom(custom) = style {
1481            return custom.value_color(self);
1482        }
1483
1484        let palette = self.extended_palette();
1485
1486        palette.background.base.text
1487    }
1488
1489    fn selection_color(&self, style: &Self::Style) -> Color {
1490        if let TextEditor::Custom(custom) = style {
1491            return custom.selection_color(self);
1492        }
1493
1494        let palette = self.extended_palette();
1495
1496        palette.primary.weak.color
1497    }
1498
1499    fn disabled(&self, style: &Self::Style) -> text_editor::Appearance {
1500        if let TextEditor::Custom(custom) = style {
1501            return custom.disabled(self);
1502        }
1503
1504        let palette = self.extended_palette();
1505
1506        text_editor::Appearance {
1507            background: palette.background.weak.color.into(),
1508            border: Border {
1509                radius: 2.0.into(),
1510                width: 1.0,
1511                color: palette.background.strong.color,
1512            },
1513        }
1514    }
1515
1516    fn disabled_color(&self, style: &Self::Style) -> Color {
1517        if let TextEditor::Custom(custom) = style {
1518            return custom.disabled_color(self);
1519        }
1520
1521        self.placeholder_color(style)
1522    }
1523}