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