1#![cfg(feature = "parser")]
26
27use crate::{
28 corety::{AzString, OptionF32, OptionString, OptionU16},
29 css::Css,
30 parser2::{new_from_str, CssParseWarnMsg},
31 props::{
32 basic::{
33 color::{parse_css_color, ColorU, OptionColorU},
34 pixel::{OptionPixelValue, PixelValue},
35 },
36 style::scrollbar::{
37 ComputedScrollbarStyle, OverscrollBehavior, ScrollBehavior, ScrollPhysics,
38 },
39 },
40};
41use alloc::{
42 boxed::Box,
43 string::{String, ToString},
44 vec::Vec,
45};
46
47use crate::dynamic_selector::{BoolCondition, OsVersion};
48use core::fmt::Write;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
56pub enum RicingMode {
57 Off,
60 #[default]
63 Default,
64 Force,
68}
69
70#[must_use]
74pub fn ricing_mode() -> RicingMode {
75 let Ok(raw) = std::env::var("AZ_RICING") else {
76 return RicingMode::Default;
77 };
78 match raw.trim().to_ascii_lowercase().as_str() {
79 "off" | "disabled" | "none" | "0" | "false" => RicingMode::Off,
80 "force" | "prefer" | "aggressive" | "1" | "true" => RicingMode::Force,
81 _ => RicingMode::Default,
82 }
83}
84
85#[must_use]
88pub fn ricing_enabled() -> bool {
89 !matches!(ricing_mode(), RicingMode::Off)
90}
91
92#[allow(variant_size_differences)]
94#[derive(Debug, Default, Clone, PartialEq, Eq)]
97#[repr(C, u8)]
98pub enum Platform {
99 Windows,
100 MacOs,
101 Linux(DesktopEnvironment),
102 Android,
103 Ios,
104 #[default]
105 Unknown,
106}
107
108impl Platform {
109 #[inline]
111 #[must_use]
112 pub const fn current() -> Self {
113 #[cfg(target_os = "macos")]
114 {
115 Self::MacOs
116 }
117 #[cfg(target_os = "windows")]
118 {
119 Self::Windows
120 }
121 #[cfg(target_os = "linux")]
122 {
123 Self::Linux(DesktopEnvironment::Other(AzString::from_const_str(
124 "unknown",
125 )))
126 }
127 #[cfg(target_os = "android")]
128 {
129 Self::Android
130 }
131 #[cfg(target_os = "ios")]
132 {
133 Self::Ios
134 }
135 #[cfg(not(any(
136 target_os = "macos",
137 target_os = "windows",
138 target_os = "linux",
139 target_os = "android",
140 target_os = "ios"
141 )))]
142 {
143 Self::Unknown
144 }
145 }
146}
147#[allow(variant_size_differences)]
148#[derive(Debug, Clone, PartialEq, Eq)]
151#[repr(C, u8)]
152pub enum DesktopEnvironment {
153 Gnome,
154 Kde,
155 Other(AzString),
156}
157
158#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
160#[repr(C)]
161pub enum Theme {
162 #[default]
163 Light,
164 Dark,
165}
166
167#[derive(Debug, Clone, PartialEq)]
169#[repr(C)]
170pub struct SystemStyle {
171 pub fonts: SystemFonts,
172 pub metrics: SystemMetrics,
173 pub linux: LinuxCustomization,
175 pub platform: Platform,
176 pub focus_visuals: FocusVisuals,
178 pub language: AzString,
181 pub app_specific_stylesheet: Option<Box<Css>>,
186 pub scrollbar: Option<Box<ComputedScrollbarStyle>>,
188 pub scroll_physics: ScrollPhysics,
192 pub theme: Theme,
193 pub os_version: OsVersion,
195 pub prefers_reduced_motion: BoolCondition,
197 pub prefers_high_contrast: BoolCondition,
199 pub accessibility: AccessibilitySettings,
201 pub handedness: Handedness,
209 pub input: InputMetrics,
211 pub text_rendering: TextRenderingHints,
213 pub scrollbar_preferences: ScrollbarPreferences,
215 pub visual_hints: VisualHints,
217 pub animation: AnimationMetrics,
219 pub colors: SystemColors,
220 pub icon_style: IconStyleOptions,
222 pub audio: AudioMetrics,
224 pub run_destructor: bool,
236}
237
238impl Default for SystemStyle {
239 fn default() -> Self {
240 Self {
241 fonts: SystemFonts::default(),
242 metrics: SystemMetrics::default(),
243 linux: LinuxCustomization::default(),
244 platform: Platform::default(),
245 focus_visuals: FocusVisuals::default(),
246 handedness: Handedness::default(),
247 language: AzString::default(),
248 app_specific_stylesheet: None,
249 scrollbar: None,
250 scroll_physics: ScrollPhysics::default(),
251 theme: Theme::default(),
252 os_version: OsVersion::default(),
253 prefers_reduced_motion: BoolCondition::default(),
254 prefers_high_contrast: BoolCondition::default(),
255 accessibility: AccessibilitySettings::default(),
256 input: InputMetrics::default(),
257 text_rendering: TextRenderingHints::default(),
258 scrollbar_preferences: ScrollbarPreferences::default(),
259 visual_hints: VisualHints::default(),
260 animation: AnimationMetrics::default(),
261 colors: SystemColors::default(),
262 icon_style: IconStyleOptions::default(),
263 audio: AudioMetrics::default(),
264 run_destructor: true,
265 }
266 }
267}
268
269impl Drop for SystemStyle {
270 fn drop(&mut self) {
271 if self.run_destructor {
281 self.run_destructor = false;
282 } else {
283 core::mem::forget(self.app_specific_stylesheet.take());
284 core::mem::forget(self.scrollbar.take());
285 }
286 }
287}
288
289#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
294#[repr(C)]
295pub struct IconStyleOptions {
296 pub prefer_grayscale: bool,
299 pub tint_color: OptionColorU,
302 pub inherit_text_color: bool,
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
318#[repr(C)]
319pub enum SystemFontType {
320 #[default]
322 Ui,
323 UiBold,
325 Monospace,
327 MonospaceBold,
329 MonospaceItalic,
331 Title,
333 TitleBold,
335 Menu,
337 Small,
339 Serif,
341 SerifBold,
343}
344
345impl SystemFontType {
346 #[must_use]
356 pub fn from_css_str(s: &str) -> Option<Self> {
357 let s = s.trim();
358 if !s.starts_with("system:") {
359 return None;
360 }
361 let rest = &s[7..]; match rest {
363 "ui" => Some(Self::Ui),
364 "ui:bold" => Some(Self::UiBold),
365 "monospace" => Some(Self::Monospace),
366 "monospace:bold" => Some(Self::MonospaceBold),
367 "monospace:italic" => Some(Self::MonospaceItalic),
368 "title" => Some(Self::Title),
369 "title:bold" => Some(Self::TitleBold),
370 "menu" => Some(Self::Menu),
371 "small" => Some(Self::Small),
372 "serif" => Some(Self::Serif),
373 "serif:bold" => Some(Self::SerifBold),
374 _ => None,
375 }
376 }
377
378 #[must_use]
380 pub const fn as_css_str(&self) -> &'static str {
381 match self {
382 Self::Ui => "system:ui",
383 Self::UiBold => "system:ui:bold",
384 Self::Monospace => "system:monospace",
385 Self::MonospaceBold => "system:monospace:bold",
386 Self::MonospaceItalic => "system:monospace:italic",
387 Self::Title => "system:title",
388 Self::TitleBold => "system:title:bold",
389 Self::Menu => "system:menu",
390 Self::Small => "system:small",
391 Self::Serif => "system:serif",
392 Self::SerifBold => "system:serif:bold",
393 }
394 }
395
396 #[must_use]
399 pub const fn is_bold(&self) -> bool {
400 matches!(
401 self,
402 Self::UiBold | Self::MonospaceBold | Self::TitleBold | Self::SerifBold
403 )
404 }
405
406 #[must_use]
408 pub const fn is_italic(&self) -> bool {
409 matches!(self, Self::MonospaceItalic)
410 }
411}
412
413#[derive(Debug, Default, Clone, Copy, PartialEq)]
421#[repr(C)]
422pub struct AccessibilitySettings {
423 pub text_scale_factor: f32,
425 pub prefers_bold_text: bool,
430 pub prefers_larger_text: bool,
435 pub prefers_high_contrast: bool,
440 pub prefers_reduced_motion: bool,
445 pub prefers_reduced_transparency: bool,
450 pub screen_reader_active: bool,
452 pub differentiate_without_color: bool,
455}
456
457#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
466#[repr(C)]
467pub struct SystemColors {
468 pub text: OptionColorU,
471 pub secondary_text: OptionColorU,
473 pub tertiary_text: OptionColorU,
475 pub background: OptionColorU,
477
478 pub accent: OptionColorU,
481 pub accent_text: OptionColorU,
483
484 pub button_face: OptionColorU,
487 pub button_text: OptionColorU,
489 pub disabled_text: OptionColorU,
491
492 pub window_background: OptionColorU,
495 pub under_page_background: OptionColorU,
497
498 pub selection_background: OptionColorU,
501 pub selection_text: OptionColorU,
503 pub selection_background_inactive: OptionColorU,
506 pub selection_text_inactive: OptionColorU,
508
509 pub link: OptionColorU,
512 pub separator: OptionColorU,
514 pub grid: OptionColorU,
516 pub find_highlight: OptionColorU,
518
519 pub sidebar_background: OptionColorU,
522 pub sidebar_selection: OptionColorU,
524}
525
526#[derive(Debug, Default, Clone, PartialEq, Eq)]
532#[repr(C)]
533pub struct SystemFonts {
534 pub ui_font: OptionString,
539 pub ui_font_size: OptionF32,
541 pub monospace_font: OptionString,
546 pub monospace_font_size: OptionF32,
548 pub ui_font_bold: OptionString,
550 pub title_font: OptionString,
552 pub title_font_size: OptionF32,
554 pub menu_font: OptionString,
556 pub menu_font_size: OptionF32,
558 pub small_font: OptionString,
560 pub small_font_size: OptionF32,
562}
563
564#[derive(Debug, Default, Clone, PartialEq, Eq)]
566#[repr(C)]
567pub struct SystemMetrics {
568 pub corner_radius: OptionPixelValue,
570 pub border_width: OptionPixelValue,
572 pub button_padding_horizontal: OptionPixelValue,
574 pub button_padding_vertical: OptionPixelValue,
576 pub titlebar: TitlebarMetrics,
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
582#[repr(C)]
583pub enum TitlebarButtonSide {
584 Left,
586 #[default]
588 Right,
589}
590
591#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
593#[repr(C)]
594pub struct TitlebarButtons {
595 pub has_close: bool,
597 pub has_minimize: bool,
599 pub has_maximize: bool,
601 pub has_fullscreen: bool,
603}
604
605impl Default for TitlebarButtons {
606 fn default() -> Self {
607 Self {
608 has_close: true,
609 has_minimize: true,
610 has_maximize: true,
611 has_fullscreen: false,
612 }
613 }
614}
615
616#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
622#[repr(C)]
623pub struct SafeAreaInsets {
624 pub top: OptionPixelValue,
626 pub bottom: OptionPixelValue,
628 pub left: OptionPixelValue,
630 pub right: OptionPixelValue,
632 pub keyboard: OptionPixelValue,
646}
647
648#[derive(Debug, Clone, PartialEq, Eq)]
653#[repr(C)]
654pub struct TitlebarMetrics {
655 pub button_side: TitlebarButtonSide,
657 pub buttons: TitlebarButtons,
659 pub height: OptionPixelValue,
661 pub button_area_width: OptionPixelValue,
664 pub padding_horizontal: OptionPixelValue,
666 pub safe_area: SafeAreaInsets,
668 pub title_font: OptionString,
670 pub title_font_size: OptionF32,
672 pub title_font_weight: OptionU16,
674 pub background_active: OptionColorU,
683 pub background_inactive: OptionColorU,
685 pub text_active: OptionColorU,
687 pub text_inactive: OptionColorU,
689 pub button_hover_background: OptionColorU,
691 pub close_button_hover_background: OptionColorU,
695}
696
697impl Default for TitlebarMetrics {
698 fn default() -> Self {
699 Self {
700 button_side: TitlebarButtonSide::Right,
701 buttons: TitlebarButtons::default(),
702 height: OptionPixelValue::None,
707 button_area_width: OptionPixelValue::None,
708 padding_horizontal: OptionPixelValue::None,
709 safe_area: SafeAreaInsets::default(),
710 title_font: OptionString::None,
711 title_font_size: OptionF32::Some(13.0),
712 title_font_weight: OptionU16::Some(600), background_active: OptionColorU::None,
714 background_inactive: OptionColorU::None,
715 text_active: OptionColorU::None,
716 text_inactive: OptionColorU::None,
717 button_hover_background: OptionColorU::None,
718 close_button_hover_background: OptionColorU::None,
719 }
720 }
721}
722
723impl TitlebarMetrics {
724 #[must_use]
726 pub fn windows() -> Self {
727 Self {
728 button_side: TitlebarButtonSide::Right,
729 buttons: TitlebarButtons {
730 has_close: true,
731 has_minimize: true,
732 has_maximize: true,
733 has_fullscreen: false,
734 },
735 height: OptionPixelValue::Some(PixelValue::px(32.0)),
736 button_area_width: OptionPixelValue::Some(PixelValue::px(138.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
738 safe_area: SafeAreaInsets::default(),
739 title_font: OptionString::Some("Segoe UI Variable Text".into()),
740 title_font_size: OptionF32::Some(12.0),
741 title_font_weight: OptionU16::Some(400), background_active: OptionColorU::None,
743 background_inactive: OptionColorU::None,
744 text_active: OptionColorU::None,
745 text_inactive: OptionColorU::None,
746 button_hover_background: OptionColorU::None,
747 close_button_hover_background: OptionColorU::None,
748 }
749 }
750
751 #[must_use]
753 pub fn macos() -> Self {
754 Self {
755 button_side: TitlebarButtonSide::Left,
756 buttons: TitlebarButtons {
757 has_close: true,
758 has_minimize: true,
759 has_maximize: false, has_fullscreen: true,
761 },
762 height: OptionPixelValue::Some(PixelValue::px(28.0)),
763 button_area_width: OptionPixelValue::Some(PixelValue::px(78.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
765 safe_area: SafeAreaInsets::default(),
766 title_font: OptionString::Some(".SF NS".into()),
767 title_font_size: OptionF32::Some(13.0),
768 title_font_weight: OptionU16::Some(600), background_active: OptionColorU::None,
770 background_inactive: OptionColorU::None,
771 text_active: OptionColorU::None,
772 text_inactive: OptionColorU::None,
773 button_hover_background: OptionColorU::None,
774 close_button_hover_background: OptionColorU::None,
775 }
776 }
777
778 #[must_use]
780 pub fn linux_gnome() -> Self {
781 Self {
782 button_side: TitlebarButtonSide::Right, buttons: TitlebarButtons {
784 has_close: true,
785 has_minimize: true,
786 has_maximize: true,
787 has_fullscreen: false,
788 },
789 height: OptionPixelValue::Some(PixelValue::px(35.0)),
790 button_area_width: OptionPixelValue::Some(PixelValue::px(100.0)),
791 padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
792 safe_area: SafeAreaInsets::default(),
793 title_font: OptionString::Some("Cantarell".into()),
794 title_font_size: OptionF32::Some(11.0),
795 title_font_weight: OptionU16::Some(700), background_active: OptionColorU::None,
797 background_inactive: OptionColorU::None,
798 text_active: OptionColorU::None,
799 text_inactive: OptionColorU::None,
800 button_hover_background: OptionColorU::None,
801 close_button_hover_background: OptionColorU::None,
802 }
803 }
804
805 #[must_use]
807 pub fn ios() -> Self {
808 Self {
809 button_side: TitlebarButtonSide::Left,
810 buttons: TitlebarButtons {
811 has_close: false, has_minimize: false,
813 has_maximize: false,
814 has_fullscreen: false,
815 },
816 height: OptionPixelValue::Some(PixelValue::px(44.0)),
817 button_area_width: OptionPixelValue::Some(PixelValue::px(0.0)),
818 padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
819 safe_area: SafeAreaInsets {
820 top: OptionPixelValue::Some(PixelValue::px(47.0)),
822 bottom: OptionPixelValue::Some(PixelValue::px(34.0)),
823 left: OptionPixelValue::None,
824 right: OptionPixelValue::None,
825 keyboard: OptionPixelValue::None,
828 },
829 title_font: OptionString::Some(".SFUI-Semibold".into()),
830 title_font_size: OptionF32::Some(17.0),
831 title_font_weight: OptionU16::Some(600),
832 background_active: OptionColorU::None,
833 background_inactive: OptionColorU::None,
834 text_active: OptionColorU::None,
835 text_inactive: OptionColorU::None,
836 button_hover_background: OptionColorU::None,
837 close_button_hover_background: OptionColorU::None,
838 }
839 }
840
841 #[must_use]
843 pub fn android() -> Self {
844 Self {
845 button_side: TitlebarButtonSide::Left, buttons: TitlebarButtons {
847 has_close: false,
848 has_minimize: false,
849 has_maximize: false,
850 has_fullscreen: false,
851 },
852 height: OptionPixelValue::Some(PixelValue::px(56.0)),
853 button_area_width: OptionPixelValue::Some(PixelValue::px(48.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
855 safe_area: SafeAreaInsets::default(),
856 title_font: OptionString::Some("Roboto Medium".into()),
857 title_font_size: OptionF32::Some(20.0),
858 title_font_weight: OptionU16::Some(500),
859 background_active: OptionColorU::None,
860 background_inactive: OptionColorU::None,
861 text_active: OptionColorU::None,
862 text_inactive: OptionColorU::None,
863 button_hover_background: OptionColorU::None,
864 close_button_hover_background: OptionColorU::None,
865 }
866 }
867}
868
869#[derive(Debug, Clone, Copy, PartialEq)]
882#[repr(C)]
883pub struct InputMetrics {
884 pub double_click_time_ms: u32,
886 pub double_click_distance_px: f32,
888 pub drag_threshold_px: f32,
890 pub caret_blink_rate_ms: u32,
892 pub caret_width_px: f32,
894 pub wheel_scroll_lines: u32,
896 pub hover_time_ms: u32,
899}
900
901impl Default for InputMetrics {
902 fn default() -> Self {
903 Self {
904 double_click_time_ms: 500,
905 double_click_distance_px: 4.0,
906 drag_threshold_px: 5.0,
907 caret_blink_rate_ms: 530,
908 caret_width_px: 1.0,
909 wheel_scroll_lines: 3,
910 hover_time_ms: 400,
911 }
912 }
913}
914
915#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
919#[repr(C)]
920pub enum SubpixelType {
921 #[default]
923 None,
924 Rgb,
926 Bgr,
928 VRgb,
930 VBgr,
932}
933
934#[derive(Debug, Clone, Copy, PartialEq, Eq)]
939#[repr(C)]
940pub struct TextRenderingHints {
941 pub subpixel_type: SubpixelType,
943 pub font_smoothing_gamma: u32,
945 pub font_smoothing_enabled: bool,
947 pub increased_contrast: bool,
949}
950
951impl Default for TextRenderingHints {
952 fn default() -> Self {
953 Self {
954 subpixel_type: SubpixelType::None,
955 font_smoothing_gamma: 1000,
956 font_smoothing_enabled: true,
957 increased_contrast: false,
958 }
959 }
960}
961
962#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
969#[repr(C)]
970pub struct FocusVisuals {
971 pub focus_ring_color: OptionColorU,
974 pub focus_border_width: OptionPixelValue,
977 pub focus_border_height: OptionPixelValue,
979}
980
981#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
985#[repr(C)]
986pub enum ScrollbarVisibility {
987 Always,
989 #[default]
991 WhenScrolling,
992 Automatic,
994}
995
996#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
998#[repr(C)]
999pub enum ScrollbarTrackClick {
1000 JumpToPosition,
1002 #[default]
1004 PageUpDown,
1005}
1006
1007#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1012#[repr(C)]
1013pub struct ScrollbarPreferences {
1014 pub visibility: ScrollbarVisibility,
1017 pub track_click: ScrollbarTrackClick,
1019}
1020
1021impl Default for ScrollbarPreferences {
1022 fn default() -> Self {
1023 Self {
1024 visibility: ScrollbarVisibility::WhenScrolling,
1025 track_click: ScrollbarTrackClick::PageUpDown,
1026 }
1027 }
1028}
1029
1030#[derive(Debug, Default, Clone, PartialEq, Eq)]
1037#[repr(C)]
1038pub struct LinuxCustomization {
1039 pub gtk_theme: OptionString,
1041 pub icon_theme: OptionString,
1043 pub cursor_theme: OptionString,
1045 pub cursor_size: u32,
1047 pub titlebar_button_layout: OptionString,
1050}
1051
1052#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
1056#[repr(C)]
1057pub enum ToolbarStyle {
1058 #[default]
1060 IconsOnly,
1061 TextOnly,
1063 TextBesideIcon,
1065 TextBelowIcon,
1067}
1068
1069#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1074#[repr(C)]
1075pub struct VisualHints {
1076 pub toolbar_style: ToolbarStyle,
1079 pub show_button_images: bool,
1082 pub show_menu_images: bool,
1085 pub show_tooltips: bool,
1087 pub flash_on_alert: bool,
1089}
1090
1091impl Default for VisualHints {
1092 fn default() -> Self {
1093 Self {
1094 toolbar_style: ToolbarStyle::IconsOnly,
1095 show_button_images: false,
1096 show_menu_images: true,
1097 show_tooltips: true,
1098 flash_on_alert: true,
1099 }
1100 }
1101}
1102
1103#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
1107#[repr(C)]
1108pub enum FocusBehavior {
1109 #[default]
1111 AlwaysVisible,
1112 KeyboardOnly,
1115}
1116
1117#[derive(Debug, Clone, Copy, PartialEq)]
1129#[repr(C)]
1130pub struct AnimationMetrics {
1131 pub animations_enabled: bool,
1133 pub animation_duration_factor: f32,
1136 pub focus_indicator_behavior: FocusBehavior,
1138}
1139
1140impl Default for AnimationMetrics {
1141 fn default() -> Self {
1142 Self {
1143 animations_enabled: true,
1144 animation_duration_factor: 1.0,
1145 focus_indicator_behavior: FocusBehavior::AlwaysVisible,
1146 }
1147 }
1148}
1149
1150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1163#[repr(C)]
1164pub struct AudioMetrics {
1165 pub event_sounds_enabled: bool,
1167 pub input_feedback_sounds_enabled: bool,
1169}
1170
1171impl Default for AudioMetrics {
1172 fn default() -> Self {
1173 Self {
1174 event_sounds_enabled: true,
1175 input_feedback_sounds_enabled: false,
1176 }
1177 }
1178}
1179
1180pub mod apple_fonts {
1186 pub const SYSTEM_FONT: &str = "System Font";
1189
1190 pub const SF_NS_ROUNDED: &str = "SF NS Rounded";
1192
1193 pub const SF_COMPACT: &str = "SF Compact";
1196
1197 pub const SF_MONO: &str = "SF NS Mono Light";
1200
1201 pub const NEW_YORK: &str = "New York";
1204
1205 pub const SF_ARABIC: &str = "SF Arabic";
1207
1208 pub const SF_ARMENIAN: &str = "SF Armenian";
1210
1211 pub const SF_GEORGIAN: &str = "SF Georgian";
1213
1214 pub const SF_HEBREW: &str = "SF Hebrew";
1216
1217 pub const MENLO: &str = "Menlo";
1219 pub const MENLO_REGULAR: &str = "Menlo Regular";
1220 pub const MENLO_BOLD: &str = "Menlo Bold";
1221 pub const MONACO: &str = "Monaco";
1222 pub const LUCIDA_GRANDE: &str = "Lucida Grande";
1223 pub const LUCIDA_GRANDE_BOLD: &str = "Lucida Grande Bold";
1224 pub const HELVETICA_NEUE: &str = "Helvetica Neue";
1225 pub const HELVETICA_NEUE_BOLD: &str = "Helvetica Neue Bold";
1226}
1227
1228pub mod windows_fonts {
1230 pub const SEGOE_UI_VARIABLE: &str = "Segoe UI Variable";
1232 pub const SEGOE_UI_VARIABLE_TEXT: &str = "Segoe UI Variable Text";
1233 pub const SEGOE_UI_VARIABLE_DISPLAY: &str = "Segoe UI Variable Display";
1234
1235 pub const SEGOE_UI: &str = "Segoe UI";
1237 pub const CONSOLAS: &str = "Consolas";
1238 pub const CASCADIA_CODE: &str = "Cascadia Code";
1239 pub const CASCADIA_MONO: &str = "Cascadia Mono";
1240
1241 pub const TAHOMA: &str = "Tahoma";
1243 pub const MS_SANS_SERIF: &str = "MS Sans Serif";
1244 pub const LUCIDA_CONSOLE: &str = "Lucida Console";
1245 pub const COURIER_NEW: &str = "Courier New";
1246}
1247
1248pub mod linux_fonts {
1250 pub const CANTARELL: &str = "Cantarell";
1252 pub const ADWAITA: &str = "Adwaita";
1253
1254 pub const UBUNTU: &str = "Ubuntu";
1256 pub const UBUNTU_MONO: &str = "Ubuntu Mono";
1257
1258 pub const DEJAVU_SANS: &str = "DejaVu Sans";
1260 pub const DEJAVU_SANS_MONO: &str = "DejaVu Sans Mono";
1261 pub const DEJAVU_SERIF: &str = "DejaVu Serif";
1262
1263 pub const LIBERATION_SANS: &str = "Liberation Sans";
1265 pub const LIBERATION_MONO: &str = "Liberation Mono";
1266 pub const LIBERATION_SERIF: &str = "Liberation Serif";
1267
1268 pub const NOTO_SANS: &str = "Noto Sans";
1270 pub const NOTO_MONO: &str = "Noto Sans Mono";
1271 pub const NOTO_SERIF: &str = "Noto Serif";
1272
1273 pub const HACK: &str = "Hack";
1275
1276 pub const MONOSPACE: &str = "Monospace";
1278 pub const SANS_SERIF: &str = "Sans";
1279 pub const SERIF: &str = "Serif";
1280}
1281
1282impl SystemFontType {
1283 #[must_use]
1288 pub fn get_fallback_chain(&self, platform: &Platform) -> Vec<&'static str> {
1289 match platform {
1290 Platform::MacOs | Platform::Ios => self.macos_fallback_chain(),
1291 Platform::Windows => self.windows_fallback_chain(),
1292 Platform::Linux(_) => self.linux_fallback_chain(),
1293 Platform::Android => self.android_fallback_chain(),
1294 Platform::Unknown => self.generic_fallback_chain(),
1295 }
1296 }
1297
1298 fn macos_fallback_chain(self) -> Vec<&'static str> {
1299 match self {
1300 Self::Ui => vec![
1302 apple_fonts::SYSTEM_FONT,
1303 apple_fonts::HELVETICA_NEUE,
1304 apple_fonts::LUCIDA_GRANDE,
1305 ],
1306 Self::UiBold | Self::TitleBold => {
1308 vec![apple_fonts::HELVETICA_NEUE, apple_fonts::LUCIDA_GRANDE]
1309 }
1310 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
1312 vec![apple_fonts::MENLO, apple_fonts::MONACO]
1313 }
1314 Self::Title | Self::Menu | Self::Small => {
1316 vec![apple_fonts::SYSTEM_FONT, apple_fonts::HELVETICA_NEUE]
1317 }
1318 Self::Serif => vec![apple_fonts::NEW_YORK, "Georgia", "Times New Roman"],
1320 Self::SerifBold => vec![
1321 "Georgia", "Times New Roman",
1323 ],
1324 }
1325 }
1326
1327 fn windows_fallback_chain(self) -> Vec<&'static str> {
1328 match self {
1329 Self::Ui | Self::UiBold => vec![
1330 windows_fonts::SEGOE_UI_VARIABLE_TEXT,
1331 windows_fonts::SEGOE_UI,
1332 windows_fonts::TAHOMA,
1333 ],
1334 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
1335 windows_fonts::CASCADIA_MONO,
1336 windows_fonts::CASCADIA_CODE,
1337 windows_fonts::CONSOLAS,
1338 windows_fonts::LUCIDA_CONSOLE,
1339 windows_fonts::COURIER_NEW,
1340 ],
1341 Self::Title | Self::TitleBold => vec![
1342 windows_fonts::SEGOE_UI_VARIABLE_DISPLAY,
1343 windows_fonts::SEGOE_UI,
1344 ],
1345 Self::Menu => vec![windows_fonts::SEGOE_UI, windows_fonts::TAHOMA],
1346 Self::Small => vec![windows_fonts::SEGOE_UI],
1347 Self::Serif | Self::SerifBold => vec!["Cambria", "Georgia", "Times New Roman"],
1348 }
1349 }
1350
1351 fn linux_fallback_chain(self) -> Vec<&'static str> {
1352 match self {
1353 Self::Ui | Self::UiBold => vec![
1354 linux_fonts::CANTARELL,
1355 linux_fonts::UBUNTU,
1356 linux_fonts::NOTO_SANS,
1357 linux_fonts::DEJAVU_SANS,
1358 linux_fonts::LIBERATION_SANS,
1359 linux_fonts::SANS_SERIF,
1360 ],
1361 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
1362 linux_fonts::UBUNTU_MONO,
1363 linux_fonts::HACK,
1364 linux_fonts::NOTO_MONO,
1365 linux_fonts::DEJAVU_SANS_MONO,
1366 linux_fonts::LIBERATION_MONO,
1367 linux_fonts::MONOSPACE,
1368 ],
1369 Self::Title | Self::TitleBold | Self::Menu | Self::Small => vec![
1370 linux_fonts::CANTARELL,
1371 linux_fonts::UBUNTU,
1372 linux_fonts::NOTO_SANS,
1373 ],
1374 Self::Serif | Self::SerifBold => vec![
1375 linux_fonts::NOTO_SERIF,
1376 linux_fonts::DEJAVU_SERIF,
1377 linux_fonts::LIBERATION_SERIF,
1378 linux_fonts::SERIF,
1379 ],
1380 }
1381 }
1382
1383 fn android_fallback_chain(self) -> Vec<&'static str> {
1384 match self {
1385 Self::Ui | Self::UiBold | Self::Title | Self::TitleBold => vec!["Roboto", "Noto Sans"],
1386 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
1387 vec!["Roboto Mono", "Droid Sans Mono", "monospace"]
1388 }
1389 Self::Menu | Self::Small => vec!["Roboto"],
1390 Self::Serif | Self::SerifBold => vec!["Noto Serif", "Droid Serif", "serif"],
1391 }
1392 }
1393
1394 fn generic_fallback_chain(self) -> Vec<&'static str> {
1395 match self {
1396 Self::Ui | Self::UiBold | Self::Title | Self::TitleBold | Self::Menu | Self::Small => {
1397 vec!["sans-serif"]
1398 }
1399 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
1400 vec!["monospace"]
1401 }
1402 Self::Serif | Self::SerifBold => vec!["serif"],
1403 }
1404 }
1405}
1406
1407impl SystemStyle {
1408 #[allow(clippy::too_many_lines)]
1413 #[must_use]
1415 pub fn to_json_string(&self) -> AzString {
1416 use alloc::format;
1417
1418 fn opt_color(c: OptionColorU) -> alloc::string::String {
1419 c.as_ref().map_or_else(
1420 || "null".into(),
1421 |c| format!("\"#{:02x}{:02x}{:02x}{:02x}\"", c.r, c.g, c.b, c.a),
1422 )
1423 }
1424 fn opt_str(s: &OptionString) -> alloc::string::String {
1425 s.as_ref()
1426 .map_or_else(|| "null".into(), |s| format!("\"{}\"", s.as_str()))
1427 }
1428 fn opt_f32(v: OptionF32) -> alloc::string::String {
1429 v.into_option()
1430 .map_or_else(|| "null".into(), |v| format!("{v:.2}"))
1431 }
1432 fn opt_u16(v: OptionU16) -> alloc::string::String {
1433 v.into_option()
1434 .map_or_else(|| "null".into(), |v| format!("{v}"))
1435 }
1436 fn opt_px(v: &OptionPixelValue) -> alloc::string::String {
1437 v.as_ref().map_or_else(
1438 || "null".into(),
1439 |v| format!("{:.1}", v.to_pixels_internal(0.0, 0.0, 0.0)),
1440 )
1441 }
1442
1443 let tm = &self.metrics.titlebar;
1444 let inp = &self.input;
1445 let tr = &self.text_rendering;
1446 let acc = &self.accessibility;
1447 let sp = &self.scrollbar_preferences;
1448 let lnx = &self.linux;
1449 let vh = &self.visual_hints;
1450 let anim = &self.animation;
1451 let audio = &self.audio;
1452
1453 let json = format!(
1454 r#"{{
1455 "theme": "{:?}",
1456 "platform": "{:?}",
1457 "os_version": "{:?}:{}",
1458 "language": "{}",
1459 "prefers_reduced_motion": {:?},
1460 "prefers_high_contrast": {:?},
1461 "colors": {{
1462 "text": {},
1463 "secondary_text": {},
1464 "tertiary_text": {},
1465 "background": {},
1466 "accent": {},
1467 "accent_text": {},
1468 "button_face": {},
1469 "button_text": {},
1470 "disabled_text": {},
1471 "window_background": {},
1472 "under_page_background": {},
1473 "selection_background": {},
1474 "selection_text": {},
1475 "selection_background_inactive": {},
1476 "selection_text_inactive": {},
1477 "link": {},
1478 "separator": {},
1479 "grid": {},
1480 "find_highlight": {},
1481 "sidebar_background": {},
1482 "sidebar_selection": {}
1483 }},
1484 "fonts": {{
1485 "ui_font": {},
1486 "ui_font_size": {},
1487 "monospace_font": {},
1488 "title_font": {},
1489 "menu_font": {},
1490 "small_font": {}
1491 }},
1492 "titlebar": {{
1493 "button_side": "{:?}",
1494 "height": {},
1495 "button_area_width": {},
1496 "padding_horizontal": {},
1497 "title_font": {},
1498 "title_font_size": {},
1499 "title_font_weight": {},
1500 "has_close": {},
1501 "has_minimize": {},
1502 "has_maximize": {},
1503 "has_fullscreen": {}
1504 }},
1505 "input": {{
1506 "double_click_time_ms": {},
1507 "double_click_distance_px": {:.1},
1508 "drag_threshold_px": {:.1},
1509 "caret_blink_rate_ms": {},
1510 "caret_width_px": {:.1},
1511 "wheel_scroll_lines": {},
1512 "hover_time_ms": {}
1513 }},
1514 "text_rendering": {{
1515 "font_smoothing_enabled": {},
1516 "subpixel_type": "{:?}",
1517 "font_smoothing_gamma": {},
1518 "increased_contrast": {}
1519 }},
1520 "accessibility": {{
1521 "prefers_bold_text": {},
1522 "prefers_larger_text": {},
1523 "text_scale_factor": {:.2},
1524 "prefers_high_contrast": {},
1525 "prefers_reduced_motion": {},
1526 "prefers_reduced_transparency": {},
1527 "screen_reader_active": {},
1528 "differentiate_without_color": {}
1529 }},
1530 "scrollbar_preferences": {{
1531 "visibility": "{:?}",
1532 "track_click": "{:?}"
1533 }},
1534 "linux": {{
1535 "gtk_theme": {},
1536 "icon_theme": {},
1537 "cursor_theme": {},
1538 "cursor_size": {},
1539 "titlebar_button_layout": {}
1540 }},
1541 "visual_hints": {{
1542 "show_button_images": {},
1543 "show_menu_images": {},
1544 "toolbar_style": "{:?}",
1545 "show_tooltips": {}
1546 }},
1547 "animation": {{
1548 "animations_enabled": {},
1549 "animation_duration_factor": {:.2},
1550 "focus_indicator_behavior": "{:?}"
1551 }},
1552 "audio": {{
1553 "event_sounds_enabled": {},
1554 "input_feedback_sounds_enabled": {}
1555 }}
1556}}"#,
1557 self.theme,
1559 self.platform,
1560 self.os_version.os,
1561 self.os_version.version_id,
1562 self.language.as_str(),
1563 self.prefers_reduced_motion,
1564 self.prefers_high_contrast,
1565 opt_color(self.colors.text),
1567 opt_color(self.colors.secondary_text),
1568 opt_color(self.colors.tertiary_text),
1569 opt_color(self.colors.background),
1570 opt_color(self.colors.accent),
1571 opt_color(self.colors.accent_text),
1572 opt_color(self.colors.button_face),
1573 opt_color(self.colors.button_text),
1574 opt_color(self.colors.disabled_text),
1575 opt_color(self.colors.window_background),
1576 opt_color(self.colors.under_page_background),
1577 opt_color(self.colors.selection_background),
1578 opt_color(self.colors.selection_text),
1579 opt_color(self.colors.selection_background_inactive),
1580 opt_color(self.colors.selection_text_inactive),
1581 opt_color(self.colors.link),
1582 opt_color(self.colors.separator),
1583 opt_color(self.colors.grid),
1584 opt_color(self.colors.find_highlight),
1585 opt_color(self.colors.sidebar_background),
1586 opt_color(self.colors.sidebar_selection),
1587 opt_str(&self.fonts.ui_font),
1589 opt_f32(self.fonts.ui_font_size),
1590 opt_str(&self.fonts.monospace_font),
1591 opt_str(&self.fonts.title_font),
1592 opt_str(&self.fonts.menu_font),
1593 opt_str(&self.fonts.small_font),
1594 tm.button_side,
1596 opt_px(&tm.height),
1597 opt_px(&tm.button_area_width),
1598 opt_px(&tm.padding_horizontal),
1599 opt_str(&tm.title_font),
1600 opt_f32(tm.title_font_size),
1601 opt_u16(tm.title_font_weight),
1602 tm.buttons.has_close,
1603 tm.buttons.has_minimize,
1604 tm.buttons.has_maximize,
1605 tm.buttons.has_fullscreen,
1606 inp.double_click_time_ms,
1608 inp.double_click_distance_px,
1609 inp.drag_threshold_px,
1610 inp.caret_blink_rate_ms,
1611 inp.caret_width_px,
1612 inp.wheel_scroll_lines,
1613 inp.hover_time_ms,
1614 tr.font_smoothing_enabled,
1616 tr.subpixel_type,
1617 tr.font_smoothing_gamma,
1618 tr.increased_contrast,
1619 acc.prefers_bold_text,
1621 acc.prefers_larger_text,
1622 acc.text_scale_factor,
1623 acc.prefers_high_contrast,
1624 acc.prefers_reduced_motion,
1625 acc.prefers_reduced_transparency,
1626 acc.screen_reader_active,
1627 acc.differentiate_without_color,
1628 sp.visibility,
1630 sp.track_click,
1631 opt_str(&lnx.gtk_theme),
1633 opt_str(&lnx.icon_theme),
1634 opt_str(&lnx.cursor_theme),
1635 lnx.cursor_size,
1636 opt_str(&lnx.titlebar_button_layout),
1637 vh.show_button_images,
1639 vh.show_menu_images,
1640 vh.toolbar_style,
1641 vh.show_tooltips,
1642 anim.animations_enabled,
1644 anim.animation_duration_factor,
1645 anim.focus_indicator_behavior,
1646 audio.event_sounds_enabled,
1648 audio.input_feedback_sounds_enabled,
1649 );
1650
1651 AzString::from(json)
1652 }
1653
1654 #[must_use]
1660 pub fn detect() -> Self {
1661 Self::default_for_platform()
1662 }
1663
1664 #[must_use]
1666 pub fn default_for_platform() -> Self {
1667 #[cfg(target_os = "windows")]
1668 {
1669 defaults::windows_11_light()
1670 }
1671 #[cfg(target_os = "macos")]
1672 {
1673 defaults::macos_modern_light()
1674 }
1675 #[cfg(target_os = "linux")]
1676 {
1677 defaults::gnome_adwaita_light()
1678 }
1679 #[cfg(target_os = "android")]
1680 {
1681 defaults::android_material_light()
1682 }
1683 #[cfg(target_os = "ios")]
1684 {
1685 defaults::ios_light()
1686 }
1687 #[cfg(not(any(
1688 target_os = "linux",
1689 target_os = "windows",
1690 target_os = "macos",
1691 target_os = "android",
1692 target_os = "ios"
1693 )))]
1694 {
1695 Self::default()
1696 }
1697 }
1698
1699 #[inline]
1701 #[must_use]
1702 pub fn new() -> Self {
1703 Self::detect()
1704 }
1705
1706 #[must_use]
1712 pub fn create_csd_stylesheet(&self) -> Css {
1713 use alloc::format;
1714
1715 use crate::parser2::new_from_str;
1716
1717 let mut css = String::new();
1719
1720 let bg_color = self
1722 .colors
1723 .window_background
1724 .as_option()
1725 .copied()
1726 .unwrap_or(ColorU::new_rgb(240, 240, 240));
1727 let text_color = self
1728 .colors
1729 .text
1730 .as_option()
1731 .copied()
1732 .unwrap_or(ColorU::new_rgb(0, 0, 0));
1733 let accent_color = self
1734 .colors
1735 .accent
1736 .as_option()
1737 .copied()
1738 .unwrap_or(ColorU::new_rgb(0, 120, 215));
1739 let border_color = match self.theme {
1740 Theme::Dark => ColorU::new_rgb(60, 60, 60),
1741 Theme::Light => ColorU::new_rgb(200, 200, 200),
1742 };
1743
1744 let corner_radius = self
1746 .metrics
1747 .corner_radius
1748 .map(|px| {
1749 use crate::props::basic::pixel::DEFAULT_FONT_SIZE;
1750 format!(
1751 "{}px",
1752 px.to_pixels_internal(1.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE)
1753 )
1754 })
1755 .unwrap_or_else(|| "4px".to_string());
1756
1757 let _ = write!(
1759 css,
1760 ".csd-titlebar {{ width: 100%; height: 32px; background: rgb({}, {}, {}); \
1761 border-bottom: 1px solid rgb({}, {}, {}); display: flex; flex-direction: row; \
1762 align-items: center; justify-content: space-between; padding: 0 8px; \
1763 cursor: grab; user-select: none; }} ",
1764 bg_color.r, bg_color.g, bg_color.b, border_color.r, border_color.g, border_color.b,
1765 );
1766
1767 let _ = write!(
1769 css,
1770 ".csd-title {{ color: rgb({}, {}, {}); font-size: 13px; flex-grow: 1; text-align: \
1771 center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; \
1772 user-select: none; }} ",
1773 text_color.r, text_color.g, text_color.b,
1774 );
1775
1776 css.push_str(".csd-buttons { display: flex; flex-direction: row; gap: 4px; } ");
1778
1779 let _ = write!(
1781 css,
1782 ".csd-button {{ width: 32px; height: 24px; border-radius: {}; background: \
1783 transparent; color: rgb({}, {}, {}); font-size: 16px; line-height: 24px; text-align: \
1784 center; cursor: pointer; user-select: none; }} ",
1785 corner_radius, text_color.r, text_color.g, text_color.b,
1786 );
1787
1788 let hover_color = match self.theme {
1790 Theme::Dark => ColorU::new_rgb(60, 60, 60),
1791 Theme::Light => ColorU::new_rgb(220, 220, 220),
1792 };
1793 let _ = write!(
1794 css,
1795 ".csd-button:hover {{ background: rgb({}, {}, {}); }} ",
1796 hover_color.r, hover_color.g, hover_color.b,
1797 );
1798
1799 css.push_str(
1801 ".csd-close:hover { background: rgb(232, 17, 35); color: rgb(255, 255, 255); } ",
1802 );
1803
1804 match self.platform {
1806 Platform::MacOs => {
1807 css.push_str(".csd-buttons { position: absolute; left: 8px; } ");
1809 css.push_str(
1810 ".csd-close { background: rgb(255, 95, 86); width: 12px; height: 12px; \
1811 border-radius: 50%; } ",
1812 );
1813 css.push_str(
1814 ".csd-minimize { background: rgb(255, 189, 46); width: 12px; height: 12px; \
1815 border-radius: 50%; } ",
1816 );
1817 css.push_str(
1818 ".csd-maximize { background: rgb(40, 201, 64); width: 12px; height: 12px; \
1819 border-radius: 50%; } ",
1820 );
1821 }
1822 Platform::Linux(_) => {
1823 css.push_str(".csd-title { text-align: left; } ");
1825 }
1826 _ => {
1827 }
1829 }
1830
1831 let (mut parsed_css, _warnings) = new_from_str(&css);
1833 for rule in parsed_css.rules.as_mut() {
1835 rule.priority = crate::css::rule_priority::SYSTEM;
1836 }
1837 parsed_css
1838 }
1839}
1840
1841#[must_use]
1846pub fn detect_linux_desktop_env() -> DesktopEnvironment {
1847 if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") {
1849 let desktop_lower = desktop.to_lowercase();
1850 if desktop_lower.contains("gnome") {
1851 return DesktopEnvironment::Gnome;
1852 }
1853 if desktop_lower.contains("kde") || desktop_lower.contains("plasma") {
1854 return DesktopEnvironment::Kde;
1855 }
1856 if desktop_lower.contains("xfce") {
1857 return DesktopEnvironment::Other(AzString::from_const_str("XFCE"));
1858 }
1859 if desktop_lower.contains("unity") {
1860 return DesktopEnvironment::Other(AzString::from_const_str("Unity"));
1861 }
1862 if desktop_lower.contains("cinnamon") {
1863 return DesktopEnvironment::Other(AzString::from_const_str("Cinnamon"));
1864 }
1865 if desktop_lower.contains("mate") {
1866 return DesktopEnvironment::Other(AzString::from_const_str("MATE"));
1867 }
1868 if desktop_lower.contains("lxde") || desktop_lower.contains("lxqt") {
1869 return DesktopEnvironment::Other(AzString::from(desktop.to_uppercase()));
1870 }
1871 if desktop_lower.contains("budgie") {
1872 return DesktopEnvironment::Other(AzString::from_const_str("Budgie"));
1873 }
1874 if desktop_lower.contains("pantheon") {
1875 return DesktopEnvironment::Other(AzString::from_const_str("Pantheon"));
1876 }
1877 if desktop_lower.contains("deepin") {
1878 return DesktopEnvironment::Other(AzString::from_const_str("Deepin"));
1879 }
1880 if desktop_lower.contains("hyprland") {
1881 return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
1882 }
1883 if desktop_lower.contains("sway") {
1884 return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
1885 }
1886 if desktop_lower.contains("i3") {
1887 return DesktopEnvironment::Other(AzString::from_const_str("i3"));
1888 }
1889 return DesktopEnvironment::Other(AzString::from(desktop));
1890 }
1891
1892 if let Ok(session) = std::env::var("DESKTOP_SESSION") {
1894 let session_lower = session.to_lowercase();
1895 if session_lower.contains("gnome") {
1896 return DesktopEnvironment::Gnome;
1897 }
1898 if session_lower.contains("plasma") || session_lower.contains("kde") {
1899 return DesktopEnvironment::Kde;
1900 }
1901 if session_lower.contains("xfce") {
1902 return DesktopEnvironment::Other(AzString::from_const_str("XFCE"));
1903 }
1904 if session_lower.contains("cinnamon") {
1905 return DesktopEnvironment::Other(AzString::from_const_str("Cinnamon"));
1906 }
1907 return DesktopEnvironment::Other(AzString::from(session));
1908 }
1909
1910 if std::env::var("GNOME_DESKTOP_SESSION_ID").is_ok() {
1912 return DesktopEnvironment::Gnome;
1913 }
1914 if std::env::var("KDE_FULL_SESSION").is_ok() {
1915 return DesktopEnvironment::Kde;
1916 }
1917 if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
1918 return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
1919 }
1920 if std::env::var("SWAYSOCK").is_ok() {
1921 return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
1922 }
1923 if std::env::var("I3SOCK").is_ok() {
1924 return DesktopEnvironment::Other(AzString::from_const_str("i3"));
1925 }
1926
1927 DesktopEnvironment::Other(AzString::from_const_str("Unknown"))
1928}
1929
1930#[must_use]
1936pub fn detect_system_language() -> AzString {
1937 let env_vars = ["LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"];
1938 for var in &env_vars {
1939 if let Ok(value) = std::env::var(var) {
1940 let value = value.trim();
1941 if value.is_empty() || value == "C" || value == "POSIX" {
1942 continue;
1943 }
1944 let lang = value
1946 .split('.') .next()
1948 .unwrap_or(value)
1949 .split(':') .next()
1951 .unwrap_or(value);
1952 if !lang.is_empty() {
1953 return AzString::from(lang.replace('_', "-"));
1954 }
1955 }
1956 }
1957 AzString::from_const_str("en-US")
1958}
1959
1960pub mod defaults {
1961 use super::{
1969 AccessibilitySettings, AnimationMetrics, AudioMetrics, FocusVisuals, Handedness,
1970 InputMetrics, LinuxCustomization, ScrollbarPreferences, TextRenderingHints, VisualHints,
1971 };
1972 use crate::{
1973 corety::{AzString, OptionF32, OptionString},
1974 dynamic_selector::{BoolCondition, OsVersion},
1975 props::{
1976 basic::{
1977 color::{ColorU, OptionColorU},
1978 pixel::{OptionPixelValue, PixelValue},
1979 },
1980 layout::{
1981 dimensions::LayoutWidth,
1982 spacing::{LayoutPaddingLeft, LayoutPaddingRight},
1983 },
1984 style::{
1985 background::StyleBackgroundContent,
1986 scrollbar::{
1987 ComputedScrollbarStyle, OverflowScrolling, OverscrollBehavior, ScrollBehavior,
1988 ScrollPhysics, ScrollbarInfo, SCROLLBAR_ANDROID_DARK, SCROLLBAR_ANDROID_LIGHT,
1989 SCROLLBAR_CLASSIC_DARK, SCROLLBAR_CLASSIC_LIGHT, SCROLLBAR_IOS_DARK,
1990 SCROLLBAR_IOS_LIGHT, SCROLLBAR_MACOS_DARK, SCROLLBAR_MACOS_LIGHT,
1991 SCROLLBAR_WINDOWS_DARK, SCROLLBAR_WINDOWS_LIGHT,
1992 },
1993 },
1994 },
1995 system::{
1996 DesktopEnvironment, IconStyleOptions, Platform, SystemColors, SystemFonts,
1997 SystemMetrics, SystemStyle, Theme, TitlebarMetrics,
1998 },
1999 };
2000
2001 pub const SCROLLBAR_WINDOWS_CLASSIC: ScrollbarInfo = ScrollbarInfo {
2005 width: LayoutWidth::Px(PixelValue::const_px(17)),
2006 padding_left: LayoutPaddingLeft {
2007 inner: PixelValue::const_px(0),
2008 },
2009 padding_right: LayoutPaddingRight {
2010 inner: PixelValue::const_px(0),
2011 },
2012 track: StyleBackgroundContent::Color(ColorU {
2013 r: 223,
2014 g: 223,
2015 b: 223,
2016 a: 255,
2017 }), thumb: StyleBackgroundContent::Color(ColorU {
2019 r: 208,
2020 g: 208,
2021 b: 208,
2022 a: 255,
2023 }), button: StyleBackgroundContent::Color(ColorU {
2025 r: 208,
2026 g: 208,
2027 b: 208,
2028 a: 255,
2029 }),
2030 corner: StyleBackgroundContent::Color(ColorU {
2031 r: 223,
2032 g: 223,
2033 b: 223,
2034 a: 255,
2035 }),
2036 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
2037 clip_to_container_border: false,
2038 scroll_behavior: ScrollBehavior::Auto,
2039 overscroll_behavior_x: OverscrollBehavior::None,
2040 overscroll_behavior_y: OverscrollBehavior::None,
2041 overflow_scrolling: OverflowScrolling::Auto,
2042 };
2043
2044 pub const SCROLLBAR_MACOS_AQUA: ScrollbarInfo = ScrollbarInfo {
2046 width: LayoutWidth::Px(PixelValue::const_px(15)),
2047 padding_left: LayoutPaddingLeft {
2048 inner: PixelValue::const_px(0),
2049 },
2050 padding_right: LayoutPaddingRight {
2051 inner: PixelValue::const_px(0),
2052 },
2053 track: StyleBackgroundContent::Color(ColorU {
2054 r: 238,
2055 g: 238,
2056 b: 238,
2057 a: 128,
2058 }), thumb: StyleBackgroundContent::Color(ColorU {
2060 r: 105,
2061 g: 173,
2062 b: 255,
2063 a: 255,
2064 }), button: StyleBackgroundContent::Color(ColorU {
2066 r: 105,
2067 g: 173,
2068 b: 255,
2069 a: 255,
2070 }),
2071 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
2072 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
2073 clip_to_container_border: true,
2074 scroll_behavior: ScrollBehavior::Smooth,
2075 overscroll_behavior_x: OverscrollBehavior::Auto,
2076 overscroll_behavior_y: OverscrollBehavior::Auto,
2077 overflow_scrolling: OverflowScrolling::Auto,
2078 };
2079
2080 pub const SCROLLBAR_KDE_OXYGEN: ScrollbarInfo = ScrollbarInfo {
2082 width: LayoutWidth::Px(PixelValue::const_px(14)),
2083 padding_left: LayoutPaddingLeft {
2084 inner: PixelValue::const_px(2),
2085 },
2086 padding_right: LayoutPaddingRight {
2087 inner: PixelValue::const_px(2),
2088 },
2089 track: StyleBackgroundContent::Color(ColorU {
2090 r: 242,
2091 g: 242,
2092 b: 242,
2093 a: 255,
2094 }),
2095 thumb: StyleBackgroundContent::Color(ColorU {
2096 r: 177,
2097 g: 177,
2098 b: 177,
2099 a: 255,
2100 }),
2101 button: StyleBackgroundContent::Color(ColorU {
2102 r: 216,
2103 g: 216,
2104 b: 216,
2105 a: 255,
2106 }),
2107 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
2108 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
2109 clip_to_container_border: false,
2110 scroll_behavior: ScrollBehavior::Auto,
2111 overscroll_behavior_x: OverscrollBehavior::Auto,
2112 overscroll_behavior_y: OverscrollBehavior::Auto,
2113 overflow_scrolling: OverflowScrolling::Auto,
2114 };
2115
2116 fn scrollbar_info_to_computed(info: &ScrollbarInfo) -> ComputedScrollbarStyle {
2118 ComputedScrollbarStyle {
2119 width: Some(info.width.clone()),
2120 handle_width: None,
2123 handle_radius: None,
2124 thumb_color: match info.thumb {
2125 StyleBackgroundContent::Color(c) => Some(c),
2126 _ => None,
2127 },
2128 track_color: match info.track {
2129 StyleBackgroundContent::Color(c) => Some(c),
2130 _ => None,
2131 },
2132 }
2133 }
2134
2135 #[must_use]
2139 pub fn windows_11_light() -> SystemStyle {
2140 SystemStyle {
2141 theme: Theme::Light,
2142 platform: Platform::Windows,
2143 colors: SystemColors {
2144 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2145 background: OptionColorU::Some(ColorU::new_rgb(243, 243, 243)),
2146 accent: OptionColorU::Some(ColorU::new_rgb(0, 95, 184)),
2147 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2148 selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2149 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2150 ..Default::default()
2151 },
2152 fonts: SystemFonts {
2153 ui_font: OptionString::Some("Segoe UI Variable Text".into()),
2154 ui_font_size: OptionF32::Some(9.0),
2155 monospace_font: OptionString::Some("Consolas".into()),
2156 ..Default::default()
2157 },
2158 metrics: SystemMetrics {
2159 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2160 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2161 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2162 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2163 titlebar: TitlebarMetrics::windows(),
2164 },
2165 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2166 &SCROLLBAR_WINDOWS_LIGHT,
2167 ))),
2168 app_specific_stylesheet: None,
2169 run_destructor: true,
2170 icon_style: IconStyleOptions::default(),
2171 language: AzString::from_const_str("en-US"),
2172 os_version: OsVersion::WIN_11,
2173 prefers_reduced_motion: BoolCondition::False,
2174 prefers_high_contrast: BoolCondition::False,
2175 scroll_physics: ScrollPhysics::windows(),
2176 linux: LinuxCustomization::default(),
2177 focus_visuals: FocusVisuals::default(),
2178 handedness: Handedness::default(),
2179 accessibility: AccessibilitySettings::default(),
2180 input: InputMetrics::default(),
2181 text_rendering: TextRenderingHints::default(),
2182 scrollbar_preferences: ScrollbarPreferences::default(),
2183 visual_hints: VisualHints::default(),
2184 animation: AnimationMetrics::default(),
2185 audio: AudioMetrics::default(),
2186 }
2187 }
2188
2189 #[must_use]
2191 pub fn windows_11_dark() -> SystemStyle {
2192 SystemStyle {
2193 theme: Theme::Dark,
2194 platform: Platform::Windows,
2195 colors: SystemColors {
2196 text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2197 background: OptionColorU::Some(ColorU::new_rgb(32, 32, 32)),
2198 accent: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2199 window_background: OptionColorU::Some(ColorU::new_rgb(25, 25, 25)),
2200 selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2201 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2202 ..Default::default()
2203 },
2204 fonts: SystemFonts {
2205 ui_font: OptionString::Some("Segoe UI Variable Text".into()),
2206 ui_font_size: OptionF32::Some(9.0),
2207 monospace_font: OptionString::Some("Consolas".into()),
2208 ..Default::default()
2209 },
2210 metrics: SystemMetrics {
2211 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2212 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2213 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2214 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2215 titlebar: TitlebarMetrics::windows(),
2216 },
2217 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2218 &SCROLLBAR_WINDOWS_DARK,
2219 ))),
2220 app_specific_stylesheet: None,
2221 run_destructor: true,
2222 icon_style: IconStyleOptions::default(),
2223 language: AzString::from_const_str("en-US"),
2224 os_version: OsVersion::WIN_11,
2225 prefers_reduced_motion: BoolCondition::False,
2226 prefers_high_contrast: BoolCondition::False,
2227 scroll_physics: ScrollPhysics::windows(),
2228 linux: LinuxCustomization::default(),
2229 focus_visuals: FocusVisuals::default(),
2230 handedness: Handedness::default(),
2231 accessibility: AccessibilitySettings::default(),
2232 input: InputMetrics::default(),
2233 text_rendering: TextRenderingHints::default(),
2234 scrollbar_preferences: ScrollbarPreferences::default(),
2235 visual_hints: VisualHints::default(),
2236 animation: AnimationMetrics::default(),
2237 audio: AudioMetrics::default(),
2238 }
2239 }
2240
2241 #[must_use]
2243 pub fn windows_7_aero() -> SystemStyle {
2244 SystemStyle {
2245 theme: Theme::Light,
2246 platform: Platform::Windows,
2247 colors: SystemColors {
2248 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2249 background: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
2250 accent: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
2251 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2252 selection_background: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
2253 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2254 ..Default::default()
2255 },
2256 fonts: SystemFonts {
2257 ui_font: OptionString::Some("Segoe UI".into()),
2258 ui_font_size: OptionF32::Some(9.0),
2259 monospace_font: OptionString::Some("Consolas".into()),
2260 ..Default::default()
2261 },
2262 metrics: SystemMetrics {
2263 corner_radius: OptionPixelValue::Some(PixelValue::px(6.0)),
2264 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2265 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
2266 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(5.0)),
2267 titlebar: TitlebarMetrics::windows(),
2268 },
2269 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2270 &SCROLLBAR_CLASSIC_LIGHT,
2271 ))),
2272 app_specific_stylesheet: None,
2273 run_destructor: true,
2274 icon_style: IconStyleOptions::default(),
2275 language: AzString::from_const_str("en-US"),
2276 os_version: OsVersion::WIN_7,
2277 prefers_reduced_motion: BoolCondition::False,
2278 prefers_high_contrast: BoolCondition::False,
2279 scroll_physics: ScrollPhysics::windows(),
2280 linux: LinuxCustomization::default(),
2281 focus_visuals: FocusVisuals::default(),
2282 handedness: Handedness::default(),
2283 accessibility: AccessibilitySettings::default(),
2284 input: InputMetrics::default(),
2285 text_rendering: TextRenderingHints::default(),
2286 scrollbar_preferences: ScrollbarPreferences::default(),
2287 visual_hints: VisualHints::default(),
2288 animation: AnimationMetrics::default(),
2289 audio: AudioMetrics::default(),
2290 }
2291 }
2292
2293 #[must_use]
2295 pub fn windows_xp_luna() -> SystemStyle {
2296 SystemStyle {
2297 theme: Theme::Light,
2298 platform: Platform::Windows,
2299 colors: SystemColors {
2300 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2301 background: OptionColorU::Some(ColorU::new_rgb(236, 233, 216)),
2302 accent: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
2303 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2304 selection_background: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
2305 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2306 ..Default::default()
2307 },
2308 fonts: SystemFonts {
2309 ui_font: OptionString::Some("Tahoma".into()),
2310 ui_font_size: OptionF32::Some(8.0),
2311 monospace_font: OptionString::Some("Lucida Console".into()),
2312 ..Default::default()
2313 },
2314 metrics: SystemMetrics {
2315 corner_radius: OptionPixelValue::Some(PixelValue::px(3.0)),
2316 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2317 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
2318 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
2319 titlebar: TitlebarMetrics::windows(),
2320 },
2321 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2322 &SCROLLBAR_WINDOWS_CLASSIC,
2323 ))),
2324 app_specific_stylesheet: None,
2325 run_destructor: true,
2326 icon_style: IconStyleOptions::default(),
2327 language: AzString::from_const_str("en-US"),
2328 os_version: OsVersion::WIN_XP,
2329 prefers_reduced_motion: BoolCondition::False,
2330 prefers_high_contrast: BoolCondition::False,
2331 scroll_physics: ScrollPhysics::windows(),
2332 linux: LinuxCustomization::default(),
2333 focus_visuals: FocusVisuals::default(),
2334 handedness: Handedness::default(),
2335 accessibility: AccessibilitySettings::default(),
2336 input: InputMetrics::default(),
2337 text_rendering: TextRenderingHints::default(),
2338 scrollbar_preferences: ScrollbarPreferences::default(),
2339 visual_hints: VisualHints::default(),
2340 animation: AnimationMetrics::default(),
2341 audio: AudioMetrics::default(),
2342 }
2343 }
2344
2345 #[must_use]
2349 pub fn macos_modern_light() -> SystemStyle {
2350 SystemStyle {
2351 platform: Platform::MacOs,
2352 theme: Theme::Light,
2353 colors: SystemColors {
2354 text: OptionColorU::Some(ColorU::new(0, 0, 0, 221)),
2355 background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
2356 accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
2357 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2358 selection_background: OptionColorU::Some(ColorU::new(0, 122, 255, 128)),
2360 selection_text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2361 ..Default::default()
2362 },
2363 fonts: SystemFonts {
2364 ui_font: OptionString::Some(".SF NS".into()),
2365 ui_font_size: OptionF32::Some(13.0),
2366 monospace_font: OptionString::Some("Menlo".into()),
2367 ..Default::default()
2368 },
2369 metrics: SystemMetrics {
2370 corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
2371 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2372 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2373 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2374 titlebar: TitlebarMetrics::macos(),
2375 },
2376 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_LIGHT))),
2377 app_specific_stylesheet: None,
2378 run_destructor: true,
2379 icon_style: IconStyleOptions::default(),
2380 language: AzString::from_const_str("en-US"),
2381 os_version: OsVersion::MACOS_SONOMA,
2382 prefers_reduced_motion: BoolCondition::False,
2383 prefers_high_contrast: BoolCondition::False,
2384 scroll_physics: ScrollPhysics::macos(),
2385 linux: LinuxCustomization::default(),
2386 focus_visuals: FocusVisuals::default(),
2387 handedness: Handedness::default(),
2388 accessibility: AccessibilitySettings::default(),
2389 input: InputMetrics::default(),
2390 text_rendering: TextRenderingHints::default(),
2391 scrollbar_preferences: ScrollbarPreferences::default(),
2392 visual_hints: VisualHints::default(),
2393 animation: AnimationMetrics::default(),
2394 audio: AudioMetrics::default(),
2395 }
2396 }
2397
2398 #[must_use]
2400 pub fn macos_modern_dark() -> SystemStyle {
2401 SystemStyle {
2402 platform: Platform::MacOs,
2403 theme: Theme::Dark,
2404 colors: SystemColors {
2405 text: OptionColorU::Some(ColorU::new(255, 255, 255, 221)),
2406 background: OptionColorU::Some(ColorU::new_rgb(28, 28, 30)),
2407 accent: OptionColorU::Some(ColorU::new_rgb(10, 132, 255)),
2408 window_background: OptionColorU::Some(ColorU::new_rgb(44, 44, 46)),
2409 selection_background: OptionColorU::Some(ColorU::new(10, 132, 255, 128)),
2411 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2412 ..Default::default()
2413 },
2414 fonts: SystemFonts {
2415 ui_font: OptionString::Some(".SF NS".into()),
2416 ui_font_size: OptionF32::Some(13.0),
2417 monospace_font: OptionString::Some("SF Mono".into()),
2418 monospace_font_size: OptionF32::Some(12.0),
2419 title_font: OptionString::Some(".SF NS".into()),
2420 title_font_size: OptionF32::Some(13.0),
2421 menu_font: OptionString::Some(".SF NS".into()),
2422 menu_font_size: OptionF32::Some(13.0),
2423 small_font: OptionString::Some(".SF NS".into()),
2424 small_font_size: OptionF32::Some(11.0),
2425 ..Default::default()
2426 },
2427 metrics: SystemMetrics {
2428 corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
2429 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2430 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2431 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2432 titlebar: TitlebarMetrics::macos(),
2433 },
2434 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_DARK))),
2435 app_specific_stylesheet: None,
2436 run_destructor: true,
2437 icon_style: IconStyleOptions::default(),
2438 language: AzString::from_const_str("en-US"),
2439 os_version: OsVersion::MACOS_SONOMA,
2440 prefers_reduced_motion: BoolCondition::False,
2441 prefers_high_contrast: BoolCondition::False,
2442 scroll_physics: ScrollPhysics::macos(),
2443 linux: LinuxCustomization::default(),
2444 focus_visuals: FocusVisuals::default(),
2445 handedness: Handedness::default(),
2446 accessibility: AccessibilitySettings::default(),
2447 input: InputMetrics::default(),
2448 text_rendering: TextRenderingHints::default(),
2449 scrollbar_preferences: ScrollbarPreferences::default(),
2450 visual_hints: VisualHints::default(),
2451 animation: AnimationMetrics::default(),
2452 audio: AudioMetrics::default(),
2453 }
2454 }
2455
2456 #[must_use]
2458 pub fn macos_aqua() -> SystemStyle {
2459 SystemStyle {
2460 platform: Platform::MacOs,
2461 theme: Theme::Light,
2462 colors: SystemColors {
2463 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2464 background: OptionColorU::Some(ColorU::new_rgb(229, 229, 229)),
2465 accent: OptionColorU::Some(ColorU::new_rgb(63, 128, 234)),
2466 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2467 ..Default::default()
2468 },
2469 fonts: SystemFonts {
2470 ui_font: OptionString::Some("Lucida Grande".into()),
2471 ui_font_size: OptionF32::Some(13.0),
2472 monospace_font: OptionString::Some("Monaco".into()),
2473 monospace_font_size: OptionF32::Some(12.0),
2474 ..Default::default()
2475 },
2476 metrics: SystemMetrics {
2477 corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
2478 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2479 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2480 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2481 titlebar: TitlebarMetrics::macos(),
2482 },
2483 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_AQUA))),
2484 app_specific_stylesheet: None,
2485 run_destructor: true,
2486 icon_style: IconStyleOptions::default(),
2487 language: AzString::from_const_str("en-US"),
2488 os_version: OsVersion::MACOS_TIGER,
2489 prefers_reduced_motion: BoolCondition::False,
2490 prefers_high_contrast: BoolCondition::False,
2491 scroll_physics: ScrollPhysics::macos(),
2492 linux: LinuxCustomization::default(),
2493 focus_visuals: FocusVisuals::default(),
2494 handedness: Handedness::default(),
2495 accessibility: AccessibilitySettings::default(),
2496 input: InputMetrics::default(),
2497 text_rendering: TextRenderingHints::default(),
2498 scrollbar_preferences: ScrollbarPreferences::default(),
2499 visual_hints: VisualHints::default(),
2500 animation: AnimationMetrics::default(),
2501 audio: AudioMetrics::default(),
2502 }
2503 }
2504
2505 #[must_use]
2509 pub fn gnome_adwaita_light() -> SystemStyle {
2510 SystemStyle {
2511 platform: Platform::Linux(DesktopEnvironment::Gnome),
2512 theme: Theme::Light,
2513 colors: SystemColors {
2514 text: OptionColorU::Some(ColorU::new_rgb(46, 52, 54)),
2515 background: OptionColorU::Some(ColorU::new_rgb(249, 249, 249)),
2516 accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
2517 window_background: OptionColorU::Some(ColorU::new_rgb(237, 237, 237)),
2518 ..Default::default()
2519 },
2520 fonts: SystemFonts {
2521 ui_font: OptionString::Some("Cantarell".into()),
2522 ui_font_size: OptionF32::Some(11.0),
2523 monospace_font: OptionString::Some("Monospace".into()),
2524 ..Default::default()
2525 },
2526 metrics: SystemMetrics {
2527 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2528 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2529 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2530 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2531 titlebar: TitlebarMetrics::linux_gnome(),
2532 },
2533 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2534 &SCROLLBAR_CLASSIC_LIGHT,
2535 ))),
2536 app_specific_stylesheet: None,
2537 run_destructor: true,
2538 icon_style: IconStyleOptions::default(),
2539 language: AzString::from_const_str("en-US"),
2540 os_version: OsVersion::LINUX_6_0,
2541 prefers_reduced_motion: BoolCondition::False,
2542 prefers_high_contrast: BoolCondition::False,
2543 scroll_physics: ScrollPhysics::default(),
2544 linux: LinuxCustomization::default(),
2545 focus_visuals: FocusVisuals::default(),
2546 handedness: Handedness::default(),
2547 accessibility: AccessibilitySettings::default(),
2548 input: InputMetrics::default(),
2549 text_rendering: TextRenderingHints::default(),
2550 scrollbar_preferences: ScrollbarPreferences::default(),
2551 visual_hints: VisualHints::default(),
2552 animation: AnimationMetrics::default(),
2553 audio: AudioMetrics::default(),
2554 }
2555 }
2556
2557 #[must_use]
2559 pub fn gnome_adwaita_dark() -> SystemStyle {
2560 SystemStyle {
2561 platform: Platform::Linux(DesktopEnvironment::Gnome),
2562 theme: Theme::Dark,
2563 colors: SystemColors {
2564 text: OptionColorU::Some(ColorU::new_rgb(238, 238, 236)),
2565 background: OptionColorU::Some(ColorU::new_rgb(36, 36, 36)),
2566 accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
2567 window_background: OptionColorU::Some(ColorU::new_rgb(48, 48, 48)),
2568 ..Default::default()
2569 },
2570 fonts: SystemFonts {
2571 ui_font: OptionString::Some("Cantarell".into()),
2572 ui_font_size: OptionF32::Some(11.0),
2573 monospace_font: OptionString::Some("Monospace".into()),
2574 ..Default::default()
2575 },
2576 metrics: SystemMetrics {
2577 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2578 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2579 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2580 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2581 titlebar: TitlebarMetrics::linux_gnome(),
2582 },
2583 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2584 &SCROLLBAR_CLASSIC_DARK,
2585 ))),
2586 app_specific_stylesheet: None,
2587 run_destructor: true,
2588 icon_style: IconStyleOptions::default(),
2589 language: AzString::from_const_str("en-US"),
2590 os_version: OsVersion::LINUX_6_0,
2591 prefers_reduced_motion: BoolCondition::False,
2592 prefers_high_contrast: BoolCondition::False,
2593 scroll_physics: ScrollPhysics::default(),
2594 linux: LinuxCustomization::default(),
2595 focus_visuals: FocusVisuals::default(),
2596 handedness: Handedness::default(),
2597 accessibility: AccessibilitySettings::default(),
2598 input: InputMetrics::default(),
2599 text_rendering: TextRenderingHints::default(),
2600 scrollbar_preferences: ScrollbarPreferences::default(),
2601 visual_hints: VisualHints::default(),
2602 animation: AnimationMetrics::default(),
2603 audio: AudioMetrics::default(),
2604 }
2605 }
2606
2607 #[must_use]
2609 pub fn gtk2_clearlooks() -> SystemStyle {
2610 SystemStyle {
2611 platform: Platform::Linux(DesktopEnvironment::Gnome),
2612 theme: Theme::Light,
2613 colors: SystemColors {
2614 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2615 background: OptionColorU::Some(ColorU::new_rgb(239, 239, 239)),
2616 accent: OptionColorU::Some(ColorU::new_rgb(245, 121, 0)),
2617 ..Default::default()
2618 },
2619 fonts: SystemFonts {
2620 ui_font: OptionString::Some("DejaVu Sans".into()),
2621 ui_font_size: OptionF32::Some(10.0),
2622 monospace_font: OptionString::Some("DejaVu Sans Mono".into()),
2623 ..Default::default()
2624 },
2625 metrics: SystemMetrics {
2626 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2627 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2628 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
2629 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2630 titlebar: TitlebarMetrics::linux_gnome(),
2631 },
2632 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2633 &SCROLLBAR_CLASSIC_LIGHT,
2634 ))),
2635 app_specific_stylesheet: None,
2636 run_destructor: true,
2637 icon_style: IconStyleOptions::default(),
2638 language: AzString::from_const_str("en-US"),
2639 os_version: OsVersion::LINUX_2_6,
2640 prefers_reduced_motion: BoolCondition::False,
2641 prefers_high_contrast: BoolCondition::False,
2642 scroll_physics: ScrollPhysics::default(),
2643 linux: LinuxCustomization::default(),
2644 focus_visuals: FocusVisuals::default(),
2645 handedness: Handedness::default(),
2646 accessibility: AccessibilitySettings::default(),
2647 input: InputMetrics::default(),
2648 text_rendering: TextRenderingHints::default(),
2649 scrollbar_preferences: ScrollbarPreferences::default(),
2650 visual_hints: VisualHints::default(),
2651 animation: AnimationMetrics::default(),
2652 audio: AudioMetrics::default(),
2653 }
2654 }
2655
2656 #[must_use]
2666 pub fn kde_breeze_light() -> SystemStyle {
2667 SystemStyle {
2668 platform: Platform::Linux(DesktopEnvironment::Kde),
2669 theme: Theme::Light,
2670 colors: SystemColors {
2671 text: OptionColorU::Some(ColorU::new_rgb(35, 38, 41)),
2673 background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2674 window_background: OptionColorU::Some(ColorU::new_rgb(239, 240, 241)),
2676 under_page_background: OptionColorU::Some(ColorU::new_rgb(239, 240, 241)),
2677 accent: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
2679 accent_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2680 selection_background: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
2681 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2682 button_face: OptionColorU::Some(ColorU::new_rgb(252, 252, 252)),
2684 button_text: OptionColorU::Some(ColorU::new_rgb(35, 38, 41)),
2685 disabled_text: OptionColorU::Some(ColorU::new_rgb(112, 125, 138)),
2687 secondary_text: OptionColorU::Some(ColorU::new_rgb(112, 125, 138)),
2688 link: OptionColorU::Some(ColorU::new_rgb(41, 128, 185)),
2689 separator: OptionColorU::Some(ColorU::new_rgb(227, 229, 231)),
2690 ..Default::default()
2691 },
2692 fonts: SystemFonts {
2693 ui_font: OptionString::Some("Noto Sans".into()),
2694 ui_font_size: OptionF32::Some(10.0),
2695 monospace_font: OptionString::Some("Hack".into()),
2696 menu_font: OptionString::Some("Noto Sans".into()),
2699 menu_font_size: OptionF32::Some(10.0),
2700 small_font: OptionString::Some("Noto Sans".into()),
2701 small_font_size: OptionF32::Some(8.0),
2702 ..Default::default()
2703 },
2704 metrics: SystemMetrics {
2705 corner_radius: OptionPixelValue::Some(PixelValue::px(3.0)),
2706 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2707 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
2709 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
2710 titlebar: TitlebarMetrics::linux_gnome(),
2711 },
2712 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_KDE_OXYGEN))),
2713 app_specific_stylesheet: None,
2714 run_destructor: true,
2715 icon_style: IconStyleOptions::default(),
2716 language: AzString::from_const_str("en-US"),
2717 os_version: OsVersion::LINUX_6_0,
2718 prefers_reduced_motion: BoolCondition::False,
2719 prefers_high_contrast: BoolCondition::False,
2720 scroll_physics: ScrollPhysics::default(),
2721 linux: LinuxCustomization::default(),
2722 focus_visuals: FocusVisuals::default(),
2723 handedness: Handedness::default(),
2724 accessibility: AccessibilitySettings::default(),
2725 input: InputMetrics::default(),
2726 text_rendering: TextRenderingHints::default(),
2727 scrollbar_preferences: ScrollbarPreferences::default(),
2728 visual_hints: VisualHints::default(),
2729 animation: AnimationMetrics::default(),
2730 audio: AudioMetrics::default(),
2731 }
2732 }
2733
2734 #[must_use]
2742 pub fn kde_breeze_dark() -> SystemStyle {
2743 SystemStyle {
2744 platform: Platform::Linux(DesktopEnvironment::Kde),
2745 theme: Theme::Dark,
2746 colors: SystemColors {
2747 text: OptionColorU::Some(ColorU::new_rgb(252, 252, 252)),
2749 background: OptionColorU::Some(ColorU::new_rgb(27, 30, 32)),
2750 window_background: OptionColorU::Some(ColorU::new_rgb(42, 46, 50)),
2752 under_page_background: OptionColorU::Some(ColorU::new_rgb(42, 46, 50)),
2753 accent: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
2755 accent_text: OptionColorU::Some(ColorU::new_rgb(252, 252, 252)),
2756 selection_background: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
2757 selection_text: OptionColorU::Some(ColorU::new_rgb(252, 252, 252)),
2758 button_face: OptionColorU::Some(ColorU::new_rgb(49, 54, 59)),
2760 button_text: OptionColorU::Some(ColorU::new_rgb(252, 252, 252)),
2761 disabled_text: OptionColorU::Some(ColorU::new_rgb(161, 169, 177)),
2762 secondary_text: OptionColorU::Some(ColorU::new_rgb(161, 169, 177)),
2763 link: OptionColorU::Some(ColorU::new_rgb(29, 153, 243)),
2764 separator: OptionColorU::Some(ColorU::new_rgb(49, 54, 59)),
2765 ..Default::default()
2766 },
2767 fonts: SystemFonts {
2768 ui_font: OptionString::Some("Noto Sans".into()),
2769 ui_font_size: OptionF32::Some(10.0),
2770 monospace_font: OptionString::Some("Hack".into()),
2771 menu_font: OptionString::Some("Noto Sans".into()),
2772 menu_font_size: OptionF32::Some(10.0),
2773 small_font: OptionString::Some("Noto Sans".into()),
2774 small_font_size: OptionF32::Some(8.0),
2775 ..Default::default()
2776 },
2777 metrics: SystemMetrics {
2778 corner_radius: OptionPixelValue::Some(PixelValue::px(3.0)),
2779 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2780 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
2781 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
2782 titlebar: TitlebarMetrics::linux_gnome(),
2783 },
2784 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_DARK))),
2789 app_specific_stylesheet: None,
2790 run_destructor: true,
2791 icon_style: IconStyleOptions::default(),
2792 language: AzString::from_const_str("en-US"),
2793 os_version: OsVersion::LINUX_6_0,
2794 prefers_reduced_motion: BoolCondition::False,
2795 prefers_high_contrast: BoolCondition::False,
2796 scroll_physics: ScrollPhysics::default(),
2797 linux: LinuxCustomization::default(),
2798 focus_visuals: FocusVisuals::default(),
2799 handedness: Handedness::default(),
2800 accessibility: AccessibilitySettings::default(),
2801 input: InputMetrics::default(),
2802 text_rendering: TextRenderingHints::default(),
2803 scrollbar_preferences: ScrollbarPreferences::default(),
2804 visual_hints: VisualHints::default(),
2805 animation: AnimationMetrics::default(),
2806 audio: AudioMetrics::default(),
2807 }
2808 }
2809
2810 #[must_use]
2814 pub fn android_material_light() -> SystemStyle {
2815 SystemStyle {
2816 platform: Platform::Android,
2817 theme: Theme::Light,
2818 colors: SystemColors {
2819 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2820 background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2821 accent: OptionColorU::Some(ColorU::new_rgb(98, 0, 238)),
2822 ..Default::default()
2823 },
2824 fonts: SystemFonts {
2825 ui_font: OptionString::Some("Roboto".into()),
2826 ui_font_size: OptionF32::Some(14.0),
2827 monospace_font: OptionString::Some("Droid Sans Mono".into()),
2828 ..Default::default()
2829 },
2830 metrics: SystemMetrics {
2831 corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
2832 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2833 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2834 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(10.0)),
2835 titlebar: TitlebarMetrics::android(),
2836 },
2837 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2838 &SCROLLBAR_ANDROID_LIGHT,
2839 ))),
2840 app_specific_stylesheet: None,
2841 run_destructor: true,
2842 icon_style: IconStyleOptions::default(),
2843 language: AzString::from_const_str("en-US"),
2844 os_version: OsVersion::ANDROID_14,
2845 prefers_reduced_motion: BoolCondition::False,
2846 prefers_high_contrast: BoolCondition::False,
2847 scroll_physics: ScrollPhysics::android(),
2848 linux: LinuxCustomization::default(),
2849 focus_visuals: FocusVisuals::default(),
2850 handedness: Handedness::default(),
2851 accessibility: AccessibilitySettings::default(),
2852 input: InputMetrics::default(),
2853 text_rendering: TextRenderingHints::default(),
2854 scrollbar_preferences: ScrollbarPreferences::default(),
2855 visual_hints: VisualHints::default(),
2856 animation: AnimationMetrics::default(),
2857 audio: AudioMetrics::default(),
2858 }
2859 }
2860
2861 #[must_use]
2863 pub fn android_holo_dark() -> SystemStyle {
2864 SystemStyle {
2865 platform: Platform::Android,
2866 theme: Theme::Dark,
2867 colors: SystemColors {
2868 text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2869 background: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2870 accent: OptionColorU::Some(ColorU::new_rgb(51, 181, 229)),
2871 ..Default::default()
2872 },
2873 fonts: SystemFonts {
2874 ui_font: OptionString::Some("Roboto".into()),
2875 ui_font_size: OptionF32::Some(14.0),
2876 monospace_font: OptionString::Some("Droid Sans Mono".into()),
2877 ..Default::default()
2878 },
2879 metrics: SystemMetrics {
2880 corner_radius: OptionPixelValue::Some(PixelValue::px(2.0)),
2881 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2882 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2883 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2884 titlebar: TitlebarMetrics::android(),
2885 },
2886 scrollbar: Some(Box::new(scrollbar_info_to_computed(
2887 &SCROLLBAR_ANDROID_DARK,
2888 ))),
2889 app_specific_stylesheet: None,
2890 run_destructor: true,
2891 icon_style: IconStyleOptions::default(),
2892 language: AzString::from_const_str("en-US"),
2893 os_version: OsVersion::ANDROID_ICE_CREAM_SANDWICH,
2894 prefers_reduced_motion: BoolCondition::False,
2895 prefers_high_contrast: BoolCondition::False,
2896 scroll_physics: ScrollPhysics::android(),
2897 linux: LinuxCustomization::default(),
2898 focus_visuals: FocusVisuals::default(),
2899 handedness: Handedness::default(),
2900 accessibility: AccessibilitySettings::default(),
2901 input: InputMetrics::default(),
2902 text_rendering: TextRenderingHints::default(),
2903 scrollbar_preferences: ScrollbarPreferences::default(),
2904 visual_hints: VisualHints::default(),
2905 animation: AnimationMetrics::default(),
2906 audio: AudioMetrics::default(),
2907 }
2908 }
2909
2910 #[must_use]
2912 pub fn ios_light() -> SystemStyle {
2913 SystemStyle {
2914 platform: Platform::Ios,
2915 theme: Theme::Light,
2916 colors: SystemColors {
2917 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2918 background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
2919 accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
2920 ..Default::default()
2921 },
2922 fonts: SystemFonts {
2923 ui_font: OptionString::Some(".SFUI-Display-Regular".into()),
2924 ui_font_size: OptionF32::Some(17.0),
2925 monospace_font: OptionString::Some("Menlo".into()),
2926 ..Default::default()
2927 },
2928 metrics: SystemMetrics {
2929 corner_radius: OptionPixelValue::Some(PixelValue::px(10.0)),
2930 border_width: OptionPixelValue::Some(PixelValue::px(0.5)),
2931 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(20.0)),
2932 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(12.0)),
2933 titlebar: TitlebarMetrics::ios(),
2934 },
2935 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_IOS_LIGHT))),
2936 app_specific_stylesheet: None,
2937 run_destructor: true,
2938 icon_style: IconStyleOptions::default(),
2939 language: AzString::from_const_str("en-US"),
2940 os_version: OsVersion::IOS_17,
2941 prefers_reduced_motion: BoolCondition::False,
2942 prefers_high_contrast: BoolCondition::False,
2943 scroll_physics: ScrollPhysics::ios(),
2944 linux: LinuxCustomization::default(),
2945 focus_visuals: FocusVisuals::default(),
2946 handedness: Handedness::default(),
2947 accessibility: AccessibilitySettings::default(),
2948 input: InputMetrics::default(),
2949 text_rendering: TextRenderingHints::default(),
2950 scrollbar_preferences: ScrollbarPreferences::default(),
2951 visual_hints: VisualHints::default(),
2952 animation: AnimationMetrics::default(),
2953 audio: AudioMetrics::default(),
2954 }
2955 }
2956}
2957
2958#[cfg(test)]
2959mod autotest_generated {
2960 use super::*;
2961 use crate::css::rule_priority;
2962
2963 const ALL_FONT_TYPES: [SystemFontType; 11] = [
2964 SystemFontType::Ui,
2965 SystemFontType::UiBold,
2966 SystemFontType::Monospace,
2967 SystemFontType::MonospaceBold,
2968 SystemFontType::MonospaceItalic,
2969 SystemFontType::Title,
2970 SystemFontType::TitleBold,
2971 SystemFontType::Menu,
2972 SystemFontType::Small,
2973 SystemFontType::Serif,
2974 SystemFontType::SerifBold,
2975 ];
2976
2977 fn all_platforms() -> Vec<Platform> {
2978 vec![
2979 Platform::Windows,
2980 Platform::MacOs,
2981 Platform::Linux(DesktopEnvironment::Gnome),
2982 Platform::Linux(DesktopEnvironment::Kde),
2983 Platform::Linux(DesktopEnvironment::Other(AzString::from_const_str(
2984 "Hyprland",
2985 ))),
2986 Platform::Android,
2987 Platform::Ios,
2988 Platform::Unknown,
2989 ]
2990 }
2991
2992 fn all_default_styles() -> Vec<(&'static str, SystemStyle)> {
2994 vec![
2995 ("windows_11_light", defaults::windows_11_light()),
2996 ("windows_11_dark", defaults::windows_11_dark()),
2997 ("windows_7_aero", defaults::windows_7_aero()),
2998 ("windows_xp_luna", defaults::windows_xp_luna()),
2999 ("macos_modern_light", defaults::macos_modern_light()),
3000 ("macos_modern_dark", defaults::macos_modern_dark()),
3001 ("macos_aqua", defaults::macos_aqua()),
3002 ("gnome_adwaita_light", defaults::gnome_adwaita_light()),
3003 ("gnome_adwaita_dark", defaults::gnome_adwaita_dark()),
3004 ("gtk2_clearlooks", defaults::gtk2_clearlooks()),
3005 ("kde_breeze_light", defaults::kde_breeze_light()),
3006 ("android_material_light", defaults::android_material_light()),
3007 ("android_holo_dark", defaults::android_holo_dark()),
3008 ("ios_light", defaults::ios_light()),
3009 ]
3010 }
3011
3012 #[test]
3015 fn from_css_str_valid_minimal() {
3016 assert_eq!(
3017 SystemFontType::from_css_str("system:ui"),
3018 Some(SystemFontType::Ui)
3019 );
3020 assert_eq!(
3021 SystemFontType::from_css_str("system:monospace:italic"),
3022 Some(SystemFontType::MonospaceItalic)
3023 );
3024 }
3025
3026 #[test]
3027 fn from_css_str_empty_input_returns_none() {
3028 assert_eq!(SystemFontType::from_css_str(""), None);
3029 }
3030
3031 #[test]
3032 fn from_css_str_whitespace_only_returns_none() {
3033 for s in [" ", "\t\n", "\r\n\r\n", "\t \t \n"] {
3034 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
3035 }
3036 }
3037
3038 #[test]
3039 fn from_css_str_prefix_only_is_none_and_does_not_panic_on_slice() {
3040 assert_eq!(SystemFontType::from_css_str("system:"), None);
3042 assert_eq!(SystemFontType::from_css_str(" system: "), None);
3043 assert_eq!(SystemFontType::from_css_str("system::"), None);
3044 }
3045
3046 #[test]
3047 fn from_css_str_garbage_returns_none() {
3048 for s in [
3049 ";;;",
3050 "{}{}",
3051 "\0\u{1}\u{2}\u{7f}",
3052 "system",
3053 "systemui",
3054 "system;ui",
3055 "system:ui:",
3056 ":system:ui",
3057 "font-family: system:ui;",
3058 "\\system:ui",
3059 "system:ui\0",
3060 "system:\u{0}ui",
3061 ] {
3062 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
3063 }
3064 }
3065
3066 #[test]
3067 fn from_css_str_leading_trailing_junk() {
3068 assert_eq!(
3070 SystemFontType::from_css_str(" system:ui "),
3071 Some(SystemFontType::Ui)
3072 );
3073 assert_eq!(
3074 SystemFontType::from_css_str("\t\nsystem:monospace\r\n"),
3075 Some(SystemFontType::Monospace)
3076 );
3077 assert_eq!(SystemFontType::from_css_str("system:ui;garbage"), None);
3079 assert_eq!(SystemFontType::from_css_str("garbage system:ui"), None);
3080 assert_eq!(SystemFontType::from_css_str("system:ui system:ui"), None);
3081 assert_eq!(SystemFontType::from_css_str("system: ui"), None);
3082 assert_eq!(SystemFontType::from_css_str("system:ui:bold:extra"), None);
3083 }
3084
3085 #[test]
3086 fn from_css_str_is_case_sensitive() {
3087 for s in [
3090 "SYSTEM:UI",
3091 "System:Ui",
3092 "system:UI",
3093 "System:ui",
3094 "sYsTeM:ui",
3095 ] {
3096 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
3097 }
3098 }
3099
3100 #[test]
3101 fn from_css_str_boundary_numbers() {
3102 for s in [
3103 "0",
3104 "-0",
3105 "9223372036854775807",
3106 "-9223372036854775808",
3107 "NaN",
3108 "inf",
3109 "-inf",
3110 "1e400",
3111 "system:0",
3112 "system:-1",
3113 "system:NaN",
3114 "system:inf",
3115 "system:9223372036854775807",
3116 ] {
3117 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
3118 }
3119 }
3120
3121 #[test]
3122 fn from_css_str_unicode_does_not_panic() {
3123 for s in [
3124 "\u{1F600}",
3125 "system:\u{1F600}",
3126 "system:ui\u{0301}", "\u{1F600}system:ui",
3128 "systém:ui", "system:ui", "system:\u{202E}ui", "system:\u{FFFD}",
3132 "system:ui\u{200B}", ] {
3134 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
3135 }
3136 }
3137
3138 #[test]
3139 fn from_css_str_extremely_long_input_does_not_hang() {
3140 let long = format!("system:{}", "u".repeat(1_000_000));
3141 assert_eq!(SystemFontType::from_css_str(&long), None);
3142
3143 let long_suffix = format!("system:ui{}", "x".repeat(1_000_000));
3145 assert_eq!(SystemFontType::from_css_str(&long_suffix), None);
3146
3147 let padded = format!("{}system:ui{}", " ".repeat(100_000), " ".repeat(100_000));
3149 assert_eq!(
3150 SystemFontType::from_css_str(&padded),
3151 Some(SystemFontType::Ui)
3152 );
3153 }
3154
3155 #[test]
3156 fn from_css_str_deeply_nested_input_does_not_stack_overflow() {
3157 let nested = format!("system:{}{}", "(".repeat(10_000), ")".repeat(10_000));
3158 assert_eq!(SystemFontType::from_css_str(&nested), None);
3159
3160 let brackets = format!("system:{}", "[".repeat(10_000));
3161 assert_eq!(SystemFontType::from_css_str(&brackets), None);
3162 }
3163
3164 #[test]
3167 fn font_type_css_str_round_trips() {
3168 for ty in ALL_FONT_TYPES {
3169 let s = ty.as_css_str();
3170 assert_eq!(
3171 SystemFontType::from_css_str(s),
3172 Some(ty),
3173 "round-trip of {ty:?}"
3174 );
3175 assert_eq!(
3177 SystemFontType::from_css_str(&format!(" {s}\t")),
3178 Some(ty),
3179 "padded round-trip of {ty:?}"
3180 );
3181 }
3182 }
3183
3184 #[test]
3185 fn font_type_css_str_is_well_formed_and_unique() {
3186 let mut seen: Vec<&'static str> = Vec::new();
3187 for ty in ALL_FONT_TYPES {
3188 let s = ty.as_css_str();
3189 assert!(s.starts_with("system:"), "{ty:?} -> {s:?}");
3190 assert!(s.len() > "system:".len(), "{ty:?} has an empty keyword");
3191 assert_eq!(s.trim(), s, "{ty:?} -> {s:?} has surrounding whitespace");
3192 assert!(s.is_ascii(), "{ty:?} -> {s:?} is not ASCII");
3193 seen.push(s);
3194 }
3195 seen.sort_unstable();
3196 assert!(
3197 seen.windows(2).all(|w| w[0] != w[1]),
3198 "as_css_str() is not injective: {seen:?}"
3199 );
3200 }
3201
3202 #[test]
3203 fn font_type_default_is_ui() {
3204 let d = SystemFontType::default();
3205 assert_eq!(d, SystemFontType::Ui);
3206 assert_eq!(d.as_css_str(), "system:ui");
3207 assert!(!d.is_bold());
3208 assert!(!d.is_italic());
3209 }
3210
3211 #[test]
3214 fn is_bold_matches_exactly_the_bold_variants() {
3215 assert!(SystemFontType::UiBold.is_bold());
3216 assert!(SystemFontType::MonospaceBold.is_bold());
3217 assert!(SystemFontType::TitleBold.is_bold());
3218 assert!(SystemFontType::SerifBold.is_bold());
3219
3220 assert!(!SystemFontType::Ui.is_bold());
3221 assert!(!SystemFontType::Monospace.is_bold());
3222 assert!(!SystemFontType::MonospaceItalic.is_bold());
3223 assert!(!SystemFontType::Title.is_bold());
3224 assert!(!SystemFontType::Menu.is_bold());
3225 assert!(!SystemFontType::Small.is_bold());
3226 assert!(!SystemFontType::Serif.is_bold());
3227 }
3228
3229 #[test]
3230 fn is_italic_matches_exactly_the_italic_variant() {
3231 assert!(SystemFontType::MonospaceItalic.is_italic());
3232 for ty in ALL_FONT_TYPES {
3233 if ty != SystemFontType::MonospaceItalic {
3234 assert!(!ty.is_italic(), "{ty:?} must not be italic");
3235 }
3236 }
3237 }
3238
3239 #[test]
3240 fn predicates_agree_with_the_css_keyword() {
3241 for ty in ALL_FONT_TYPES {
3242 let s = ty.as_css_str();
3243 assert_eq!(ty.is_bold(), s.ends_with(":bold"), "{ty:?} -> {s:?}");
3244 assert_eq!(ty.is_italic(), s.ends_with(":italic"), "{ty:?} -> {s:?}");
3245 assert!(
3247 !(ty.is_bold() && ty.is_italic()),
3248 "{ty:?} is bold *and* italic"
3249 );
3250 }
3251 }
3252
3253 #[test]
3256 fn fallback_chains_are_non_empty_and_deduplicated() {
3257 for platform in all_platforms() {
3258 for ty in ALL_FONT_TYPES {
3259 let chain = ty.get_fallback_chain(&platform);
3260 assert!(
3261 !chain.is_empty(),
3262 "{ty:?} on {platform:?} has an empty chain"
3263 );
3264 assert!(
3265 chain.iter().all(|f| !f.trim().is_empty()),
3266 "{ty:?} on {platform:?} has a blank family: {chain:?}"
3267 );
3268 let mut sorted = chain.clone();
3269 sorted.sort_unstable();
3270 assert!(
3271 sorted.windows(2).all(|w| w[0] != w[1]),
3272 "{ty:?} on {platform:?} lists a duplicate family: {chain:?}"
3273 );
3274 }
3275 }
3276 }
3277
3278 #[test]
3279 fn fallback_chain_is_deterministic() {
3280 for platform in all_platforms() {
3281 for ty in ALL_FONT_TYPES {
3282 assert_eq!(
3283 ty.get_fallback_chain(&platform),
3284 ty.get_fallback_chain(&platform),
3285 "{ty:?} on {platform:?} is not deterministic"
3286 );
3287 }
3288 }
3289 }
3290
3291 #[test]
3292 fn ios_shares_the_macos_fallback_chain() {
3293 for ty in ALL_FONT_TYPES {
3294 assert_eq!(
3295 ty.get_fallback_chain(&Platform::Ios),
3296 ty.get_fallback_chain(&Platform::MacOs),
3297 "{ty:?}"
3298 );
3299 }
3300 }
3301
3302 #[test]
3303 fn linux_fallback_chain_ignores_the_desktop_environment() {
3304 let gnome = Platform::Linux(DesktopEnvironment::Gnome);
3305 let kde = Platform::Linux(DesktopEnvironment::Kde);
3306 let other = Platform::Linux(DesktopEnvironment::Other(AzString::from_const_str("")));
3307 for ty in ALL_FONT_TYPES {
3308 let a = ty.get_fallback_chain(&gnome);
3309 assert_eq!(a, ty.get_fallback_chain(&kde), "{ty:?}");
3310 assert_eq!(a, ty.get_fallback_chain(&other), "{ty:?}");
3311 }
3312 }
3313
3314 #[test]
3315 fn unknown_platform_falls_back_to_generic_css_families() {
3316 for ty in ALL_FONT_TYPES {
3317 let chain = ty.get_fallback_chain(&Platform::Unknown);
3318 assert_eq!(chain.len(), 1, "{ty:?} -> {chain:?}");
3319 let expected = if ty.is_italic()
3320 || matches!(
3321 ty,
3322 SystemFontType::Monospace | SystemFontType::MonospaceBold
3323 ) {
3324 "monospace"
3325 } else if matches!(ty, SystemFontType::Serif | SystemFontType::SerifBold) {
3326 "serif"
3327 } else {
3328 "sans-serif"
3329 };
3330 assert_eq!(chain[0], expected, "{ty:?}");
3331 }
3332 }
3333
3334 #[test]
3335 fn monospace_variants_share_one_chain_per_platform() {
3336 for platform in all_platforms() {
3337 let base = SystemFontType::Monospace.get_fallback_chain(&platform);
3338 assert_eq!(
3339 SystemFontType::MonospaceBold.get_fallback_chain(&platform),
3340 base,
3341 "{platform:?}"
3342 );
3343 assert_eq!(
3344 SystemFontType::MonospaceItalic.get_fallback_chain(&platform),
3345 base,
3346 "{platform:?}"
3347 );
3348 }
3349 }
3350
3351 #[test]
3354 fn platform_current_is_deterministic_and_matches_target_os() {
3355 let a = Platform::current();
3356 assert_eq!(a, Platform::current());
3357
3358 #[cfg(target_os = "linux")]
3359 assert!(matches!(a, Platform::Linux(_)), "{a:?}");
3360 #[cfg(target_os = "windows")]
3361 assert_eq!(a, Platform::Windows);
3362 #[cfg(target_os = "macos")]
3363 assert_eq!(a, Platform::MacOs);
3364 #[cfg(target_os = "android")]
3365 assert_eq!(a, Platform::Android);
3366 #[cfg(target_os = "ios")]
3367 assert_eq!(a, Platform::Ios);
3368
3369 #[cfg(any(
3371 target_os = "linux",
3372 target_os = "windows",
3373 target_os = "macos",
3374 target_os = "android",
3375 target_os = "ios"
3376 ))]
3377 assert_ne!(a, Platform::Unknown);
3378
3379 assert_eq!(Platform::default(), Platform::Unknown);
3381 }
3382
3383 #[test]
3386 fn titlebar_metrics_have_sane_geometry() {
3387 let all = [
3392 ("windows", TitlebarMetrics::windows()),
3393 ("macos", TitlebarMetrics::macos()),
3394 ("linux_gnome", TitlebarMetrics::linux_gnome()),
3395 ("ios", TitlebarMetrics::ios()),
3396 ("android", TitlebarMetrics::android()),
3397 ];
3398 for (name, tm) in all {
3399 let height = tm
3400 .height
3401 .as_ref()
3402 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3403 .expect("titlebar height must be set");
3404 assert!(
3405 height.is_finite() && height > 0.0,
3406 "{name}: height {height}"
3407 );
3408
3409 let button_area = tm
3410 .button_area_width
3411 .as_ref()
3412 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3413 .expect("button area width must be set");
3414 assert!(
3415 button_area.is_finite() && button_area >= 0.0,
3416 "{name}: button_area_width {button_area}"
3417 );
3418
3419 let padding = tm
3420 .padding_horizontal
3421 .as_ref()
3422 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3423 .expect("padding must be set");
3424 assert!(
3425 padding.is_finite() && padding >= 0.0,
3426 "{name}: padding {padding}"
3427 );
3428
3429 let size = tm
3430 .title_font_size
3431 .into_option()
3432 .expect("font size must be set");
3433 assert!(size.is_finite() && size > 0.0, "{name}: font size {size}");
3434
3435 let weight = tm
3436 .title_font_weight
3437 .into_option()
3438 .expect("font weight must be set");
3439 assert!((100..=900).contains(&weight), "{name}: weight {weight}");
3440 }
3441 }
3442
3443 #[test]
3444 fn titlebar_metrics_match_their_platform_conventions() {
3445 let win = TitlebarMetrics::windows();
3446 assert_eq!(win.button_side, TitlebarButtonSide::Right);
3447 assert!(win.buttons.has_close && win.buttons.has_minimize && win.buttons.has_maximize);
3448 assert!(!win.buttons.has_fullscreen);
3449
3450 let mac = TitlebarMetrics::macos();
3452 assert_eq!(mac.button_side, TitlebarButtonSide::Left);
3453 assert!(mac.buttons.has_fullscreen);
3454 assert!(!mac.buttons.has_maximize);
3455
3456 assert_eq!(
3457 TitlebarMetrics::linux_gnome().button_side,
3458 TitlebarButtonSide::Right
3459 );
3460
3461 for (name, tm) in [
3463 ("ios", TitlebarMetrics::ios()),
3464 ("android", TitlebarMetrics::android()),
3465 ] {
3466 let b = tm.buttons;
3467 assert!(
3468 !b.has_close && !b.has_minimize && !b.has_maximize && !b.has_fullscreen,
3469 "{name} must not expose window controls"
3470 );
3471 }
3472
3473 let ios = TitlebarMetrics::ios();
3475 assert!(ios.safe_area.top.is_some());
3476 assert!(ios.safe_area.bottom.is_some());
3477 assert_eq!(
3478 TitlebarMetrics::windows().safe_area,
3479 SafeAreaInsets::default()
3480 );
3481 }
3482
3483 #[test]
3486 fn system_style_new_detect_and_default_for_platform_agree() {
3487 let a = SystemStyle::new();
3488 let b = SystemStyle::detect();
3489 let c = SystemStyle::default_for_platform();
3490 assert_eq!(a, b);
3491 assert_eq!(b, c);
3492 }
3493
3494 #[test]
3495 fn system_style_constructors_arm_the_ffi_drop_guard() {
3496 assert!(SystemStyle::default().run_destructor);
3499 assert!(SystemStyle::new().run_destructor);
3500 assert!(SystemStyle::detect().run_destructor);
3501 for (name, style) in all_default_styles() {
3502 assert!(
3503 style.run_destructor,
3504 "{name} does not own its heap pointers"
3505 );
3506 assert!(
3507 style.clone().run_destructor,
3508 "clone of {name} lost the guard"
3509 );
3510 }
3511 }
3512
3513 #[test]
3514 fn system_style_default_is_empty_but_valid() {
3515 let d = SystemStyle::default();
3516 assert_eq!(d.platform, Platform::Unknown);
3517 assert_eq!(d.theme, Theme::Light);
3518 assert!(d.app_specific_stylesheet.is_none());
3519 assert!(d.scrollbar.is_none());
3520 assert!(d.language.as_str().is_empty());
3521 assert!(d.colors.text.is_none());
3522 }
3523
3524 #[test]
3527 fn default_styles_are_fully_populated() {
3528 for (name, style) in all_default_styles() {
3529 assert!(style.colors.text.is_some(), "{name}: no text color");
3530 assert!(
3531 style.colors.background.is_some(),
3532 "{name}: no background color"
3533 );
3534 assert!(style.colors.accent.is_some(), "{name}: no accent color");
3535 assert!(style.fonts.ui_font.is_some(), "{name}: no UI font");
3536 assert!(
3537 style.fonts.monospace_font.is_some(),
3538 "{name}: no monospace font"
3539 );
3540 assert!(
3541 !style.language.as_str().is_empty(),
3542 "{name}: empty language"
3543 );
3544 assert_ne!(
3545 style.platform,
3546 Platform::Unknown,
3547 "{name}: unknown platform"
3548 );
3549
3550 let size = style
3551 .fonts
3552 .ui_font_size
3553 .into_option()
3554 .expect("ui font size");
3555 assert!(
3556 size.is_finite() && size > 0.0,
3557 "{name}: ui font size {size}"
3558 );
3559
3560 let radius = style
3561 .metrics
3562 .corner_radius
3563 .as_ref()
3564 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3565 .expect("corner radius");
3566 assert!(
3567 radius.is_finite() && radius >= 0.0,
3568 "{name}: corner radius {radius}"
3569 );
3570 }
3571 }
3572
3573 #[test]
3574 fn default_styles_carry_a_fully_resolved_scrollbar() {
3575 for (name, style) in all_default_styles() {
3578 let sb = style
3579 .scrollbar
3580 .as_ref()
3581 .unwrap_or_else(|| panic!("{name}: no scrollbar"));
3582 assert!(sb.width.is_some(), "{name}: scrollbar width lost");
3583 assert!(sb.thumb_color.is_some(), "{name}: thumb color lost");
3584 assert!(sb.track_color.is_some(), "{name}: track color lost");
3585 }
3586 }
3587
3588 #[test]
3589 fn light_and_dark_default_styles_differ() {
3590 assert_ne!(defaults::windows_11_light(), defaults::windows_11_dark());
3591 assert_ne!(
3592 defaults::macos_modern_light(),
3593 defaults::macos_modern_dark()
3594 );
3595 assert_ne!(
3596 defaults::gnome_adwaita_light(),
3597 defaults::gnome_adwaita_dark()
3598 );
3599 assert_ne!(
3600 defaults::android_material_light(),
3601 defaults::android_holo_dark()
3602 );
3603
3604 assert_eq!(defaults::windows_11_dark().theme, Theme::Dark);
3605 assert_eq!(defaults::macos_modern_dark().theme, Theme::Dark);
3606 assert_eq!(defaults::gnome_adwaita_dark().theme, Theme::Dark);
3607 assert_eq!(defaults::android_holo_dark().theme, Theme::Dark);
3608
3609 assert_eq!(
3610 defaults::kde_breeze_light().platform,
3611 Platform::Linux(DesktopEnvironment::Kde)
3612 );
3613 assert_eq!(defaults::ios_light().platform, Platform::Ios);
3614 }
3615
3616 #[test]
3617 fn default_style_constructors_are_deterministic() {
3618 for _ in 0..3 {
3619 assert_eq!(defaults::windows_xp_luna(), defaults::windows_xp_luna());
3620 assert_eq!(defaults::macos_aqua(), defaults::macos_aqua());
3621 assert_eq!(defaults::gtk2_clearlooks(), defaults::gtk2_clearlooks());
3622 assert_eq!(defaults::windows_7_aero(), defaults::windows_7_aero());
3623 }
3624 }
3625
3626 #[test]
3629 fn to_json_string_has_balanced_braces_for_every_default() {
3630 let mut styles = all_default_styles();
3631 styles.push(("default", SystemStyle::default()));
3632 for (name, style) in styles {
3633 let json = style.to_json_string();
3634 let s = json.as_str();
3635 assert!(s.starts_with('{'), "{name}: does not start with '{{'");
3636 assert!(s.ends_with('}'), "{name}: does not end with '}}'");
3637 let open = s.chars().filter(|c| *c == '{').count();
3638 let close = s.chars().filter(|c| *c == '}').count();
3639 assert_eq!(open, close, "{name}: unbalanced braces");
3640 for key in [
3641 "\"theme\"",
3642 "\"platform\"",
3643 "\"colors\"",
3644 "\"fonts\"",
3645 "\"titlebar\"",
3646 "\"input\"",
3647 "\"accessibility\"",
3648 "\"audio\"",
3649 ] {
3650 assert!(s.contains(key), "{name}: missing {key}");
3651 }
3652 }
3653 }
3654
3655 #[test]
3656 fn to_json_string_reports_known_values() {
3657 let json = defaults::windows_11_light().to_json_string();
3658 let s = json.as_str();
3659 assert!(s.contains("\"theme\": \"Light\""), "{s}");
3660 assert!(s.contains("\"platform\": \"Windows\""), "{s}");
3661 assert!(s.contains("\"language\": \"en-US\""), "{s}");
3662 assert!(s.contains("\"text\": \"#000000ff\""), "{s}");
3664 assert!(s.contains("\"height\": 32.0"), "{s}");
3666 assert!(s.contains("\"grid\": null"), "{s}");
3668 }
3669
3670 #[test]
3671 fn to_json_string_survives_nan_and_infinite_metrics() {
3672 let mut style = SystemStyle::default();
3673 style.accessibility.text_scale_factor = f32::NAN;
3674 style.animation.animation_duration_factor = f32::INFINITY;
3675 style.input.double_click_distance_px = f32::NEG_INFINITY;
3676 style.input.drag_threshold_px = f32::MAX;
3677 style.input.caret_width_px = f32::MIN_POSITIVE;
3678 style.input.double_click_time_ms = u32::MAX;
3679 style.input.caret_blink_rate_ms = u32::MAX;
3680 style.input.wheel_scroll_lines = u32::MAX;
3681 style.input.hover_time_ms = u32::MAX;
3682 style.text_rendering.font_smoothing_gamma = u32::MAX;
3683 style.linux.cursor_size = u32::MAX;
3684
3685 let json = style.to_json_string();
3687 let s = json.as_str();
3688 assert!(!s.is_empty());
3689 assert!(s.contains(&format!("\"cursor_size\": {}", u32::MAX)), "{s}");
3690 assert!(
3691 s.contains(&format!("\"double_click_time_ms\": {}", u32::MAX)),
3692 "{s}"
3693 );
3694 }
3695
3696 #[test]
3697 fn to_json_string_survives_extreme_pixel_metrics() {
3698 let mut style = SystemStyle::default();
3699 style.metrics.titlebar.height = OptionPixelValue::Some(PixelValue::px(f32::NAN));
3700 style.metrics.titlebar.button_area_width =
3701 OptionPixelValue::Some(PixelValue::px(f32::INFINITY));
3702 style.metrics.titlebar.padding_horizontal =
3703 OptionPixelValue::Some(PixelValue::px(f32::NEG_INFINITY));
3704 style.metrics.titlebar.title_font_size = OptionF32::Some(f32::MAX);
3705 style.metrics.titlebar.title_font_weight = OptionU16::Some(u16::MAX);
3706
3707 let json = style.to_json_string();
3708 assert!(!json.as_str().is_empty());
3709
3710 let nan_px = PixelValue::px(f32::NAN).to_pixels_internal(0.0, 0.0, 0.0);
3713 assert_eq!(nan_px, 0.0);
3714 assert!(PixelValue::px(f32::INFINITY)
3715 .to_pixels_internal(0.0, 0.0, 0.0)
3716 .is_finite());
3717 assert!(PixelValue::px(f32::NEG_INFINITY)
3718 .to_pixels_internal(0.0, 0.0, 0.0)
3719 .is_finite());
3720 }
3721
3722 #[test]
3723 fn to_json_string_survives_hostile_strings() {
3724 let mut style = SystemStyle::default();
3727 style.language = AzString::from("\"\\\n\t\u{1F600}");
3728 style.fonts.ui_font = OptionString::Some(AzString::from("a\"b\\c"));
3729 style.linux.gtk_theme = OptionString::Some(AzString::from("\u{202E}evil"));
3730
3731 let json = style.to_json_string();
3732 let s = json.as_str();
3733 assert!(!s.is_empty());
3734 assert!(s.contains("\"language\":"), "{s}");
3735 }
3736
3737 #[test]
3738 fn to_json_string_is_deterministic() {
3739 let style = defaults::gnome_adwaita_dark();
3740 assert_eq!(style.to_json_string(), style.to_json_string());
3741 assert_ne!(
3742 defaults::gnome_adwaita_dark().to_json_string(),
3743 defaults::gnome_adwaita_light().to_json_string()
3744 );
3745 }
3746
3747 #[test]
3750 fn csd_stylesheet_rules_all_carry_system_priority() {
3751 let mut styles = all_default_styles();
3752 styles.push(("default", SystemStyle::default()));
3753 for (name, style) in styles {
3754 let css = style.create_csd_stylesheet();
3755 let rules = css.rules.as_slice();
3756 assert!(!rules.is_empty(), "{name}: produced no rules");
3757 for rule in rules {
3758 assert_eq!(
3759 rule.priority,
3760 rule_priority::SYSTEM,
3761 "{name}: rule escaped the SYSTEM layer"
3762 );
3763 }
3764 const _: () = assert!(rule_priority::SYSTEM < rule_priority::AUTHOR);
3766 }
3767 }
3768
3769 #[test]
3770 fn csd_stylesheet_uses_fallback_colors_when_the_system_reports_none() {
3771 let css = SystemStyle::default().create_csd_stylesheet();
3773 assert!(!css.rules.as_slice().is_empty());
3774 assert_ne!(css, Css::default());
3775 }
3776
3777 #[test]
3778 fn csd_stylesheet_is_platform_specific() {
3779 let mac = defaults::macos_modern_light().create_csd_stylesheet();
3780 let win = defaults::windows_11_light().create_csd_stylesheet();
3781 let lin = defaults::gnome_adwaita_light().create_csd_stylesheet();
3782 assert_ne!(mac, win);
3783 assert_ne!(win, lin);
3784 assert_ne!(mac, lin);
3785 assert!(mac.rules.as_slice().len() > win.rules.as_slice().len());
3787 }
3788
3789 #[test]
3790 fn csd_stylesheet_survives_extreme_corner_radius() {
3791 for radius in [
3792 PixelValue::px(f32::NAN),
3793 PixelValue::px(f32::INFINITY),
3794 PixelValue::px(f32::NEG_INFINITY),
3795 PixelValue::px(f32::MAX),
3796 PixelValue::px(-1.0),
3797 PixelValue::percent(f32::MAX),
3798 PixelValue::em(f32::MIN),
3799 ] {
3800 let mut style = defaults::windows_11_light();
3801 style.metrics.corner_radius = OptionPixelValue::Some(radius);
3802 let css = style.create_csd_stylesheet();
3803 assert!(
3804 !css.rules.as_slice().is_empty(),
3805 "radius {radius:?} produced no rules"
3806 );
3807 for rule in css.rules.as_slice() {
3808 assert_eq!(rule.priority, rule_priority::SYSTEM);
3809 }
3810 }
3811 }
3812
3813 #[test]
3814 fn csd_stylesheet_is_deterministic() {
3815 let style = defaults::kde_breeze_light();
3816 assert_eq!(style.create_csd_stylesheet(), style.create_csd_stylesheet());
3817 }
3818
3819 #[test]
3827 fn ricing_mode_is_deterministic_and_total() {
3828 let mode = ricing_mode();
3829 assert_eq!(mode, ricing_mode(), "ricing_mode() is not deterministic");
3830 assert!(
3831 matches!(
3832 mode,
3833 RicingMode::Off | RicingMode::Default | RicingMode::Force
3834 ),
3835 "{mode:?}"
3836 );
3837 assert_eq!(RicingMode::default(), RicingMode::Default);
3838 }
3839
3840 #[test]
3841 fn ricing_enabled_is_the_inverse_of_off() {
3842 assert_eq!(ricing_enabled(), ricing_mode() != RicingMode::Off);
3843 assert_eq!(ricing_enabled(), ricing_enabled());
3844 }
3845
3846 #[test]
3847 fn detect_linux_desktop_env_is_deterministic() {
3848 let a = detect_linux_desktop_env();
3849 assert_eq!(a, detect_linux_desktop_env());
3850
3851 let blank_env = |k: &str| std::env::var(k).map(|v| v.is_empty()).unwrap_or(false);
3855 if !blank_env("XDG_CURRENT_DESKTOP") && !blank_env("DESKTOP_SESSION") {
3856 if let DesktopEnvironment::Other(ref name) = a {
3857 assert!(!name.as_str().is_empty(), "empty desktop-environment label");
3858 }
3859 }
3860 }
3861
3862 #[test]
3863 fn detect_system_language_is_a_normalized_tag() {
3864 let lang = detect_system_language();
3865 let s = lang.as_str();
3866 assert!(!s.is_empty(), "language tag must never be empty");
3867 assert!(!s.contains('.'), "{s:?} still carries an encoding suffix");
3870 assert!(!s.contains(':'), "{s:?} still carries a locale list");
3871 assert!(!s.contains('_'), "{s:?} is not BCP 47 (underscore)");
3872 assert_eq!(lang, detect_system_language(), "not deterministic");
3873 }
3874}
3875
3876#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
3882#[repr(C)]
3883pub enum Handedness {
3884 #[default]
3886 RightHanded,
3887 LeftHanded,
3889}
3890
3891