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        let selected_fill = self.cosmic().accent.base.into();
1374        let selected_text_color = Some(self.cosmic().on_accent_color().into());
1375        match class {
1376            Text::Accent => iced_widget::text::Style {
1377                color: Some(self.cosmic().accent_text_color().into()),
1378                selected_fill,
1379                selected_text_color,
1380            },
1381            Text::Default => iced_widget::text::Style {
1382                color: None,
1383                selected_fill,
1384                selected_text_color,
1385            },
1386            Text::Color(c) => iced_widget::text::Style {
1387                color: Some(*c),
1388                selected_fill,
1389                selected_text_color,
1390            },
1391            Text::Custom(f) => f(self),
1392        }
1393    }
1394}
1395
1396#[derive(Copy, Clone, Default)]
1397pub enum TextInput {
1398    #[default]
1399    Default,
1400    Search,
1401}
1402
1403/*
1404 * TODO: Text Input
1405 */
1406impl text_input::Catalog for Theme {
1407    type Class<'a> = TextInput;
1408
1409    fn default<'a>() -> Self::Class<'a> {
1410        TextInput::default()
1411    }
1412
1413    fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style {
1414        let palette = self.cosmic();
1415        let bg = self.current_container().small_widget.with_alpha(0.25);
1416
1417        let neutral_9 = palette.palette.neutral_9;
1418        let value = neutral_9.into();
1419        let placeholder = neutral_9.with_alpha(0.7).into();
1420        let selection = palette.accent.base.into();
1421
1422        let mut appearance = match class {
1423            TextInput::Default => text_input::Style {
1424                background: Color::from(bg).into(),
1425                border: Border {
1426                    radius: palette.corner_radii.radius_s.into(),
1427                    width: 1.0,
1428                    color: self.current_container().component.divider.into(),
1429                },
1430                icon: self.current_container().on.into(),
1431                placeholder,
1432                value,
1433                selection,
1434            },
1435            TextInput::Search => text_input::Style {
1436                background: Color::from(bg).into(),
1437                border: Border {
1438                    radius: palette.corner_radii.radius_m.into(),
1439                    ..Default::default()
1440                },
1441                icon: self.current_container().on.into(),
1442                placeholder,
1443                value,
1444                selection,
1445            },
1446        };
1447
1448        match status {
1449            text_input::Status::Active => appearance,
1450            text_input::Status::Hovered => {
1451                let bg = self.current_container().small_widget.with_alpha(0.25);
1452
1453                match class {
1454                    TextInput::Default => text_input::Style {
1455                        background: Color::from(bg).into(),
1456                        border: Border {
1457                            radius: palette.corner_radii.radius_s.into(),
1458                            width: 1.0,
1459                            color: self.current_container().on.into(),
1460                        },
1461                        icon: self.current_container().on.into(),
1462                        placeholder,
1463                        value,
1464                        selection,
1465                    },
1466                    TextInput::Search => text_input::Style {
1467                        background: Color::from(bg).into(),
1468                        border: Border {
1469                            radius: palette.corner_radii.radius_m.into(),
1470                            ..Default::default()
1471                        },
1472                        icon: self.current_container().on.into(),
1473                        placeholder,
1474                        value,
1475                        selection,
1476                    },
1477                }
1478            }
1479            text_input::Status::Focused { is_hovered } => {
1480                let bg = self.current_container().small_widget.with_alpha(0.25);
1481
1482                match class {
1483                    TextInput::Default => text_input::Style {
1484                        background: Color::from(bg).into(),
1485                        border: Border {
1486                            radius: palette.corner_radii.radius_s.into(),
1487                            width: 1.0,
1488                            color: palette.accent.base.into(),
1489                        },
1490                        icon: self.current_container().on.into(),
1491                        placeholder,
1492                        value,
1493                        selection,
1494                    },
1495                    TextInput::Search => text_input::Style {
1496                        background: Color::from(bg).into(),
1497                        border: Border {
1498                            radius: palette.corner_radii.radius_m.into(),
1499                            ..Default::default()
1500                        },
1501                        icon: self.current_container().on.into(),
1502                        placeholder,
1503                        value,
1504                        selection,
1505                    },
1506                }
1507            }
1508            text_input::Status::Disabled => {
1509                appearance.background = match appearance.background {
1510                    Background::Color(color) => Background::Color(Color {
1511                        a: color.a * 0.5,
1512                        ..color
1513                    }),
1514                    Background::Gradient(gradient) => {
1515                        Background::Gradient(gradient.scale_alpha(0.5))
1516                    }
1517                };
1518                appearance.border.color.a /= 2.;
1519                appearance.icon.a /= 2.;
1520                appearance.placeholder.a /= 2.;
1521                appearance.value.a /= 2.;
1522                appearance
1523            }
1524        }
1525    }
1526}
1527
1528#[derive(Default)]
1529pub enum TextEditor<'a> {
1530    #[default]
1531    Default,
1532    Custom(text_editor::StyleFn<'a, Theme>),
1533}
1534
1535impl<'a> From<text_editor::StyleFn<'a, Theme>> for TextEditor<'a> {
1536    fn from(style: text_editor::StyleFn<'a, Theme>) -> Self {
1537        Self::Custom(style)
1538    }
1539}
1540
1541impl iced_widget::text_editor::Catalog for Theme {
1542    type Class<'a> = TextEditor<'a>;
1543
1544    fn default<'a>() -> Self::Class<'a> {
1545        TextEditor::default()
1546    }
1547
1548    fn style(
1549        &self,
1550        class: &Self::Class<'_>,
1551        status: iced_widget::text_editor::Status,
1552    ) -> iced_widget::text_editor::Style {
1553        if let TextEditor::Custom(style) = class {
1554            return style(self, status);
1555        }
1556
1557        let cosmic = self.cosmic();
1558
1559        let selection = cosmic.accent.base.into();
1560        let value = cosmic.palette.neutral_9.into();
1561        let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into();
1562        let icon: Color = cosmic.background(self.transparent).on.into();
1563        // TODO do we need to add icon color back?
1564
1565        match status {
1566            iced_widget::text_editor::Status::Active
1567            | iced_widget::text_editor::Status::Hovered
1568            | iced_widget::text_editor::Status::Disabled => iced_widget::text_editor::Style {
1569                background: iced::Color::from(cosmic.bg_color()).into(),
1570                border: Border {
1571                    radius: cosmic.corner_radii.radius_0.into(),
1572                    width: f32::from(cosmic.space_xxxs()),
1573                    color: iced::Color::from(cosmic.bg_divider()),
1574                },
1575                placeholder,
1576                value,
1577                selection,
1578            },
1579            iced_widget::text_editor::Status::Focused { is_hovered } => {
1580                iced_widget::text_editor::Style {
1581                    background: iced::Color::from(cosmic.bg_color()).into(),
1582                    border: Border {
1583                        radius: cosmic.corner_radii.radius_0.into(),
1584                        width: f32::from(cosmic.space_xxxs()),
1585                        color: iced::Color::from(cosmic.accent.base),
1586                    },
1587                    placeholder,
1588                    value,
1589                    selection,
1590                }
1591            }
1592        }
1593    }
1594}
1595
1596#[cfg(feature = "markdown")]
1597impl iced_widget::markdown::Catalog for Theme {
1598    fn code_block<'a>() -> <Self as iced_container::Catalog>::Class<'a> {
1599        Container::custom(|_| iced_container::Style {
1600            background: Some(iced::color!(0x111111).into()),
1601            text_color: Some(Color::WHITE),
1602            border: iced::border::rounded(2),
1603            ..iced_container::Style::default()
1604        })
1605    }
1606}
1607
1608impl iced_widget::table::Catalog for Theme {
1609    type Class<'a> = iced_widget::table::StyleFn<'a, Self>;
1610
1611    fn default<'a>() -> Self::Class<'a> {
1612        Box::new(|theme| iced_widget::table::Style {
1613            separator_x: theme.current_container().divider.into(),
1614            separator_y: theme.current_container().divider.into(),
1615        })
1616    }
1617
1618    fn style(&self, class: &Self::Class<'_>) -> iced_widget::table::Style {
1619        class(self)
1620    }
1621}
1622
1623#[cfg(feature = "qr_code")]
1624impl iced_widget::qr_code::Catalog for Theme {
1625    type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>;
1626
1627    fn default<'a>() -> Self::Class<'a> {
1628        Box::new(|_theme| iced_widget::qr_code::Style {
1629            cell: Color::BLACK,
1630            background: Color::WHITE,
1631        })
1632    }
1633
1634    fn style(&self, class: &Self::Class<'_>) -> iced_widget::qr_code::Style {
1635        class(self)
1636    }
1637}
1638
1639impl combo_box::Catalog for Theme {}
1640
1641impl Base for Theme {
1642    fn default(preference: iced::theme::Mode) -> Self {
1643        match preference {
1644            iced::theme::Mode::Light => Theme::light(),
1645            iced::theme::Mode::Dark | iced::theme::Mode::None => Theme::dark(),
1646        }
1647    }
1648
1649    fn mode(&self) -> iced::theme::Mode {
1650        if self.theme_type.is_dark() {
1651            iced::theme::Mode::Dark
1652        } else {
1653            iced::theme::Mode::Light
1654        }
1655    }
1656
1657    fn base(&self) -> iced::theme::Style {
1658        iced::theme::Style {
1659            background_color: self.cosmic().bg_color().into(),
1660            text_color: self.cosmic().on_bg_color().into(),
1661            icon_color: self.cosmic().on_bg_color().into(),
1662        }
1663    }
1664
1665    fn palette(&self) -> Option<iced::theme::Palette> {
1666        Some(iced::theme::Palette {
1667            primary: self.cosmic().accent.base.into(),
1668            success: self.cosmic().success.base.into(),
1669            warning: self.cosmic().warning.base.into(),
1670            danger: self.cosmic().destructive.base.into(),
1671            background: iced::Color::from(self.cosmic().bg_color()),
1672            text: iced::Color::from(self.cosmic().on_bg_color()),
1673        })
1674    }
1675
1676    fn name(&self) -> &str {
1677        match &self.theme_type {
1678            crate::theme::ThemeType::Dark => "Cosmic Dark Theme",
1679            crate::theme::ThemeType::Light => "Cosmic Light Theme",
1680            crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme",
1681            crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme",
1682            crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme",
1683            crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name,
1684        }
1685    }
1686}