cosmic/theme/style/
iced.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Contains stylesheet implementations for widgets native to iced.
5
6use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme};
7use cosmic_theme::composite::over;
8use iced::{
9    overlay::menu,
10    theme::Base,
11    widget::{
12        button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container,
13        pane_grid, pick_list, progress_bar, radio, rule, scrollable,
14        slider::{self, Rail},
15        svg, toggler,
16    },
17};
18use iced_core::{Background, Border, Color, Shadow, Vector};
19use iced_widget::{pane_grid::Highlight, scrollable::AutoScroll, text_editor, text_input};
20use palette::WithAlpha;
21use std::rc::Rc;
22
23pub mod application {
24    use crate::Theme;
25    use iced_runtime::Appearance;
26
27    #[derive(Default)]
28    pub enum Application {
29        #[default]
30        Default,
31        Custom(Box<dyn Fn(&Theme) -> Appearance>),
32    }
33
34    impl Application {
35        pub fn custom<F: Fn(&Theme) -> Appearance + 'static>(f: F) -> Self {
36            Self::Custom(Box::new(f))
37        }
38    }
39
40    pub fn style(theme: &Theme) -> iced::theme::Style {
41        let cosmic = theme.cosmic();
42
43        iced::theme::Style {
44            background_color: cosmic.bg_color().into(),
45            text_color: cosmic.on_bg_color().into(),
46            icon_color: cosmic.on_bg_color().into(),
47        }
48    }
49}
50
51/// Styles for the button widget from iced-rs.
52#[derive(Default)]
53pub enum Button {
54    Deactivated,
55    Destructive,
56    Positive,
57    #[default]
58    Primary,
59    Secondary,
60    Text,
61    Link,
62    LinkActive,
63    Transparent,
64    Card,
65    Custom(Box<dyn Fn(&Theme, iced_button::Status) -> iced_button::Style>),
66}
67
68impl iced_button::Catalog for Theme {
69    type Class<'a> = Button;
70
71    fn default<'a>() -> Self::Class<'a> {
72        Button::default()
73    }
74
75    fn style(&self, class: &Self::Class<'_>, status: iced_button::Status) -> iced_button::Style {
76        if let Button::Custom(f) = class {
77            return f(self, status);
78        }
79        let cosmic = self.cosmic();
80        let corner_radii = &cosmic.corner_radii;
81        let component = class.cosmic(self);
82
83        let mut appearance = iced_button::Style {
84            border_radius: match class {
85                Button::Link => corner_radii.radius_0.into(),
86                Button::Card => corner_radii.radius_xs.into(),
87                _ => corner_radii.radius_xl.into(),
88            },
89            border: Border {
90                radius: match class {
91                    Button::Link => corner_radii.radius_0.into(),
92                    Button::Card => corner_radii.radius_xs.into(),
93                    _ => corner_radii.radius_xl.into(),
94                },
95                ..Default::default()
96            },
97            background: match class {
98                Button::Link | Button::Text => None,
99                Button::LinkActive => Some(Background::Color(component.divider.into())),
100                _ => Some(Background::Color(component.base.into())),
101            },
102            text_color: match class {
103                Button::Link | Button::LinkActive => component.base.into(),
104                _ => component.on.into(),
105            },
106            ..iced_button::Style::default()
107        };
108
109        match status {
110            iced_button::Status::Active => {}
111            iced_button::Status::Hovered => {
112                appearance.background = match class {
113                    Button::Link => None,
114                    Button::LinkActive => Some(Background::Color(component.divider.into())),
115                    _ => Some(Background::Color(component.hover.into())),
116                };
117            }
118            iced_button::Status::Pressed => {
119                appearance.background = match class {
120                    Button::Link => None,
121                    Button::LinkActive => Some(Background::Color(component.divider.into())),
122                    _ => Some(Background::Color(component.pressed.into())),
123                };
124            }
125            iced_button::Status::Disabled => {
126                // Card color is not transparent when it isn't clickable
127                if matches!(class, Button::Card) {
128                    return appearance;
129                }
130                appearance.background = appearance.background.map(|background| match background {
131                    Background::Color(color) => Background::Color(Color {
132                        a: color.a * 0.5,
133                        ..color
134                    }),
135                    Background::Gradient(gradient) => {
136                        Background::Gradient(gradient.scale_alpha(0.5))
137                    }
138                });
139                appearance.text_color = Color {
140                    a: appearance.text_color.a * 0.5,
141                    ..appearance.text_color
142                };
143            }
144        };
145        appearance
146    }
147}
148
149impl Button {
150    #[allow(clippy::trivially_copy_pass_by_ref)]
151    #[allow(clippy::match_same_arms)]
152    fn cosmic<'a>(&'a self, theme: &'a Theme) -> &'a CosmicComponent {
153        let cosmic = theme.cosmic();
154        match self {
155            Self::Primary => &cosmic.accent_button,
156            Self::Secondary => &theme.current_container().component,
157            Self::Positive => &cosmic.success_button,
158            Self::Destructive => &cosmic.destructive_button,
159            Self::Text => &cosmic.text_button,
160            Self::Link => &cosmic.link_button,
161            Self::LinkActive => &cosmic.link_button,
162            Self::Transparent => &TRANSPARENT_COMPONENT,
163            Self::Deactivated => &theme.current_container().component,
164            Self::Card => &theme.current_container().component,
165            Self::Custom { .. } => &TRANSPARENT_COMPONENT,
166        }
167    }
168}
169
170/*
171 * TODO: Checkbox
172 */
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub enum Checkbox {
175    Primary,
176    Secondary,
177    Success,
178    Danger,
179}
180
181impl Default for Checkbox {
182    fn default() -> Self {
183        Self::Primary
184    }
185}
186
187impl iced_checkbox::Catalog for Theme {
188    type Class<'a> = Checkbox;
189
190    fn default<'a>() -> Self::Class<'a> {
191        Checkbox::default()
192    }
193
194    #[allow(clippy::too_many_lines)]
195    fn style(
196        &self,
197        class: &Self::Class<'_>,
198        status: iced_checkbox::Status,
199    ) -> iced_checkbox::Style {
200        let cosmic = self.cosmic();
201
202        let corners = &cosmic.corner_radii;
203
204        let disabled = matches!(status, iced_checkbox::Status::Disabled { .. });
205        match status {
206            iced_checkbox::Status::Active { is_checked }
207            | iced_checkbox::Status::Disabled { is_checked } => {
208                let mut active = match class {
209                    Checkbox::Primary => iced_checkbox::Style {
210                        background: Background::Color(if is_checked {
211                            cosmic.accent.base.into()
212                        } else {
213                            self.current_container().small_widget.into()
214                        }),
215                        icon_color: cosmic.accent.on.into(),
216                        border: Border {
217                            radius: corners.radius_xs.into(),
218                            width: if is_checked { 0.0 } else { 1.0 },
219                            color: if is_checked {
220                                cosmic.accent.base
221                            } else {
222                                cosmic.palette.neutral_8
223                            }
224                            .into(),
225                        },
226
227                        text_color: None,
228                    },
229                    Checkbox::Secondary => iced_checkbox::Style {
230                        background: Background::Color(if is_checked {
231                            cosmic.background.component.base.into()
232                        } else {
233                            self.current_container().small_widget.into()
234                        }),
235                        icon_color: cosmic.background.on.into(),
236                        border: Border {
237                            radius: corners.radius_xs.into(),
238                            width: if is_checked { 0.0 } else { 1.0 },
239                            color: cosmic.palette.neutral_8.into(),
240                        },
241                        text_color: None,
242                    },
243                    Checkbox::Success => iced_checkbox::Style {
244                        background: Background::Color(if is_checked {
245                            cosmic.success.base.into()
246                        } else {
247                            self.current_container().small_widget.into()
248                        }),
249                        icon_color: cosmic.success.on.into(),
250                        border: Border {
251                            radius: corners.radius_xs.into(),
252                            width: if is_checked { 0.0 } else { 1.0 },
253                            color: if is_checked {
254                                cosmic.success.base
255                            } else {
256                                cosmic.palette.neutral_8
257                            }
258                            .into(),
259                        },
260                        text_color: None,
261                    },
262                    Checkbox::Danger => iced_checkbox::Style {
263                        background: Background::Color(if is_checked {
264                            cosmic.destructive.base.into()
265                        } else {
266                            self.current_container().small_widget.into()
267                        }),
268                        icon_color: cosmic.destructive.on.into(),
269                        border: Border {
270                            radius: corners.radius_xs.into(),
271                            width: if is_checked { 0.0 } else { 1.0 },
272                            color: if is_checked {
273                                cosmic.destructive.base
274                            } else {
275                                cosmic.palette.neutral_8
276                            }
277                            .into(),
278                        },
279                        text_color: None,
280                    },
281                };
282                if disabled {
283                    match &mut active.background {
284                        Background::Color(color) => {
285                            color.a /= 2.;
286                        }
287                        Background::Gradient(gradient) => {
288                            *gradient = gradient.scale_alpha(0.5);
289                        }
290                    }
291                    if let Some(c) = active.text_color.as_mut() {
292                        c.a /= 2.
293                    };
294                    active.border.color.a /= 2.;
295                }
296                active
297            }
298            iced_checkbox::Status::Hovered { is_checked } => {
299                let cur_container = self.current_container().small_widget;
300                // TODO: this should probably be done with a custom widget instead, or the theme needs more small widget variables.
301                let hovered_bg = over(cosmic.palette.neutral_0.with_alpha(0.1), cur_container);
302                match class {
303                    Checkbox::Primary => iced_checkbox::Style {
304                        background: Background::Color(if is_checked {
305                            cosmic.accent.hover_state_color().into()
306                        } else {
307                            hovered_bg.into()
308                        }),
309                        icon_color: cosmic.accent.on.into(),
310                        border: Border {
311                            radius: corners.radius_xs.into(),
312                            width: if is_checked { 0.0 } else { 1.0 },
313                            color: if is_checked {
314                                cosmic.accent.base
315                            } else {
316                                cosmic.palette.neutral_8
317                            }
318                            .into(),
319                        },
320                        text_color: None,
321                    },
322                    Checkbox::Secondary => iced_checkbox::Style {
323                        background: Background::Color(if is_checked {
324                            self.current_container().component.hover.into()
325                        } else {
326                            hovered_bg.into()
327                        }),
328                        icon_color: self.current_container().on.into(),
329                        border: Border {
330                            radius: corners.radius_xs.into(),
331                            width: if is_checked { 0.0 } else { 1.0 },
332                            color: if is_checked {
333                                self.current_container().base
334                            } else {
335                                cosmic.palette.neutral_8
336                            }
337                            .into(),
338                        },
339                        text_color: None,
340                    },
341                    Checkbox::Success => iced_checkbox::Style {
342                        background: Background::Color(if is_checked {
343                            cosmic.success.hover.into()
344                        } else {
345                            hovered_bg.into()
346                        }),
347                        icon_color: cosmic.success.on.into(),
348                        border: Border {
349                            radius: corners.radius_xs.into(),
350                            width: if is_checked { 0.0 } else { 1.0 },
351                            color: if is_checked {
352                                cosmic.success.base
353                            } else {
354                                cosmic.palette.neutral_8
355                            }
356                            .into(),
357                        },
358                        text_color: None,
359                    },
360                    Checkbox::Danger => iced_checkbox::Style {
361                        background: Background::Color(if is_checked {
362                            cosmic.destructive.hover.into()
363                        } else {
364                            hovered_bg.into()
365                        }),
366                        icon_color: cosmic.destructive.on.into(),
367                        border: Border {
368                            radius: corners.radius_xs.into(),
369                            width: if is_checked { 0.0 } else { 1.0 },
370                            color: if is_checked {
371                                cosmic.destructive.base
372                            } else {
373                                cosmic.palette.neutral_8
374                            }
375                            .into(),
376                        },
377                        text_color: None,
378                    },
379                }
380            }
381        }
382    }
383}
384
385/*
386 * TODO: Container
387 */
388#[derive(Default)]
389pub enum Container<'a> {
390    WindowBackground,
391    Background,
392    Card,
393    ContextDrawer,
394    Custom(Box<dyn Fn(&Theme) -> iced_container::Style + 'a>),
395    Dialog,
396    Dropdown,
397    HeaderBar {
398        focused: bool,
399        sharp_corners: bool,
400        transparent: bool,
401    },
402    List,
403    Primary,
404    Secondary,
405    Tooltip,
406    #[default]
407    Transparent,
408}
409
410impl<'a> Container<'a> {
411    pub fn custom<F: Fn(&Theme) -> iced_container::Style + 'a>(f: F) -> Self {
412        Self::Custom(Box::new(f))
413    }
414
415    #[must_use]
416    pub fn background(theme: &cosmic_theme::Theme) -> iced_container::Style {
417        iced_container::Style {
418            icon_color: Some(Color::from(theme.background.on)),
419            text_color: Some(Color::from(theme.background.on)),
420            background: Some(iced::Background::Color(theme.background.base.into())),
421            border: Border {
422                radius: theme.corner_radii.radius_s.into(),
423                ..Default::default()
424            },
425            shadow: Shadow::default(),
426            snap: true,
427        }
428    }
429
430    #[must_use]
431    pub fn primary(theme: &cosmic_theme::Theme) -> iced_container::Style {
432        iced_container::Style {
433            icon_color: Some(Color::from(theme.primary.on)),
434            text_color: Some(Color::from(theme.primary.on)),
435            background: Some(iced::Background::Color(theme.primary.base.into())),
436            border: Border {
437                radius: theme.corner_radii.radius_s.into(),
438                ..Default::default()
439            },
440            shadow: Shadow::default(),
441            snap: true,
442        }
443    }
444
445    #[must_use]
446    pub fn secondary(theme: &cosmic_theme::Theme) -> iced_container::Style {
447        iced_container::Style {
448            icon_color: Some(Color::from(theme.secondary.on)),
449            text_color: Some(Color::from(theme.secondary.on)),
450            background: Some(iced::Background::Color(theme.secondary.base.into())),
451            border: Border {
452                radius: theme.corner_radii.radius_s.into(),
453                ..Default::default()
454            },
455            shadow: Shadow::default(),
456            snap: true,
457        }
458    }
459}
460
461impl<'a> From<iced_container::StyleFn<'a, Theme>> for Container<'a> {
462    fn from(value: iced_container::StyleFn<'a, Theme>) -> Self {
463        Self::custom(value)
464    }
465}
466
467impl iced_container::Catalog for Theme {
468    type Class<'a> = Container<'a>;
469
470    fn default<'a>() -> Self::Class<'a> {
471        Container::default()
472    }
473
474    fn style(&self, class: &Self::Class<'_>) -> iced_container::Style {
475        let cosmic = self.cosmic();
476
477        // Ensures visually aligned radii for content and window corners
478        let window_corner_radius = cosmic.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
479
480        match class {
481            Container::Transparent => iced_container::Style::default(),
482
483            Container::Custom(f) => f(self),
484
485            Container::WindowBackground => iced_container::Style {
486                icon_color: Some(Color::from(cosmic.background.on)),
487                text_color: Some(Color::from(cosmic.background.on)),
488                background: Some(iced::Background::Color(cosmic.background.base.into())),
489                border: Border {
490                    radius: [
491                        cosmic.corner_radii.radius_0[0],
492                        cosmic.corner_radii.radius_0[1],
493                        window_corner_radius[2],
494                        window_corner_radius[3],
495                    ]
496                    .into(),
497                    ..Default::default()
498                },
499                shadow: Shadow::default(),
500                snap: true,
501            },
502
503            Container::List => {
504                let component = &self.current_container().component;
505                iced_container::Style {
506                    icon_color: Some(component.on.into()),
507                    text_color: Some(component.on.into()),
508                    background: Some(Background::Color(component.base.into())),
509                    border: iced::Border {
510                        radius: cosmic.corner_radii.radius_s.into(),
511                        ..Default::default()
512                    },
513                    shadow: Shadow::default(),
514                    snap: true,
515                }
516            }
517
518            Container::HeaderBar {
519                focused,
520                sharp_corners,
521                transparent,
522            } => {
523                let (icon_color, text_color) = if *focused {
524                    (
525                        Color::from(cosmic.accent_text_color()),
526                        Color::from(cosmic.background.on),
527                    )
528                } else {
529                    use crate::ext::ColorExt;
530                    let unfocused_color = Color::from(cosmic.background.component.on)
531                        .blend_alpha(cosmic.background.base.into(), 0.5);
532                    (unfocused_color, unfocused_color)
533                };
534
535                iced_container::Style {
536                    icon_color: Some(icon_color),
537                    text_color: Some(text_color),
538                    background: if *transparent {
539                        None
540                    } else {
541                        Some(iced::Background::Color(cosmic.background.base.into()))
542                    },
543                    border: Border {
544                        radius: [
545                            if *sharp_corners {
546                                cosmic.corner_radii.radius_0[0]
547                            } else {
548                                window_corner_radius[0]
549                            },
550                            if *sharp_corners {
551                                cosmic.corner_radii.radius_0[1]
552                            } else {
553                                window_corner_radius[1]
554                            },
555                            cosmic.corner_radii.radius_0[2],
556                            cosmic.corner_radii.radius_0[3],
557                        ]
558                        .into(),
559                        ..Default::default()
560                    },
561                    snap: true,
562                    shadow: Shadow::default(),
563                }
564            }
565
566            Container::ContextDrawer => {
567                let mut a = Container::primary(cosmic);
568
569                if cosmic.is_high_contrast {
570                    a.border.width = 1.;
571                    a.border.color = cosmic.primary.divider.into();
572                }
573                a
574            }
575
576            Container::Background => Container::background(cosmic),
577
578            Container::Primary => Container::primary(cosmic),
579
580            Container::Secondary => Container::secondary(cosmic),
581
582            Container::Dropdown => iced_container::Style {
583                icon_color: None,
584                text_color: None,
585                background: Some(iced::Background::Color(cosmic.bg_component_color().into())),
586                border: Border {
587                    color: cosmic.bg_component_divider().into(),
588                    width: 1.0,
589                    radius: cosmic.corner_radii.radius_s.into(),
590                },
591                shadow: Shadow::default(),
592                snap: true,
593            },
594
595            Container::Tooltip => iced_container::Style {
596                icon_color: None,
597                text_color: None,
598                background: Some(iced::Background::Color(cosmic.palette.neutral_2.into())),
599                border: Border {
600                    radius: cosmic.corner_radii.radius_l.into(),
601                    ..Default::default()
602                },
603                shadow: Shadow::default(),
604                snap: true,
605            },
606
607            Container::Card => {
608                let cosmic = self.cosmic();
609
610                match self.layer {
611                    cosmic_theme::Layer::Background => iced_container::Style {
612                        icon_color: Some(Color::from(cosmic.background.component.on)),
613                        text_color: Some(Color::from(cosmic.background.component.on)),
614                        background: Some(iced::Background::Color(
615                            cosmic.background.component.base.into(),
616                        )),
617                        border: Border {
618                            radius: cosmic.corner_radii.radius_s.into(),
619                            ..Default::default()
620                        },
621                        shadow: Shadow::default(),
622                        snap: true,
623                    },
624                    cosmic_theme::Layer::Primary => iced_container::Style {
625                        icon_color: Some(Color::from(cosmic.primary.component.on)),
626                        text_color: Some(Color::from(cosmic.primary.component.on)),
627                        background: Some(iced::Background::Color(
628                            cosmic.primary.component.base.into(),
629                        )),
630                        border: Border {
631                            radius: cosmic.corner_radii.radius_s.into(),
632                            ..Default::default()
633                        },
634                        shadow: Shadow::default(),
635                        snap: true,
636                    },
637                    cosmic_theme::Layer::Secondary => iced_container::Style {
638                        icon_color: Some(Color::from(cosmic.secondary.component.on)),
639                        text_color: Some(Color::from(cosmic.secondary.component.on)),
640                        background: Some(iced::Background::Color(
641                            cosmic.secondary.component.base.into(),
642                        )),
643                        border: Border {
644                            radius: cosmic.corner_radii.radius_s.into(),
645                            ..Default::default()
646                        },
647                        shadow: Shadow::default(),
648                        snap: true,
649                    },
650                }
651            }
652
653            Container::Dialog => iced_container::Style {
654                icon_color: Some(Color::from(cosmic.primary.on)),
655                text_color: Some(Color::from(cosmic.primary.on)),
656                background: Some(iced::Background::Color(cosmic.primary.base.into())),
657                border: Border {
658                    color: cosmic.primary.divider.into(),
659                    width: 1.0,
660                    radius: cosmic.corner_radii.radius_m.into(),
661                },
662                shadow: Shadow {
663                    color: cosmic.shade.into(),
664                    offset: Vector::new(0.0, 4.0),
665                    blur_radius: 16.0,
666                },
667                snap: true,
668            },
669        }
670    }
671}
672
673#[derive(Default)]
674pub enum Slider {
675    #[default]
676    Standard,
677    Custom {
678        active: Rc<dyn Fn(&Theme) -> slider::Style>,
679        hovered: Rc<dyn Fn(&Theme) -> slider::Style>,
680        dragging: Rc<dyn Fn(&Theme) -> slider::Style>,
681    },
682}
683
684/*
685 * Slider
686 */
687impl slider::Catalog for Theme {
688    type Class<'a> = Slider;
689
690    fn default<'a>() -> Self::Class<'a> {
691        Slider::default()
692    }
693
694    fn style(&self, class: &Self::Class<'_>, status: slider::Status) -> slider::Style {
695        let cosmic: &cosmic_theme::Theme = self.cosmic();
696        let hc = self.theme_type.is_high_contrast();
697        let is_dark = self.theme_type.is_dark();
698
699        let mut appearance = match class {
700            Slider::Standard =>
701            //TODO: no way to set rail thickness
702            {
703                let (active_track, inactive_track) = if hc {
704                    (
705                        cosmic.accent_text_color(),
706                        if is_dark {
707                            cosmic.palette.neutral_5
708                        } else {
709                            cosmic.palette.neutral_3
710                        },
711                    )
712                } else {
713                    (cosmic.accent.base, cosmic.palette.neutral_6)
714                };
715                slider::Style {
716                    rail: Rail {
717                        backgrounds: (
718                            Background::Color(active_track.into()),
719                            Background::Color(inactive_track.into()),
720                        ),
721                        border: Border {
722                            radius: cosmic.corner_radii.radius_xs.into(),
723                            color: if hc && !is_dark {
724                                self.current_container().component.border.into()
725                            } else {
726                                Color::TRANSPARENT
727                            },
728                            width: if hc && !is_dark { 1. } else { 0. },
729                        },
730                        width: 4.0,
731                    },
732
733                    handle: slider::Handle {
734                        shape: slider::HandleShape::Rectangle {
735                            height: 20,
736                            width: 20,
737                            border_radius: cosmic.corner_radii.radius_m.into(),
738                        },
739                        border_color: Color::TRANSPARENT,
740                        border_width: 0.0,
741                        background: Background::Color(cosmic.accent.base.into()),
742                    },
743
744                    breakpoint: slider::Breakpoint {
745                        color: cosmic.on_bg_color().into(),
746                    },
747                }
748            }
749            Slider::Custom { active, .. } => active(self),
750        };
751        match status {
752            slider::Status::Active => appearance,
753            slider::Status::Hovered => match class {
754                Slider::Standard => {
755                    appearance.handle.shape = slider::HandleShape::Rectangle {
756                        height: 26,
757                        width: 26,
758                        border_radius: cosmic.corner_radii.radius_m.into(),
759                    };
760                    appearance.handle.border_width = 3.0;
761                    appearance.handle.border_color =
762                        self.cosmic().palette.neutral_10.with_alpha(0.1).into();
763                    appearance
764                }
765                Slider::Custom { hovered, .. } => hovered(self),
766            },
767            slider::Status::Dragged => match class {
768                Slider::Standard => {
769                    let mut style = {
770                        appearance.handle.shape = slider::HandleShape::Rectangle {
771                            height: 26,
772                            width: 26,
773                            border_radius: cosmic.corner_radii.radius_m.into(),
774                        };
775                        appearance.handle.border_width = 3.0;
776                        appearance.handle.border_color =
777                            self.cosmic().palette.neutral_10.with_alpha(0.1).into();
778                        appearance
779                    };
780                    style.handle.border_color =
781                        self.cosmic().palette.neutral_10.with_alpha(0.2).into();
782                    style
783                }
784                Slider::Custom { dragging, .. } => dragging(self),
785            },
786        }
787    }
788}
789
790impl menu::Catalog for Theme {
791    type Class<'a> = ();
792
793    fn default<'a>() -> <Self as menu::Catalog>::Class<'a> {}
794
795    fn style(&self, class: &<Self as menu::Catalog>::Class<'_>) -> menu::Style {
796        let cosmic = self.cosmic();
797
798        menu::Style {
799            text_color: cosmic.on_bg_color().into(),
800            background: Background::Color(cosmic.background.base.into()),
801            border: Border {
802                radius: cosmic.corner_radii.radius_m.into(),
803                ..Default::default()
804            },
805            selected_text_color: cosmic.accent_text_color().into(),
806            selected_background: Background::Color(cosmic.background.component.hover.into()),
807            shadow: Default::default(),
808        }
809    }
810}
811
812impl pick_list::Catalog for Theme {
813    type Class<'a> = ();
814
815    fn default<'a>() -> <Self as pick_list::Catalog>::Class<'a> {}
816
817    fn style(
818        &self,
819        class: &<Self as pick_list::Catalog>::Class<'_>,
820        status: pick_list::Status,
821    ) -> pick_list::Style {
822        let cosmic = &self.cosmic();
823        let hc = cosmic.is_high_contrast;
824        let appearance = pick_list::Style {
825            text_color: cosmic.on_bg_color().into(),
826            background: Color::TRANSPARENT.into(),
827            placeholder_color: cosmic.on_bg_color().into(),
828            border: Border {
829                radius: cosmic.corner_radii.radius_m.into(),
830                width: if hc { 1. } else { 0. },
831                color: if hc {
832                    self.current_container().component.border.into()
833                } else {
834                    Color::TRANSPARENT
835                },
836            },
837            // icon_size: 0.7, // TODO: how to replace
838            handle_color: cosmic.on_bg_color().into(),
839        };
840
841        match status {
842            pick_list::Status::Active => appearance,
843            pick_list::Status::Hovered => pick_list::Style {
844                background: Background::Color(cosmic.background.base.into()),
845                ..appearance
846            },
847            pick_list::Status::Opened { is_hovered: _ } => appearance,
848        }
849    }
850}
851
852/*
853 * TODO: Radio
854 */
855impl radio::Catalog for Theme {
856    type Class<'a> = ();
857
858    fn default<'a>() -> Self::Class<'a> {}
859
860    fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style {
861        let cur_container = self.current_container();
862        let theme = self.cosmic();
863
864        match status {
865            radio::Status::Active { is_selected } => radio::Style {
866                background: if is_selected {
867                    Color::from(theme.accent.base).into()
868                } else {
869                    // TODO: this seems to be defined weirdly in FIGMA
870                    Color::from(cur_container.small_widget).into()
871                },
872                dot_color: theme.accent.on.into(),
873                border_width: 1.0,
874                border_color: if is_selected {
875                    Color::from(theme.accent.base)
876                } else {
877                    Color::from(theme.palette.neutral_8)
878                },
879                text_color: None,
880            },
881            radio::Status::Hovered { is_selected } => {
882                let bg = if is_selected {
883                    theme.accent.base
884                } else {
885                    self.current_container().small_widget
886                };
887                // TODO: this should probably be done with a custom widget instead, or the theme needs more small widget variables.
888                let hovered_bg = Color::from(over(theme.palette.neutral_0.with_alpha(0.1), bg));
889                radio::Style {
890                    background: hovered_bg.into(),
891                    dot_color: theme.accent.on.into(),
892                    border_width: 1.0,
893                    border_color: if is_selected {
894                        Color::from(theme.accent.base)
895                    } else {
896                        Color::from(theme.palette.neutral_8)
897                    },
898                    text_color: None,
899                }
900            }
901        }
902    }
903}
904
905/*
906 * Toggler
907 */
908impl toggler::Catalog for Theme {
909    type Class<'a> = ();
910
911    fn default<'a>() -> Self::Class<'a> {}
912
913    fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style {
914        let cosmic = self.cosmic();
915        const HANDLE_MARGIN: f32 = 2.0;
916        let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1);
917
918        let mut active = toggler::Style {
919            background: if matches!(status, toggler::Status::Active { is_toggled: true }) {
920                cosmic.accent.base.into()
921            } else if cosmic.is_dark {
922                cosmic.palette.neutral_6.into()
923            } else {
924                cosmic.palette.neutral_5.into()
925            },
926            foreground: cosmic.palette.neutral_2.into(),
927            border_radius: cosmic.radius_xl().into(),
928            handle_radius: cosmic
929                .radius_xl()
930                .map(|x| (x - HANDLE_MARGIN).max(0.0))
931                .into(),
932            handle_margin: HANDLE_MARGIN,
933            background_border_width: 0.0,
934            background_border_color: Color::TRANSPARENT,
935            foreground_border_width: 0.0,
936            foreground_border_color: Color::TRANSPARENT,
937            text_color: None,
938            padding_ratio: 0.0,
939        };
940        match status {
941            toggler::Status::Active { is_toggled } => active,
942            toggler::Status::Hovered { is_toggled } => {
943                let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true });
944                toggler::Style {
945                    background: if is_active {
946                        over(neutral_10, cosmic.accent_color())
947                    } else {
948                        over(
949                            neutral_10,
950                            if cosmic.is_dark {
951                                cosmic.palette.neutral_6
952                            } else {
953                                cosmic.palette.neutral_5
954                            },
955                        )
956                    }
957                    .into(),
958                    ..active
959                }
960            }
961            toggler::Status::Disabled { is_toggled } => {
962                active.background = active.background.scale_alpha(0.5);
963                active.foreground = active.foreground.scale_alpha(0.5);
964                active
965            }
966        }
967    }
968}
969
970/*
971 * TODO: Pane Grid
972 */
973impl pane_grid::Catalog for Theme {
974    type Class<'a> = ();
975
976    fn default<'a>() -> <Self as pane_grid::Catalog>::Class<'a> {}
977
978    fn style(&self, class: &<Self as pane_grid::Catalog>::Class<'_>) -> pane_grid::Style {
979        let theme = self.cosmic();
980
981        pane_grid::Style {
982            hovered_region: Highlight {
983                background: Background::Color(theme.bg_color().into()),
984                border: Border {
985                    radius: theme.corner_radii.radius_0.into(),
986                    width: 2.0,
987                    color: theme.bg_divider().into(),
988                },
989            },
990            picked_split: pane_grid::Line {
991                color: theme.accent.base.into(),
992                width: 2.0,
993            },
994            hovered_split: pane_grid::Line {
995                color: theme.accent.hover.into(),
996                width: 2.0,
997            },
998        }
999    }
1000}
1001
1002/*
1003 * TODO: Progress Bar
1004 */
1005#[derive(Default)]
1006pub enum ProgressBar {
1007    #[default]
1008    Primary,
1009    Success,
1010    Danger,
1011    Custom(Box<dyn Fn(&Theme) -> progress_bar::Style>),
1012}
1013
1014impl ProgressBar {
1015    pub fn custom<F: Fn(&Theme) -> progress_bar::Style + 'static>(f: F) -> Self {
1016        Self::Custom(Box::new(f))
1017    }
1018}
1019
1020impl progress_bar::Catalog for Theme {
1021    type Class<'a> = ProgressBar;
1022
1023    fn default<'a>() -> Self::Class<'a> {
1024        ProgressBar::default()
1025    }
1026
1027    fn style(&self, class: &Self::Class<'_>) -> progress_bar::Style {
1028        let theme = self.cosmic();
1029
1030        let (active_track, inactive_track) = if theme.is_high_contrast {
1031            (
1032                theme.accent_text_color(),
1033                if theme.is_dark {
1034                    theme.palette.neutral_6
1035                } else {
1036                    theme.palette.neutral_4
1037                },
1038            )
1039        } else {
1040            (theme.accent.base, theme.background.divider)
1041        };
1042        let border = Border {
1043            radius: theme.corner_radii.radius_xl.into(),
1044            color: if theme.is_high_contrast && !theme.is_dark {
1045                self.current_container().component.border.into()
1046            } else {
1047                Color::TRANSPARENT
1048            },
1049            width: if theme.is_high_contrast && !theme.is_dark {
1050                1.
1051            } else {
1052                0.
1053            },
1054        };
1055        match class {
1056            ProgressBar::Primary => progress_bar::Style {
1057                background: Color::from(inactive_track).into(),
1058                bar: Color::from(active_track).into(),
1059                border,
1060            },
1061            ProgressBar::Success => progress_bar::Style {
1062                background: Color::from(inactive_track).into(),
1063                bar: Color::from(theme.success.base).into(),
1064                border,
1065            },
1066            ProgressBar::Danger => progress_bar::Style {
1067                background: Color::from(inactive_track).into(),
1068                bar: Color::from(theme.destructive.base).into(),
1069                border,
1070            },
1071            ProgressBar::Custom(f) => f(self),
1072        }
1073    }
1074}
1075
1076/*
1077 * TODO: Rule
1078 */
1079#[derive(Default)]
1080pub enum Rule {
1081    #[default]
1082    Default,
1083    LightDivider,
1084    HeavyDivider,
1085    Custom(Box<dyn Fn(&Theme) -> rule::Style>),
1086}
1087
1088impl Rule {
1089    pub fn custom<F: Fn(&Theme) -> rule::Style + 'static>(f: F) -> Self {
1090        Self::Custom(Box::new(f))
1091    }
1092}
1093
1094impl rule::Catalog for Theme {
1095    type Class<'a> = Rule;
1096
1097    fn default<'a>() -> Self::Class<'a> {
1098        Rule::default()
1099    }
1100
1101    fn style(&self, class: &Self::Class<'_>) -> rule::Style {
1102        match class {
1103            Rule::Default => rule::Style {
1104                color: self.current_container().divider.into(),
1105                radius: 0.0.into(),
1106                fill_mode: rule::FillMode::Full,
1107                snap: true,
1108            },
1109            Rule::LightDivider => rule::Style {
1110                color: self.current_container().divider.into(),
1111                radius: 0.0.into(),
1112                fill_mode: rule::FillMode::Padded(8),
1113                snap: true,
1114            },
1115            Rule::HeavyDivider => rule::Style {
1116                color: self.current_container().divider.into(),
1117                radius: 2.0.into(),
1118                fill_mode: rule::FillMode::Full,
1119                snap: true,
1120            },
1121            Rule::Custom(f) => f(self),
1122        }
1123    }
1124}
1125
1126#[derive(Default, Clone, Copy)]
1127pub enum Scrollable {
1128    #[default]
1129    Permanent,
1130    Minimal,
1131}
1132
1133/*
1134 * TODO: Scrollable
1135 */
1136impl scrollable::Catalog for Theme {
1137    type Class<'a> = Scrollable;
1138
1139    fn default<'a>() -> Self::Class<'a> {
1140        Scrollable::default()
1141    }
1142
1143    fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style {
1144        match status {
1145            scrollable::Status::Active {
1146                is_horizontal_scrollbar_disabled,
1147                is_vertical_scrollbar_disabled,
1148            } => {
1149                let cosmic = self.cosmic();
1150                let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
1151                let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
1152                let mut a = scrollable::Style {
1153                    container: iced_container::transparent(self),
1154                    vertical_rail: scrollable::Rail {
1155                        border: Border {
1156                            radius: cosmic.corner_radii.radius_s.into(),
1157                            ..Default::default()
1158                        },
1159                        background: None,
1160                        scroller: scrollable::Scroller {
1161                            background: if cosmic.is_dark {
1162                                neutral_6.into()
1163                            } else {
1164                                neutral_5.into()
1165                            },
1166                            border: Border {
1167                                radius: cosmic.corner_radii.radius_s.into(),
1168                                ..Default::default()
1169                            },
1170                        },
1171                    },
1172                    horizontal_rail: scrollable::Rail {
1173                        border: Border {
1174                            radius: cosmic.corner_radii.radius_s.into(),
1175                            ..Default::default()
1176                        },
1177                        background: None,
1178                        scroller: scrollable::Scroller {
1179                            background: if cosmic.is_dark {
1180                                neutral_6.into()
1181                            } else {
1182                                neutral_5.into()
1183                            },
1184                            border: Border {
1185                                radius: cosmic.corner_radii.radius_s.into(),
1186                                ..Default::default()
1187                            },
1188                        },
1189                    },
1190                    gap: None,
1191                    // TODO: what is auto scroll?
1192                    auto_scroll: AutoScroll {
1193                        background: Color::TRANSPARENT.into(),
1194                        border: Border::default(),
1195                        shadow: Shadow::default(),
1196                        icon: Color::TRANSPARENT.into(),
1197                    },
1198                };
1199                let small_widget_container = self.current_container().small_widget.with_alpha(0.7);
1200
1201                if matches!(class, Scrollable::Permanent) {
1202                    a.horizontal_rail.background =
1203                        Some(Background::Color(small_widget_container.into()));
1204                    a.vertical_rail.background =
1205                        Some(Background::Color(small_widget_container.into()));
1206                }
1207
1208                a
1209            }
1210            // TODO handle vertical / horizontal
1211            scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => {
1212                let cosmic = self.cosmic();
1213                let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
1214                let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
1215
1216                // if is_mouse_over_scrollbar {
1217                //     let hover_overlay = cosmic.palette.neutral_0.with_alpha(0.2);
1218                //     neutral_5 = over(hover_overlay, neutral_5);
1219                // }
1220                let mut a: scrollable::Style = scrollable::Style {
1221                    container: iced_container::Style::default(),
1222                    vertical_rail: scrollable::Rail {
1223                        border: Border {
1224                            radius: cosmic.corner_radii.radius_s.into(),
1225                            ..Default::default()
1226                        },
1227                        background: None,
1228                        scroller: scrollable::Scroller {
1229                            background: if cosmic.is_dark {
1230                                neutral_6.into()
1231                            } else {
1232                                neutral_5.into()
1233                            },
1234                            border: Border {
1235                                radius: cosmic.corner_radii.radius_s.into(),
1236                                ..Default::default()
1237                            },
1238                        },
1239                    },
1240                    horizontal_rail: scrollable::Rail {
1241                        border: Border {
1242                            radius: cosmic.corner_radii.radius_s.into(),
1243                            ..Default::default()
1244                        },
1245                        background: None,
1246                        scroller: scrollable::Scroller {
1247                            background: if cosmic.is_dark {
1248                                neutral_6.into()
1249                            } else {
1250                                neutral_5.into()
1251                            },
1252                            border: Border {
1253                                radius: cosmic.corner_radii.radius_s.into(),
1254                                ..Default::default()
1255                            },
1256                        },
1257                    },
1258                    gap: None,
1259                    // TODO: what is auto scroll?
1260                    auto_scroll: AutoScroll {
1261                        background: Color::TRANSPARENT.into(),
1262                        border: Border::default(),
1263                        shadow: Shadow::default(),
1264                        icon: Color::TRANSPARENT.into(),
1265                    },
1266                };
1267
1268                if matches!(class, Scrollable::Permanent) {
1269                    let small_widget_container =
1270                        self.current_container().small_widget.with_alpha(0.7);
1271
1272                    a.horizontal_rail.background =
1273                        Some(Background::Color(small_widget_container.into()));
1274                    a.vertical_rail.background =
1275                        Some(Background::Color(small_widget_container.into()));
1276                }
1277
1278                a
1279            }
1280        }
1281    }
1282}
1283
1284#[derive(Clone, Default)]
1285pub enum Svg {
1286    /// Apply a custom appearance filter
1287    Custom(Rc<dyn Fn(&Theme) -> svg::Style>),
1288    /// No filtering is applied
1289    #[default]
1290    Default,
1291}
1292
1293impl Svg {
1294    pub fn custom<F: Fn(&Theme) -> svg::Style + 'static>(f: F) -> Self {
1295        Self::Custom(Rc::new(f))
1296    }
1297}
1298
1299impl svg::Catalog for Theme {
1300    type Class<'a> = Svg;
1301
1302    fn default<'a>() -> Self::Class<'a> {
1303        Svg::default()
1304    }
1305
1306    fn style(&self, class: &Self::Class<'_>, status: svg::Status) -> svg::Style {
1307        #[allow(clippy::match_same_arms)]
1308        match class {
1309            Svg::Default => svg::Style::default(),
1310            Svg::Custom(appearance) => appearance(self),
1311        }
1312    }
1313}
1314
1315/*
1316 * TODO: Text
1317 */
1318#[derive(Clone, Copy, Default)]
1319pub enum Text {
1320    Accent,
1321    #[default]
1322    Default,
1323    Color(Color),
1324    // TODO: Can't use dyn Fn since this must be copy
1325    Custom(fn(&Theme) -> iced_widget::text::Style),
1326}
1327
1328impl From<Color> for Text {
1329    fn from(color: Color) -> Self {
1330        Self::Color(color)
1331    }
1332}
1333
1334impl iced_widget::text::Catalog for Theme {
1335    type Class<'a> = Text;
1336
1337    fn default<'a>() -> Self::Class<'a> {
1338        Text::default()
1339    }
1340
1341    fn style(&self, class: &Self::Class<'_>) -> iced_widget::text::Style {
1342        match class {
1343            Text::Accent => iced_widget::text::Style {
1344                color: Some(self.cosmic().accent_text_color().into()),
1345            },
1346            Text::Default => iced_widget::text::Style { color: None },
1347            Text::Color(c) => iced_widget::text::Style { color: Some(*c) },
1348            Text::Custom(f) => f(self),
1349        }
1350    }
1351}
1352
1353#[derive(Copy, Clone, Default)]
1354pub enum TextInput {
1355    #[default]
1356    Default,
1357    Search,
1358}
1359
1360/*
1361 * TODO: Text Input
1362 */
1363impl text_input::Catalog for Theme {
1364    type Class<'a> = TextInput;
1365
1366    fn default<'a>() -> Self::Class<'a> {
1367        TextInput::default()
1368    }
1369
1370    fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style {
1371        let palette = self.cosmic();
1372        let bg = self.current_container().small_widget.with_alpha(0.25);
1373
1374        let neutral_9 = palette.palette.neutral_9;
1375        let value = neutral_9.into();
1376        let placeholder = neutral_9.with_alpha(0.7).into();
1377        let selection = palette.accent.base.into();
1378
1379        let mut appearance = match class {
1380            TextInput::Default => text_input::Style {
1381                background: Color::from(bg).into(),
1382                border: Border {
1383                    radius: palette.corner_radii.radius_s.into(),
1384                    width: 1.0,
1385                    color: self.current_container().component.divider.into(),
1386                },
1387                icon: self.current_container().on.into(),
1388                placeholder,
1389                value,
1390                selection,
1391            },
1392            TextInput::Search => text_input::Style {
1393                background: Color::from(bg).into(),
1394                border: Border {
1395                    radius: palette.corner_radii.radius_m.into(),
1396                    ..Default::default()
1397                },
1398                icon: self.current_container().on.into(),
1399                placeholder,
1400                value,
1401                selection,
1402            },
1403        };
1404
1405        match status {
1406            text_input::Status::Active => appearance,
1407            text_input::Status::Hovered => {
1408                let bg = self.current_container().small_widget.with_alpha(0.25);
1409
1410                match class {
1411                    TextInput::Default => text_input::Style {
1412                        background: Color::from(bg).into(),
1413                        border: Border {
1414                            radius: palette.corner_radii.radius_s.into(),
1415                            width: 1.0,
1416                            color: self.current_container().on.into(),
1417                        },
1418                        icon: self.current_container().on.into(),
1419                        placeholder,
1420                        value,
1421                        selection,
1422                    },
1423                    TextInput::Search => text_input::Style {
1424                        background: Color::from(bg).into(),
1425                        border: Border {
1426                            radius: palette.corner_radii.radius_m.into(),
1427                            ..Default::default()
1428                        },
1429                        icon: self.current_container().on.into(),
1430                        placeholder,
1431                        value,
1432                        selection,
1433                    },
1434                }
1435            }
1436            text_input::Status::Focused { is_hovered } => {
1437                let bg = self.current_container().small_widget.with_alpha(0.25);
1438
1439                match class {
1440                    TextInput::Default => text_input::Style {
1441                        background: Color::from(bg).into(),
1442                        border: Border {
1443                            radius: palette.corner_radii.radius_s.into(),
1444                            width: 1.0,
1445                            color: palette.accent.base.into(),
1446                        },
1447                        icon: self.current_container().on.into(),
1448                        placeholder,
1449                        value,
1450                        selection,
1451                    },
1452                    TextInput::Search => text_input::Style {
1453                        background: Color::from(bg).into(),
1454                        border: Border {
1455                            radius: palette.corner_radii.radius_m.into(),
1456                            ..Default::default()
1457                        },
1458                        icon: self.current_container().on.into(),
1459                        placeholder,
1460                        value,
1461                        selection,
1462                    },
1463                }
1464            }
1465            text_input::Status::Disabled => {
1466                appearance.background = match appearance.background {
1467                    Background::Color(color) => Background::Color(Color {
1468                        a: color.a * 0.5,
1469                        ..color
1470                    }),
1471                    Background::Gradient(gradient) => {
1472                        Background::Gradient(gradient.scale_alpha(0.5))
1473                    }
1474                };
1475                appearance.border.color.a /= 2.;
1476                appearance.icon.a /= 2.;
1477                appearance.placeholder.a /= 2.;
1478                appearance.value.a /= 2.;
1479                appearance
1480            }
1481        }
1482    }
1483}
1484
1485#[derive(Default)]
1486pub enum TextEditor<'a> {
1487    #[default]
1488    Default,
1489    Custom(text_editor::StyleFn<'a, Theme>),
1490}
1491
1492impl iced_widget::text_editor::Catalog for Theme {
1493    type Class<'a> = TextEditor<'a>;
1494
1495    fn default<'a>() -> Self::Class<'a> {
1496        TextEditor::default()
1497    }
1498
1499    fn style(
1500        &self,
1501        class: &Self::Class<'_>,
1502        status: iced_widget::text_editor::Status,
1503    ) -> iced_widget::text_editor::Style {
1504        if let TextEditor::Custom(style) = class {
1505            return style(self, status);
1506        }
1507
1508        let cosmic = self.cosmic();
1509
1510        let selection = cosmic.accent.base.into();
1511        let value = cosmic.palette.neutral_9.into();
1512        let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into();
1513        let icon: Color = cosmic.background.on.into();
1514        // TODO do we need to add icon color back?
1515
1516        match status {
1517            iced_widget::text_editor::Status::Active
1518            | iced_widget::text_editor::Status::Hovered
1519            | iced_widget::text_editor::Status::Disabled => iced_widget::text_editor::Style {
1520                background: iced::Color::from(cosmic.bg_color()).into(),
1521                border: Border {
1522                    radius: cosmic.corner_radii.radius_0.into(),
1523                    width: f32::from(cosmic.space_xxxs()),
1524                    color: iced::Color::from(cosmic.bg_divider()),
1525                },
1526                placeholder,
1527                value,
1528                selection,
1529            },
1530            iced_widget::text_editor::Status::Focused { is_hovered } => {
1531                iced_widget::text_editor::Style {
1532                    background: iced::Color::from(cosmic.bg_color()).into(),
1533                    border: Border {
1534                        radius: cosmic.corner_radii.radius_0.into(),
1535                        width: f32::from(cosmic.space_xxxs()),
1536                        color: iced::Color::from(cosmic.accent.base),
1537                    },
1538                    placeholder,
1539                    value,
1540                    selection,
1541                }
1542            }
1543        }
1544    }
1545}
1546
1547#[cfg(feature = "markdown")]
1548impl iced_widget::markdown::Catalog for Theme {
1549    fn code_block<'a>() -> <Self as iced_container::Catalog>::Class<'a> {
1550        Container::custom(|_| iced_container::Style {
1551            background: Some(iced::color!(0x111111).into()),
1552            text_color: Some(Color::WHITE),
1553            border: iced::border::rounded(2),
1554            ..iced_container::Style::default()
1555        })
1556    }
1557}
1558
1559impl iced_widget::table::Catalog for Theme {
1560    type Class<'a> = iced_widget::table::StyleFn<'a, Self>;
1561
1562    fn default<'a>() -> Self::Class<'a> {
1563        Box::new(|theme| iced_widget::table::Style {
1564            separator_x: theme.current_container().divider.into(),
1565            separator_y: theme.current_container().divider.into(),
1566        })
1567    }
1568
1569    fn style(&self, class: &Self::Class<'_>) -> iced_widget::table::Style {
1570        class(self)
1571    }
1572}
1573
1574#[cfg(feature = "qr_code")]
1575impl iced_widget::qr_code::Catalog for Theme {
1576    type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>;
1577
1578    fn default<'a>() -> Self::Class<'a> {
1579        Box::new(|_theme| iced_widget::qr_code::Style {
1580            cell: Color::BLACK,
1581            background: Color::WHITE,
1582        })
1583    }
1584
1585    fn style(&self, class: &Self::Class<'_>) -> iced_widget::qr_code::Style {
1586        class(self)
1587    }
1588}
1589
1590impl combo_box::Catalog for Theme {}
1591
1592impl Base for Theme {
1593    fn default(preference: iced::theme::Mode) -> Self {
1594        match preference {
1595            iced::theme::Mode::Light => Theme::light(),
1596            iced::theme::Mode::Dark | iced::theme::Mode::None => Theme::dark(),
1597        }
1598    }
1599
1600    fn mode(&self) -> iced::theme::Mode {
1601        if self.theme_type.is_dark() {
1602            iced::theme::Mode::Dark
1603        } else {
1604            iced::theme::Mode::Light
1605        }
1606    }
1607
1608    fn base(&self) -> iced::theme::Style {
1609        iced::theme::Style {
1610            background_color: self.cosmic().bg_color().into(),
1611            text_color: self.cosmic().on_bg_color().into(),
1612            icon_color: self.cosmic().on_bg_color().into(),
1613        }
1614    }
1615
1616    fn palette(&self) -> Option<iced::theme::Palette> {
1617        Some(iced::theme::Palette {
1618            primary: self.cosmic().accent.base.into(),
1619            success: self.cosmic().success.base.into(),
1620            warning: self.cosmic().warning.base.into(),
1621            danger: self.cosmic().destructive.base.into(),
1622            background: iced::Color::from(self.cosmic().bg_color()),
1623            text: iced::Color::from(self.cosmic().on_bg_color()),
1624        })
1625    }
1626
1627    fn name(&self) -> &str {
1628        match &self.theme_type {
1629            crate::theme::ThemeType::Dark => "Cosmic Dark Theme",
1630            crate::theme::ThemeType::Light => "Cosmic Light Theme",
1631            crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme",
1632            crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme",
1633            crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme",
1634            crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name,
1635        }
1636    }
1637}