Skip to main content

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