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