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(self.transparent).component.base.into()
231                        } else {
232                            self.current_container().small_widget.into()
233                        }),
234                        icon_color: cosmic.background(self.transparent).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        transparent: bool,
394    },
395    Custom(Box<dyn Fn(&Theme) -> iced_container::Style + 'a>),
396    Dialog(bool),
397    Dropdown,
398    HeaderBar {
399        focused: bool,
400        sharp_corners: bool,
401        transparent: bool,
402    },
403    List,
404    Primary,
405    Secondary,
406    Tooltip,
407    #[default]
408    Transparent,
409}
410
411impl<'a> Container<'a> {
412    pub fn custom<F: Fn(&Theme) -> iced_container::Style + 'a>(f: F) -> Self {
413        Self::Custom(Box::new(f))
414    }
415
416    #[must_use]
417    pub fn background(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
418        iced_container::Style {
419            icon_color: Some(Color::from(theme.background(transparent).on)),
420            text_color: Some(Color::from(theme.background(transparent).on)),
421            background: Some(iced::Background::Color(
422                theme.background(transparent).base.into(),
423            )),
424            border: Border {
425                radius: theme.corner_radii.radius_s.into(),
426                ..Default::default()
427            },
428            shadow: Shadow::default(),
429            snap: true,
430        }
431    }
432
433    #[must_use]
434    pub fn primary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
435        iced_container::Style {
436            icon_color: Some(Color::from(theme.primary(transparent).on)),
437            text_color: Some(Color::from(theme.primary(transparent).on)),
438            background: Some(iced::Background::Color(
439                theme.primary(transparent).base.into(),
440            )),
441            border: Border {
442                radius: theme.corner_radii.radius_s.into(),
443                ..Default::default()
444            },
445            shadow: Shadow::default(),
446            snap: true,
447        }
448    }
449
450    #[must_use]
451    pub fn secondary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style {
452        iced_container::Style {
453            icon_color: Some(Color::from(theme.secondary(transparent).on)),
454            text_color: Some(Color::from(theme.secondary(transparent).on)),
455            background: Some(iced::Background::Color(
456                theme.secondary(transparent).base.into(),
457            )),
458            border: Border {
459                radius: theme.corner_radii.radius_s.into(),
460                ..Default::default()
461            },
462            shadow: Shadow::default(),
463            snap: true,
464        }
465    }
466}
467
468impl<'a> From<iced_container::StyleFn<'a, Theme>> for Container<'a> {
469    fn from(value: iced_container::StyleFn<'a, Theme>) -> Self {
470        Self::custom(value)
471    }
472}
473
474impl iced_container::Catalog for Theme {
475    type Class<'a> = Container<'a>;
476
477    fn default<'a>() -> Self::Class<'a> {
478        Container::default()
479    }
480
481    fn style(&self, class: &Self::Class<'_>) -> iced_container::Style {
482        let cosmic = self.cosmic();
483
484        // Ensures visually aligned radii for content and window corners
485        let window_corner_radius = cosmic.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
486
487        match class {
488            Container::Transparent => {
489                let component = &self.current_container().component;
490
491                iced_container::Style {
492                    icon_color: Some(component.on.into()),
493                    text_color: Some(component.on.into()),
494                    background: None,
495                    border: Border {
496                        radius: 0.into(),
497                        ..Default::default()
498                    },
499                    shadow: Shadow::default(),
500                    snap: true,
501                }
502            }
503
504            Container::Custom(f) => f(self),
505
506            Container::WindowBackground => iced_container::Style {
507                icon_color: Some(Color::from(cosmic.background(self.transparent).on)),
508                text_color: Some(Color::from(cosmic.background(self.transparent).on)),
509                background: Some(iced::Background::Color(
510                    cosmic.background(self.transparent).base.into(),
511                )),
512                border: Border {
513                    radius: [
514                        cosmic.corner_radii.radius_0[0],
515                        cosmic.corner_radii.radius_0[1],
516                        window_corner_radius[2],
517                        window_corner_radius[3],
518                    ]
519                    .into(),
520                    ..Default::default()
521                },
522                shadow: Shadow::default(),
523                snap: true,
524            },
525
526            Container::List => {
527                let component = &self.current_container().component;
528                iced_container::Style {
529                    icon_color: Some(component.on.into()),
530                    text_color: Some(component.on.into()),
531                    background: Some(Background::Color(component.base.into())),
532                    border: iced::Border {
533                        radius: cosmic.corner_radii.radius_s.into(),
534                        ..Default::default()
535                    },
536                    shadow: Shadow::default(),
537                    snap: true,
538                }
539            }
540
541            Container::HeaderBar {
542                focused,
543                sharp_corners,
544                transparent,
545            } => {
546                let (icon_color, text_color) = if *focused {
547                    (
548                        Color::from(cosmic.accent_text_color()),
549                        Color::from(cosmic.background(self.transparent).on),
550                    )
551                } else {
552                    use crate::ext::ColorExt;
553                    let unfocused_color =
554                        Color::from(cosmic.background(self.transparent).component.on)
555                            .blend_alpha(cosmic.background(self.transparent).base.into(), 0.5);
556                    (unfocused_color, unfocused_color)
557                };
558
559                iced_container::Style {
560                    icon_color: Some(icon_color),
561                    text_color: Some(text_color),
562                    background: if *transparent {
563                        None
564                    } else {
565                        Some(iced::Background::Color(
566                            cosmic.background(self.transparent).base.into(),
567                        ))
568                    },
569                    border: Border {
570                        radius: [
571                            if *sharp_corners {
572                                cosmic.corner_radii.radius_0[0]
573                            } else {
574                                window_corner_radius[0]
575                            },
576                            if *sharp_corners {
577                                cosmic.corner_radii.radius_0[1]
578                            } else {
579                                window_corner_radius[1]
580                            },
581                            cosmic.corner_radii.radius_0[2],
582                            cosmic.corner_radii.radius_0[3],
583                        ]
584                        .into(),
585                        ..Default::default()
586                    },
587                    snap: true,
588                    shadow: Shadow::default(),
589                }
590            }
591
592            Container::ContextDrawer { transparent } => {
593                let mut a = Container::primary(cosmic, self.transparent && *transparent);
594
595                if cosmic.is_high_contrast {
596                    a.border.width = 1.;
597                    a.border.color = cosmic.primary(self.transparent).divider.into();
598                }
599                a
600            }
601
602            Container::Background => Container::background(cosmic, self.transparent),
603
604            Container::Primary => Container::primary(cosmic, self.transparent),
605
606            Container::Secondary => Container::secondary(cosmic, self.transparent),
607
608            Container::Dropdown => iced_container::Style {
609                icon_color: None,
610                text_color: None,
611                background: Some(iced::Background::Color(cosmic.bg_component_color().into())),
612                border: Border {
613                    color: cosmic.bg_component_divider().into(),
614                    width: 1.0,
615                    radius: cosmic.corner_radii.radius_s.into(),
616                },
617                shadow: Shadow::default(),
618                snap: true,
619            },
620
621            Container::Tooltip => iced_container::Style {
622                icon_color: None,
623                text_color: None,
624                background: Some(iced::Background::Color(cosmic.palette.neutral_2.into())),
625                border: Border {
626                    radius: cosmic.corner_radii.radius_l.into(),
627                    ..Default::default()
628                },
629                shadow: Shadow::default(),
630                snap: true,
631            },
632
633            Container::Card => {
634                let cosmic = self.cosmic();
635
636                match self.layer {
637                    cosmic_theme::Layer::Background => iced_container::Style {
638                        icon_color: Some(Color::from(
639                            cosmic.background(self.transparent).component.on,
640                        )),
641                        text_color: Some(Color::from(
642                            cosmic.background(self.transparent).component.on,
643                        )),
644                        background: Some(iced::Background::Color(
645                            cosmic.background(self.transparent).component.base.into(),
646                        )),
647                        border: Border {
648                            radius: cosmic.corner_radii.radius_s.into(),
649                            ..Default::default()
650                        },
651                        shadow: Shadow::default(),
652                        snap: true,
653                    },
654                    cosmic_theme::Layer::Primary => iced_container::Style {
655                        icon_color: Some(Color::from(
656                            cosmic.primary(self.transparent).component.on,
657                        )),
658                        text_color: Some(Color::from(
659                            cosmic.primary(self.transparent).component.on,
660                        )),
661                        background: Some(iced::Background::Color(
662                            cosmic.primary(self.transparent).component.base.into(),
663                        )),
664                        border: Border {
665                            radius: cosmic.corner_radii.radius_s.into(),
666                            ..Default::default()
667                        },
668                        shadow: Shadow::default(),
669                        snap: true,
670                    },
671                    cosmic_theme::Layer::Secondary => iced_container::Style {
672                        icon_color: Some(Color::from(
673                            cosmic.secondary(self.transparent).component.on,
674                        )),
675                        text_color: Some(Color::from(
676                            cosmic.secondary(self.transparent).component.on,
677                        )),
678                        background: Some(iced::Background::Color(
679                            cosmic.secondary(self.transparent).component.base.into(),
680                        )),
681                        border: Border {
682                            radius: cosmic.corner_radii.radius_s.into(),
683                            ..Default::default()
684                        },
685                        shadow: Shadow::default(),
686                        snap: true,
687                    },
688                }
689            }
690
691            Container::Dialog(is_overlay) => iced_container::Style {
692                icon_color: Some(Color::from(cosmic.primary(self.transparent).on)),
693                text_color: Some(Color::from(cosmic.primary(self.transparent).on)),
694                background: Some(iced::Background::Color(
695                    cosmic.primary(self.transparent && !is_overlay).base.into(),
696                )),
697                border: Border {
698                    color: cosmic
699                        .primary(self.transparent && !is_overlay)
700                        .divider
701                        .into(),
702                    width: 1.0,
703                    radius: cosmic.corner_radii.radius_m.into(),
704                },
705                shadow: Shadow {
706                    color: cosmic.shade.into(),
707                    offset: Vector::new(0.0, 4.0),
708                    blur_radius: 16.0,
709                },
710                snap: true,
711            },
712        }
713    }
714}
715
716#[derive(Default)]
717pub enum Slider {
718    #[default]
719    Standard,
720    Custom {
721        active: Rc<dyn Fn(&Theme) -> slider::Style>,
722        hovered: Rc<dyn Fn(&Theme) -> slider::Style>,
723        dragging: Rc<dyn Fn(&Theme) -> slider::Style>,
724    },
725}
726
727/*
728 * Slider
729 */
730impl slider::Catalog for Theme {
731    type Class<'a> = Slider;
732
733    fn default<'a>() -> Self::Class<'a> {
734        Slider::default()
735    }
736
737    fn style(&self, class: &Self::Class<'_>, status: slider::Status) -> slider::Style {
738        let cosmic: &cosmic_theme::Theme = self.cosmic();
739        let hc = self.theme_type.is_high_contrast();
740        let is_dark = self.theme_type.is_dark();
741
742        let mut appearance = match class {
743            Slider::Standard =>
744            //TODO: no way to set rail thickness
745            {
746                let (active_track, inactive_track) = if hc {
747                    (
748                        cosmic.accent_text_color(),
749                        if is_dark {
750                            cosmic.palette.neutral_5
751                        } else {
752                            cosmic.palette.neutral_3
753                        },
754                    )
755                } else {
756                    (cosmic.accent.base, cosmic.palette.neutral_6)
757                };
758                slider::Style {
759                    rail: Rail {
760                        backgrounds: (
761                            Background::Color(active_track.into()),
762                            Background::Color(inactive_track.into()),
763                        ),
764                        border: Border {
765                            radius: cosmic.corner_radii.radius_xs.into(),
766                            color: if hc && !is_dark {
767                                self.current_container().component.border.into()
768                            } else {
769                                Color::TRANSPARENT
770                            },
771                            width: if hc && !is_dark { 1. } else { 0. },
772                        },
773                        width: 4.0,
774                    },
775
776                    handle: slider::Handle {
777                        shape: slider::HandleShape::Rectangle {
778                            height: 26,
779                            width: 26,
780                            border_radius: cosmic.corner_radii.radius_m.into(),
781                        },
782                        border_color: Color::TRANSPARENT,
783                        border_width: 3.0,
784                        background: Background::Color(cosmic.accent.base.into()),
785                    },
786
787                    breakpoint: slider::Breakpoint {
788                        color: cosmic.on_bg_color().into(),
789                    },
790                }
791            }
792            Slider::Custom { active, .. } => active(self),
793        };
794        match status {
795            slider::Status::Active => appearance,
796            slider::Status::Hovered => match class {
797                Slider::Standard => {
798                    appearance.handle.border_color =
799                        self.cosmic().palette.neutral_10.with_alpha(0.1).into();
800                    appearance
801                }
802                Slider::Custom { hovered, .. } => hovered(self),
803            },
804            slider::Status::Dragged => match class {
805                Slider::Standard => {
806                    appearance.handle.border_color =
807                        self.cosmic().palette.neutral_10.with_alpha(0.2).into();
808                    appearance
809                }
810                Slider::Custom { dragging, .. } => dragging(self),
811            },
812        }
813    }
814}
815
816impl menu::Catalog for Theme {
817    type Class<'a> = ();
818
819    fn default<'a>() -> <Self as menu::Catalog>::Class<'a> {}
820
821    fn style(&self, class: &<Self as menu::Catalog>::Class<'_>) -> menu::Style {
822        let cosmic = self.cosmic();
823
824        menu::Style {
825            text_color: cosmic.on_bg_color().into(),
826            background: Background::Color(cosmic.background(self.transparent).base.into()),
827            border: Border {
828                radius: cosmic.corner_radii.radius_m.into(),
829                ..Default::default()
830            },
831            selected_text_color: cosmic.accent_text_color().into(),
832            selected_background: Background::Color(
833                cosmic.background(self.transparent).component.hover.into(),
834            ),
835            shadow: Default::default(),
836        }
837    }
838}
839
840impl pick_list::Catalog for Theme {
841    type Class<'a> = ();
842
843    fn default<'a>() -> <Self as pick_list::Catalog>::Class<'a> {}
844
845    fn style(
846        &self,
847        class: &<Self as pick_list::Catalog>::Class<'_>,
848        status: pick_list::Status,
849    ) -> pick_list::Style {
850        let cosmic = &self.cosmic();
851        let hc = cosmic.is_high_contrast;
852        let appearance = pick_list::Style {
853            text_color: cosmic.on_bg_color().into(),
854            background: Color::TRANSPARENT.into(),
855            placeholder_color: cosmic.on_bg_color().into(),
856            border: Border {
857                radius: cosmic.corner_radii.radius_m.into(),
858                width: if hc { 1. } else { 0. },
859                color: if hc {
860                    self.current_container().component.border.into()
861                } else {
862                    Color::TRANSPARENT
863                },
864            },
865            // icon_size: 0.7, // TODO: how to replace
866            handle_color: cosmic.on_bg_color().into(),
867        };
868
869        match status {
870            pick_list::Status::Active => appearance,
871            pick_list::Status::Hovered => pick_list::Style {
872                background: Background::Color(cosmic.background(self.transparent).base.into()),
873                ..appearance
874            },
875            pick_list::Status::Opened { is_hovered: _ } => appearance,
876        }
877    }
878}
879
880/*
881 * TODO: Radio
882 */
883impl radio::Catalog for Theme {
884    type Class<'a> = ();
885
886    fn default<'a>() -> Self::Class<'a> {}
887
888    fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style {
889        let cur_container = self.current_container();
890        let theme = self.cosmic();
891
892        match status {
893            radio::Status::Active { is_selected } => radio::Style {
894                background: if is_selected {
895                    Color::from(theme.accent.base).into()
896                } else {
897                    // TODO: this seems to be defined weirdly in FIGMA
898                    Color::from(cur_container.small_widget).into()
899                },
900                dot_color: theme.accent.on.into(),
901                border_width: 1.0,
902                border_color: if is_selected {
903                    Color::from(theme.accent.base)
904                } else {
905                    Color::from(theme.palette.neutral_8)
906                },
907                text_color: None,
908            },
909            radio::Status::Hovered { is_selected } => {
910                let bg = if is_selected {
911                    theme.accent.base
912                } else {
913                    self.current_container().small_widget
914                };
915                // TODO: this should probably be done with a custom widget instead, or the theme needs more small widget variables.
916                let hovered_bg = Color::from(over(theme.palette.neutral_0.with_alpha(0.1), bg));
917                radio::Style {
918                    background: hovered_bg.into(),
919                    dot_color: theme.accent.on.into(),
920                    border_width: 1.0,
921                    border_color: if is_selected {
922                        Color::from(theme.accent.base)
923                    } else {
924                        Color::from(theme.palette.neutral_8)
925                    },
926                    text_color: None,
927                }
928            }
929        }
930    }
931}
932
933/*
934 * Toggler
935 */
936impl toggler::Catalog for Theme {
937    type Class<'a> = ();
938
939    fn default<'a>() -> Self::Class<'a> {}
940
941    fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style {
942        let cosmic = self.cosmic();
943        const HANDLE_MARGIN: f32 = 2.0;
944        let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1);
945
946        let mut active = toggler::Style {
947            background: if matches!(status, toggler::Status::Active { is_toggled: true }) {
948                cosmic.accent.base.into()
949            } else if cosmic.is_dark {
950                cosmic.palette.neutral_6.into()
951            } else {
952                cosmic.palette.neutral_5.into()
953            },
954            foreground: cosmic.palette.neutral_2.into(),
955            border_radius: cosmic.radius_xl().into(),
956            handle_radius: cosmic
957                .radius_xl()
958                .map(|x| (x - HANDLE_MARGIN).max(0.0))
959                .into(),
960            handle_margin: HANDLE_MARGIN,
961            background_border_width: 0.0,
962            background_border_color: Color::TRANSPARENT,
963            foreground_border_width: 0.0,
964            foreground_border_color: Color::TRANSPARENT,
965            text_color: None,
966            padding_ratio: 0.0,
967        };
968        match status {
969            toggler::Status::Active { is_toggled } => active,
970            toggler::Status::Hovered { is_toggled } => {
971                let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true });
972                toggler::Style {
973                    background: if is_active {
974                        over(neutral_10, cosmic.accent_color())
975                    } else {
976                        over(
977                            neutral_10,
978                            if cosmic.is_dark {
979                                cosmic.palette.neutral_6
980                            } else {
981                                cosmic.palette.neutral_5
982                            },
983                        )
984                    }
985                    .into(),
986                    ..active
987                }
988            }
989            toggler::Status::Disabled { is_toggled } => {
990                active.background = active.background.scale_alpha(0.5);
991                active.foreground = active.foreground.scale_alpha(0.5);
992                active
993            }
994        }
995    }
996}
997
998/*
999 * TODO: Pane Grid
1000 */
1001impl pane_grid::Catalog for Theme {
1002    type Class<'a> = ();
1003
1004    fn default<'a>() -> <Self as pane_grid::Catalog>::Class<'a> {}
1005
1006    fn style(&self, class: &<Self as pane_grid::Catalog>::Class<'_>) -> pane_grid::Style {
1007        let theme = self.cosmic();
1008
1009        pane_grid::Style {
1010            hovered_region: Highlight {
1011                background: Background::Color(theme.bg_color().into()),
1012                border: Border {
1013                    radius: theme.corner_radii.radius_0.into(),
1014                    width: 2.0,
1015                    color: theme.bg_divider().into(),
1016                },
1017            },
1018            picked_split: pane_grid::Line {
1019                color: theme.accent.base.into(),
1020                width: 2.0,
1021            },
1022            hovered_split: pane_grid::Line {
1023                color: theme.accent.hover.into(),
1024                width: 2.0,
1025            },
1026        }
1027    }
1028}
1029
1030/*
1031 * TODO: Progress Bar
1032 */
1033#[derive(Default)]
1034pub enum ProgressBar {
1035    #[default]
1036    Primary,
1037    Success,
1038    Danger,
1039    Custom(Box<dyn Fn(&Theme) -> progress_bar::Style>),
1040}
1041
1042impl ProgressBar {
1043    pub fn custom<F: Fn(&Theme) -> progress_bar::Style + 'static>(f: F) -> Self {
1044        Self::Custom(Box::new(f))
1045    }
1046}
1047
1048impl progress_bar::Catalog for Theme {
1049    type Class<'a> = ProgressBar;
1050
1051    fn default<'a>() -> Self::Class<'a> {
1052        ProgressBar::default()
1053    }
1054
1055    fn style(&self, class: &Self::Class<'_>) -> progress_bar::Style {
1056        let theme = self.cosmic();
1057
1058        let (active_track, inactive_track) = if theme.is_high_contrast {
1059            (
1060                theme.accent_text_color(),
1061                if theme.is_dark {
1062                    theme.palette.neutral_6
1063                } else {
1064                    theme.palette.neutral_4
1065                },
1066            )
1067        } else {
1068            (
1069                theme.accent.base,
1070                theme.background(self.transparent).divider,
1071            )
1072        };
1073        let border = Border {
1074            radius: theme.corner_radii.radius_xl.into(),
1075            color: if theme.is_high_contrast && !theme.is_dark {
1076                self.current_container().component.border.into()
1077            } else {
1078                Color::TRANSPARENT
1079            },
1080            width: if theme.is_high_contrast && !theme.is_dark {
1081                1.
1082            } else {
1083                0.
1084            },
1085        };
1086        match class {
1087            ProgressBar::Primary => progress_bar::Style {
1088                background: Color::from(inactive_track).into(),
1089                bar: Color::from(active_track).into(),
1090                border,
1091            },
1092            ProgressBar::Success => progress_bar::Style {
1093                background: Color::from(inactive_track).into(),
1094                bar: Color::from(theme.success.base).into(),
1095                border,
1096            },
1097            ProgressBar::Danger => progress_bar::Style {
1098                background: Color::from(inactive_track).into(),
1099                bar: Color::from(theme.destructive.base).into(),
1100                border,
1101            },
1102            ProgressBar::Custom(f) => f(self),
1103        }
1104    }
1105}
1106
1107/*
1108 * TODO: Rule
1109 */
1110#[derive(Default)]
1111pub enum Rule {
1112    #[default]
1113    Default,
1114    LightDivider,
1115    HeavyDivider,
1116    Custom(Box<dyn Fn(&Theme) -> rule::Style>),
1117}
1118
1119impl Rule {
1120    pub fn custom<F: Fn(&Theme) -> rule::Style + 'static>(f: F) -> Self {
1121        Self::Custom(Box::new(f))
1122    }
1123}
1124
1125impl rule::Catalog for Theme {
1126    type Class<'a> = Rule;
1127
1128    fn default<'a>() -> Self::Class<'a> {
1129        Rule::default()
1130    }
1131
1132    fn style(&self, class: &Self::Class<'_>) -> rule::Style {
1133        match class {
1134            Rule::Default => rule::Style {
1135                color: self.current_container().divider.into(),
1136                radius: 0.0.into(),
1137                fill_mode: rule::FillMode::Full,
1138                snap: true,
1139            },
1140            Rule::LightDivider => rule::Style {
1141                color: self.current_container().divider.into(),
1142                radius: 0.0.into(),
1143                fill_mode: rule::FillMode::Padded(8),
1144                snap: true,
1145            },
1146            Rule::HeavyDivider => rule::Style {
1147                color: self.current_container().divider.into(),
1148                radius: 2.0.into(),
1149                fill_mode: rule::FillMode::Full,
1150                snap: true,
1151            },
1152            Rule::Custom(f) => f(self),
1153        }
1154    }
1155}
1156
1157#[derive(Default, Clone, Copy)]
1158pub enum Scrollable {
1159    #[default]
1160    Permanent,
1161    Minimal,
1162}
1163
1164/*
1165 * TODO: Scrollable
1166 */
1167impl scrollable::Catalog for Theme {
1168    type Class<'a> = Scrollable;
1169
1170    fn default<'a>() -> Self::Class<'a> {
1171        Scrollable::default()
1172    }
1173
1174    fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style {
1175        match status {
1176            scrollable::Status::Active {
1177                is_horizontal_scrollbar_disabled,
1178                is_vertical_scrollbar_disabled,
1179            } => {
1180                let cosmic = self.cosmic();
1181                let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
1182                let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
1183                let mut a = scrollable::Style {
1184                    container: iced_container::transparent(self),
1185                    vertical_rail: scrollable::Rail {
1186                        border: Border {
1187                            radius: cosmic.corner_radii.radius_s.into(),
1188                            ..Default::default()
1189                        },
1190                        background: None,
1191                        scroller: scrollable::Scroller {
1192                            background: if cosmic.is_dark {
1193                                neutral_6.into()
1194                            } else {
1195                                neutral_5.into()
1196                            },
1197                            border: Border {
1198                                radius: cosmic.corner_radii.radius_s.into(),
1199                                ..Default::default()
1200                            },
1201                        },
1202                    },
1203                    horizontal_rail: scrollable::Rail {
1204                        border: Border {
1205                            radius: cosmic.corner_radii.radius_s.into(),
1206                            ..Default::default()
1207                        },
1208                        background: None,
1209                        scroller: scrollable::Scroller {
1210                            background: if cosmic.is_dark {
1211                                neutral_6.into()
1212                            } else {
1213                                neutral_5.into()
1214                            },
1215                            border: Border {
1216                                radius: cosmic.corner_radii.radius_s.into(),
1217                                ..Default::default()
1218                            },
1219                        },
1220                    },
1221                    gap: None,
1222                    // TODO: what is auto scroll?
1223                    auto_scroll: AutoScroll {
1224                        background: Color::TRANSPARENT.into(),
1225                        border: Border::default(),
1226                        shadow: Shadow::default(),
1227                        icon: Color::TRANSPARENT.into(),
1228                    },
1229                };
1230                let small_widget_container = self.current_container().small_widget.with_alpha(0.7);
1231
1232                if matches!(class, Scrollable::Permanent) {
1233                    a.horizontal_rail.background =
1234                        Some(Background::Color(small_widget_container.into()));
1235                    a.vertical_rail.background =
1236                        Some(Background::Color(small_widget_container.into()));
1237                }
1238
1239                a
1240            }
1241            // TODO handle vertical / horizontal
1242            scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => {
1243                let cosmic = self.cosmic();
1244                let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
1245                let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
1246
1247                // if is_mouse_over_scrollbar {
1248                //     let hover_overlay = cosmic.palette.neutral_0.with_alpha(0.2);
1249                //     neutral_5 = over(hover_overlay, neutral_5);
1250                // }
1251                let mut a: scrollable::Style = scrollable::Style {
1252                    container: iced_container::Style::default(),
1253                    vertical_rail: scrollable::Rail {
1254                        border: Border {
1255                            radius: cosmic.corner_radii.radius_s.into(),
1256                            ..Default::default()
1257                        },
1258                        background: None,
1259                        scroller: scrollable::Scroller {
1260                            background: if cosmic.is_dark {
1261                                neutral_6.into()
1262                            } else {
1263                                neutral_5.into()
1264                            },
1265                            border: Border {
1266                                radius: cosmic.corner_radii.radius_s.into(),
1267                                ..Default::default()
1268                            },
1269                        },
1270                    },
1271                    horizontal_rail: scrollable::Rail {
1272                        border: Border {
1273                            radius: cosmic.corner_radii.radius_s.into(),
1274                            ..Default::default()
1275                        },
1276                        background: None,
1277                        scroller: scrollable::Scroller {
1278                            background: if cosmic.is_dark {
1279                                neutral_6.into()
1280                            } else {
1281                                neutral_5.into()
1282                            },
1283                            border: Border {
1284                                radius: cosmic.corner_radii.radius_s.into(),
1285                                ..Default::default()
1286                            },
1287                        },
1288                    },
1289                    gap: None,
1290                    // TODO: what is auto scroll?
1291                    auto_scroll: AutoScroll {
1292                        background: Color::TRANSPARENT.into(),
1293                        border: Border::default(),
1294                        shadow: Shadow::default(),
1295                        icon: Color::TRANSPARENT.into(),
1296                    },
1297                };
1298
1299                if matches!(class, Scrollable::Permanent) {
1300                    let small_widget_container =
1301                        self.current_container().small_widget.with_alpha(0.7);
1302
1303                    a.horizontal_rail.background =
1304                        Some(Background::Color(small_widget_container.into()));
1305                    a.vertical_rail.background =
1306                        Some(Background::Color(small_widget_container.into()));
1307                }
1308
1309                a
1310            }
1311        }
1312    }
1313}
1314
1315#[derive(Clone, Default)]
1316pub enum Svg {
1317    /// Apply a custom appearance filter
1318    Custom(Rc<dyn Fn(&Theme) -> svg::Style>),
1319    /// No filtering is applied
1320    #[default]
1321    Default,
1322}
1323
1324impl Svg {
1325    pub fn custom<F: Fn(&Theme) -> svg::Style + 'static>(f: F) -> Self {
1326        Self::Custom(Rc::new(f))
1327    }
1328}
1329
1330impl svg::Catalog for Theme {
1331    type Class<'a> = Svg;
1332
1333    fn default<'a>() -> Self::Class<'a> {
1334        Svg::default()
1335    }
1336
1337    fn style(&self, class: &Self::Class<'_>, status: svg::Status) -> svg::Style {
1338        #[allow(clippy::match_same_arms)]
1339        match class {
1340            Svg::Default => svg::Style::default(),
1341            Svg::Custom(appearance) => appearance(self),
1342        }
1343    }
1344}
1345
1346/*
1347 * TODO: Text
1348 */
1349#[derive(Clone, Copy, Default)]
1350pub enum Text {
1351    Accent,
1352    #[default]
1353    Default,
1354    Color(Color),
1355    // TODO: Can't use dyn Fn since this must be copy
1356    Custom(fn(&Theme) -> iced_widget::text::Style),
1357}
1358
1359impl From<Color> for Text {
1360    fn from(color: Color) -> Self {
1361        Self::Color(color)
1362    }
1363}
1364
1365impl iced_widget::text::Catalog for Theme {
1366    type Class<'a> = Text;
1367
1368    fn default<'a>() -> Self::Class<'a> {
1369        Text::default()
1370    }
1371
1372    fn style(&self, class: &Self::Class<'_>) -> iced_widget::text::Style {
1373        match class {
1374            Text::Accent => iced_widget::text::Style {
1375                color: Some(self.cosmic().accent_text_color().into()),
1376                ..Default::default()
1377            },
1378            Text::Default => iced_widget::text::Style {
1379                color: None,
1380                ..Default::default()
1381            },
1382            Text::Color(c) => iced_widget::text::Style {
1383                color: Some(*c),
1384                ..Default::default()
1385            },
1386            Text::Custom(f) => f(self),
1387        }
1388    }
1389}
1390
1391#[derive(Copy, Clone, Default)]
1392pub enum TextInput {
1393    #[default]
1394    Default,
1395    Search,
1396}
1397
1398/*
1399 * TODO: Text Input
1400 */
1401impl text_input::Catalog for Theme {
1402    type Class<'a> = TextInput;
1403
1404    fn default<'a>() -> Self::Class<'a> {
1405        TextInput::default()
1406    }
1407
1408    fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style {
1409        let palette = self.cosmic();
1410        let bg = self.current_container().small_widget.with_alpha(0.25);
1411
1412        let neutral_9 = palette.palette.neutral_9;
1413        let value = neutral_9.into();
1414        let placeholder = neutral_9.with_alpha(0.7).into();
1415        let selection = palette.accent.base.into();
1416
1417        let mut appearance = match class {
1418            TextInput::Default => text_input::Style {
1419                background: Color::from(bg).into(),
1420                border: Border {
1421                    radius: palette.corner_radii.radius_s.into(),
1422                    width: 1.0,
1423                    color: self.current_container().component.divider.into(),
1424                },
1425                icon: self.current_container().on.into(),
1426                placeholder,
1427                value,
1428                selection,
1429            },
1430            TextInput::Search => text_input::Style {
1431                background: Color::from(bg).into(),
1432                border: Border {
1433                    radius: palette.corner_radii.radius_m.into(),
1434                    ..Default::default()
1435                },
1436                icon: self.current_container().on.into(),
1437                placeholder,
1438                value,
1439                selection,
1440            },
1441        };
1442
1443        match status {
1444            text_input::Status::Active => appearance,
1445            text_input::Status::Hovered => {
1446                let bg = self.current_container().small_widget.with_alpha(0.25);
1447
1448                match class {
1449                    TextInput::Default => text_input::Style {
1450                        background: Color::from(bg).into(),
1451                        border: Border {
1452                            radius: palette.corner_radii.radius_s.into(),
1453                            width: 1.0,
1454                            color: self.current_container().on.into(),
1455                        },
1456                        icon: self.current_container().on.into(),
1457                        placeholder,
1458                        value,
1459                        selection,
1460                    },
1461                    TextInput::Search => text_input::Style {
1462                        background: Color::from(bg).into(),
1463                        border: Border {
1464                            radius: palette.corner_radii.radius_m.into(),
1465                            ..Default::default()
1466                        },
1467                        icon: self.current_container().on.into(),
1468                        placeholder,
1469                        value,
1470                        selection,
1471                    },
1472                }
1473            }
1474            text_input::Status::Focused { is_hovered } => {
1475                let bg = self.current_container().small_widget.with_alpha(0.25);
1476
1477                match class {
1478                    TextInput::Default => text_input::Style {
1479                        background: Color::from(bg).into(),
1480                        border: Border {
1481                            radius: palette.corner_radii.radius_s.into(),
1482                            width: 1.0,
1483                            color: palette.accent.base.into(),
1484                        },
1485                        icon: self.current_container().on.into(),
1486                        placeholder,
1487                        value,
1488                        selection,
1489                    },
1490                    TextInput::Search => text_input::Style {
1491                        background: Color::from(bg).into(),
1492                        border: Border {
1493                            radius: palette.corner_radii.radius_m.into(),
1494                            ..Default::default()
1495                        },
1496                        icon: self.current_container().on.into(),
1497                        placeholder,
1498                        value,
1499                        selection,
1500                    },
1501                }
1502            }
1503            text_input::Status::Disabled => {
1504                appearance.background = match appearance.background {
1505                    Background::Color(color) => Background::Color(Color {
1506                        a: color.a * 0.5,
1507                        ..color
1508                    }),
1509                    Background::Gradient(gradient) => {
1510                        Background::Gradient(gradient.scale_alpha(0.5))
1511                    }
1512                };
1513                appearance.border.color.a /= 2.;
1514                appearance.icon.a /= 2.;
1515                appearance.placeholder.a /= 2.;
1516                appearance.value.a /= 2.;
1517                appearance
1518            }
1519        }
1520    }
1521}
1522
1523#[derive(Default)]
1524pub enum TextEditor<'a> {
1525    #[default]
1526    Default,
1527    Custom(text_editor::StyleFn<'a, Theme>),
1528}
1529
1530impl iced_widget::text_editor::Catalog for Theme {
1531    type Class<'a> = TextEditor<'a>;
1532
1533    fn default<'a>() -> Self::Class<'a> {
1534        TextEditor::default()
1535    }
1536
1537    fn style(
1538        &self,
1539        class: &Self::Class<'_>,
1540        status: iced_widget::text_editor::Status,
1541    ) -> iced_widget::text_editor::Style {
1542        if let TextEditor::Custom(style) = class {
1543            return style(self, status);
1544        }
1545
1546        let cosmic = self.cosmic();
1547
1548        let selection = cosmic.accent.base.into();
1549        let value = cosmic.palette.neutral_9.into();
1550        let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into();
1551        let icon: Color = cosmic.background(self.transparent).on.into();
1552        // TODO do we need to add icon color back?
1553
1554        match status {
1555            iced_widget::text_editor::Status::Active
1556            | iced_widget::text_editor::Status::Hovered
1557            | iced_widget::text_editor::Status::Disabled => iced_widget::text_editor::Style {
1558                background: iced::Color::from(cosmic.bg_color()).into(),
1559                border: Border {
1560                    radius: cosmic.corner_radii.radius_0.into(),
1561                    width: f32::from(cosmic.space_xxxs()),
1562                    color: iced::Color::from(cosmic.bg_divider()),
1563                },
1564                placeholder,
1565                value,
1566                selection,
1567            },
1568            iced_widget::text_editor::Status::Focused { is_hovered } => {
1569                iced_widget::text_editor::Style {
1570                    background: iced::Color::from(cosmic.bg_color()).into(),
1571                    border: Border {
1572                        radius: cosmic.corner_radii.radius_0.into(),
1573                        width: f32::from(cosmic.space_xxxs()),
1574                        color: iced::Color::from(cosmic.accent.base),
1575                    },
1576                    placeholder,
1577                    value,
1578                    selection,
1579                }
1580            }
1581        }
1582    }
1583}
1584
1585#[cfg(feature = "markdown")]
1586impl iced_widget::markdown::Catalog for Theme {
1587    fn code_block<'a>() -> <Self as iced_container::Catalog>::Class<'a> {
1588        Container::custom(|_| iced_container::Style {
1589            background: Some(iced::color!(0x111111).into()),
1590            text_color: Some(Color::WHITE),
1591            border: iced::border::rounded(2),
1592            ..iced_container::Style::default()
1593        })
1594    }
1595}
1596
1597impl iced_widget::table::Catalog for Theme {
1598    type Class<'a> = iced_widget::table::StyleFn<'a, Self>;
1599
1600    fn default<'a>() -> Self::Class<'a> {
1601        Box::new(|theme| iced_widget::table::Style {
1602            separator_x: theme.current_container().divider.into(),
1603            separator_y: theme.current_container().divider.into(),
1604        })
1605    }
1606
1607    fn style(&self, class: &Self::Class<'_>) -> iced_widget::table::Style {
1608        class(self)
1609    }
1610}
1611
1612#[cfg(feature = "qr_code")]
1613impl iced_widget::qr_code::Catalog for Theme {
1614    type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>;
1615
1616    fn default<'a>() -> Self::Class<'a> {
1617        Box::new(|_theme| iced_widget::qr_code::Style {
1618            cell: Color::BLACK,
1619            background: Color::WHITE,
1620        })
1621    }
1622
1623    fn style(&self, class: &Self::Class<'_>) -> iced_widget::qr_code::Style {
1624        class(self)
1625    }
1626}
1627
1628impl combo_box::Catalog for Theme {}
1629
1630impl Base for Theme {
1631    fn default(preference: iced::theme::Mode) -> Self {
1632        match preference {
1633            iced::theme::Mode::Light => Theme::light(),
1634            iced::theme::Mode::Dark | iced::theme::Mode::None => Theme::dark(),
1635        }
1636    }
1637
1638    fn mode(&self) -> iced::theme::Mode {
1639        if self.theme_type.is_dark() {
1640            iced::theme::Mode::Dark
1641        } else {
1642            iced::theme::Mode::Light
1643        }
1644    }
1645
1646    fn base(&self) -> iced::theme::Style {
1647        iced::theme::Style {
1648            background_color: self.cosmic().bg_color().into(),
1649            text_color: self.cosmic().on_bg_color().into(),
1650            icon_color: self.cosmic().on_bg_color().into(),
1651        }
1652    }
1653
1654    fn palette(&self) -> Option<iced::theme::Palette> {
1655        Some(iced::theme::Palette {
1656            primary: self.cosmic().accent.base.into(),
1657            success: self.cosmic().success.base.into(),
1658            warning: self.cosmic().warning.base.into(),
1659            danger: self.cosmic().destructive.base.into(),
1660            background: iced::Color::from(self.cosmic().bg_color()),
1661            text: iced::Color::from(self.cosmic().on_bg_color()),
1662        })
1663    }
1664
1665    fn name(&self) -> &str {
1666        match &self.theme_type {
1667            crate::theme::ThemeType::Dark => "Cosmic Dark Theme",
1668            crate::theme::ThemeType::Light => "Cosmic Light Theme",
1669            crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme",
1670            crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme",
1671            crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme",
1672            crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name,
1673        }
1674    }
1675}