1use crate::{
2 composite::over,
3 steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps},
4 Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, ThemeMode,
5 DARK_PALETTE, LIGHT_PALETTE, NAME,
6};
7use cosmic_config::{Config, CosmicConfigEntry};
8use palette::{
9 color_difference::Wcag21RelativeContrast, rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba, WithAlpha,
10};
11use serde::{Deserialize, Serialize};
12use std::num::NonZeroUsize;
13
14pub const DARK_THEME_BUILDER_ID: &str = "com.system76.CosmicTheme.Dark.Builder";
16
17pub const DARK_THEME_ID: &str = "com.system76.CosmicTheme.Dark";
19
20pub const LIGHT_THEME_BUILDER_ID: &str = "com.system76.CosmicTheme.Light.Builder";
22
23pub const LIGHT_THEME_ID: &str = "com.system76.CosmicTheme.Light";
25
26#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
27pub enum Layer {
29 #[default]
31 Background,
32 Primary,
34 Secondary,
36}
37
38#[must_use]
39#[derive(
41 Clone,
42 Debug,
43 Serialize,
44 Deserialize,
45 PartialEq,
46 cosmic_config::cosmic_config_derive::CosmicConfigEntry,
47)]
48#[version = 1]
49pub struct Theme {
50 pub name: String,
52 pub background: Container,
54 pub primary: Container,
56 pub secondary: Container,
58 pub accent: Component,
60 pub success: Component,
62 pub destructive: Component,
64 pub warning: Component,
66 pub accent_button: Component,
68 pub success_button: Component,
70 pub destructive_button: Component,
72 pub warning_button: Component,
74 pub icon_button: Component,
76 pub link_button: Component,
78 pub text_button: Component,
80 pub button: Component,
82 pub palette: CosmicPaletteInner,
84 pub spacing: Spacing,
86 pub corner_radii: CornerRadii,
88 pub is_dark: bool,
90 pub is_high_contrast: bool,
92 pub gaps: (u32, u32),
94 pub active_hint: u32,
96 pub window_hint: Option<Srgb>,
98 pub is_frosted: bool,
100 pub shade: Srgba,
102 pub accent_text: Option<Srgba>,
105 pub control_tint: Option<Srgb>,
107 pub text_tint: Option<Srgb>,
109}
110
111impl Default for Theme {
112 #[inline]
113 fn default() -> Self {
114 Self::preferred_theme()
115 }
116}
117
118pub trait LayeredTheme {
120 fn set_layer(&mut self, layer: Layer);
122}
123
124impl Theme {
125 #[must_use]
126 pub fn id() -> &'static str {
128 NAME
129 }
130
131 #[inline]
132 pub fn dark_config() -> Result<Config, cosmic_config::Error> {
134 Config::new(DARK_THEME_ID, Self::VERSION)
135 }
136
137 #[inline]
138 pub fn light_config() -> Result<Config, cosmic_config::Error> {
140 Config::new(LIGHT_THEME_ID, Self::VERSION)
141 }
142
143 #[inline]
144 pub fn light_default() -> Self {
146 LIGHT_PALETTE.clone().into()
147 }
148
149 #[inline]
150 pub fn dark_default() -> Self {
152 DARK_PALETTE.clone().into()
153 }
154
155 #[inline]
156 pub fn high_contrast_dark_default() -> Self {
158 CosmicPalette::HighContrastDark(DARK_PALETTE.as_ref().clone()).into()
159 }
160
161 #[inline]
162 pub fn high_contrast_light_default() -> Self {
164 CosmicPalette::HighContrastLight(LIGHT_PALETTE.as_ref().clone()).into()
165 }
166
167 #[inline]
168 pub fn to_high_contrast(&self) -> Self {
170 todo!();
171 }
172
173 #[must_use]
174 #[allow(clippy::doc_markdown)]
175 #[inline]
176 pub fn control_0(&self) -> Srgba {
178 self.tint_neutral(self.palette.neutral_0)
179 }
180
181 #[must_use]
182 #[allow(clippy::doc_markdown)]
183 #[inline]
184 pub fn control_1(&self) -> Srgba {
186 self.tint_neutral(self.palette.neutral_1)
187 }
188
189 #[must_use]
190 #[allow(clippy::doc_markdown)]
191 #[inline]
192 pub fn control_2(&self) -> Srgba {
194 self.tint_neutral(self.palette.neutral_2)
195 }
196
197 #[must_use]
198 #[allow(clippy::doc_markdown)]
199 #[inline]
200 pub fn control_3(&self) -> Srgba {
202 self.tint_neutral(self.palette.neutral_3)
203 }
204
205 #[must_use]
206 #[allow(clippy::doc_markdown)]
207 #[inline]
208 pub fn control_4(&self) -> Srgba {
210 self.tint_neutral(self.palette.neutral_4)
211 }
212
213 #[must_use]
214 #[allow(clippy::doc_markdown)]
215 #[inline]
216 pub fn control_5(&self) -> Srgba {
218 self.tint_neutral(self.palette.neutral_5)
219 }
220
221 #[must_use]
222 #[allow(clippy::doc_markdown)]
223 #[inline]
224 pub fn control_6(&self) -> Srgba {
226 self.tint_neutral(self.palette.neutral_6)
227 }
228
229 #[must_use]
230 #[allow(clippy::doc_markdown)]
231 #[inline]
232 pub fn control_7(&self) -> Srgba {
234 self.tint_neutral(self.palette.neutral_7)
235 }
236
237 #[must_use]
238 #[allow(clippy::doc_markdown)]
239 #[inline]
240 pub fn control_8(&self) -> Srgba {
242 self.tint_neutral(self.palette.neutral_8)
243 }
244
245 #[must_use]
246 #[allow(clippy::doc_markdown)]
247 #[inline]
248 pub fn control_9(&self) -> Srgba {
250 self.tint_neutral(self.palette.neutral_9)
251 }
252
253 #[must_use]
254 #[allow(clippy::doc_markdown)]
255 #[inline]
256 pub fn control_10(&self) -> Srgba {
258 self.tint_neutral(self.palette.neutral_10)
259 }
260
261 #[must_use]
262 #[allow(clippy::doc_markdown)]
263 #[inline]
264 fn tint_neutral(&self, neutral: Srgba) -> Srgba {
266 let Some(tint) = self.control_tint else {
267 return neutral;
268 };
269 let mut oklch_neutral: Oklcha = neutral.into_color();
270 let oklch_tint: Oklcha = tint.into_color();
271 oklch_neutral.hue = oklch_tint.hue;
272 oklch_neutral.chroma = oklch_tint.chroma;
273 oklch_neutral.into_color()
274 }
275
276 #[must_use]
278 #[allow(clippy::doc_markdown)]
279 #[inline]
280 pub fn accent_color(&self) -> Srgba {
282 self.accent.base
283 }
284
285 #[must_use]
286 #[allow(clippy::doc_markdown)]
287 #[inline]
288 pub fn success_color(&self) -> Srgba {
290 self.success.base
291 }
292
293 #[must_use]
294 #[allow(clippy::doc_markdown)]
295 #[inline]
296 pub fn destructive_color(&self) -> Srgba {
298 self.destructive.base
299 }
300
301 #[must_use]
302 #[allow(clippy::doc_markdown)]
303 #[inline]
304 pub fn warning_color(&self) -> Srgba {
306 self.warning.base
307 }
308
309 #[must_use]
310 #[allow(clippy::doc_markdown)]
311 #[inline]
312 pub fn small_widget_divider(&self) -> Srgba {
314 self.palette.neutral_9.with_alpha(0.2)
315 }
316
317 #[must_use]
319 #[allow(clippy::doc_markdown)]
320 #[inline]
321 pub fn bg_color(&self) -> Srgba {
323 self.background.base
324 }
325
326 #[must_use]
327 #[allow(clippy::doc_markdown)]
328 #[inline]
329 pub fn bg_component_color(&self) -> Srgba {
331 self.background.component.base
332 }
333
334 #[must_use]
335 #[allow(clippy::doc_markdown)]
336 #[inline]
337 pub fn primary_container_color(&self) -> Srgba {
339 self.primary.base
340 }
341
342 #[must_use]
343 #[allow(clippy::doc_markdown)]
344 #[inline]
345 pub fn primary_component_color(&self) -> Srgba {
347 self.primary.component.base
348 }
349
350 #[must_use]
351 #[allow(clippy::doc_markdown)]
352 #[inline]
353 pub fn secondary_container_color(&self) -> Srgba {
355 self.secondary.base
356 }
357
358 #[must_use]
359 #[allow(clippy::doc_markdown)]
360 #[inline]
361 pub fn secondary_component_color(&self) -> Srgba {
363 self.secondary.component.base
364 }
365
366 #[must_use]
367 #[allow(clippy::doc_markdown)]
368 #[inline]
369 pub fn button_bg_color(&self) -> Srgba {
371 self.button.base
372 }
373
374 #[must_use]
376 #[allow(clippy::doc_markdown)]
377 #[inline]
378 pub fn on_bg_color(&self) -> Srgba {
380 self.background.on
381 }
382
383 #[must_use]
384 #[allow(clippy::doc_markdown)]
385 #[inline]
386 pub fn on_bg_component_color(&self) -> Srgba {
388 self.background.component.on
389 }
390
391 #[must_use]
392 #[allow(clippy::doc_markdown)]
393 #[inline]
394 pub fn on_primary_container_color(&self) -> Srgba {
396 self.primary.on
397 }
398
399 #[must_use]
400 #[allow(clippy::doc_markdown)]
401 #[inline]
402 pub fn on_primary_component_color(&self) -> Srgba {
404 self.primary.component.on
405 }
406
407 #[must_use]
408 #[allow(clippy::doc_markdown)]
409 #[inline]
410 pub fn on_secondary_container_color(&self) -> Srgba {
412 self.secondary.on
413 }
414
415 #[must_use]
416 #[allow(clippy::doc_markdown)]
417 #[inline]
418 pub fn on_secondary_component_color(&self) -> Srgba {
420 self.secondary.component.on
421 }
422
423 #[must_use]
424 #[allow(clippy::doc_markdown)]
425 #[inline]
426 pub fn accent_text_color(&self) -> Srgba {
428 self.accent_text.unwrap_or(self.accent.base)
429 }
430
431 #[must_use]
432 #[allow(clippy::doc_markdown)]
433 #[inline]
434 pub fn success_text_color(&self) -> Srgba {
436 self.success.base
437 }
438
439 #[must_use]
440 #[allow(clippy::doc_markdown)]
441 #[inline]
442 pub fn warning_text_color(&self) -> Srgba {
444 self.warning.base
445 }
446
447 #[must_use]
448 #[allow(clippy::doc_markdown)]
449 #[inline]
450 pub fn destructive_text_color(&self) -> Srgba {
452 self.destructive.base
453 }
454
455 #[must_use]
456 #[allow(clippy::doc_markdown)]
457 #[inline]
458 pub fn on_accent_color(&self) -> Srgba {
460 self.accent.on
461 }
462
463 #[must_use]
464 #[allow(clippy::doc_markdown)]
465 #[inline]
466 pub fn on_success_color(&self) -> Srgba {
468 self.success.on
469 }
470
471 #[must_use]
472 #[allow(clippy::doc_markdown)]
473 #[inline]
474 pub fn on_warning_color(&self) -> Srgba {
476 self.warning.on
477 }
478
479 #[must_use]
480 #[allow(clippy::doc_markdown)]
481 #[inline]
482 pub fn on_destructive_color(&self) -> Srgba {
484 self.destructive.on
485 }
486
487 #[must_use]
488 #[allow(clippy::doc_markdown)]
489 #[inline]
490 pub fn button_color(&self) -> Srgba {
492 self.button.on
493 }
494
495 #[must_use]
497 #[allow(clippy::doc_markdown)]
498 #[inline]
499 pub fn bg_divider(&self) -> Srgba {
501 self.background.divider
502 }
503
504 #[must_use]
505 #[allow(clippy::doc_markdown)]
506 #[inline]
507 pub fn bg_component_divider(&self) -> Srgba {
509 self.background.component.divider
510 }
511
512 #[must_use]
513 #[allow(clippy::doc_markdown)]
514 #[inline]
515 pub fn primary_container_divider(&self) -> Srgba {
517 self.primary.divider
518 }
519
520 #[must_use]
521 #[allow(clippy::doc_markdown)]
522 #[inline]
523 pub fn primary_component_divider(&self) -> Srgba {
525 self.primary.component.divider
526 }
527
528 #[must_use]
529 #[allow(clippy::doc_markdown)]
530 #[inline]
531 pub fn secondary_container_divider(&self) -> Srgba {
533 self.secondary.divider
534 }
535
536 #[must_use]
537 #[allow(clippy::doc_markdown)]
538 #[inline]
539 pub fn button_divider(&self) -> Srgba {
541 self.button.divider
542 }
543
544 #[must_use]
545 #[allow(clippy::doc_markdown)]
546 #[inline]
547 pub fn window_header_bg(&self) -> Srgba {
549 self.background.base
550 }
551
552 #[must_use]
553 #[allow(clippy::doc_markdown)]
554 #[inline]
555 pub fn space_none(&self) -> u16 {
557 self.spacing.space_none
558 }
559
560 #[must_use]
561 #[allow(clippy::doc_markdown)]
562 #[inline]
563 pub fn space_xxxs(&self) -> u16 {
565 self.spacing.space_xxxs
566 }
567
568 #[must_use]
569 #[allow(clippy::doc_markdown)]
570 #[inline]
571 pub fn space_xxs(&self) -> u16 {
573 self.spacing.space_xxs
574 }
575
576 #[must_use]
577 #[allow(clippy::doc_markdown)]
578 #[inline]
579 pub fn space_xs(&self) -> u16 {
581 self.spacing.space_xs
582 }
583
584 #[must_use]
585 #[allow(clippy::doc_markdown)]
586 #[inline]
587 pub fn space_s(&self) -> u16 {
589 self.spacing.space_s
590 }
591
592 #[must_use]
593 #[allow(clippy::doc_markdown)]
594 #[inline]
595 pub fn space_m(&self) -> u16 {
597 self.spacing.space_m
598 }
599
600 #[must_use]
601 #[allow(clippy::doc_markdown)]
602 #[inline]
603 pub fn space_l(&self) -> u16 {
605 self.spacing.space_l
606 }
607
608 #[must_use]
609 #[allow(clippy::doc_markdown)]
610 #[inline]
611 pub fn space_xl(&self) -> u16 {
613 self.spacing.space_xl
614 }
615
616 #[must_use]
617 #[allow(clippy::doc_markdown)]
618 #[inline]
619 pub fn space_xxl(&self) -> u16 {
621 self.spacing.space_xxl
622 }
623
624 #[must_use]
625 #[allow(clippy::doc_markdown)]
626 #[inline]
627 pub fn space_xxxl(&self) -> u16 {
629 self.spacing.space_xxxl
630 }
631
632 #[must_use]
633 #[allow(clippy::doc_markdown)]
634 #[inline]
635 pub fn radius_0(&self) -> [f32; 4] {
637 self.corner_radii.radius_0
638 }
639
640 #[must_use]
641 #[allow(clippy::doc_markdown)]
642 #[inline]
643 pub fn radius_xs(&self) -> [f32; 4] {
645 self.corner_radii.radius_xs
646 }
647
648 #[must_use]
649 #[allow(clippy::doc_markdown)]
650 #[inline]
651 pub fn radius_s(&self) -> [f32; 4] {
653 self.corner_radii.radius_s
654 }
655
656 #[must_use]
657 #[allow(clippy::doc_markdown)]
658 #[inline]
659 pub fn radius_m(&self) -> [f32; 4] {
661 self.corner_radii.radius_m
662 }
663
664 #[must_use]
665 #[allow(clippy::doc_markdown)]
666 #[inline]
667 pub fn radius_l(&self) -> [f32; 4] {
669 self.corner_radii.radius_l
670 }
671
672 #[must_use]
673 #[allow(clippy::doc_markdown)]
674 #[inline]
675 pub fn radius_xl(&self) -> [f32; 4] {
677 self.corner_radii.radius_xl
678 }
679
680 #[must_use]
681 #[allow(clippy::doc_markdown)]
682 #[inline]
683 pub fn shade_color(&self) -> Srgba {
685 self.shade
686 }
687
688 pub fn get_active() -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
690 let config =
691 Config::new(Self::id(), Self::VERSION).map_err(|e| (vec![e], Self::default()))?;
692 let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?;
693 let config = if is_dark {
694 Self::dark_config()
695 } else {
696 Self::light_config()
697 }
698 .map_err(|e| (vec![e], Self::default()))?;
699 Self::get_entry(&config)
700 }
701
702 #[must_use]
703 pub fn with_accent(&self, c: Srgba) -> Self {
705 let mut oklcha: Oklcha = c.into_color();
706 let cur_oklcha: Oklcha = self.accent_color().into_color();
707 oklcha.l = cur_oklcha.l;
708 let adjusted_c: Srgb = oklcha.into_color();
709
710 let is_dark = self.is_dark;
711
712 let mut builder = if is_dark {
713 ThemeBuilder::dark_config()
714 .ok()
715 .and_then(|h| ThemeBuilder::get_entry(&h).ok())
716 .unwrap_or_else(ThemeBuilder::dark)
717 } else {
718 ThemeBuilder::light_config()
719 .ok()
720 .and_then(|h| ThemeBuilder::get_entry(&h).ok())
721 .unwrap_or_else(ThemeBuilder::light)
722 };
723 builder = builder.accent(adjusted_c);
724 builder.build()
725 }
726
727 pub fn gtk_prefer_colorscheme() -> Self {
729 let gsettings = "/usr/bin/gsettings";
730
731 let cmd = std::process::Command::new(gsettings)
732 .arg("get")
733 .arg("org.gnome.desktop.interface")
734 .arg("color-scheme")
735 .output();
736
737 if let Ok(cmd) = cmd {
738 let color_scheme = String::from_utf8_lossy(&cmd.stdout);
739
740 if color_scheme.trim().contains("default") || color_scheme.trim().contains("light") {
741 return Self::light_default();
742 }
743 };
744
745 Self::dark_default()
746 }
747
748 pub fn preferred_theme() -> Self {
750 let current_desktop = std::env::var("XDG_CURRENT_DESKTOP");
751
752 if let Ok(desktop) = current_desktop {
753 if desktop.trim().to_lowercase().contains("gnome") {
754 return Self::gtk_prefer_colorscheme();
755 }
756 }
757
758 Self::dark_default()
759 }
760}
761
762impl From<CosmicPalette> for Theme {
763 fn from(p: CosmicPalette) -> Self {
764 ThemeBuilder::palette(p).build()
765 }
766}
767
768#[must_use]
769#[derive(
771 Clone,
772 Debug,
773 Serialize,
774 Deserialize,
775 cosmic_config::cosmic_config_derive::CosmicConfigEntry,
776 PartialEq,
777)]
778#[version = 1]
779pub struct ThemeBuilder {
780 pub palette: CosmicPalette,
782 pub spacing: Spacing,
784 pub corner_radii: CornerRadii,
786 pub neutral_tint: Option<Srgb>,
788 pub bg_color: Option<Srgba>,
790 pub primary_container_bg: Option<Srgba>,
792 pub secondary_container_bg: Option<Srgba>,
794 pub text_tint: Option<Srgb>,
796 pub accent: Option<Srgb>,
798 pub success: Option<Srgb>,
800 pub warning: Option<Srgb>,
802 pub destructive: Option<Srgb>,
804 pub is_frosted: bool, pub gaps: (u32, u32),
808 pub active_hint: u32,
810 pub window_hint: Option<Srgb>,
812}
813
814impl Default for ThemeBuilder {
815 fn default() -> Self {
816 Self {
817 palette: DARK_PALETTE.to_owned().into(),
818 spacing: Spacing::default(),
819 corner_radii: CornerRadii::default(),
820 neutral_tint: Default::default(),
821 text_tint: Default::default(),
822 bg_color: Default::default(),
823 primary_container_bg: Default::default(),
824 secondary_container_bg: Default::default(),
825 accent: Default::default(),
826 success: Default::default(),
827 warning: Default::default(),
828 destructive: Default::default(),
829 is_frosted: false,
830 gaps: (0, 8),
832 active_hint: 3,
833 window_hint: None,
834 }
835 }
836}
837
838impl ThemeBuilder {
839 #[inline]
840 pub fn dark() -> Self {
842 Self {
843 palette: DARK_PALETTE.to_owned(),
844 ..Default::default()
845 }
846 }
847
848 #[inline]
849 pub fn light() -> Self {
851 Self {
852 palette: LIGHT_PALETTE.to_owned(),
853 ..Default::default()
854 }
855 }
856
857 #[inline]
858 pub fn dark_high_contrast() -> Self {
860 let palette: CosmicPalette = DARK_PALETTE.to_owned();
861 Self {
862 palette: CosmicPalette::HighContrastDark(palette.inner()),
863 ..Default::default()
864 }
865 }
866
867 #[inline]
868 pub fn light_high_contrast() -> Self {
870 let palette: CosmicPalette = LIGHT_PALETTE.to_owned();
871 Self {
872 palette: CosmicPalette::HighContrastLight(palette.inner()),
873 ..Default::default()
874 }
875 }
876
877 #[inline]
878 pub fn palette(palette: CosmicPalette) -> Self {
880 Self {
881 palette,
882 ..Default::default()
883 }
884 }
885
886 #[inline]
887 pub fn spacing(mut self, spacing: Spacing) -> Self {
889 self.spacing = spacing;
890 self
891 }
892
893 #[inline]
894 pub fn corner_radii(mut self, corner_radii: CornerRadii) -> Self {
896 self.corner_radii = corner_radii;
897 self
898 }
899
900 #[inline]
901 pub fn neutral_tint(mut self, tint: Srgb) -> Self {
903 self.neutral_tint = Some(tint);
904 self
905 }
906
907 #[inline]
908 pub fn text_tint(mut self, tint: Srgb) -> Self {
910 self.text_tint = Some(tint);
911 self
912 }
913
914 #[inline]
915 pub fn bg_color(mut self, c: Srgba) -> Self {
917 self.bg_color = Some(c);
918 self
919 }
920
921 #[inline]
922 pub fn primary_container_bg(mut self, c: Srgba) -> Self {
924 self.primary_container_bg = Some(c);
925 self
926 }
927
928 #[inline]
929 pub fn accent(mut self, c: Srgb) -> Self {
931 self.accent = Some(c);
932 self
933 }
934
935 #[inline]
936 pub fn success(mut self, c: Srgb) -> Self {
938 self.success = Some(c);
939 self
940 }
941
942 #[inline]
943 pub fn warning(mut self, c: Srgb) -> Self {
945 self.warning = Some(c);
946 self
947 }
948
949 #[inline]
950 pub fn destructive(mut self, c: Srgb) -> Self {
952 self.destructive = Some(c);
953 self
954 }
955
956 #[allow(clippy::too_many_lines)]
957 pub fn build(self) -> Theme {
959 let Self {
960 palette,
961 spacing,
962 corner_radii,
963 neutral_tint,
964 text_tint,
965 bg_color,
966 primary_container_bg,
967 secondary_container_bg,
968 accent,
969 success,
970 warning,
971 destructive,
972 gaps,
973 active_hint,
974 window_hint,
975 is_frosted,
976 } = self;
977
978 let is_dark = palette.is_dark();
979 let is_high_contrast = palette.is_high_contrast();
980
981 let accent = if let Some(accent) = accent {
982 accent.into_color()
983 } else {
984 palette.as_ref().accent_blue
985 };
986
987 let success = if let Some(success) = success {
988 success.into_color()
989 } else {
990 palette.as_ref().accent_green
991 };
992
993 let warning = if let Some(warning) = warning {
994 warning.into_color()
995 } else {
996 palette.as_ref().accent_yellow
997 };
998
999 let destructive = if let Some(destructive) = destructive {
1000 destructive.into_color()
1001 } else {
1002 palette.as_ref().accent_red
1003 };
1004
1005 let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap()));
1006
1007 let mut control_steps_array = if let Some(neutral_tint) = neutral_tint {
1008 steps(neutral_tint, NonZeroUsize::new(11).unwrap())
1009 } else {
1010 steps(palette.as_ref().neutral_2, NonZeroUsize::new(11).unwrap())
1011 };
1012 if !is_dark {
1013 control_steps_array.reverse();
1014 }
1015
1016 let p_ref = palette.as_ref();
1017
1018 let neutral_steps = steps(
1019 neutral_tint.unwrap_or(Rgb::new(0.0, 0.0, 0.0)),
1020 NonZeroUsize::new(100).unwrap(),
1021 );
1022
1023 let bg = if let Some(bg_color) = bg_color {
1024 bg_color
1025 } else {
1026 p_ref.gray_1
1027 };
1028
1029 let step_array = steps(bg, NonZeroUsize::new(100).unwrap());
1030 let bg_index = color_index(bg, step_array.len());
1031
1032 let mut component_hovered_overlay = if bg_index < 91 {
1033 control_steps_array[10]
1034 } else {
1035 control_steps_array[0]
1036 };
1037 component_hovered_overlay.alpha = 0.1;
1038
1039 let mut component_pressed_overlay = component_hovered_overlay;
1040 component_pressed_overlay.alpha = 0.2;
1041
1042 let button_bg = control_steps_array[7].with_alpha(0.25);
1044
1045 let (button_hovered_overlay, button_pressed_overlay) = (
1046 control_steps_array[5].with_alpha(0.2),
1047 control_steps_array[2].with_alpha(0.5),
1048 );
1049
1050 let bg_component = get_surface_color(bg_index, 8, &step_array, is_dark, &p_ref.neutral_2);
1051 let on_bg_component = get_text(
1052 color_index(bg_component, step_array.len()),
1053 &step_array,
1054 &control_steps_array[8],
1055 text_steps_array.as_deref(),
1056 );
1057
1058 let primary = {
1059 let container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
1060 primary_container_bg_color
1061 } else {
1062 get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1])
1063 };
1064
1065 let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
1066 let base_index: usize = color_index(container_bg, step_array.len());
1067 let component_base =
1068 get_surface_color(base_index, 6, &step_array, is_dark, &control_steps_array[3]);
1069
1070 component_hovered_overlay = if base_index < 91 {
1071 control_steps_array[10]
1072 } else {
1073 control_steps_array[0]
1074 };
1075 component_hovered_overlay.alpha = 0.1;
1076
1077 component_pressed_overlay = component_hovered_overlay;
1078 component_pressed_overlay.alpha = 0.2;
1079
1080 let container = Container::new(
1081 Component::component(
1082 component_base,
1083 accent,
1084 get_text(
1085 color_index(component_base, step_array.len()),
1086 &step_array,
1087 &control_steps_array[8],
1088 text_steps_array.as_deref(),
1089 ),
1090 component_hovered_overlay,
1091 component_pressed_overlay,
1092 is_high_contrast,
1093 control_steps_array[8],
1094 ),
1095 container_bg,
1096 get_text(
1097 base_index,
1098 &step_array,
1099 &control_steps_array[8],
1100 text_steps_array.as_deref(),
1101 ),
1102 get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
1103 is_high_contrast,
1104 );
1105
1106 container
1107 };
1108
1109 let accent_text = if is_dark {
1110 (primary.base.relative_contrast(accent.color) < 4.).then(|| {
1111 let step_array = steps(accent, NonZeroUsize::new(100).unwrap());
1112 let primary_color_index = color_index(primary.base, 100);
1113 let steps = if is_high_contrast { 60 } else { 50 };
1114 let accent_text = get_surface_color(
1115 primary_color_index,
1116 steps,
1117 &step_array,
1118 is_dark,
1119 &Srgba::new(1., 1., 1., 1.),
1120 );
1121 if primary.base.relative_contrast(accent_text.color) < 4. {
1122 Srgba::new(1., 1., 1., 1.)
1123 } else {
1124 accent_text
1125 }
1126 })
1127 } else {
1128 let darkest = if bg.relative_luminance().luma < primary.base.relative_luminance().luma {
1129 bg
1130 } else {
1131 primary.base
1132 };
1133
1134 (darkest.relative_contrast(accent.color) < 4.).then(|| {
1135 let step_array = steps(accent, NonZeroUsize::new(100).unwrap());
1136 let primary_color_index = color_index(darkest, 100);
1137 let steps = if is_high_contrast { 60 } else { 50 };
1138 let accent_text = get_surface_color(
1139 primary_color_index,
1140 steps,
1141 &step_array,
1142 is_dark,
1143 &Srgba::new(1., 1., 1., 1.),
1144 );
1145 if darkest.relative_contrast(accent_text.color) < 4. {
1146 Srgba::new(0., 0., 0., 1.)
1147 } else {
1148 accent_text
1149 }
1150 })
1151 };
1152
1153 let mut theme: Theme = Theme {
1154 name: palette.name().to_string(),
1155 shade: if palette.is_dark() {
1156 Srgba::new(0., 0., 0., 0.32)
1157 } else {
1158 Srgba::new(0., 0., 0., 0.08)
1159 },
1160 background: Container::new(
1161 Component::component(
1162 bg_component,
1163 accent,
1164 on_bg_component,
1165 component_hovered_overlay,
1166 component_pressed_overlay,
1167 is_high_contrast,
1168 control_steps_array[8],
1169 ),
1170 bg,
1171 get_text(
1172 bg_index,
1173 &step_array,
1174 &control_steps_array[8],
1175 text_steps_array.as_deref(),
1176 ),
1177 get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]),
1178 is_high_contrast,
1179 ),
1180 primary,
1181 secondary: {
1182 let container_bg = if let Some(secondary_container_bg) = secondary_container_bg {
1183 secondary_container_bg
1184 } else {
1185 get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2])
1186 };
1187
1188 let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
1189 let base_index = color_index(container_bg, step_array.len());
1190 let secondary_component =
1191 get_surface_color(base_index, 3, &step_array, is_dark, &control_steps_array[4]);
1192
1193 component_hovered_overlay = if base_index < 91 {
1194 control_steps_array[10]
1195 } else {
1196 control_steps_array[0]
1197 };
1198 component_hovered_overlay.alpha = 0.1;
1199
1200 component_pressed_overlay = component_hovered_overlay;
1201 component_pressed_overlay.alpha = 0.2;
1202
1203 Container::new(
1204 Component::component(
1205 secondary_component,
1206 accent,
1207 get_text(
1208 color_index(secondary_component, step_array.len()),
1209 &step_array,
1210 &control_steps_array[8],
1211 text_steps_array.as_deref(),
1212 ),
1213 component_hovered_overlay,
1214 component_pressed_overlay,
1215 is_high_contrast,
1216 control_steps_array[8],
1217 ),
1218 container_bg,
1219 get_text(
1220 base_index,
1221 &step_array,
1222 &control_steps_array[8],
1223 text_steps_array.as_deref(),
1224 ),
1225 get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
1226 is_high_contrast,
1227 )
1228 },
1229 accent: Component::colored_component(
1230 accent,
1231 control_steps_array[0],
1232 accent,
1233 button_hovered_overlay,
1234 button_pressed_overlay,
1235 ),
1236 accent_button: Component::colored_button(
1237 accent,
1238 control_steps_array[1],
1239 control_steps_array[0],
1240 accent,
1241 button_hovered_overlay,
1242 button_pressed_overlay,
1243 ),
1244 button: Component::component(
1245 button_bg,
1246 accent,
1247 on_bg_component,
1248 button_hovered_overlay,
1249 button_pressed_overlay,
1250 is_high_contrast,
1251 control_steps_array[8],
1252 ),
1253 destructive: Component::colored_component(
1254 destructive,
1255 control_steps_array[0],
1256 accent,
1257 button_hovered_overlay,
1258 button_pressed_overlay,
1259 ),
1260 destructive_button: Component::colored_button(
1261 destructive,
1262 control_steps_array[1],
1263 control_steps_array[0],
1264 accent,
1265 button_hovered_overlay,
1266 button_pressed_overlay,
1267 ),
1268 icon_button: Component::component(
1269 Srgba::new(0.0, 0.0, 0.0, 0.0),
1270 accent,
1271 control_steps_array[8],
1272 button_hovered_overlay,
1273 button_pressed_overlay,
1274 is_high_contrast,
1275 control_steps_array[8],
1276 ),
1277 link_button: {
1278 let mut component = Component::component(
1279 Srgba::new(0.0, 0.0, 0.0, 0.0),
1280 accent,
1281 accent_text.unwrap_or(accent),
1282 Srgba::new(0.0, 0.0, 0.0, 0.0),
1283 Srgba::new(0.0, 0.0, 0.0, 0.0),
1284 is_high_contrast,
1285 control_steps_array[8],
1286 );
1287
1288 component.on_disabled = over(component.on.with_alpha(0.5), component.base);
1289 component
1290 },
1291 success: Component::colored_component(
1292 success,
1293 control_steps_array[0],
1294 accent,
1295 button_hovered_overlay,
1296 button_pressed_overlay,
1297 ),
1298 success_button: Component::colored_button(
1299 success,
1300 control_steps_array[1],
1301 control_steps_array[0],
1302 accent,
1303 button_hovered_overlay,
1304 button_pressed_overlay,
1305 ),
1306 text_button: Component::component(
1307 Srgba::new(0.0, 0.0, 0.0, 0.0),
1308 accent,
1309 accent_text.unwrap_or(accent),
1310 button_hovered_overlay,
1311 button_pressed_overlay,
1312 is_high_contrast,
1313 control_steps_array[8],
1314 ),
1315 warning: Component::colored_component(
1316 warning,
1317 control_steps_array[0],
1318 accent,
1319 button_hovered_overlay,
1320 button_pressed_overlay,
1321 ),
1322 warning_button: Component::colored_button(
1323 warning,
1324 control_steps_array[10],
1325 control_steps_array[0],
1326 accent,
1327 button_hovered_overlay,
1328 button_pressed_overlay,
1329 ),
1330 palette: palette.inner(),
1331 spacing,
1332 corner_radii,
1333 is_dark,
1334 is_high_contrast,
1335 gaps,
1336 active_hint,
1337 window_hint,
1338 is_frosted,
1339 accent_text,
1340 control_tint: neutral_tint,
1341 text_tint,
1342 };
1343 theme.spacing = spacing;
1344 theme.corner_radii = corner_radii;
1345 theme
1346 }
1347
1348 #[inline]
1349 pub fn dark_config() -> Result<Config, cosmic_config::Error> {
1351 Config::new(DARK_THEME_BUILDER_ID, Self::VERSION)
1352 }
1353
1354 #[inline]
1355 pub fn light_config() -> Result<Config, cosmic_config::Error> {
1357 Config::new(LIGHT_THEME_BUILDER_ID, Self::VERSION)
1358 }
1359}