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: 20,
779                            width: 20,
780                            border_radius: cosmic.corner_radii.radius_m.into(),
781                        },
782                        border_color: Color::TRANSPARENT,
783                        border_width: 0.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.shape = slider::HandleShape::Rectangle {
799                        height: 26,
800                        width: 26,
801                        border_radius: cosmic.corner_radii.radius_m.into(),
802                    };
803                    appearance.handle.border_width = 3.0;
804                    appearance.handle.border_color =
805                        self.cosmic().palette.neutral_10.with_alpha(0.1).into();
806                    appearance
807                }
808                Slider::Custom { hovered, .. } => hovered(self),
809            },
810            slider::Status::Dragged => match class {
811                Slider::Standard => {
812                    let mut style = {
813                        appearance.handle.shape = slider::HandleShape::Rectangle {
814                            height: 26,
815                            width: 26,
816                            border_radius: cosmic.corner_radii.radius_m.into(),
817                        };
818                        appearance.handle.border_width = 3.0;
819                        appearance.handle.border_color =
820                            self.cosmic().palette.neutral_10.with_alpha(0.1).into();
821                        appearance
822                    };
823                    style.handle.border_color =
824                        self.cosmic().palette.neutral_10.with_alpha(0.2).into();
825                    style
826                }
827                Slider::Custom { dragging, .. } => dragging(self),
828            },
829        }
830    }
831}
832
833impl menu::Catalog for Theme {
834    type Class<'a> = ();
835
836    fn default<'a>() -> <Self as menu::Catalog>::Class<'a> {}
837
838    fn style(&self, class: &<Self as menu::Catalog>::Class<'_>) -> menu::Style {
839        let cosmic = self.cosmic();
840
841        menu::Style {
842            text_color: cosmic.on_bg_color().into(),
843            background: Background::Color(cosmic.background(self.transparent).base.into()),
844            border: Border {
845                radius: cosmic.corner_radii.radius_m.into(),
846                ..Default::default()
847            },
848            selected_text_color: cosmic.accent_text_color().into(),
849            selected_background: Background::Color(
850                cosmic.background(self.transparent).component.hover.into(),
851            ),
852            shadow: Default::default(),
853        }
854    }
855}
856
857impl pick_list::Catalog for Theme {
858    type Class<'a> = ();
859
860    fn default<'a>() -> <Self as pick_list::Catalog>::Class<'a> {}
861
862    fn style(
863        &self,
864        class: &<Self as pick_list::Catalog>::Class<'_>,
865        status: pick_list::Status,
866    ) -> pick_list::Style {
867        let cosmic = &self.cosmic();
868        let hc = cosmic.is_high_contrast;
869        let appearance = pick_list::Style {
870            text_color: cosmic.on_bg_color().into(),
871            background: Color::TRANSPARENT.into(),
872            placeholder_color: cosmic.on_bg_color().into(),
873            border: Border {
874                radius: cosmic.corner_radii.radius_m.into(),
875                width: if hc { 1. } else { 0. },
876                color: if hc {
877                    self.current_container().component.border.into()
878                } else {
879                    Color::TRANSPARENT
880                },
881            },
882            // icon_size: 0.7, // TODO: how to replace
883            handle_color: cosmic.on_bg_color().into(),
884        };
885
886        match status {
887            pick_list::Status::Active => appearance,
888            pick_list::Status::Hovered => pick_list::Style {
889                background: Background::Color(cosmic.background(self.transparent).base.into()),
890                ..appearance
891            },
892            pick_list::Status::Opened { is_hovered: _ } => appearance,
893        }
894    }
895}
896
897/*
898 * TODO: Radio
899 */
900impl radio::Catalog for Theme {
901    type Class<'a> = ();
902
903    fn default<'a>() -> Self::Class<'a> {}
904
905    fn style(&self, class: &Self::Class<'_>, status: radio::Status) -> radio::Style {
906        let cur_container = self.current_container();
907        let theme = self.cosmic();
908
909        match status {
910            radio::Status::Active { is_selected } => radio::Style {
911                background: if is_selected {
912                    Color::from(theme.accent.base).into()
913                } else {
914                    // TODO: this seems to be defined weirdly in FIGMA
915                    Color::from(cur_container.small_widget).into()
916                },
917                dot_color: theme.accent.on.into(),
918                border_width: 1.0,
919                border_color: if is_selected {
920                    Color::from(theme.accent.base)
921                } else {
922                    Color::from(theme.palette.neutral_8)
923                },
924                text_color: None,
925            },
926            radio::Status::Hovered { is_selected } => {
927                let bg = if is_selected {
928                    theme.accent.base
929                } else {
930                    self.current_container().small_widget
931                };
932                // TODO: this should probably be done with a custom widget instead, or the theme needs more small widget variables.
933                let hovered_bg = Color::from(over(theme.palette.neutral_0.with_alpha(0.1), bg));
934                radio::Style {
935                    background: hovered_bg.into(),
936                    dot_color: theme.accent.on.into(),
937                    border_width: 1.0,
938                    border_color: if is_selected {
939                        Color::from(theme.accent.base)
940                    } else {
941                        Color::from(theme.palette.neutral_8)
942                    },
943                    text_color: None,
944                }
945            }
946        }
947    }
948}
949
950/*
951 * Toggler
952 */
953impl toggler::Catalog for Theme {
954    type Class<'a> = ();
955
956    fn default<'a>() -> Self::Class<'a> {}
957
958    fn style(&self, class: &Self::Class<'_>, status: toggler::Status) -> toggler::Style {
959        let cosmic = self.cosmic();
960        const HANDLE_MARGIN: f32 = 2.0;
961        let neutral_10 = cosmic.palette.neutral_10.with_alpha(0.1);
962
963        let mut active = toggler::Style {
964            background: if matches!(status, toggler::Status::Active { is_toggled: true }) {
965                cosmic.accent.base.into()
966            } else if cosmic.is_dark {
967                cosmic.palette.neutral_6.into()
968            } else {
969                cosmic.palette.neutral_5.into()
970            },
971            foreground: cosmic.palette.neutral_2.into(),
972            border_radius: cosmic.radius_xl().into(),
973            handle_radius: cosmic
974                .radius_xl()
975                .map(|x| (x - HANDLE_MARGIN).max(0.0))
976                .into(),
977            handle_margin: HANDLE_MARGIN,
978            background_border_width: 0.0,
979            background_border_color: Color::TRANSPARENT,
980            foreground_border_width: 0.0,
981            foreground_border_color: Color::TRANSPARENT,
982            text_color: None,
983            padding_ratio: 0.0,
984        };
985        match status {
986            toggler::Status::Active { is_toggled } => active,
987            toggler::Status::Hovered { is_toggled } => {
988                let is_active = matches!(status, toggler::Status::Hovered { is_toggled: true });
989                toggler::Style {
990                    background: if is_active {
991                        over(neutral_10, cosmic.accent_color())
992                    } else {
993                        over(
994                            neutral_10,
995                            if cosmic.is_dark {
996                                cosmic.palette.neutral_6
997                            } else {
998                                cosmic.palette.neutral_5
999                            },
1000                        )
1001                    }
1002                    .into(),
1003                    ..active
1004                }
1005            }
1006            toggler::Status::Disabled { is_toggled } => {
1007                active.background = active.background.scale_alpha(0.5);
1008                active.foreground = active.foreground.scale_alpha(0.5);
1009                active
1010            }
1011        }
1012    }
1013}
1014
1015/*
1016 * TODO: Pane Grid
1017 */
1018impl pane_grid::Catalog for Theme {
1019    type Class<'a> = ();
1020
1021    fn default<'a>() -> <Self as pane_grid::Catalog>::Class<'a> {}
1022
1023    fn style(&self, class: &<Self as pane_grid::Catalog>::Class<'_>) -> pane_grid::Style {
1024        let theme = self.cosmic();
1025
1026        pane_grid::Style {
1027            hovered_region: Highlight {
1028                background: Background::Color(theme.bg_color().into()),
1029                border: Border {
1030                    radius: theme.corner_radii.radius_0.into(),
1031                    width: 2.0,
1032                    color: theme.bg_divider().into(),
1033                },
1034            },
1035            picked_split: pane_grid::Line {
1036                color: theme.accent.base.into(),
1037                width: 2.0,
1038            },
1039            hovered_split: pane_grid::Line {
1040                color: theme.accent.hover.into(),
1041                width: 2.0,
1042            },
1043        }
1044    }
1045}
1046
1047/*
1048 * TODO: Progress Bar
1049 */
1050#[derive(Default)]
1051pub enum ProgressBar {
1052    #[default]
1053    Primary,
1054    Success,
1055    Danger,
1056    Custom(Box<dyn Fn(&Theme) -> progress_bar::Style>),
1057}
1058
1059impl ProgressBar {
1060    pub fn custom<F: Fn(&Theme) -> progress_bar::Style + 'static>(f: F) -> Self {
1061        Self::Custom(Box::new(f))
1062    }
1063}
1064
1065impl progress_bar::Catalog for Theme {
1066    type Class<'a> = ProgressBar;
1067
1068    fn default<'a>() -> Self::Class<'a> {
1069        ProgressBar::default()
1070    }
1071
1072    fn style(&self, class: &Self::Class<'_>) -> progress_bar::Style {
1073        let theme = self.cosmic();
1074
1075        let (active_track, inactive_track) = if theme.is_high_contrast {
1076            (
1077                theme.accent_text_color(),
1078                if theme.is_dark {
1079                    theme.palette.neutral_6
1080                } else {
1081                    theme.palette.neutral_4
1082                },
1083            )
1084        } else {
1085            (
1086                theme.accent.base,
1087                theme.background(self.transparent).divider,
1088            )
1089        };
1090        let border = Border {
1091            radius: theme.corner_radii.radius_xl.into(),
1092            color: if theme.is_high_contrast && !theme.is_dark {
1093                self.current_container().component.border.into()
1094            } else {
1095                Color::TRANSPARENT
1096            },
1097            width: if theme.is_high_contrast && !theme.is_dark {
1098                1.
1099            } else {
1100                0.
1101            },
1102        };
1103        match class {
1104            ProgressBar::Primary => progress_bar::Style {
1105                background: Color::from(inactive_track).into(),
1106                bar: Color::from(active_track).into(),
1107                border,
1108            },
1109            ProgressBar::Success => progress_bar::Style {
1110                background: Color::from(inactive_track).into(),
1111                bar: Color::from(theme.success.base).into(),
1112                border,
1113            },
1114            ProgressBar::Danger => progress_bar::Style {
1115                background: Color::from(inactive_track).into(),
1116                bar: Color::from(theme.destructive.base).into(),
1117                border,
1118            },
1119            ProgressBar::Custom(f) => f(self),
1120        }
1121    }
1122}
1123
1124/*
1125 * TODO: Rule
1126 */
1127#[derive(Default)]
1128pub enum Rule {
1129    #[default]
1130    Default,
1131    LightDivider,
1132    HeavyDivider,
1133    Custom(Box<dyn Fn(&Theme) -> rule::Style>),
1134}
1135
1136impl Rule {
1137    pub fn custom<F: Fn(&Theme) -> rule::Style + 'static>(f: F) -> Self {
1138        Self::Custom(Box::new(f))
1139    }
1140}
1141
1142impl rule::Catalog for Theme {
1143    type Class<'a> = Rule;
1144
1145    fn default<'a>() -> Self::Class<'a> {
1146        Rule::default()
1147    }
1148
1149    fn style(&self, class: &Self::Class<'_>) -> rule::Style {
1150        match class {
1151            Rule::Default => rule::Style {
1152                color: self.current_container().divider.into(),
1153                radius: 0.0.into(),
1154                fill_mode: rule::FillMode::Full,
1155                snap: true,
1156            },
1157            Rule::LightDivider => rule::Style {
1158                color: self.current_container().divider.into(),
1159                radius: 0.0.into(),
1160                fill_mode: rule::FillMode::Padded(8),
1161                snap: true,
1162            },
1163            Rule::HeavyDivider => rule::Style {
1164                color: self.current_container().divider.into(),
1165                radius: 2.0.into(),
1166                fill_mode: rule::FillMode::Full,
1167                snap: true,
1168            },
1169            Rule::Custom(f) => f(self),
1170        }
1171    }
1172}
1173
1174#[derive(Default, Clone, Copy)]
1175pub enum Scrollable {
1176    #[default]
1177    Permanent,
1178    Minimal,
1179}
1180
1181/*
1182 * TODO: Scrollable
1183 */
1184impl scrollable::Catalog for Theme {
1185    type Class<'a> = Scrollable;
1186
1187    fn default<'a>() -> Self::Class<'a> {
1188        Scrollable::default()
1189    }
1190
1191    fn style(&self, class: &Self::Class<'_>, status: scrollable::Status) -> scrollable::Style {
1192        match status {
1193            scrollable::Status::Active {
1194                is_horizontal_scrollbar_disabled,
1195                is_vertical_scrollbar_disabled,
1196            } => {
1197                let cosmic = self.cosmic();
1198                let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
1199                let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
1200                let mut a = scrollable::Style {
1201                    container: iced_container::transparent(self),
1202                    vertical_rail: scrollable::Rail {
1203                        border: Border {
1204                            radius: cosmic.corner_radii.radius_s.into(),
1205                            ..Default::default()
1206                        },
1207                        background: None,
1208                        scroller: scrollable::Scroller {
1209                            background: if cosmic.is_dark {
1210                                neutral_6.into()
1211                            } else {
1212                                neutral_5.into()
1213                            },
1214                            border: Border {
1215                                radius: cosmic.corner_radii.radius_s.into(),
1216                                ..Default::default()
1217                            },
1218                        },
1219                    },
1220                    horizontal_rail: scrollable::Rail {
1221                        border: Border {
1222                            radius: cosmic.corner_radii.radius_s.into(),
1223                            ..Default::default()
1224                        },
1225                        background: None,
1226                        scroller: scrollable::Scroller {
1227                            background: if cosmic.is_dark {
1228                                neutral_6.into()
1229                            } else {
1230                                neutral_5.into()
1231                            },
1232                            border: Border {
1233                                radius: cosmic.corner_radii.radius_s.into(),
1234                                ..Default::default()
1235                            },
1236                        },
1237                    },
1238                    gap: None,
1239                    // TODO: what is auto scroll?
1240                    auto_scroll: AutoScroll {
1241                        background: Color::TRANSPARENT.into(),
1242                        border: Border::default(),
1243                        shadow: Shadow::default(),
1244                        icon: Color::TRANSPARENT.into(),
1245                    },
1246                };
1247                let small_widget_container = self.current_container().small_widget.with_alpha(0.7);
1248
1249                if matches!(class, Scrollable::Permanent) {
1250                    a.horizontal_rail.background =
1251                        Some(Background::Color(small_widget_container.into()));
1252                    a.vertical_rail.background =
1253                        Some(Background::Color(small_widget_container.into()));
1254                }
1255
1256                a
1257            }
1258            // TODO handle vertical / horizontal
1259            scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => {
1260                let cosmic = self.cosmic();
1261                let neutral_5 = cosmic.palette.neutral_5.with_alpha(0.7);
1262                let neutral_6 = cosmic.palette.neutral_6.with_alpha(0.7);
1263
1264                // if is_mouse_over_scrollbar {
1265                //     let hover_overlay = cosmic.palette.neutral_0.with_alpha(0.2);
1266                //     neutral_5 = over(hover_overlay, neutral_5);
1267                // }
1268                let mut a: scrollable::Style = scrollable::Style {
1269                    container: iced_container::Style::default(),
1270                    vertical_rail: scrollable::Rail {
1271                        border: Border {
1272                            radius: cosmic.corner_radii.radius_s.into(),
1273                            ..Default::default()
1274                        },
1275                        background: None,
1276                        scroller: scrollable::Scroller {
1277                            background: if cosmic.is_dark {
1278                                neutral_6.into()
1279                            } else {
1280                                neutral_5.into()
1281                            },
1282                            border: Border {
1283                                radius: cosmic.corner_radii.radius_s.into(),
1284                                ..Default::default()
1285                            },
1286                        },
1287                    },
1288                    horizontal_rail: scrollable::Rail {
1289                        border: Border {
1290                            radius: cosmic.corner_radii.radius_s.into(),
1291                            ..Default::default()
1292                        },
1293                        background: None,
1294                        scroller: scrollable::Scroller {
1295                            background: if cosmic.is_dark {
1296                                neutral_6.into()
1297                            } else {
1298                                neutral_5.into()
1299                            },
1300                            border: Border {
1301                                radius: cosmic.corner_radii.radius_s.into(),
1302                                ..Default::default()
1303                            },
1304                        },
1305                    },
1306                    gap: None,
1307                    // TODO: what is auto scroll?
1308                    auto_scroll: AutoScroll {
1309                        background: Color::TRANSPARENT.into(),
1310                        border: Border::default(),
1311                        shadow: Shadow::default(),
1312                        icon: Color::TRANSPARENT.into(),
1313                    },
1314                };
1315
1316                if matches!(class, Scrollable::Permanent) {
1317                    let small_widget_container =
1318                        self.current_container().small_widget.with_alpha(0.7);
1319
1320                    a.horizontal_rail.background =
1321                        Some(Background::Color(small_widget_container.into()));
1322                    a.vertical_rail.background =
1323                        Some(Background::Color(small_widget_container.into()));
1324                }
1325
1326                a
1327            }
1328        }
1329    }
1330}
1331
1332#[derive(Clone, Default)]
1333pub enum Svg {
1334    /// Apply a custom appearance filter
1335    Custom(Rc<dyn Fn(&Theme) -> svg::Style>),
1336    /// No filtering is applied
1337    #[default]
1338    Default,
1339}
1340
1341impl Svg {
1342    pub fn custom<F: Fn(&Theme) -> svg::Style + 'static>(f: F) -> Self {
1343        Self::Custom(Rc::new(f))
1344    }
1345}
1346
1347impl svg::Catalog for Theme {
1348    type Class<'a> = Svg;
1349
1350    fn default<'a>() -> Self::Class<'a> {
1351        Svg::default()
1352    }
1353
1354    fn style(&self, class: &Self::Class<'_>, status: svg::Status) -> svg::Style {
1355        #[allow(clippy::match_same_arms)]
1356        match class {
1357            Svg::Default => svg::Style::default(),
1358            Svg::Custom(appearance) => appearance(self),
1359        }
1360    }
1361}
1362
1363/*
1364 * TODO: Text
1365 */
1366#[derive(Clone, Copy, Default)]
1367pub enum Text {
1368    Accent,
1369    #[default]
1370    Default,
1371    Color(Color),
1372    // TODO: Can't use dyn Fn since this must be copy
1373    Custom(fn(&Theme) -> iced_widget::text::Style),
1374}
1375
1376impl From<Color> for Text {
1377    fn from(color: Color) -> Self {
1378        Self::Color(color)
1379    }
1380}
1381
1382impl iced_widget::text::Catalog for Theme {
1383    type Class<'a> = Text;
1384
1385    fn default<'a>() -> Self::Class<'a> {
1386        Text::default()
1387    }
1388
1389    fn style(&self, class: &Self::Class<'_>) -> iced_widget::text::Style {
1390        match class {
1391            Text::Accent => iced_widget::text::Style {
1392                color: Some(self.cosmic().accent_text_color().into()),
1393                ..Default::default()
1394            },
1395            Text::Default => iced_widget::text::Style {
1396                color: None,
1397                ..Default::default()
1398            },
1399            Text::Color(c) => iced_widget::text::Style {
1400                color: Some(*c),
1401                ..Default::default()
1402            },
1403            Text::Custom(f) => f(self),
1404        }
1405    }
1406}
1407
1408#[derive(Copy, Clone, Default)]
1409pub enum TextInput {
1410    #[default]
1411    Default,
1412    Search,
1413}
1414
1415/*
1416 * TODO: Text Input
1417 */
1418impl text_input::Catalog for Theme {
1419    type Class<'a> = TextInput;
1420
1421    fn default<'a>() -> Self::Class<'a> {
1422        TextInput::default()
1423    }
1424
1425    fn style(&self, class: &Self::Class<'_>, status: text_input::Status) -> text_input::Style {
1426        let palette = self.cosmic();
1427        let bg = self.current_container().small_widget.with_alpha(0.25);
1428
1429        let neutral_9 = palette.palette.neutral_9;
1430        let value = neutral_9.into();
1431        let placeholder = neutral_9.with_alpha(0.7).into();
1432        let selection = palette.accent.base.into();
1433
1434        let mut appearance = match class {
1435            TextInput::Default => text_input::Style {
1436                background: Color::from(bg).into(),
1437                border: Border {
1438                    radius: palette.corner_radii.radius_s.into(),
1439                    width: 1.0,
1440                    color: self.current_container().component.divider.into(),
1441                },
1442                icon: self.current_container().on.into(),
1443                placeholder,
1444                value,
1445                selection,
1446            },
1447            TextInput::Search => text_input::Style {
1448                background: Color::from(bg).into(),
1449                border: Border {
1450                    radius: palette.corner_radii.radius_m.into(),
1451                    ..Default::default()
1452                },
1453                icon: self.current_container().on.into(),
1454                placeholder,
1455                value,
1456                selection,
1457            },
1458        };
1459
1460        match status {
1461            text_input::Status::Active => appearance,
1462            text_input::Status::Hovered => {
1463                let bg = self.current_container().small_widget.with_alpha(0.25);
1464
1465                match class {
1466                    TextInput::Default => text_input::Style {
1467                        background: Color::from(bg).into(),
1468                        border: Border {
1469                            radius: palette.corner_radii.radius_s.into(),
1470                            width: 1.0,
1471                            color: self.current_container().on.into(),
1472                        },
1473                        icon: self.current_container().on.into(),
1474                        placeholder,
1475                        value,
1476                        selection,
1477                    },
1478                    TextInput::Search => text_input::Style {
1479                        background: Color::from(bg).into(),
1480                        border: Border {
1481                            radius: palette.corner_radii.radius_m.into(),
1482                            ..Default::default()
1483                        },
1484                        icon: self.current_container().on.into(),
1485                        placeholder,
1486                        value,
1487                        selection,
1488                    },
1489                }
1490            }
1491            text_input::Status::Focused { is_hovered } => {
1492                let bg = self.current_container().small_widget.with_alpha(0.25);
1493
1494                match class {
1495                    TextInput::Default => text_input::Style {
1496                        background: Color::from(bg).into(),
1497                        border: Border {
1498                            radius: palette.corner_radii.radius_s.into(),
1499                            width: 1.0,
1500                            color: palette.accent.base.into(),
1501                        },
1502                        icon: self.current_container().on.into(),
1503                        placeholder,
1504                        value,
1505                        selection,
1506                    },
1507                    TextInput::Search => text_input::Style {
1508                        background: Color::from(bg).into(),
1509                        border: Border {
1510                            radius: palette.corner_radii.radius_m.into(),
1511                            ..Default::default()
1512                        },
1513                        icon: self.current_container().on.into(),
1514                        placeholder,
1515                        value,
1516                        selection,
1517                    },
1518                }
1519            }
1520            text_input::Status::Disabled => {
1521                appearance.background = match appearance.background {
1522                    Background::Color(color) => Background::Color(Color {
1523                        a: color.a * 0.5,
1524                        ..color
1525                    }),
1526                    Background::Gradient(gradient) => {
1527                        Background::Gradient(gradient.scale_alpha(0.5))
1528                    }
1529                };
1530                appearance.border.color.a /= 2.;
1531                appearance.icon.a /= 2.;
1532                appearance.placeholder.a /= 2.;
1533                appearance.value.a /= 2.;
1534                appearance
1535            }
1536        }
1537    }
1538}
1539
1540#[derive(Default)]
1541pub enum TextEditor<'a> {
1542    #[default]
1543    Default,
1544    Custom(text_editor::StyleFn<'a, Theme>),
1545}
1546
1547impl iced_widget::text_editor::Catalog for Theme {
1548    type Class<'a> = TextEditor<'a>;
1549
1550    fn default<'a>() -> Self::Class<'a> {
1551        TextEditor::default()
1552    }
1553
1554    fn style(
1555        &self,
1556        class: &Self::Class<'_>,
1557        status: iced_widget::text_editor::Status,
1558    ) -> iced_widget::text_editor::Style {
1559        if let TextEditor::Custom(style) = class {
1560            return style(self, status);
1561        }
1562
1563        let cosmic = self.cosmic();
1564
1565        let selection = cosmic.accent.base.into();
1566        let value = cosmic.palette.neutral_9.into();
1567        let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into();
1568        let icon: Color = cosmic.background(self.transparent).on.into();
1569        // TODO do we need to add icon color back?
1570
1571        match status {
1572            iced_widget::text_editor::Status::Active
1573            | iced_widget::text_editor::Status::Hovered
1574            | iced_widget::text_editor::Status::Disabled => iced_widget::text_editor::Style {
1575                background: iced::Color::from(cosmic.bg_color()).into(),
1576                border: Border {
1577                    radius: cosmic.corner_radii.radius_0.into(),
1578                    width: f32::from(cosmic.space_xxxs()),
1579                    color: iced::Color::from(cosmic.bg_divider()),
1580                },
1581                placeholder,
1582                value,
1583                selection,
1584            },
1585            iced_widget::text_editor::Status::Focused { is_hovered } => {
1586                iced_widget::text_editor::Style {
1587                    background: iced::Color::from(cosmic.bg_color()).into(),
1588                    border: Border {
1589                        radius: cosmic.corner_radii.radius_0.into(),
1590                        width: f32::from(cosmic.space_xxxs()),
1591                        color: iced::Color::from(cosmic.accent.base),
1592                    },
1593                    placeholder,
1594                    value,
1595                    selection,
1596                }
1597            }
1598        }
1599    }
1600}
1601
1602#[cfg(feature = "markdown")]
1603impl iced_widget::markdown::Catalog for Theme {
1604    fn code_block<'a>() -> <Self as iced_container::Catalog>::Class<'a> {
1605        Container::custom(|_| iced_container::Style {
1606            background: Some(iced::color!(0x111111).into()),
1607            text_color: Some(Color::WHITE),
1608            border: iced::border::rounded(2),
1609            ..iced_container::Style::default()
1610        })
1611    }
1612}
1613
1614impl iced_widget::table::Catalog for Theme {
1615    type Class<'a> = iced_widget::table::StyleFn<'a, Self>;
1616
1617    fn default<'a>() -> Self::Class<'a> {
1618        Box::new(|theme| iced_widget::table::Style {
1619            separator_x: theme.current_container().divider.into(),
1620            separator_y: theme.current_container().divider.into(),
1621        })
1622    }
1623
1624    fn style(&self, class: &Self::Class<'_>) -> iced_widget::table::Style {
1625        class(self)
1626    }
1627}
1628
1629#[cfg(feature = "qr_code")]
1630impl iced_widget::qr_code::Catalog for Theme {
1631    type Class<'a> = iced_widget::qr_code::StyleFn<'a, Self>;
1632
1633    fn default<'a>() -> Self::Class<'a> {
1634        Box::new(|_theme| iced_widget::qr_code::Style {
1635            cell: Color::BLACK,
1636            background: Color::WHITE,
1637        })
1638    }
1639
1640    fn style(&self, class: &Self::Class<'_>) -> iced_widget::qr_code::Style {
1641        class(self)
1642    }
1643}
1644
1645impl combo_box::Catalog for Theme {}
1646
1647impl Base for Theme {
1648    fn default(preference: iced::theme::Mode) -> Self {
1649        match preference {
1650            iced::theme::Mode::Light => Theme::light(),
1651            iced::theme::Mode::Dark | iced::theme::Mode::None => Theme::dark(),
1652        }
1653    }
1654
1655    fn mode(&self) -> iced::theme::Mode {
1656        if self.theme_type.is_dark() {
1657            iced::theme::Mode::Dark
1658        } else {
1659            iced::theme::Mode::Light
1660        }
1661    }
1662
1663    fn base(&self) -> iced::theme::Style {
1664        iced::theme::Style {
1665            background_color: self.cosmic().bg_color().into(),
1666            text_color: self.cosmic().on_bg_color().into(),
1667            icon_color: self.cosmic().on_bg_color().into(),
1668        }
1669    }
1670
1671    fn palette(&self) -> Option<iced::theme::Palette> {
1672        Some(iced::theme::Palette {
1673            primary: self.cosmic().accent.base.into(),
1674            success: self.cosmic().success.base.into(),
1675            warning: self.cosmic().warning.base.into(),
1676            danger: self.cosmic().destructive.base.into(),
1677            background: iced::Color::from(self.cosmic().bg_color()),
1678            text: iced::Color::from(self.cosmic().on_bg_color()),
1679        })
1680    }
1681
1682    fn name(&self) -> &str {
1683        match &self.theme_type {
1684            crate::theme::ThemeType::Dark => "Cosmic Dark Theme",
1685            crate::theme::ThemeType::Light => "Cosmic Light Theme",
1686            crate::theme::ThemeType::HighContrastDark => "Cosmic High Contrast Dark Theme",
1687            crate::theme::ThemeType::HighContrastLight => "Cosmic High Contrast Light Theme",
1688            crate::theme::ThemeType::Custom(theme) => "Custom Cosmic Theme",
1689            crate::theme::ThemeType::System { prefer_dark, theme } => &theme.name,
1690        }
1691    }
1692}