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