1#![cfg(feature = "parser")]
26
27use alloc::{
28 boxed::Box,
29 string::{String, ToString},
30 vec::Vec,
31};
32use crate::{
33 corety::{AzString, OptionF32, OptionString, OptionU16},
34 css::Css,
35 parser2::{new_from_str, CssParseWarnMsg},
36 props::{
37 basic::{
38 color::{parse_css_color, ColorU, OptionColorU},
39 pixel::{PixelValue, OptionPixelValue},
40 },
41 style::scrollbar::{ComputedScrollbarStyle, OverscrollBehavior, ScrollBehavior, ScrollPhysics},
42 },
43};
44
45use crate::dynamic_selector::{BoolCondition, OsVersion};
46use core::fmt::Write;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54#[derive(Default)]
55pub enum RicingMode {
56 Off,
59 #[default]
62 Default,
63 Force,
67}
68
69
70#[must_use] pub fn ricing_mode() -> RicingMode {
74 let Ok(raw) = std::env::var("AZ_RICING") else {
75 return RicingMode::Default;
76 };
77 match raw.trim().to_ascii_lowercase().as_str() {
78 "off" | "disabled" | "none" | "0" | "false" => RicingMode::Off,
79 "force" | "prefer" | "aggressive" | "1" | "true" => RicingMode::Force,
80 _ => RicingMode::Default,
81 }
82}
83
84#[must_use] pub fn ricing_enabled() -> bool {
87 !matches!(ricing_mode(), RicingMode::Off)
88}
89
90#[allow(variant_size_differences)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
94#[repr(C, u8)]
95pub enum Platform {
96 Windows,
97 MacOs,
98 Linux(DesktopEnvironment),
99 Android,
100 Ios,
101 #[default]
102 Unknown,
103}
104
105impl Platform {
106 #[inline]
108 #[must_use] pub const fn current() -> Self {
109 #[cfg(target_os = "macos")]
110 { Self::MacOs }
111 #[cfg(target_os = "windows")]
112 { Self::Windows }
113 #[cfg(target_os = "linux")]
114 { Self::Linux(DesktopEnvironment::Other(AzString::from_const_str("unknown"))) }
115 #[cfg(target_os = "android")]
116 { Self::Android }
117 #[cfg(target_os = "ios")]
118 { Self::Ios }
119 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux", target_os = "android", target_os = "ios")))]
120 { Self::Unknown }
121 }
122}
123#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, Eq)]
126#[repr(C, u8)]
127pub enum DesktopEnvironment {
128 Gnome,
129 Kde,
130 Other(AzString),
131}
132
133#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
135#[repr(C)]
136pub enum Theme {
137 #[default]
138 Light,
139 Dark,
140}
141
142#[derive(Debug, Clone, PartialEq)]
144#[repr(C)]
145pub struct SystemStyle {
146 pub fonts: SystemFonts,
147 pub metrics: SystemMetrics,
148 pub linux: LinuxCustomization,
150 pub platform: Platform,
151 pub focus_visuals: FocusVisuals,
153 pub language: AzString,
156 pub app_specific_stylesheet: Option<Box<Css>>,
161 pub scrollbar: Option<Box<ComputedScrollbarStyle>>,
163 pub scroll_physics: ScrollPhysics,
167 pub theme: Theme,
168 pub os_version: OsVersion,
170 pub prefers_reduced_motion: BoolCondition,
172 pub prefers_high_contrast: BoolCondition,
174 pub accessibility: AccessibilitySettings,
176 pub handedness: Handedness,
184 pub input: InputMetrics,
186 pub text_rendering: TextRenderingHints,
188 pub scrollbar_preferences: ScrollbarPreferences,
190 pub visual_hints: VisualHints,
192 pub animation: AnimationMetrics,
194 pub colors: SystemColors,
195 pub icon_style: IconStyleOptions,
197 pub audio: AudioMetrics,
199 pub run_destructor: bool,
211}
212
213impl Default for SystemStyle {
214 fn default() -> Self {
215 Self {
216 fonts: SystemFonts::default(),
217 metrics: SystemMetrics::default(),
218 linux: LinuxCustomization::default(),
219 platform: Platform::default(),
220 focus_visuals: FocusVisuals::default(),
221 handedness: Handedness::default(),
222 language: AzString::default(),
223 app_specific_stylesheet: None,
224 scrollbar: None,
225 scroll_physics: ScrollPhysics::default(),
226 theme: Theme::default(),
227 os_version: OsVersion::default(),
228 prefers_reduced_motion: BoolCondition::default(),
229 prefers_high_contrast: BoolCondition::default(),
230 accessibility: AccessibilitySettings::default(),
231 input: InputMetrics::default(),
232 text_rendering: TextRenderingHints::default(),
233 scrollbar_preferences: ScrollbarPreferences::default(),
234 visual_hints: VisualHints::default(),
235 animation: AnimationMetrics::default(),
236 colors: SystemColors::default(),
237 icon_style: IconStyleOptions::default(),
238 audio: AudioMetrics::default(),
239 run_destructor: true,
240 }
241 }
242}
243
244impl Drop for SystemStyle {
245 fn drop(&mut self) {
246 if self.run_destructor {
256 self.run_destructor = false;
257 } else {
258 core::mem::forget(self.app_specific_stylesheet.take());
259 core::mem::forget(self.scrollbar.take());
260 }
261 }
262}
263
264#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
269#[repr(C)]
270pub struct IconStyleOptions {
271 pub prefer_grayscale: bool,
274 pub tint_color: OptionColorU,
277 pub inherit_text_color: bool,
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
293#[repr(C)]
294pub enum SystemFontType {
295 #[default]
297 Ui,
298 UiBold,
300 Monospace,
302 MonospaceBold,
304 MonospaceItalic,
306 Title,
308 TitleBold,
310 Menu,
312 Small,
314 Serif,
316 SerifBold,
318}
319
320
321impl SystemFontType {
322 #[must_use] pub fn from_css_str(s: &str) -> Option<Self> {
332 let s = s.trim();
333 if !s.starts_with("system:") {
334 return None;
335 }
336 let rest = &s[7..]; match rest {
338 "ui" => Some(Self::Ui),
339 "ui:bold" => Some(Self::UiBold),
340 "monospace" => Some(Self::Monospace),
341 "monospace:bold" => Some(Self::MonospaceBold),
342 "monospace:italic" => Some(Self::MonospaceItalic),
343 "title" => Some(Self::Title),
344 "title:bold" => Some(Self::TitleBold),
345 "menu" => Some(Self::Menu),
346 "small" => Some(Self::Small),
347 "serif" => Some(Self::Serif),
348 "serif:bold" => Some(Self::SerifBold),
349 _ => None,
350 }
351 }
352
353 #[must_use] pub const fn as_css_str(&self) -> &'static str {
355 match self {
356 Self::Ui => "system:ui",
357 Self::UiBold => "system:ui:bold",
358 Self::Monospace => "system:monospace",
359 Self::MonospaceBold => "system:monospace:bold",
360 Self::MonospaceItalic => "system:monospace:italic",
361 Self::Title => "system:title",
362 Self::TitleBold => "system:title:bold",
363 Self::Menu => "system:menu",
364 Self::Small => "system:small",
365 Self::Serif => "system:serif",
366 Self::SerifBold => "system:serif:bold",
367 }
368 }
369
370 #[must_use] pub const fn is_bold(&self) -> bool {
373 matches!(
374 self,
375 Self::UiBold
376 | Self::MonospaceBold
377 | Self::TitleBold
378 | Self::SerifBold
379 )
380 }
381
382 #[must_use] pub const fn is_italic(&self) -> bool {
384 matches!(self, Self::MonospaceItalic)
385 }
386}
387
388#[derive(Debug, Default, Clone, Copy, PartialEq)]
396#[repr(C)]
397pub struct AccessibilitySettings {
398 pub text_scale_factor: f32,
400 pub prefers_bold_text: bool,
405 pub prefers_larger_text: bool,
410 pub prefers_high_contrast: bool,
415 pub prefers_reduced_motion: bool,
420 pub prefers_reduced_transparency: bool,
425 pub screen_reader_active: bool,
427 pub differentiate_without_color: bool,
430}
431
432#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
441#[repr(C)]
442pub struct SystemColors {
443 pub text: OptionColorU,
446 pub secondary_text: OptionColorU,
448 pub tertiary_text: OptionColorU,
450 pub background: OptionColorU,
452
453 pub accent: OptionColorU,
456 pub accent_text: OptionColorU,
458
459 pub button_face: OptionColorU,
462 pub button_text: OptionColorU,
464 pub disabled_text: OptionColorU,
466
467 pub window_background: OptionColorU,
470 pub under_page_background: OptionColorU,
472
473 pub selection_background: OptionColorU,
476 pub selection_text: OptionColorU,
478 pub selection_background_inactive: OptionColorU,
481 pub selection_text_inactive: OptionColorU,
483
484 pub link: OptionColorU,
487 pub separator: OptionColorU,
489 pub grid: OptionColorU,
491 pub find_highlight: OptionColorU,
493
494 pub sidebar_background: OptionColorU,
497 pub sidebar_selection: OptionColorU,
499}
500
501#[derive(Debug, Default, Clone, PartialEq, Eq)]
507#[repr(C)]
508pub struct SystemFonts {
509 pub ui_font: OptionString,
514 pub ui_font_size: OptionF32,
516 pub monospace_font: OptionString,
521 pub monospace_font_size: OptionF32,
523 pub ui_font_bold: OptionString,
525 pub title_font: OptionString,
527 pub title_font_size: OptionF32,
529 pub menu_font: OptionString,
531 pub menu_font_size: OptionF32,
533 pub small_font: OptionString,
535 pub small_font_size: OptionF32,
537}
538
539#[derive(Debug, Default, Clone, PartialEq, Eq)]
541#[repr(C)]
542pub struct SystemMetrics {
543 pub corner_radius: OptionPixelValue,
545 pub border_width: OptionPixelValue,
547 pub button_padding_horizontal: OptionPixelValue,
549 pub button_padding_vertical: OptionPixelValue,
551 pub titlebar: TitlebarMetrics,
553}
554
555#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
557#[repr(C)]
558pub enum TitlebarButtonSide {
559 Left,
561 #[default]
563 Right,
564}
565
566#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
568#[repr(C)]
569pub struct TitlebarButtons {
570 pub has_close: bool,
572 pub has_minimize: bool,
574 pub has_maximize: bool,
576 pub has_fullscreen: bool,
578}
579
580impl Default for TitlebarButtons {
581 fn default() -> Self {
582 Self {
583 has_close: true,
584 has_minimize: true,
585 has_maximize: true,
586 has_fullscreen: false,
587 }
588 }
589}
590
591#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
597#[repr(C)]
598pub struct SafeAreaInsets {
599 pub top: OptionPixelValue,
601 pub bottom: OptionPixelValue,
603 pub left: OptionPixelValue,
605 pub right: OptionPixelValue,
607}
608
609#[derive(Debug, Clone, PartialEq, Eq)]
614#[repr(C)]
615pub struct TitlebarMetrics {
616 pub button_side: TitlebarButtonSide,
618 pub buttons: TitlebarButtons,
620 pub height: OptionPixelValue,
622 pub button_area_width: OptionPixelValue,
625 pub padding_horizontal: OptionPixelValue,
627 pub safe_area: SafeAreaInsets,
629 pub title_font: OptionString,
631 pub title_font_size: OptionF32,
633 pub title_font_weight: OptionU16,
635}
636
637impl Default for TitlebarMetrics {
638 fn default() -> Self {
639 Self {
640 button_side: TitlebarButtonSide::Right,
641 buttons: TitlebarButtons::default(),
642 height: OptionPixelValue::None,
647 button_area_width: OptionPixelValue::None,
648 padding_horizontal: OptionPixelValue::None,
649 safe_area: SafeAreaInsets::default(),
650 title_font: OptionString::None,
651 title_font_size: OptionF32::Some(13.0),
652 title_font_weight: OptionU16::Some(600), }
654 }
655}
656
657impl TitlebarMetrics {
658 #[must_use] pub fn windows() -> Self {
660 Self {
661 button_side: TitlebarButtonSide::Right,
662 buttons: TitlebarButtons {
663 has_close: true,
664 has_minimize: true,
665 has_maximize: true,
666 has_fullscreen: false,
667 },
668 height: OptionPixelValue::Some(PixelValue::px(32.0)),
669 button_area_width: OptionPixelValue::Some(PixelValue::px(138.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
671 safe_area: SafeAreaInsets::default(),
672 title_font: OptionString::Some("Segoe UI Variable Text".into()),
673 title_font_size: OptionF32::Some(12.0),
674 title_font_weight: OptionU16::Some(400), }
676 }
677
678 #[must_use] pub fn macos() -> Self {
680 Self {
681 button_side: TitlebarButtonSide::Left,
682 buttons: TitlebarButtons {
683 has_close: true,
684 has_minimize: true,
685 has_maximize: false, has_fullscreen: true,
687 },
688 height: OptionPixelValue::Some(PixelValue::px(28.0)),
689 button_area_width: OptionPixelValue::Some(PixelValue::px(78.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
691 safe_area: SafeAreaInsets::default(),
692 title_font: OptionString::Some(".SF NS".into()),
693 title_font_size: OptionF32::Some(13.0),
694 title_font_weight: OptionU16::Some(600), }
696 }
697
698 #[must_use] pub fn linux_gnome() -> Self {
700 Self {
701 button_side: TitlebarButtonSide::Right, buttons: TitlebarButtons {
703 has_close: true,
704 has_minimize: true,
705 has_maximize: true,
706 has_fullscreen: false,
707 },
708 height: OptionPixelValue::Some(PixelValue::px(35.0)),
709 button_area_width: OptionPixelValue::Some(PixelValue::px(100.0)),
710 padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
711 safe_area: SafeAreaInsets::default(),
712 title_font: OptionString::Some("Cantarell".into()),
713 title_font_size: OptionF32::Some(11.0),
714 title_font_weight: OptionU16::Some(700), }
716 }
717
718 #[must_use] pub fn ios() -> Self {
720 Self {
721 button_side: TitlebarButtonSide::Left,
722 buttons: TitlebarButtons {
723 has_close: false, has_minimize: false,
725 has_maximize: false,
726 has_fullscreen: false,
727 },
728 height: OptionPixelValue::Some(PixelValue::px(44.0)),
729 button_area_width: OptionPixelValue::Some(PixelValue::px(0.0)),
730 padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
731 safe_area: SafeAreaInsets {
732 top: OptionPixelValue::Some(PixelValue::px(47.0)),
734 bottom: OptionPixelValue::Some(PixelValue::px(34.0)),
735 left: OptionPixelValue::None,
736 right: OptionPixelValue::None,
737 },
738 title_font: OptionString::Some(".SFUI-Semibold".into()),
739 title_font_size: OptionF32::Some(17.0),
740 title_font_weight: OptionU16::Some(600),
741 }
742 }
743
744 #[must_use] pub fn android() -> Self {
746 Self {
747 button_side: TitlebarButtonSide::Left, buttons: TitlebarButtons {
749 has_close: false,
750 has_minimize: false,
751 has_maximize: false,
752 has_fullscreen: false,
753 },
754 height: OptionPixelValue::Some(PixelValue::px(56.0)),
755 button_area_width: OptionPixelValue::Some(PixelValue::px(48.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
757 safe_area: SafeAreaInsets::default(),
758 title_font: OptionString::Some("Roboto Medium".into()),
759 title_font_size: OptionF32::Some(20.0),
760 title_font_weight: OptionU16::Some(500),
761 }
762 }
763}
764
765#[derive(Debug, Clone, Copy, PartialEq)]
778#[repr(C)]
779pub struct InputMetrics {
780 pub double_click_time_ms: u32,
782 pub double_click_distance_px: f32,
784 pub drag_threshold_px: f32,
786 pub caret_blink_rate_ms: u32,
788 pub caret_width_px: f32,
790 pub wheel_scroll_lines: u32,
792 pub hover_time_ms: u32,
795}
796
797impl Default for InputMetrics {
798 fn default() -> Self {
799 Self {
800 double_click_time_ms: 500,
801 double_click_distance_px: 4.0,
802 drag_threshold_px: 5.0,
803 caret_blink_rate_ms: 530,
804 caret_width_px: 1.0,
805 wheel_scroll_lines: 3,
806 hover_time_ms: 400,
807 }
808 }
809}
810
811#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
815#[repr(C)]
816pub enum SubpixelType {
817 #[default]
819 None,
820 Rgb,
822 Bgr,
824 VRgb,
826 VBgr,
828}
829
830#[derive(Debug, Clone, Copy, PartialEq, Eq)]
835#[repr(C)]
836pub struct TextRenderingHints {
837 pub subpixel_type: SubpixelType,
839 pub font_smoothing_gamma: u32,
841 pub font_smoothing_enabled: bool,
843 pub increased_contrast: bool,
845}
846
847impl Default for TextRenderingHints {
848 fn default() -> Self {
849 Self {
850 subpixel_type: SubpixelType::None,
851 font_smoothing_gamma: 1000,
852 font_smoothing_enabled: true,
853 increased_contrast: false,
854 }
855 }
856}
857
858#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
865#[repr(C)]
866pub struct FocusVisuals {
867 pub focus_ring_color: OptionColorU,
870 pub focus_border_width: OptionPixelValue,
873 pub focus_border_height: OptionPixelValue,
875}
876
877#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
881#[repr(C)]
882pub enum ScrollbarVisibility {
883 Always,
885 #[default]
887 WhenScrolling,
888 Automatic,
890}
891
892#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
894#[repr(C)]
895pub enum ScrollbarTrackClick {
896 JumpToPosition,
898 #[default]
900 PageUpDown,
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq)]
908#[repr(C)]
909pub struct ScrollbarPreferences {
910 pub visibility: ScrollbarVisibility,
913 pub track_click: ScrollbarTrackClick,
915}
916
917impl Default for ScrollbarPreferences {
918 fn default() -> Self {
919 Self {
920 visibility: ScrollbarVisibility::WhenScrolling,
921 track_click: ScrollbarTrackClick::PageUpDown,
922 }
923 }
924}
925
926#[derive(Debug, Default, Clone, PartialEq, Eq)]
933#[repr(C)]
934pub struct LinuxCustomization {
935 pub gtk_theme: OptionString,
937 pub icon_theme: OptionString,
939 pub cursor_theme: OptionString,
941 pub cursor_size: u32,
943 pub titlebar_button_layout: OptionString,
946}
947
948#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
952#[repr(C)]
953pub enum ToolbarStyle {
954 #[default]
956 IconsOnly,
957 TextOnly,
959 TextBesideIcon,
961 TextBelowIcon,
963}
964
965#[derive(Debug, Clone, Copy, PartialEq, Eq)]
970#[repr(C)]
971pub struct VisualHints {
972 pub toolbar_style: ToolbarStyle,
975 pub show_button_images: bool,
978 pub show_menu_images: bool,
981 pub show_tooltips: bool,
983 pub flash_on_alert: bool,
985}
986
987impl Default for VisualHints {
988 fn default() -> Self {
989 Self {
990 toolbar_style: ToolbarStyle::IconsOnly,
991 show_button_images: false,
992 show_menu_images: true,
993 show_tooltips: true,
994 flash_on_alert: true,
995 }
996 }
997}
998
999#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
1003#[repr(C)]
1004pub enum FocusBehavior {
1005 #[default]
1007 AlwaysVisible,
1008 KeyboardOnly,
1011}
1012
1013#[derive(Debug, Clone, Copy, PartialEq)]
1025#[repr(C)]
1026pub struct AnimationMetrics {
1027 pub animations_enabled: bool,
1029 pub animation_duration_factor: f32,
1032 pub focus_indicator_behavior: FocusBehavior,
1034}
1035
1036impl Default for AnimationMetrics {
1037 fn default() -> Self {
1038 Self {
1039 animations_enabled: true,
1040 animation_duration_factor: 1.0,
1041 focus_indicator_behavior: FocusBehavior::AlwaysVisible,
1042 }
1043 }
1044}
1045
1046#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1059#[repr(C)]
1060pub struct AudioMetrics {
1061 pub event_sounds_enabled: bool,
1063 pub input_feedback_sounds_enabled: bool,
1065}
1066
1067impl Default for AudioMetrics {
1068 fn default() -> Self {
1069 Self {
1070 event_sounds_enabled: true,
1071 input_feedback_sounds_enabled: false,
1072 }
1073 }
1074}
1075
1076pub mod apple_fonts {
1082 pub const SYSTEM_FONT: &str = "System Font";
1085
1086 pub const SF_NS_ROUNDED: &str = "SF NS Rounded";
1088
1089 pub const SF_COMPACT: &str = "SF Compact";
1092
1093 pub const SF_MONO: &str = "SF NS Mono Light";
1096
1097 pub const NEW_YORK: &str = "New York";
1100
1101 pub const SF_ARABIC: &str = "SF Arabic";
1103
1104 pub const SF_ARMENIAN: &str = "SF Armenian";
1106
1107 pub const SF_GEORGIAN: &str = "SF Georgian";
1109
1110 pub const SF_HEBREW: &str = "SF Hebrew";
1112
1113 pub const MENLO: &str = "Menlo";
1115 pub const MENLO_REGULAR: &str = "Menlo Regular";
1116 pub const MENLO_BOLD: &str = "Menlo Bold";
1117 pub const MONACO: &str = "Monaco";
1118 pub const LUCIDA_GRANDE: &str = "Lucida Grande";
1119 pub const LUCIDA_GRANDE_BOLD: &str = "Lucida Grande Bold";
1120 pub const HELVETICA_NEUE: &str = "Helvetica Neue";
1121 pub const HELVETICA_NEUE_BOLD: &str = "Helvetica Neue Bold";
1122}
1123
1124pub mod windows_fonts {
1126 pub const SEGOE_UI_VARIABLE: &str = "Segoe UI Variable";
1128 pub const SEGOE_UI_VARIABLE_TEXT: &str = "Segoe UI Variable Text";
1129 pub const SEGOE_UI_VARIABLE_DISPLAY: &str = "Segoe UI Variable Display";
1130
1131 pub const SEGOE_UI: &str = "Segoe UI";
1133 pub const CONSOLAS: &str = "Consolas";
1134 pub const CASCADIA_CODE: &str = "Cascadia Code";
1135 pub const CASCADIA_MONO: &str = "Cascadia Mono";
1136
1137 pub const TAHOMA: &str = "Tahoma";
1139 pub const MS_SANS_SERIF: &str = "MS Sans Serif";
1140 pub const LUCIDA_CONSOLE: &str = "Lucida Console";
1141 pub const COURIER_NEW: &str = "Courier New";
1142}
1143
1144pub mod linux_fonts {
1146 pub const CANTARELL: &str = "Cantarell";
1148 pub const ADWAITA: &str = "Adwaita";
1149
1150 pub const UBUNTU: &str = "Ubuntu";
1152 pub const UBUNTU_MONO: &str = "Ubuntu Mono";
1153
1154 pub const DEJAVU_SANS: &str = "DejaVu Sans";
1156 pub const DEJAVU_SANS_MONO: &str = "DejaVu Sans Mono";
1157 pub const DEJAVU_SERIF: &str = "DejaVu Serif";
1158
1159 pub const LIBERATION_SANS: &str = "Liberation Sans";
1161 pub const LIBERATION_MONO: &str = "Liberation Mono";
1162 pub const LIBERATION_SERIF: &str = "Liberation Serif";
1163
1164 pub const NOTO_SANS: &str = "Noto Sans";
1166 pub const NOTO_MONO: &str = "Noto Sans Mono";
1167 pub const NOTO_SERIF: &str = "Noto Serif";
1168
1169 pub const HACK: &str = "Hack";
1171
1172 pub const MONOSPACE: &str = "Monospace";
1174 pub const SANS_SERIF: &str = "Sans";
1175 pub const SERIF: &str = "Serif";
1176}
1177
1178impl SystemFontType {
1179 #[must_use] pub fn get_fallback_chain(&self, platform: &Platform) -> Vec<&'static str> {
1184 match platform {
1185 Platform::MacOs | Platform::Ios => self.macos_fallback_chain(),
1186 Platform::Windows => self.windows_fallback_chain(),
1187 Platform::Linux(_) => self.linux_fallback_chain(),
1188 Platform::Android => self.android_fallback_chain(),
1189 Platform::Unknown => self.generic_fallback_chain(),
1190 }
1191 }
1192
1193 fn macos_fallback_chain(self) -> Vec<&'static str> {
1194 match self {
1195 Self::Ui => vec![
1197 apple_fonts::SYSTEM_FONT,
1198 apple_fonts::HELVETICA_NEUE,
1199 apple_fonts::LUCIDA_GRANDE,
1200 ],
1201 Self::UiBold | Self::TitleBold => vec![
1203 apple_fonts::HELVETICA_NEUE,
1204 apple_fonts::LUCIDA_GRANDE,
1205 ],
1206 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
1208 apple_fonts::MENLO,
1209 apple_fonts::MONACO,
1210 ],
1211 Self::Title | Self::Menu | Self::Small => vec![
1213 apple_fonts::SYSTEM_FONT,
1214 apple_fonts::HELVETICA_NEUE,
1215 ],
1216 Self::Serif => vec![
1218 apple_fonts::NEW_YORK,
1219 "Georgia",
1220 "Times New Roman",
1221 ],
1222 Self::SerifBold => vec![
1223 "Georgia", "Times New Roman",
1225 ],
1226 }
1227 }
1228
1229 fn windows_fallback_chain(self) -> Vec<&'static str> {
1230 match self {
1231 Self::Ui | Self::UiBold => vec![
1232 windows_fonts::SEGOE_UI_VARIABLE_TEXT,
1233 windows_fonts::SEGOE_UI,
1234 windows_fonts::TAHOMA,
1235 ],
1236 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
1237 windows_fonts::CASCADIA_MONO,
1238 windows_fonts::CASCADIA_CODE,
1239 windows_fonts::CONSOLAS,
1240 windows_fonts::LUCIDA_CONSOLE,
1241 windows_fonts::COURIER_NEW,
1242 ],
1243 Self::Title | Self::TitleBold => vec![
1244 windows_fonts::SEGOE_UI_VARIABLE_DISPLAY,
1245 windows_fonts::SEGOE_UI,
1246 ],
1247 Self::Menu => vec![
1248 windows_fonts::SEGOE_UI,
1249 windows_fonts::TAHOMA,
1250 ],
1251 Self::Small => vec![
1252 windows_fonts::SEGOE_UI,
1253 ],
1254 Self::Serif | Self::SerifBold => vec![
1255 "Cambria",
1256 "Georgia",
1257 "Times New Roman",
1258 ],
1259 }
1260 }
1261
1262 fn linux_fallback_chain(self) -> Vec<&'static str> {
1263 match self {
1264 Self::Ui | Self::UiBold => vec![
1265 linux_fonts::CANTARELL,
1266 linux_fonts::UBUNTU,
1267 linux_fonts::NOTO_SANS,
1268 linux_fonts::DEJAVU_SANS,
1269 linux_fonts::LIBERATION_SANS,
1270 linux_fonts::SANS_SERIF,
1271 ],
1272 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
1273 linux_fonts::UBUNTU_MONO,
1274 linux_fonts::HACK,
1275 linux_fonts::NOTO_MONO,
1276 linux_fonts::DEJAVU_SANS_MONO,
1277 linux_fonts::LIBERATION_MONO,
1278 linux_fonts::MONOSPACE,
1279 ],
1280 Self::Title | Self::TitleBold | Self::Menu | Self::Small => vec![
1281 linux_fonts::CANTARELL,
1282 linux_fonts::UBUNTU,
1283 linux_fonts::NOTO_SANS,
1284 ],
1285 Self::Serif | Self::SerifBold => vec![
1286 linux_fonts::NOTO_SERIF,
1287 linux_fonts::DEJAVU_SERIF,
1288 linux_fonts::LIBERATION_SERIF,
1289 linux_fonts::SERIF,
1290 ],
1291 }
1292 }
1293
1294 fn android_fallback_chain(self) -> Vec<&'static str> {
1295 match self {
1296 Self::Ui | Self::UiBold | Self::Title | Self::TitleBold => vec!["Roboto", "Noto Sans"],
1297 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
1298 vec!["Roboto Mono", "Droid Sans Mono", "monospace"]
1299 }
1300 Self::Menu | Self::Small => vec!["Roboto"],
1301 Self::Serif | Self::SerifBold => vec!["Noto Serif", "Droid Serif", "serif"],
1302 }
1303 }
1304
1305 fn generic_fallback_chain(self) -> Vec<&'static str> {
1306 match self {
1307 Self::Ui | Self::UiBold | Self::Title | Self::TitleBold | Self::Menu | Self::Small => {
1308 vec!["sans-serif"]
1309 }
1310 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
1311 vec!["monospace"]
1312 }
1313 Self::Serif | Self::SerifBold => vec!["serif"],
1314 }
1315 }
1316}
1317
1318impl SystemStyle {
1319
1320 #[allow(clippy::too_many_lines)] #[must_use] pub fn to_json_string(&self) -> AzString {
1326 use alloc::format;
1327
1328 fn opt_color(c: OptionColorU) -> alloc::string::String {
1329 c.as_ref().map_or_else(
1330 || "null".into(),
1331 |c| format!("\"#{:02x}{:02x}{:02x}{:02x}\"", c.r, c.g, c.b, c.a),
1332 )
1333 }
1334 fn opt_str(s: &OptionString) -> alloc::string::String {
1335 s.as_ref()
1336 .map_or_else(|| "null".into(), |s| format!("\"{}\"", s.as_str()))
1337 }
1338 fn opt_f32(v: OptionF32) -> alloc::string::String {
1339 v.into_option()
1340 .map_or_else(|| "null".into(), |v| format!("{v:.2}"))
1341 }
1342 fn opt_u16(v: OptionU16) -> alloc::string::String {
1343 v.into_option()
1344 .map_or_else(|| "null".into(), |v| format!("{v}"))
1345 }
1346 fn opt_px(v: &OptionPixelValue) -> alloc::string::String {
1347 v.as_ref().map_or_else(
1348 || "null".into(),
1349 |v| format!("{:.1}", v.to_pixels_internal(0.0, 0.0, 0.0)),
1350 )
1351 }
1352
1353 let tm = &self.metrics.titlebar;
1354 let inp = &self.input;
1355 let tr = &self.text_rendering;
1356 let acc = &self.accessibility;
1357 let sp = &self.scrollbar_preferences;
1358 let lnx = &self.linux;
1359 let vh = &self.visual_hints;
1360 let anim = &self.animation;
1361 let audio = &self.audio;
1362
1363 let json = format!(
1364r#"{{
1365 "theme": "{:?}",
1366 "platform": "{:?}",
1367 "os_version": "{:?}:{}",
1368 "language": "{}",
1369 "prefers_reduced_motion": {:?},
1370 "prefers_high_contrast": {:?},
1371 "colors": {{
1372 "text": {},
1373 "secondary_text": {},
1374 "tertiary_text": {},
1375 "background": {},
1376 "accent": {},
1377 "accent_text": {},
1378 "button_face": {},
1379 "button_text": {},
1380 "disabled_text": {},
1381 "window_background": {},
1382 "under_page_background": {},
1383 "selection_background": {},
1384 "selection_text": {},
1385 "selection_background_inactive": {},
1386 "selection_text_inactive": {},
1387 "link": {},
1388 "separator": {},
1389 "grid": {},
1390 "find_highlight": {},
1391 "sidebar_background": {},
1392 "sidebar_selection": {}
1393 }},
1394 "fonts": {{
1395 "ui_font": {},
1396 "ui_font_size": {},
1397 "monospace_font": {},
1398 "title_font": {},
1399 "menu_font": {},
1400 "small_font": {}
1401 }},
1402 "titlebar": {{
1403 "button_side": "{:?}",
1404 "height": {},
1405 "button_area_width": {},
1406 "padding_horizontal": {},
1407 "title_font": {},
1408 "title_font_size": {},
1409 "title_font_weight": {},
1410 "has_close": {},
1411 "has_minimize": {},
1412 "has_maximize": {},
1413 "has_fullscreen": {}
1414 }},
1415 "input": {{
1416 "double_click_time_ms": {},
1417 "double_click_distance_px": {:.1},
1418 "drag_threshold_px": {:.1},
1419 "caret_blink_rate_ms": {},
1420 "caret_width_px": {:.1},
1421 "wheel_scroll_lines": {},
1422 "hover_time_ms": {}
1423 }},
1424 "text_rendering": {{
1425 "font_smoothing_enabled": {},
1426 "subpixel_type": "{:?}",
1427 "font_smoothing_gamma": {},
1428 "increased_contrast": {}
1429 }},
1430 "accessibility": {{
1431 "prefers_bold_text": {},
1432 "prefers_larger_text": {},
1433 "text_scale_factor": {:.2},
1434 "prefers_high_contrast": {},
1435 "prefers_reduced_motion": {},
1436 "prefers_reduced_transparency": {},
1437 "screen_reader_active": {},
1438 "differentiate_without_color": {}
1439 }},
1440 "scrollbar_preferences": {{
1441 "visibility": "{:?}",
1442 "track_click": "{:?}"
1443 }},
1444 "linux": {{
1445 "gtk_theme": {},
1446 "icon_theme": {},
1447 "cursor_theme": {},
1448 "cursor_size": {},
1449 "titlebar_button_layout": {}
1450 }},
1451 "visual_hints": {{
1452 "show_button_images": {},
1453 "show_menu_images": {},
1454 "toolbar_style": "{:?}",
1455 "show_tooltips": {}
1456 }},
1457 "animation": {{
1458 "animations_enabled": {},
1459 "animation_duration_factor": {:.2},
1460 "focus_indicator_behavior": "{:?}"
1461 }},
1462 "audio": {{
1463 "event_sounds_enabled": {},
1464 "input_feedback_sounds_enabled": {}
1465 }}
1466}}"#,
1467 self.theme,
1469 self.platform,
1470 self.os_version.os, self.os_version.version_id,
1471 self.language.as_str(),
1472 self.prefers_reduced_motion,
1473 self.prefers_high_contrast,
1474 opt_color(self.colors.text),
1476 opt_color(self.colors.secondary_text),
1477 opt_color(self.colors.tertiary_text),
1478 opt_color(self.colors.background),
1479 opt_color(self.colors.accent),
1480 opt_color(self.colors.accent_text),
1481 opt_color(self.colors.button_face),
1482 opt_color(self.colors.button_text),
1483 opt_color(self.colors.disabled_text),
1484 opt_color(self.colors.window_background),
1485 opt_color(self.colors.under_page_background),
1486 opt_color(self.colors.selection_background),
1487 opt_color(self.colors.selection_text),
1488 opt_color(self.colors.selection_background_inactive),
1489 opt_color(self.colors.selection_text_inactive),
1490 opt_color(self.colors.link),
1491 opt_color(self.colors.separator),
1492 opt_color(self.colors.grid),
1493 opt_color(self.colors.find_highlight),
1494 opt_color(self.colors.sidebar_background),
1495 opt_color(self.colors.sidebar_selection),
1496 opt_str(&self.fonts.ui_font),
1498 opt_f32(self.fonts.ui_font_size),
1499 opt_str(&self.fonts.monospace_font),
1500 opt_str(&self.fonts.title_font),
1501 opt_str(&self.fonts.menu_font),
1502 opt_str(&self.fonts.small_font),
1503 tm.button_side,
1505 opt_px(&tm.height),
1506 opt_px(&tm.button_area_width),
1507 opt_px(&tm.padding_horizontal),
1508 opt_str(&tm.title_font),
1509 opt_f32(tm.title_font_size),
1510 opt_u16(tm.title_font_weight),
1511 tm.buttons.has_close,
1512 tm.buttons.has_minimize,
1513 tm.buttons.has_maximize,
1514 tm.buttons.has_fullscreen,
1515 inp.double_click_time_ms,
1517 inp.double_click_distance_px,
1518 inp.drag_threshold_px,
1519 inp.caret_blink_rate_ms,
1520 inp.caret_width_px,
1521 inp.wheel_scroll_lines,
1522 inp.hover_time_ms,
1523 tr.font_smoothing_enabled,
1525 tr.subpixel_type,
1526 tr.font_smoothing_gamma,
1527 tr.increased_contrast,
1528 acc.prefers_bold_text,
1530 acc.prefers_larger_text,
1531 acc.text_scale_factor,
1532 acc.prefers_high_contrast,
1533 acc.prefers_reduced_motion,
1534 acc.prefers_reduced_transparency,
1535 acc.screen_reader_active,
1536 acc.differentiate_without_color,
1537 sp.visibility,
1539 sp.track_click,
1540 opt_str(&lnx.gtk_theme),
1542 opt_str(&lnx.icon_theme),
1543 opt_str(&lnx.cursor_theme),
1544 lnx.cursor_size,
1545 opt_str(&lnx.titlebar_button_layout),
1546 vh.show_button_images,
1548 vh.show_menu_images,
1549 vh.toolbar_style,
1550 vh.show_tooltips,
1551 anim.animations_enabled,
1553 anim.animation_duration_factor,
1554 anim.focus_indicator_behavior,
1555 audio.event_sounds_enabled,
1557 audio.input_feedback_sounds_enabled,
1558 );
1559
1560 AzString::from(json)
1561 }
1562
1563 #[must_use] pub fn detect() -> Self {
1569 Self::default_for_platform()
1570 }
1571
1572 #[must_use] pub fn default_for_platform() -> Self {
1574 #[cfg(target_os = "windows")]
1575 { defaults::windows_11_light() }
1576 #[cfg(target_os = "macos")]
1577 { defaults::macos_modern_light() }
1578 #[cfg(target_os = "linux")]
1579 { defaults::gnome_adwaita_light() }
1580 #[cfg(target_os = "android")]
1581 { defaults::android_material_light() }
1582 #[cfg(target_os = "ios")]
1583 { defaults::ios_light() }
1584 #[cfg(not(any(
1585 target_os = "linux",
1586 target_os = "windows",
1587 target_os = "macos",
1588 target_os = "android",
1589 target_os = "ios"
1590 )))]
1591 { Self::default() }
1592 }
1593
1594 #[inline]
1596 #[must_use] pub fn new() -> Self {
1597 Self::detect()
1598 }
1599
1600 #[must_use] pub fn create_csd_stylesheet(&self) -> Css {
1606 use alloc::format;
1607
1608 use crate::parser2::new_from_str;
1609
1610 let mut css = String::new();
1612
1613 let bg_color = self
1615 .colors
1616 .window_background
1617 .as_option()
1618 .copied()
1619 .unwrap_or(ColorU::new_rgb(240, 240, 240));
1620 let text_color = self
1621 .colors
1622 .text
1623 .as_option()
1624 .copied()
1625 .unwrap_or(ColorU::new_rgb(0, 0, 0));
1626 let accent_color = self
1627 .colors
1628 .accent
1629 .as_option()
1630 .copied()
1631 .unwrap_or(ColorU::new_rgb(0, 120, 215));
1632 let border_color = match self.theme {
1633 Theme::Dark => ColorU::new_rgb(60, 60, 60),
1634 Theme::Light => ColorU::new_rgb(200, 200, 200),
1635 };
1636
1637 let corner_radius = self
1639 .metrics
1640 .corner_radius
1641 .map(|px| {
1642 use crate::props::basic::pixel::DEFAULT_FONT_SIZE;
1643 format!("{}px", px.to_pixels_internal(1.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
1644 })
1645 .unwrap_or_else(|| "4px".to_string());
1646
1647 let _ = write!(css,
1649 ".csd-titlebar {{ width: 100%; height: 32px; background: rgb({}, {}, {}); \
1650 border-bottom: 1px solid rgb({}, {}, {}); display: flex; flex-direction: row; \
1651 align-items: center; justify-content: space-between; padding: 0 8px; \
1652 cursor: grab; user-select: none; }} ",
1653 bg_color.r, bg_color.g, bg_color.b, border_color.r, border_color.g, border_color.b,
1654 );
1655
1656 let _ = write!(css,
1658 ".csd-title {{ color: rgb({}, {}, {}); font-size: 13px; flex-grow: 1; text-align: \
1659 center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; \
1660 user-select: none; }} ",
1661 text_color.r, text_color.g, text_color.b,
1662 );
1663
1664 css.push_str(".csd-buttons { display: flex; flex-direction: row; gap: 4px; } ");
1666
1667 let _ = write!(css,
1669 ".csd-button {{ width: 32px; height: 24px; border-radius: {}; background: \
1670 transparent; color: rgb({}, {}, {}); font-size: 16px; line-height: 24px; text-align: \
1671 center; cursor: pointer; user-select: none; }} ",
1672 corner_radius, text_color.r, text_color.g, text_color.b,
1673 );
1674
1675 let hover_color = match self.theme {
1677 Theme::Dark => ColorU::new_rgb(60, 60, 60),
1678 Theme::Light => ColorU::new_rgb(220, 220, 220),
1679 };
1680 let _ = write!(css,
1681 ".csd-button:hover {{ background: rgb({}, {}, {}); }} ",
1682 hover_color.r, hover_color.g, hover_color.b,
1683 );
1684
1685 css.push_str(
1687 ".csd-close:hover { background: rgb(232, 17, 35); color: rgb(255, 255, 255); } ",
1688 );
1689
1690 match self.platform {
1692 Platform::MacOs => {
1693 css.push_str(".csd-buttons { position: absolute; left: 8px; } ");
1695 css.push_str(
1696 ".csd-close { background: rgb(255, 95, 86); width: 12px; height: 12px; \
1697 border-radius: 50%; } ",
1698 );
1699 css.push_str(
1700 ".csd-minimize { background: rgb(255, 189, 46); width: 12px; height: 12px; \
1701 border-radius: 50%; } ",
1702 );
1703 css.push_str(
1704 ".csd-maximize { background: rgb(40, 201, 64); width: 12px; height: 12px; \
1705 border-radius: 50%; } ",
1706 );
1707 }
1708 Platform::Linux(_) => {
1709 css.push_str(".csd-title { text-align: left; } ");
1711 }
1712 _ => {
1713 }
1715 }
1716
1717 let (mut parsed_css, _warnings) = new_from_str(&css);
1719 for rule in parsed_css.rules.as_mut() {
1721 rule.priority = crate::css::rule_priority::SYSTEM;
1722 }
1723 parsed_css
1724 }
1725}
1726
1727#[must_use] pub fn detect_linux_desktop_env() -> DesktopEnvironment {
1732 if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") {
1734 let desktop_lower = desktop.to_lowercase();
1735 if desktop_lower.contains("gnome") {
1736 return DesktopEnvironment::Gnome;
1737 }
1738 if desktop_lower.contains("kde") || desktop_lower.contains("plasma") {
1739 return DesktopEnvironment::Kde;
1740 }
1741 if desktop_lower.contains("xfce") {
1742 return DesktopEnvironment::Other(AzString::from_const_str("XFCE"));
1743 }
1744 if desktop_lower.contains("unity") {
1745 return DesktopEnvironment::Other(AzString::from_const_str("Unity"));
1746 }
1747 if desktop_lower.contains("cinnamon") {
1748 return DesktopEnvironment::Other(AzString::from_const_str("Cinnamon"));
1749 }
1750 if desktop_lower.contains("mate") {
1751 return DesktopEnvironment::Other(AzString::from_const_str("MATE"));
1752 }
1753 if desktop_lower.contains("lxde") || desktop_lower.contains("lxqt") {
1754 return DesktopEnvironment::Other(AzString::from(desktop.to_uppercase()));
1755 }
1756 if desktop_lower.contains("budgie") {
1757 return DesktopEnvironment::Other(AzString::from_const_str("Budgie"));
1758 }
1759 if desktop_lower.contains("pantheon") {
1760 return DesktopEnvironment::Other(AzString::from_const_str("Pantheon"));
1761 }
1762 if desktop_lower.contains("deepin") {
1763 return DesktopEnvironment::Other(AzString::from_const_str("Deepin"));
1764 }
1765 if desktop_lower.contains("hyprland") {
1766 return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
1767 }
1768 if desktop_lower.contains("sway") {
1769 return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
1770 }
1771 if desktop_lower.contains("i3") {
1772 return DesktopEnvironment::Other(AzString::from_const_str("i3"));
1773 }
1774 return DesktopEnvironment::Other(AzString::from(desktop));
1775 }
1776
1777 if let Ok(session) = std::env::var("DESKTOP_SESSION") {
1779 let session_lower = session.to_lowercase();
1780 if session_lower.contains("gnome") {
1781 return DesktopEnvironment::Gnome;
1782 }
1783 if session_lower.contains("plasma") || session_lower.contains("kde") {
1784 return DesktopEnvironment::Kde;
1785 }
1786 if session_lower.contains("xfce") {
1787 return DesktopEnvironment::Other(AzString::from_const_str("XFCE"));
1788 }
1789 if session_lower.contains("cinnamon") {
1790 return DesktopEnvironment::Other(AzString::from_const_str("Cinnamon"));
1791 }
1792 return DesktopEnvironment::Other(AzString::from(session));
1793 }
1794
1795 if std::env::var("GNOME_DESKTOP_SESSION_ID").is_ok() {
1797 return DesktopEnvironment::Gnome;
1798 }
1799 if std::env::var("KDE_FULL_SESSION").is_ok() {
1800 return DesktopEnvironment::Kde;
1801 }
1802 if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
1803 return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
1804 }
1805 if std::env::var("SWAYSOCK").is_ok() {
1806 return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
1807 }
1808 if std::env::var("I3SOCK").is_ok() {
1809 return DesktopEnvironment::Other(AzString::from_const_str("i3"));
1810 }
1811
1812 DesktopEnvironment::Other(AzString::from_const_str("Unknown"))
1813}
1814
1815#[must_use] pub fn detect_system_language() -> AzString {
1821 let env_vars = ["LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"];
1822 for var in &env_vars {
1823 if let Ok(value) = std::env::var(var) {
1824 let value = value.trim();
1825 if value.is_empty() || value == "C" || value == "POSIX" {
1826 continue;
1827 }
1828 let lang = value
1830 .split('.') .next()
1832 .unwrap_or(value)
1833 .split(':') .next()
1835 .unwrap_or(value);
1836 if !lang.is_empty() {
1837 return AzString::from(lang.replace('_', "-"));
1838 }
1839 }
1840 }
1841 AzString::from_const_str("en-US")
1842}
1843
1844pub mod defaults {
1845 use super::{
1853 AccessibilitySettings, AnimationMetrics, AudioMetrics, FocusVisuals, Handedness,
1854 InputMetrics, LinuxCustomization, ScrollbarPreferences, TextRenderingHints, VisualHints,
1855 };
1856 use crate::{
1857 corety::{AzString, OptionF32, OptionString},
1858 dynamic_selector::{BoolCondition, OsVersion},
1859 props::{
1860 basic::{
1861 color::{ColorU, OptionColorU},
1862 pixel::{PixelValue, OptionPixelValue},
1863 },
1864 layout::{
1865 dimensions::LayoutWidth,
1866 spacing::{LayoutPaddingLeft, LayoutPaddingRight},
1867 },
1868 style::{
1869 background::StyleBackgroundContent,
1870 scrollbar::{
1871 ComputedScrollbarStyle, OverflowScrolling, OverscrollBehavior, ScrollBehavior,
1872 ScrollPhysics, ScrollbarInfo,
1873 SCROLLBAR_ANDROID_DARK, SCROLLBAR_ANDROID_LIGHT, SCROLLBAR_CLASSIC_DARK,
1874 SCROLLBAR_CLASSIC_LIGHT, SCROLLBAR_IOS_DARK, SCROLLBAR_IOS_LIGHT,
1875 SCROLLBAR_MACOS_DARK, SCROLLBAR_MACOS_LIGHT, SCROLLBAR_WINDOWS_DARK,
1876 SCROLLBAR_WINDOWS_LIGHT,
1877 },
1878 },
1879 },
1880 system::{
1881 DesktopEnvironment, Platform, SystemColors, SystemFonts, SystemMetrics, SystemStyle,
1882 Theme, IconStyleOptions, TitlebarMetrics,
1883 },
1884 };
1885
1886 pub const SCROLLBAR_WINDOWS_CLASSIC: ScrollbarInfo = ScrollbarInfo {
1890 width: LayoutWidth::Px(PixelValue::const_px(17)),
1891 padding_left: LayoutPaddingLeft {
1892 inner: PixelValue::const_px(0),
1893 },
1894 padding_right: LayoutPaddingRight {
1895 inner: PixelValue::const_px(0),
1896 },
1897 track: StyleBackgroundContent::Color(ColorU {
1898 r: 223,
1899 g: 223,
1900 b: 223,
1901 a: 255,
1902 }), thumb: StyleBackgroundContent::Color(ColorU {
1904 r: 208,
1905 g: 208,
1906 b: 208,
1907 a: 255,
1908 }), button: StyleBackgroundContent::Color(ColorU {
1910 r: 208,
1911 g: 208,
1912 b: 208,
1913 a: 255,
1914 }),
1915 corner: StyleBackgroundContent::Color(ColorU {
1916 r: 223,
1917 g: 223,
1918 b: 223,
1919 a: 255,
1920 }),
1921 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1922 clip_to_container_border: false,
1923 scroll_behavior: ScrollBehavior::Auto,
1924 overscroll_behavior_x: OverscrollBehavior::None,
1925 overscroll_behavior_y: OverscrollBehavior::None,
1926 overflow_scrolling: OverflowScrolling::Auto,
1927 };
1928
1929 pub const SCROLLBAR_MACOS_AQUA: ScrollbarInfo = ScrollbarInfo {
1931 width: LayoutWidth::Px(PixelValue::const_px(15)),
1932 padding_left: LayoutPaddingLeft {
1933 inner: PixelValue::const_px(0),
1934 },
1935 padding_right: LayoutPaddingRight {
1936 inner: PixelValue::const_px(0),
1937 },
1938 track: StyleBackgroundContent::Color(ColorU {
1939 r: 238,
1940 g: 238,
1941 b: 238,
1942 a: 128,
1943 }), thumb: StyleBackgroundContent::Color(ColorU {
1945 r: 105,
1946 g: 173,
1947 b: 255,
1948 a: 255,
1949 }), button: StyleBackgroundContent::Color(ColorU {
1951 r: 105,
1952 g: 173,
1953 b: 255,
1954 a: 255,
1955 }),
1956 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1957 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1958 clip_to_container_border: true,
1959 scroll_behavior: ScrollBehavior::Smooth,
1960 overscroll_behavior_x: OverscrollBehavior::Auto,
1961 overscroll_behavior_y: OverscrollBehavior::Auto,
1962 overflow_scrolling: OverflowScrolling::Auto,
1963 };
1964
1965 pub const SCROLLBAR_KDE_OXYGEN: ScrollbarInfo = ScrollbarInfo {
1967 width: LayoutWidth::Px(PixelValue::const_px(14)),
1968 padding_left: LayoutPaddingLeft {
1969 inner: PixelValue::const_px(2),
1970 },
1971 padding_right: LayoutPaddingRight {
1972 inner: PixelValue::const_px(2),
1973 },
1974 track: StyleBackgroundContent::Color(ColorU {
1975 r: 242,
1976 g: 242,
1977 b: 242,
1978 a: 255,
1979 }),
1980 thumb: StyleBackgroundContent::Color(ColorU {
1981 r: 177,
1982 g: 177,
1983 b: 177,
1984 a: 255,
1985 }),
1986 button: StyleBackgroundContent::Color(ColorU {
1987 r: 216,
1988 g: 216,
1989 b: 216,
1990 a: 255,
1991 }),
1992 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1993 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1994 clip_to_container_border: false,
1995 scroll_behavior: ScrollBehavior::Auto,
1996 overscroll_behavior_x: OverscrollBehavior::Auto,
1997 overscroll_behavior_y: OverscrollBehavior::Auto,
1998 overflow_scrolling: OverflowScrolling::Auto,
1999 };
2000
2001 fn scrollbar_info_to_computed(info: &ScrollbarInfo) -> ComputedScrollbarStyle {
2003 ComputedScrollbarStyle {
2004 width: Some(info.width.clone()),
2005 thumb_color: match info.thumb {
2006 StyleBackgroundContent::Color(c) => Some(c),
2007 _ => None,
2008 },
2009 track_color: match info.track {
2010 StyleBackgroundContent::Color(c) => Some(c),
2011 _ => None,
2012 },
2013 }
2014 }
2015
2016 #[must_use] pub fn windows_11_light() -> SystemStyle {
2020 SystemStyle {
2021 theme: Theme::Light,
2022 platform: Platform::Windows,
2023 colors: SystemColors {
2024 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2025 background: OptionColorU::Some(ColorU::new_rgb(243, 243, 243)),
2026 accent: OptionColorU::Some(ColorU::new_rgb(0, 95, 184)),
2027 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2028 selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2029 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2030 ..Default::default()
2031 },
2032 fonts: SystemFonts {
2033 ui_font: OptionString::Some("Segoe UI Variable Text".into()),
2034 ui_font_size: OptionF32::Some(9.0),
2035 monospace_font: OptionString::Some("Consolas".into()),
2036 ..Default::default()
2037 },
2038 metrics: SystemMetrics {
2039 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2040 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2041 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2042 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2043 titlebar: TitlebarMetrics::windows(),
2044 },
2045 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_LIGHT))),
2046 app_specific_stylesheet: None,
2047 run_destructor: true,
2048 icon_style: IconStyleOptions::default(),
2049 language: AzString::from_const_str("en-US"),
2050 os_version: OsVersion::WIN_11,
2051 prefers_reduced_motion: BoolCondition::False,
2052 prefers_high_contrast: BoolCondition::False,
2053 scroll_physics: ScrollPhysics::windows(),
2054 linux: LinuxCustomization::default(),
2055 focus_visuals: FocusVisuals::default(),
2056 handedness: Handedness::default(),
2057 accessibility: AccessibilitySettings::default(),
2058 input: InputMetrics::default(),
2059 text_rendering: TextRenderingHints::default(),
2060 scrollbar_preferences: ScrollbarPreferences::default(),
2061 visual_hints: VisualHints::default(),
2062 animation: AnimationMetrics::default(),
2063 audio: AudioMetrics::default(),
2064 }
2065 }
2066
2067 #[must_use] pub fn windows_11_dark() -> SystemStyle {
2069 SystemStyle {
2070 theme: Theme::Dark,
2071 platform: Platform::Windows,
2072 colors: SystemColors {
2073 text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2074 background: OptionColorU::Some(ColorU::new_rgb(32, 32, 32)),
2075 accent: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2076 window_background: OptionColorU::Some(ColorU::new_rgb(25, 25, 25)),
2077 selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2078 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2079 ..Default::default()
2080 },
2081 fonts: SystemFonts {
2082 ui_font: OptionString::Some("Segoe UI Variable Text".into()),
2083 ui_font_size: OptionF32::Some(9.0),
2084 monospace_font: OptionString::Some("Consolas".into()),
2085 ..Default::default()
2086 },
2087 metrics: SystemMetrics {
2088 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2089 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2090 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2091 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2092 titlebar: TitlebarMetrics::windows(),
2093 },
2094 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_DARK))),
2095 app_specific_stylesheet: None,
2096 run_destructor: true,
2097 icon_style: IconStyleOptions::default(),
2098 language: AzString::from_const_str("en-US"),
2099 os_version: OsVersion::WIN_11,
2100 prefers_reduced_motion: BoolCondition::False,
2101 prefers_high_contrast: BoolCondition::False,
2102 scroll_physics: ScrollPhysics::windows(),
2103 linux: LinuxCustomization::default(),
2104 focus_visuals: FocusVisuals::default(),
2105 handedness: Handedness::default(),
2106 accessibility: AccessibilitySettings::default(),
2107 input: InputMetrics::default(),
2108 text_rendering: TextRenderingHints::default(),
2109 scrollbar_preferences: ScrollbarPreferences::default(),
2110 visual_hints: VisualHints::default(),
2111 animation: AnimationMetrics::default(),
2112 audio: AudioMetrics::default(),
2113 }
2114 }
2115
2116 #[must_use] pub fn windows_7_aero() -> SystemStyle {
2118 SystemStyle {
2119 theme: Theme::Light,
2120 platform: Platform::Windows,
2121 colors: SystemColors {
2122 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2123 background: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
2124 accent: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
2125 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2126 selection_background: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
2127 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2128 ..Default::default()
2129 },
2130 fonts: SystemFonts {
2131 ui_font: OptionString::Some("Segoe UI".into()),
2132 ui_font_size: OptionF32::Some(9.0),
2133 monospace_font: OptionString::Some("Consolas".into()),
2134 ..Default::default()
2135 },
2136 metrics: SystemMetrics {
2137 corner_radius: OptionPixelValue::Some(PixelValue::px(6.0)),
2138 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2139 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
2140 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(5.0)),
2141 titlebar: TitlebarMetrics::windows(),
2142 },
2143 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
2144 app_specific_stylesheet: None,
2145 run_destructor: true,
2146 icon_style: IconStyleOptions::default(),
2147 language: AzString::from_const_str("en-US"),
2148 os_version: OsVersion::WIN_7,
2149 prefers_reduced_motion: BoolCondition::False,
2150 prefers_high_contrast: BoolCondition::False,
2151 scroll_physics: ScrollPhysics::windows(),
2152 linux: LinuxCustomization::default(),
2153 focus_visuals: FocusVisuals::default(),
2154 handedness: Handedness::default(),
2155 accessibility: AccessibilitySettings::default(),
2156 input: InputMetrics::default(),
2157 text_rendering: TextRenderingHints::default(),
2158 scrollbar_preferences: ScrollbarPreferences::default(),
2159 visual_hints: VisualHints::default(),
2160 animation: AnimationMetrics::default(),
2161 audio: AudioMetrics::default(),
2162 }
2163 }
2164
2165 #[must_use] pub fn windows_xp_luna() -> SystemStyle {
2167 SystemStyle {
2168 theme: Theme::Light,
2169 platform: Platform::Windows,
2170 colors: SystemColors {
2171 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2172 background: OptionColorU::Some(ColorU::new_rgb(236, 233, 216)),
2173 accent: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
2174 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2175 selection_background: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
2176 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2177 ..Default::default()
2178 },
2179 fonts: SystemFonts {
2180 ui_font: OptionString::Some("Tahoma".into()),
2181 ui_font_size: OptionF32::Some(8.0),
2182 monospace_font: OptionString::Some("Lucida Console".into()),
2183 ..Default::default()
2184 },
2185 metrics: SystemMetrics {
2186 corner_radius: OptionPixelValue::Some(PixelValue::px(3.0)),
2187 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2188 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
2189 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
2190 titlebar: TitlebarMetrics::windows(),
2191 },
2192 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_CLASSIC))),
2193 app_specific_stylesheet: None,
2194 run_destructor: true,
2195 icon_style: IconStyleOptions::default(),
2196 language: AzString::from_const_str("en-US"),
2197 os_version: OsVersion::WIN_XP,
2198 prefers_reduced_motion: BoolCondition::False,
2199 prefers_high_contrast: BoolCondition::False,
2200 scroll_physics: ScrollPhysics::windows(),
2201 linux: LinuxCustomization::default(),
2202 focus_visuals: FocusVisuals::default(),
2203 handedness: Handedness::default(),
2204 accessibility: AccessibilitySettings::default(),
2205 input: InputMetrics::default(),
2206 text_rendering: TextRenderingHints::default(),
2207 scrollbar_preferences: ScrollbarPreferences::default(),
2208 visual_hints: VisualHints::default(),
2209 animation: AnimationMetrics::default(),
2210 audio: AudioMetrics::default(),
2211 }
2212 }
2213
2214 #[must_use] pub fn macos_modern_light() -> SystemStyle {
2218 SystemStyle {
2219 platform: Platform::MacOs,
2220 theme: Theme::Light,
2221 colors: SystemColors {
2222 text: OptionColorU::Some(ColorU::new(0, 0, 0, 221)),
2223 background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
2224 accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
2225 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2226 selection_background: OptionColorU::Some(ColorU::new(0, 122, 255, 128)),
2228 selection_text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2229 ..Default::default()
2230 },
2231 fonts: SystemFonts {
2232 ui_font: OptionString::Some(".SF NS".into()),
2233 ui_font_size: OptionF32::Some(13.0),
2234 monospace_font: OptionString::Some("Menlo".into()),
2235 ..Default::default()
2236 },
2237 metrics: SystemMetrics {
2238 corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
2239 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2240 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2241 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2242 titlebar: TitlebarMetrics::macos(),
2243 },
2244 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_LIGHT))),
2245 app_specific_stylesheet: None,
2246 run_destructor: true,
2247 icon_style: IconStyleOptions::default(),
2248 language: AzString::from_const_str("en-US"),
2249 os_version: OsVersion::MACOS_SONOMA,
2250 prefers_reduced_motion: BoolCondition::False,
2251 prefers_high_contrast: BoolCondition::False,
2252 scroll_physics: ScrollPhysics::macos(),
2253 linux: LinuxCustomization::default(),
2254 focus_visuals: FocusVisuals::default(),
2255 handedness: Handedness::default(),
2256 accessibility: AccessibilitySettings::default(),
2257 input: InputMetrics::default(),
2258 text_rendering: TextRenderingHints::default(),
2259 scrollbar_preferences: ScrollbarPreferences::default(),
2260 visual_hints: VisualHints::default(),
2261 animation: AnimationMetrics::default(),
2262 audio: AudioMetrics::default(),
2263 }
2264 }
2265
2266 #[must_use] pub fn macos_modern_dark() -> SystemStyle {
2268 SystemStyle {
2269 platform: Platform::MacOs,
2270 theme: Theme::Dark,
2271 colors: SystemColors {
2272 text: OptionColorU::Some(ColorU::new(255, 255, 255, 221)),
2273 background: OptionColorU::Some(ColorU::new_rgb(28, 28, 30)),
2274 accent: OptionColorU::Some(ColorU::new_rgb(10, 132, 255)),
2275 window_background: OptionColorU::Some(ColorU::new_rgb(44, 44, 46)),
2276 selection_background: OptionColorU::Some(ColorU::new(10, 132, 255, 128)),
2278 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2279 ..Default::default()
2280 },
2281 fonts: SystemFonts {
2282 ui_font: OptionString::Some(".SF NS".into()),
2283 ui_font_size: OptionF32::Some(13.0),
2284 monospace_font: OptionString::Some("SF Mono".into()),
2285 monospace_font_size: OptionF32::Some(12.0),
2286 title_font: OptionString::Some(".SF NS".into()),
2287 title_font_size: OptionF32::Some(13.0),
2288 menu_font: OptionString::Some(".SF NS".into()),
2289 menu_font_size: OptionF32::Some(13.0),
2290 small_font: OptionString::Some(".SF NS".into()),
2291 small_font_size: OptionF32::Some(11.0),
2292 ..Default::default()
2293 },
2294 metrics: SystemMetrics {
2295 corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
2296 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2297 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2298 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2299 titlebar: TitlebarMetrics::macos(),
2300 },
2301 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_DARK))),
2302 app_specific_stylesheet: None,
2303 run_destructor: true,
2304 icon_style: IconStyleOptions::default(),
2305 language: AzString::from_const_str("en-US"),
2306 os_version: OsVersion::MACOS_SONOMA,
2307 prefers_reduced_motion: BoolCondition::False,
2308 prefers_high_contrast: BoolCondition::False,
2309 scroll_physics: ScrollPhysics::macos(),
2310 linux: LinuxCustomization::default(),
2311 focus_visuals: FocusVisuals::default(),
2312 handedness: Handedness::default(),
2313 accessibility: AccessibilitySettings::default(),
2314 input: InputMetrics::default(),
2315 text_rendering: TextRenderingHints::default(),
2316 scrollbar_preferences: ScrollbarPreferences::default(),
2317 visual_hints: VisualHints::default(),
2318 animation: AnimationMetrics::default(),
2319 audio: AudioMetrics::default(),
2320 }
2321 }
2322
2323 #[must_use] pub fn macos_aqua() -> SystemStyle {
2325 SystemStyle {
2326 platform: Platform::MacOs,
2327 theme: Theme::Light,
2328 colors: SystemColors {
2329 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2330 background: OptionColorU::Some(ColorU::new_rgb(229, 229, 229)),
2331 accent: OptionColorU::Some(ColorU::new_rgb(63, 128, 234)),
2332 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2333 ..Default::default()
2334 },
2335 fonts: SystemFonts {
2336 ui_font: OptionString::Some("Lucida Grande".into()),
2337 ui_font_size: OptionF32::Some(13.0),
2338 monospace_font: OptionString::Some("Monaco".into()),
2339 monospace_font_size: OptionF32::Some(12.0),
2340 ..Default::default()
2341 },
2342 metrics: SystemMetrics {
2343 corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
2344 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2345 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2346 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2347 titlebar: TitlebarMetrics::macos(),
2348 },
2349 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_AQUA))),
2350 app_specific_stylesheet: None,
2351 run_destructor: true,
2352 icon_style: IconStyleOptions::default(),
2353 language: AzString::from_const_str("en-US"),
2354 os_version: OsVersion::MACOS_TIGER,
2355 prefers_reduced_motion: BoolCondition::False,
2356 prefers_high_contrast: BoolCondition::False,
2357 scroll_physics: ScrollPhysics::macos(),
2358 linux: LinuxCustomization::default(),
2359 focus_visuals: FocusVisuals::default(),
2360 handedness: Handedness::default(),
2361 accessibility: AccessibilitySettings::default(),
2362 input: InputMetrics::default(),
2363 text_rendering: TextRenderingHints::default(),
2364 scrollbar_preferences: ScrollbarPreferences::default(),
2365 visual_hints: VisualHints::default(),
2366 animation: AnimationMetrics::default(),
2367 audio: AudioMetrics::default(),
2368 }
2369 }
2370
2371 #[must_use] pub fn gnome_adwaita_light() -> SystemStyle {
2375 SystemStyle {
2376 platform: Platform::Linux(DesktopEnvironment::Gnome),
2377 theme: Theme::Light,
2378 colors: SystemColors {
2379 text: OptionColorU::Some(ColorU::new_rgb(46, 52, 54)),
2380 background: OptionColorU::Some(ColorU::new_rgb(249, 249, 249)),
2381 accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
2382 window_background: OptionColorU::Some(ColorU::new_rgb(237, 237, 237)),
2383 ..Default::default()
2384 },
2385 fonts: SystemFonts {
2386 ui_font: OptionString::Some("Cantarell".into()),
2387 ui_font_size: OptionF32::Some(11.0),
2388 monospace_font: OptionString::Some("Monospace".into()),
2389 ..Default::default()
2390 },
2391 metrics: SystemMetrics {
2392 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2393 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2394 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2395 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2396 titlebar: TitlebarMetrics::linux_gnome(),
2397 },
2398 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
2399 app_specific_stylesheet: None,
2400 run_destructor: true,
2401 icon_style: IconStyleOptions::default(),
2402 language: AzString::from_const_str("en-US"),
2403 os_version: OsVersion::LINUX_6_0,
2404 prefers_reduced_motion: BoolCondition::False,
2405 prefers_high_contrast: BoolCondition::False,
2406 scroll_physics: ScrollPhysics::default(),
2407 linux: LinuxCustomization::default(),
2408 focus_visuals: FocusVisuals::default(),
2409 handedness: Handedness::default(),
2410 accessibility: AccessibilitySettings::default(),
2411 input: InputMetrics::default(),
2412 text_rendering: TextRenderingHints::default(),
2413 scrollbar_preferences: ScrollbarPreferences::default(),
2414 visual_hints: VisualHints::default(),
2415 animation: AnimationMetrics::default(),
2416 audio: AudioMetrics::default(),
2417 }
2418 }
2419
2420 #[must_use] pub fn gnome_adwaita_dark() -> SystemStyle {
2422 SystemStyle {
2423 platform: Platform::Linux(DesktopEnvironment::Gnome),
2424 theme: Theme::Dark,
2425 colors: SystemColors {
2426 text: OptionColorU::Some(ColorU::new_rgb(238, 238, 236)),
2427 background: OptionColorU::Some(ColorU::new_rgb(36, 36, 36)),
2428 accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
2429 window_background: OptionColorU::Some(ColorU::new_rgb(48, 48, 48)),
2430 ..Default::default()
2431 },
2432 fonts: SystemFonts {
2433 ui_font: OptionString::Some("Cantarell".into()),
2434 ui_font_size: OptionF32::Some(11.0),
2435 monospace_font: OptionString::Some("Monospace".into()),
2436 ..Default::default()
2437 },
2438 metrics: SystemMetrics {
2439 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2440 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2441 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2442 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2443 titlebar: TitlebarMetrics::linux_gnome(),
2444 },
2445 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_DARK))),
2446 app_specific_stylesheet: None,
2447 run_destructor: true,
2448 icon_style: IconStyleOptions::default(),
2449 language: AzString::from_const_str("en-US"),
2450 os_version: OsVersion::LINUX_6_0,
2451 prefers_reduced_motion: BoolCondition::False,
2452 prefers_high_contrast: BoolCondition::False,
2453 scroll_physics: ScrollPhysics::default(),
2454 linux: LinuxCustomization::default(),
2455 focus_visuals: FocusVisuals::default(),
2456 handedness: Handedness::default(),
2457 accessibility: AccessibilitySettings::default(),
2458 input: InputMetrics::default(),
2459 text_rendering: TextRenderingHints::default(),
2460 scrollbar_preferences: ScrollbarPreferences::default(),
2461 visual_hints: VisualHints::default(),
2462 animation: AnimationMetrics::default(),
2463 audio: AudioMetrics::default(),
2464 }
2465 }
2466
2467 #[must_use] pub fn gtk2_clearlooks() -> SystemStyle {
2469 SystemStyle {
2470 platform: Platform::Linux(DesktopEnvironment::Gnome),
2471 theme: Theme::Light,
2472 colors: SystemColors {
2473 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2474 background: OptionColorU::Some(ColorU::new_rgb(239, 239, 239)),
2475 accent: OptionColorU::Some(ColorU::new_rgb(245, 121, 0)),
2476 ..Default::default()
2477 },
2478 fonts: SystemFonts {
2479 ui_font: OptionString::Some("DejaVu Sans".into()),
2480 ui_font_size: OptionF32::Some(10.0),
2481 monospace_font: OptionString::Some("DejaVu Sans Mono".into()),
2482 ..Default::default()
2483 },
2484 metrics: SystemMetrics {
2485 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2486 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2487 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
2488 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2489 titlebar: TitlebarMetrics::linux_gnome(),
2490 },
2491 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
2492 app_specific_stylesheet: None,
2493 run_destructor: true,
2494 icon_style: IconStyleOptions::default(),
2495 language: AzString::from_const_str("en-US"),
2496 os_version: OsVersion::LINUX_2_6,
2497 prefers_reduced_motion: BoolCondition::False,
2498 prefers_high_contrast: BoolCondition::False,
2499 scroll_physics: ScrollPhysics::default(),
2500 linux: LinuxCustomization::default(),
2501 focus_visuals: FocusVisuals::default(),
2502 handedness: Handedness::default(),
2503 accessibility: AccessibilitySettings::default(),
2504 input: InputMetrics::default(),
2505 text_rendering: TextRenderingHints::default(),
2506 scrollbar_preferences: ScrollbarPreferences::default(),
2507 visual_hints: VisualHints::default(),
2508 animation: AnimationMetrics::default(),
2509 audio: AudioMetrics::default(),
2510 }
2511 }
2512
2513 #[must_use] pub fn kde_breeze_light() -> SystemStyle {
2515 SystemStyle {
2516 platform: Platform::Linux(DesktopEnvironment::Kde),
2517 theme: Theme::Light,
2518 colors: SystemColors {
2519 text: OptionColorU::Some(ColorU::new_rgb(31, 36, 39)),
2520 background: OptionColorU::Some(ColorU::new_rgb(239, 240, 241)),
2521 accent: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
2522 ..Default::default()
2523 },
2524 fonts: SystemFonts {
2525 ui_font: OptionString::Some("Noto Sans".into()),
2526 ui_font_size: OptionF32::Some(10.0),
2527 monospace_font: OptionString::Some("Hack".into()),
2528 ..Default::default()
2529 },
2530 metrics: SystemMetrics {
2531 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2532 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2533 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2534 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2535 titlebar: TitlebarMetrics::linux_gnome(),
2536 },
2537 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_KDE_OXYGEN))),
2538 app_specific_stylesheet: None,
2539 run_destructor: true,
2540 icon_style: IconStyleOptions::default(),
2541 language: AzString::from_const_str("en-US"),
2542 os_version: OsVersion::LINUX_6_0,
2543 prefers_reduced_motion: BoolCondition::False,
2544 prefers_high_contrast: BoolCondition::False,
2545 scroll_physics: ScrollPhysics::default(),
2546 linux: LinuxCustomization::default(),
2547 focus_visuals: FocusVisuals::default(),
2548 handedness: Handedness::default(),
2549 accessibility: AccessibilitySettings::default(),
2550 input: InputMetrics::default(),
2551 text_rendering: TextRenderingHints::default(),
2552 scrollbar_preferences: ScrollbarPreferences::default(),
2553 visual_hints: VisualHints::default(),
2554 animation: AnimationMetrics::default(),
2555 audio: AudioMetrics::default(),
2556 }
2557 }
2558
2559 #[must_use] pub fn android_material_light() -> SystemStyle {
2563 SystemStyle {
2564 platform: Platform::Android,
2565 theme: Theme::Light,
2566 colors: SystemColors {
2567 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2568 background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2569 accent: OptionColorU::Some(ColorU::new_rgb(98, 0, 238)),
2570 ..Default::default()
2571 },
2572 fonts: SystemFonts {
2573 ui_font: OptionString::Some("Roboto".into()),
2574 ui_font_size: OptionF32::Some(14.0),
2575 monospace_font: OptionString::Some("Droid Sans Mono".into()),
2576 ..Default::default()
2577 },
2578 metrics: SystemMetrics {
2579 corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
2580 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2581 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2582 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(10.0)),
2583 titlebar: TitlebarMetrics::android(),
2584 },
2585 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_LIGHT))),
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::ANDROID_14,
2591 prefers_reduced_motion: BoolCondition::False,
2592 prefers_high_contrast: BoolCondition::False,
2593 scroll_physics: ScrollPhysics::android(),
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] pub fn android_holo_dark() -> SystemStyle {
2609 SystemStyle {
2610 platform: Platform::Android,
2611 theme: Theme::Dark,
2612 colors: SystemColors {
2613 text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2614 background: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2615 accent: OptionColorU::Some(ColorU::new_rgb(51, 181, 229)),
2616 ..Default::default()
2617 },
2618 fonts: SystemFonts {
2619 ui_font: OptionString::Some("Roboto".into()),
2620 ui_font_size: OptionF32::Some(14.0),
2621 monospace_font: OptionString::Some("Droid Sans Mono".into()),
2622 ..Default::default()
2623 },
2624 metrics: SystemMetrics {
2625 corner_radius: OptionPixelValue::Some(PixelValue::px(2.0)),
2626 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2627 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2628 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2629 titlebar: TitlebarMetrics::android(),
2630 },
2631 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_DARK))),
2632 app_specific_stylesheet: None,
2633 run_destructor: true,
2634 icon_style: IconStyleOptions::default(),
2635 language: AzString::from_const_str("en-US"),
2636 os_version: OsVersion::ANDROID_ICE_CREAM_SANDWICH,
2637 prefers_reduced_motion: BoolCondition::False,
2638 prefers_high_contrast: BoolCondition::False,
2639 scroll_physics: ScrollPhysics::android(),
2640 linux: LinuxCustomization::default(),
2641 focus_visuals: FocusVisuals::default(),
2642 handedness: Handedness::default(),
2643 accessibility: AccessibilitySettings::default(),
2644 input: InputMetrics::default(),
2645 text_rendering: TextRenderingHints::default(),
2646 scrollbar_preferences: ScrollbarPreferences::default(),
2647 visual_hints: VisualHints::default(),
2648 animation: AnimationMetrics::default(),
2649 audio: AudioMetrics::default(),
2650 }
2651 }
2652
2653 #[must_use] pub fn ios_light() -> SystemStyle {
2655 SystemStyle {
2656 platform: Platform::Ios,
2657 theme: Theme::Light,
2658 colors: SystemColors {
2659 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2660 background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
2661 accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
2662 ..Default::default()
2663 },
2664 fonts: SystemFonts {
2665 ui_font: OptionString::Some(".SFUI-Display-Regular".into()),
2666 ui_font_size: OptionF32::Some(17.0),
2667 monospace_font: OptionString::Some("Menlo".into()),
2668 ..Default::default()
2669 },
2670 metrics: SystemMetrics {
2671 corner_radius: OptionPixelValue::Some(PixelValue::px(10.0)),
2672 border_width: OptionPixelValue::Some(PixelValue::px(0.5)),
2673 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(20.0)),
2674 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(12.0)),
2675 titlebar: TitlebarMetrics::ios(),
2676 },
2677 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_IOS_LIGHT))),
2678 app_specific_stylesheet: None,
2679 run_destructor: true,
2680 icon_style: IconStyleOptions::default(),
2681 language: AzString::from_const_str("en-US"),
2682 os_version: OsVersion::IOS_17,
2683 prefers_reduced_motion: BoolCondition::False,
2684 prefers_high_contrast: BoolCondition::False,
2685 scroll_physics: ScrollPhysics::ios(),
2686 linux: LinuxCustomization::default(),
2687 focus_visuals: FocusVisuals::default(),
2688 handedness: Handedness::default(),
2689 accessibility: AccessibilitySettings::default(),
2690 input: InputMetrics::default(),
2691 text_rendering: TextRenderingHints::default(),
2692 scrollbar_preferences: ScrollbarPreferences::default(),
2693 visual_hints: VisualHints::default(),
2694 animation: AnimationMetrics::default(),
2695 audio: AudioMetrics::default(),
2696 }
2697 }
2698}
2699
2700#[cfg(test)]
2701mod autotest_generated {
2702 use super::*;
2703 use crate::css::rule_priority;
2704
2705 const ALL_FONT_TYPES: [SystemFontType; 11] = [
2706 SystemFontType::Ui,
2707 SystemFontType::UiBold,
2708 SystemFontType::Monospace,
2709 SystemFontType::MonospaceBold,
2710 SystemFontType::MonospaceItalic,
2711 SystemFontType::Title,
2712 SystemFontType::TitleBold,
2713 SystemFontType::Menu,
2714 SystemFontType::Small,
2715 SystemFontType::Serif,
2716 SystemFontType::SerifBold,
2717 ];
2718
2719 fn all_platforms() -> Vec<Platform> {
2720 vec![
2721 Platform::Windows,
2722 Platform::MacOs,
2723 Platform::Linux(DesktopEnvironment::Gnome),
2724 Platform::Linux(DesktopEnvironment::Kde),
2725 Platform::Linux(DesktopEnvironment::Other(AzString::from_const_str("Hyprland"))),
2726 Platform::Android,
2727 Platform::Ios,
2728 Platform::Unknown,
2729 ]
2730 }
2731
2732 fn all_default_styles() -> Vec<(&'static str, SystemStyle)> {
2734 vec![
2735 ("windows_11_light", defaults::windows_11_light()),
2736 ("windows_11_dark", defaults::windows_11_dark()),
2737 ("windows_7_aero", defaults::windows_7_aero()),
2738 ("windows_xp_luna", defaults::windows_xp_luna()),
2739 ("macos_modern_light", defaults::macos_modern_light()),
2740 ("macos_modern_dark", defaults::macos_modern_dark()),
2741 ("macos_aqua", defaults::macos_aqua()),
2742 ("gnome_adwaita_light", defaults::gnome_adwaita_light()),
2743 ("gnome_adwaita_dark", defaults::gnome_adwaita_dark()),
2744 ("gtk2_clearlooks", defaults::gtk2_clearlooks()),
2745 ("kde_breeze_light", defaults::kde_breeze_light()),
2746 ("android_material_light", defaults::android_material_light()),
2747 ("android_holo_dark", defaults::android_holo_dark()),
2748 ("ios_light", defaults::ios_light()),
2749 ]
2750 }
2751
2752 #[test]
2755 fn from_css_str_valid_minimal() {
2756 assert_eq!(SystemFontType::from_css_str("system:ui"), Some(SystemFontType::Ui));
2757 assert_eq!(
2758 SystemFontType::from_css_str("system:monospace:italic"),
2759 Some(SystemFontType::MonospaceItalic)
2760 );
2761 }
2762
2763 #[test]
2764 fn from_css_str_empty_input_returns_none() {
2765 assert_eq!(SystemFontType::from_css_str(""), None);
2766 }
2767
2768 #[test]
2769 fn from_css_str_whitespace_only_returns_none() {
2770 for s in [" ", "\t\n", "\r\n\r\n", "\t \t \n"] {
2771 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2772 }
2773 }
2774
2775 #[test]
2776 fn from_css_str_prefix_only_is_none_and_does_not_panic_on_slice() {
2777 assert_eq!(SystemFontType::from_css_str("system:"), None);
2779 assert_eq!(SystemFontType::from_css_str(" system: "), None);
2780 assert_eq!(SystemFontType::from_css_str("system::"), None);
2781 }
2782
2783 #[test]
2784 fn from_css_str_garbage_returns_none() {
2785 for s in [
2786 ";;;",
2787 "{}{}",
2788 "\0\u{1}\u{2}\u{7f}",
2789 "system",
2790 "systemui",
2791 "system;ui",
2792 "system:ui:",
2793 ":system:ui",
2794 "font-family: system:ui;",
2795 "\\system:ui",
2796 "system:ui\0",
2797 "system:\u{0}ui",
2798 ] {
2799 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2800 }
2801 }
2802
2803 #[test]
2804 fn from_css_str_leading_trailing_junk() {
2805 assert_eq!(SystemFontType::from_css_str(" system:ui "), Some(SystemFontType::Ui));
2807 assert_eq!(
2808 SystemFontType::from_css_str("\t\nsystem:monospace\r\n"),
2809 Some(SystemFontType::Monospace)
2810 );
2811 assert_eq!(SystemFontType::from_css_str("system:ui;garbage"), None);
2813 assert_eq!(SystemFontType::from_css_str("garbage system:ui"), None);
2814 assert_eq!(SystemFontType::from_css_str("system:ui system:ui"), None);
2815 assert_eq!(SystemFontType::from_css_str("system: ui"), None);
2816 assert_eq!(SystemFontType::from_css_str("system:ui:bold:extra"), None);
2817 }
2818
2819 #[test]
2820 fn from_css_str_is_case_sensitive() {
2821 for s in ["SYSTEM:UI", "System:Ui", "system:UI", "System:ui", "sYsTeM:ui"] {
2824 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2825 }
2826 }
2827
2828 #[test]
2829 fn from_css_str_boundary_numbers() {
2830 for s in [
2831 "0",
2832 "-0",
2833 "9223372036854775807",
2834 "-9223372036854775808",
2835 "NaN",
2836 "inf",
2837 "-inf",
2838 "1e400",
2839 "system:0",
2840 "system:-1",
2841 "system:NaN",
2842 "system:inf",
2843 "system:9223372036854775807",
2844 ] {
2845 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2846 }
2847 }
2848
2849 #[test]
2850 fn from_css_str_unicode_does_not_panic() {
2851 for s in [
2852 "\u{1F600}",
2853 "system:\u{1F600}",
2854 "system:ui\u{0301}", "\u{1F600}system:ui",
2856 "systém:ui", "system:ui", "system:\u{202E}ui", "system:\u{FFFD}",
2860 "system:ui\u{200B}", ] {
2862 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2863 }
2864 }
2865
2866 #[test]
2867 fn from_css_str_extremely_long_input_does_not_hang() {
2868 let long = format!("system:{}", "u".repeat(1_000_000));
2869 assert_eq!(SystemFontType::from_css_str(&long), None);
2870
2871 let long_suffix = format!("system:ui{}", "x".repeat(1_000_000));
2873 assert_eq!(SystemFontType::from_css_str(&long_suffix), None);
2874
2875 let padded = format!("{}system:ui{}", " ".repeat(100_000), " ".repeat(100_000));
2877 assert_eq!(SystemFontType::from_css_str(&padded), Some(SystemFontType::Ui));
2878 }
2879
2880 #[test]
2881 fn from_css_str_deeply_nested_input_does_not_stack_overflow() {
2882 let nested = format!("system:{}{}", "(".repeat(10_000), ")".repeat(10_000));
2883 assert_eq!(SystemFontType::from_css_str(&nested), None);
2884
2885 let brackets = format!("system:{}", "[".repeat(10_000));
2886 assert_eq!(SystemFontType::from_css_str(&brackets), None);
2887 }
2888
2889 #[test]
2892 fn font_type_css_str_round_trips() {
2893 for ty in ALL_FONT_TYPES {
2894 let s = ty.as_css_str();
2895 assert_eq!(SystemFontType::from_css_str(s), Some(ty), "round-trip of {ty:?}");
2896 assert_eq!(
2898 SystemFontType::from_css_str(&format!(" {s}\t")),
2899 Some(ty),
2900 "padded round-trip of {ty:?}"
2901 );
2902 }
2903 }
2904
2905 #[test]
2906 fn font_type_css_str_is_well_formed_and_unique() {
2907 let mut seen: Vec<&'static str> = Vec::new();
2908 for ty in ALL_FONT_TYPES {
2909 let s = ty.as_css_str();
2910 assert!(s.starts_with("system:"), "{ty:?} -> {s:?}");
2911 assert!(s.len() > "system:".len(), "{ty:?} has an empty keyword");
2912 assert_eq!(s.trim(), s, "{ty:?} -> {s:?} has surrounding whitespace");
2913 assert!(s.is_ascii(), "{ty:?} -> {s:?} is not ASCII");
2914 seen.push(s);
2915 }
2916 seen.sort_unstable();
2917 assert!(
2918 seen.windows(2).all(|w| w[0] != w[1]),
2919 "as_css_str() is not injective: {seen:?}"
2920 );
2921 }
2922
2923 #[test]
2924 fn font_type_default_is_ui() {
2925 let d = SystemFontType::default();
2926 assert_eq!(d, SystemFontType::Ui);
2927 assert_eq!(d.as_css_str(), "system:ui");
2928 assert!(!d.is_bold());
2929 assert!(!d.is_italic());
2930 }
2931
2932 #[test]
2935 fn is_bold_matches_exactly_the_bold_variants() {
2936 assert!(SystemFontType::UiBold.is_bold());
2937 assert!(SystemFontType::MonospaceBold.is_bold());
2938 assert!(SystemFontType::TitleBold.is_bold());
2939 assert!(SystemFontType::SerifBold.is_bold());
2940
2941 assert!(!SystemFontType::Ui.is_bold());
2942 assert!(!SystemFontType::Monospace.is_bold());
2943 assert!(!SystemFontType::MonospaceItalic.is_bold());
2944 assert!(!SystemFontType::Title.is_bold());
2945 assert!(!SystemFontType::Menu.is_bold());
2946 assert!(!SystemFontType::Small.is_bold());
2947 assert!(!SystemFontType::Serif.is_bold());
2948 }
2949
2950 #[test]
2951 fn is_italic_matches_exactly_the_italic_variant() {
2952 assert!(SystemFontType::MonospaceItalic.is_italic());
2953 for ty in ALL_FONT_TYPES {
2954 if ty != SystemFontType::MonospaceItalic {
2955 assert!(!ty.is_italic(), "{ty:?} must not be italic");
2956 }
2957 }
2958 }
2959
2960 #[test]
2961 fn predicates_agree_with_the_css_keyword() {
2962 for ty in ALL_FONT_TYPES {
2963 let s = ty.as_css_str();
2964 assert_eq!(ty.is_bold(), s.ends_with(":bold"), "{ty:?} -> {s:?}");
2965 assert_eq!(ty.is_italic(), s.ends_with(":italic"), "{ty:?} -> {s:?}");
2966 assert!(!(ty.is_bold() && ty.is_italic()), "{ty:?} is bold *and* italic");
2968 }
2969 }
2970
2971 #[test]
2974 fn fallback_chains_are_non_empty_and_deduplicated() {
2975 for platform in all_platforms() {
2976 for ty in ALL_FONT_TYPES {
2977 let chain = ty.get_fallback_chain(&platform);
2978 assert!(!chain.is_empty(), "{ty:?} on {platform:?} has an empty chain");
2979 assert!(
2980 chain.iter().all(|f| !f.trim().is_empty()),
2981 "{ty:?} on {platform:?} has a blank family: {chain:?}"
2982 );
2983 let mut sorted = chain.clone();
2984 sorted.sort_unstable();
2985 assert!(
2986 sorted.windows(2).all(|w| w[0] != w[1]),
2987 "{ty:?} on {platform:?} lists a duplicate family: {chain:?}"
2988 );
2989 }
2990 }
2991 }
2992
2993 #[test]
2994 fn fallback_chain_is_deterministic() {
2995 for platform in all_platforms() {
2996 for ty in ALL_FONT_TYPES {
2997 assert_eq!(
2998 ty.get_fallback_chain(&platform),
2999 ty.get_fallback_chain(&platform),
3000 "{ty:?} on {platform:?} is not deterministic"
3001 );
3002 }
3003 }
3004 }
3005
3006 #[test]
3007 fn ios_shares_the_macos_fallback_chain() {
3008 for ty in ALL_FONT_TYPES {
3009 assert_eq!(
3010 ty.get_fallback_chain(&Platform::Ios),
3011 ty.get_fallback_chain(&Platform::MacOs),
3012 "{ty:?}"
3013 );
3014 }
3015 }
3016
3017 #[test]
3018 fn linux_fallback_chain_ignores_the_desktop_environment() {
3019 let gnome = Platform::Linux(DesktopEnvironment::Gnome);
3020 let kde = Platform::Linux(DesktopEnvironment::Kde);
3021 let other = Platform::Linux(DesktopEnvironment::Other(AzString::from_const_str("")));
3022 for ty in ALL_FONT_TYPES {
3023 let a = ty.get_fallback_chain(&gnome);
3024 assert_eq!(a, ty.get_fallback_chain(&kde), "{ty:?}");
3025 assert_eq!(a, ty.get_fallback_chain(&other), "{ty:?}");
3026 }
3027 }
3028
3029 #[test]
3030 fn unknown_platform_falls_back_to_generic_css_families() {
3031 for ty in ALL_FONT_TYPES {
3032 let chain = ty.get_fallback_chain(&Platform::Unknown);
3033 assert_eq!(chain.len(), 1, "{ty:?} -> {chain:?}");
3034 let expected = if ty.is_italic() || matches!(
3035 ty,
3036 SystemFontType::Monospace | SystemFontType::MonospaceBold
3037 ) {
3038 "monospace"
3039 } else if matches!(ty, SystemFontType::Serif | SystemFontType::SerifBold) {
3040 "serif"
3041 } else {
3042 "sans-serif"
3043 };
3044 assert_eq!(chain[0], expected, "{ty:?}");
3045 }
3046 }
3047
3048 #[test]
3049 fn monospace_variants_share_one_chain_per_platform() {
3050 for platform in all_platforms() {
3051 let base = SystemFontType::Monospace.get_fallback_chain(&platform);
3052 assert_eq!(
3053 SystemFontType::MonospaceBold.get_fallback_chain(&platform),
3054 base,
3055 "{platform:?}"
3056 );
3057 assert_eq!(
3058 SystemFontType::MonospaceItalic.get_fallback_chain(&platform),
3059 base,
3060 "{platform:?}"
3061 );
3062 }
3063 }
3064
3065 #[test]
3068 fn platform_current_is_deterministic_and_matches_target_os() {
3069 let a = Platform::current();
3070 assert_eq!(a, Platform::current());
3071
3072 #[cfg(target_os = "linux")]
3073 assert!(matches!(a, Platform::Linux(_)), "{a:?}");
3074 #[cfg(target_os = "windows")]
3075 assert_eq!(a, Platform::Windows);
3076 #[cfg(target_os = "macos")]
3077 assert_eq!(a, Platform::MacOs);
3078 #[cfg(target_os = "android")]
3079 assert_eq!(a, Platform::Android);
3080 #[cfg(target_os = "ios")]
3081 assert_eq!(a, Platform::Ios);
3082
3083 #[cfg(any(
3085 target_os = "linux",
3086 target_os = "windows",
3087 target_os = "macos",
3088 target_os = "android",
3089 target_os = "ios"
3090 ))]
3091 assert_ne!(a, Platform::Unknown);
3092
3093 assert_eq!(Platform::default(), Platform::Unknown);
3095 }
3096
3097 #[test]
3100 fn titlebar_metrics_have_sane_geometry() {
3101 let all = [
3106 ("windows", TitlebarMetrics::windows()),
3107 ("macos", TitlebarMetrics::macos()),
3108 ("linux_gnome", TitlebarMetrics::linux_gnome()),
3109 ("ios", TitlebarMetrics::ios()),
3110 ("android", TitlebarMetrics::android()),
3111 ];
3112 for (name, tm) in all {
3113 let height = tm
3114 .height
3115 .as_ref()
3116 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3117 .expect("titlebar height must be set");
3118 assert!(height.is_finite() && height > 0.0, "{name}: height {height}");
3119
3120 let button_area = tm
3121 .button_area_width
3122 .as_ref()
3123 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3124 .expect("button area width must be set");
3125 assert!(
3126 button_area.is_finite() && button_area >= 0.0,
3127 "{name}: button_area_width {button_area}"
3128 );
3129
3130 let padding = tm
3131 .padding_horizontal
3132 .as_ref()
3133 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3134 .expect("padding must be set");
3135 assert!(padding.is_finite() && padding >= 0.0, "{name}: padding {padding}");
3136
3137 let size = tm.title_font_size.into_option().expect("font size must be set");
3138 assert!(size.is_finite() && size > 0.0, "{name}: font size {size}");
3139
3140 let weight = tm.title_font_weight.into_option().expect("font weight must be set");
3141 assert!((100..=900).contains(&weight), "{name}: weight {weight}");
3142 }
3143 }
3144
3145 #[test]
3146 fn titlebar_metrics_match_their_platform_conventions() {
3147 let win = TitlebarMetrics::windows();
3148 assert_eq!(win.button_side, TitlebarButtonSide::Right);
3149 assert!(win.buttons.has_close && win.buttons.has_minimize && win.buttons.has_maximize);
3150 assert!(!win.buttons.has_fullscreen);
3151
3152 let mac = TitlebarMetrics::macos();
3154 assert_eq!(mac.button_side, TitlebarButtonSide::Left);
3155 assert!(mac.buttons.has_fullscreen);
3156 assert!(!mac.buttons.has_maximize);
3157
3158 assert_eq!(TitlebarMetrics::linux_gnome().button_side, TitlebarButtonSide::Right);
3159
3160 for (name, tm) in [("ios", TitlebarMetrics::ios()), ("android", TitlebarMetrics::android())] {
3162 let b = tm.buttons;
3163 assert!(
3164 !b.has_close && !b.has_minimize && !b.has_maximize && !b.has_fullscreen,
3165 "{name} must not expose window controls"
3166 );
3167 }
3168
3169 let ios = TitlebarMetrics::ios();
3171 assert!(ios.safe_area.top.is_some());
3172 assert!(ios.safe_area.bottom.is_some());
3173 assert_eq!(TitlebarMetrics::windows().safe_area, SafeAreaInsets::default());
3174 }
3175
3176 #[test]
3179 fn system_style_new_detect_and_default_for_platform_agree() {
3180 let a = SystemStyle::new();
3181 let b = SystemStyle::detect();
3182 let c = SystemStyle::default_for_platform();
3183 assert_eq!(a, b);
3184 assert_eq!(b, c);
3185 }
3186
3187 #[test]
3188 fn system_style_constructors_arm_the_ffi_drop_guard() {
3189 assert!(SystemStyle::default().run_destructor);
3192 assert!(SystemStyle::new().run_destructor);
3193 assert!(SystemStyle::detect().run_destructor);
3194 for (name, style) in all_default_styles() {
3195 assert!(style.run_destructor, "{name} does not own its heap pointers");
3196 assert!(style.clone().run_destructor, "clone of {name} lost the guard");
3197 }
3198 }
3199
3200 #[test]
3201 fn system_style_default_is_empty_but_valid() {
3202 let d = SystemStyle::default();
3203 assert_eq!(d.platform, Platform::Unknown);
3204 assert_eq!(d.theme, Theme::Light);
3205 assert!(d.app_specific_stylesheet.is_none());
3206 assert!(d.scrollbar.is_none());
3207 assert!(d.language.as_str().is_empty());
3208 assert!(d.colors.text.is_none());
3209 }
3210
3211 #[test]
3214 fn default_styles_are_fully_populated() {
3215 for (name, style) in all_default_styles() {
3216 assert!(style.colors.text.is_some(), "{name}: no text color");
3217 assert!(style.colors.background.is_some(), "{name}: no background color");
3218 assert!(style.colors.accent.is_some(), "{name}: no accent color");
3219 assert!(style.fonts.ui_font.is_some(), "{name}: no UI font");
3220 assert!(style.fonts.monospace_font.is_some(), "{name}: no monospace font");
3221 assert!(!style.language.as_str().is_empty(), "{name}: empty language");
3222 assert_ne!(style.platform, Platform::Unknown, "{name}: unknown platform");
3223
3224 let size = style.fonts.ui_font_size.into_option().expect("ui font size");
3225 assert!(size.is_finite() && size > 0.0, "{name}: ui font size {size}");
3226
3227 let radius = style
3228 .metrics
3229 .corner_radius
3230 .as_ref()
3231 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3232 .expect("corner radius");
3233 assert!(radius.is_finite() && radius >= 0.0, "{name}: corner radius {radius}");
3234 }
3235 }
3236
3237 #[test]
3238 fn default_styles_carry_a_fully_resolved_scrollbar() {
3239 for (name, style) in all_default_styles() {
3242 let sb = style.scrollbar.as_ref().unwrap_or_else(|| panic!("{name}: no scrollbar"));
3243 assert!(sb.width.is_some(), "{name}: scrollbar width lost");
3244 assert!(sb.thumb_color.is_some(), "{name}: thumb color lost");
3245 assert!(sb.track_color.is_some(), "{name}: track color lost");
3246 }
3247 }
3248
3249 #[test]
3250 fn light_and_dark_default_styles_differ() {
3251 assert_ne!(defaults::windows_11_light(), defaults::windows_11_dark());
3252 assert_ne!(defaults::macos_modern_light(), defaults::macos_modern_dark());
3253 assert_ne!(defaults::gnome_adwaita_light(), defaults::gnome_adwaita_dark());
3254 assert_ne!(defaults::android_material_light(), defaults::android_holo_dark());
3255
3256 assert_eq!(defaults::windows_11_dark().theme, Theme::Dark);
3257 assert_eq!(defaults::macos_modern_dark().theme, Theme::Dark);
3258 assert_eq!(defaults::gnome_adwaita_dark().theme, Theme::Dark);
3259 assert_eq!(defaults::android_holo_dark().theme, Theme::Dark);
3260
3261 assert_eq!(defaults::kde_breeze_light().platform, Platform::Linux(DesktopEnvironment::Kde));
3262 assert_eq!(defaults::ios_light().platform, Platform::Ios);
3263 }
3264
3265 #[test]
3266 fn default_style_constructors_are_deterministic() {
3267 for _ in 0..3 {
3268 assert_eq!(defaults::windows_xp_luna(), defaults::windows_xp_luna());
3269 assert_eq!(defaults::macos_aqua(), defaults::macos_aqua());
3270 assert_eq!(defaults::gtk2_clearlooks(), defaults::gtk2_clearlooks());
3271 assert_eq!(defaults::windows_7_aero(), defaults::windows_7_aero());
3272 }
3273 }
3274
3275 #[test]
3278 fn to_json_string_has_balanced_braces_for_every_default() {
3279 let mut styles = all_default_styles();
3280 styles.push(("default", SystemStyle::default()));
3281 for (name, style) in styles {
3282 let json = style.to_json_string();
3283 let s = json.as_str();
3284 assert!(s.starts_with('{'), "{name}: does not start with '{{'");
3285 assert!(s.ends_with('}'), "{name}: does not end with '}}'");
3286 let open = s.chars().filter(|c| *c == '{').count();
3287 let close = s.chars().filter(|c| *c == '}').count();
3288 assert_eq!(open, close, "{name}: unbalanced braces");
3289 for key in [
3290 "\"theme\"",
3291 "\"platform\"",
3292 "\"colors\"",
3293 "\"fonts\"",
3294 "\"titlebar\"",
3295 "\"input\"",
3296 "\"accessibility\"",
3297 "\"audio\"",
3298 ] {
3299 assert!(s.contains(key), "{name}: missing {key}");
3300 }
3301 }
3302 }
3303
3304 #[test]
3305 fn to_json_string_reports_known_values() {
3306 let json = defaults::windows_11_light().to_json_string();
3307 let s = json.as_str();
3308 assert!(s.contains("\"theme\": \"Light\""), "{s}");
3309 assert!(s.contains("\"platform\": \"Windows\""), "{s}");
3310 assert!(s.contains("\"language\": \"en-US\""), "{s}");
3311 assert!(s.contains("\"text\": \"#000000ff\""), "{s}");
3313 assert!(s.contains("\"height\": 32.0"), "{s}");
3315 assert!(s.contains("\"grid\": null"), "{s}");
3317 }
3318
3319 #[test]
3320 fn to_json_string_survives_nan_and_infinite_metrics() {
3321 let mut style = SystemStyle::default();
3322 style.accessibility.text_scale_factor = f32::NAN;
3323 style.animation.animation_duration_factor = f32::INFINITY;
3324 style.input.double_click_distance_px = f32::NEG_INFINITY;
3325 style.input.drag_threshold_px = f32::MAX;
3326 style.input.caret_width_px = f32::MIN_POSITIVE;
3327 style.input.double_click_time_ms = u32::MAX;
3328 style.input.caret_blink_rate_ms = u32::MAX;
3329 style.input.wheel_scroll_lines = u32::MAX;
3330 style.input.hover_time_ms = u32::MAX;
3331 style.text_rendering.font_smoothing_gamma = u32::MAX;
3332 style.linux.cursor_size = u32::MAX;
3333
3334 let json = style.to_json_string();
3336 let s = json.as_str();
3337 assert!(!s.is_empty());
3338 assert!(s.contains(&format!("\"cursor_size\": {}", u32::MAX)), "{s}");
3339 assert!(s.contains(&format!("\"double_click_time_ms\": {}", u32::MAX)), "{s}");
3340 }
3341
3342 #[test]
3343 fn to_json_string_survives_extreme_pixel_metrics() {
3344 let mut style = SystemStyle::default();
3345 style.metrics.titlebar.height = OptionPixelValue::Some(PixelValue::px(f32::NAN));
3346 style.metrics.titlebar.button_area_width =
3347 OptionPixelValue::Some(PixelValue::px(f32::INFINITY));
3348 style.metrics.titlebar.padding_horizontal =
3349 OptionPixelValue::Some(PixelValue::px(f32::NEG_INFINITY));
3350 style.metrics.titlebar.title_font_size = OptionF32::Some(f32::MAX);
3351 style.metrics.titlebar.title_font_weight = OptionU16::Some(u16::MAX);
3352
3353 let json = style.to_json_string();
3354 assert!(!json.as_str().is_empty());
3355
3356 let nan_px = PixelValue::px(f32::NAN).to_pixels_internal(0.0, 0.0, 0.0);
3359 assert_eq!(nan_px, 0.0);
3360 assert!(PixelValue::px(f32::INFINITY)
3361 .to_pixels_internal(0.0, 0.0, 0.0)
3362 .is_finite());
3363 assert!(PixelValue::px(f32::NEG_INFINITY)
3364 .to_pixels_internal(0.0, 0.0, 0.0)
3365 .is_finite());
3366 }
3367
3368 #[test]
3369 fn to_json_string_survives_hostile_strings() {
3370 let mut style = SystemStyle::default();
3373 style.language = AzString::from("\"\\\n\t\u{1F600}");
3374 style.fonts.ui_font = OptionString::Some(AzString::from("a\"b\\c"));
3375 style.linux.gtk_theme = OptionString::Some(AzString::from("\u{202E}evil"));
3376
3377 let json = style.to_json_string();
3378 let s = json.as_str();
3379 assert!(!s.is_empty());
3380 assert!(s.contains("\"language\":"), "{s}");
3381 }
3382
3383 #[test]
3384 fn to_json_string_is_deterministic() {
3385 let style = defaults::gnome_adwaita_dark();
3386 assert_eq!(style.to_json_string(), style.to_json_string());
3387 assert_ne!(
3388 defaults::gnome_adwaita_dark().to_json_string(),
3389 defaults::gnome_adwaita_light().to_json_string()
3390 );
3391 }
3392
3393 #[test]
3396 fn csd_stylesheet_rules_all_carry_system_priority() {
3397 let mut styles = all_default_styles();
3398 styles.push(("default", SystemStyle::default()));
3399 for (name, style) in styles {
3400 let css = style.create_csd_stylesheet();
3401 let rules = css.rules.as_slice();
3402 assert!(!rules.is_empty(), "{name}: produced no rules");
3403 for rule in rules {
3404 assert_eq!(
3405 rule.priority,
3406 rule_priority::SYSTEM,
3407 "{name}: rule escaped the SYSTEM layer"
3408 );
3409 }
3410 const _: () = assert!(rule_priority::SYSTEM < rule_priority::AUTHOR);
3412 }
3413 }
3414
3415 #[test]
3416 fn csd_stylesheet_uses_fallback_colors_when_the_system_reports_none() {
3417 let css = SystemStyle::default().create_csd_stylesheet();
3419 assert!(!css.rules.as_slice().is_empty());
3420 assert_ne!(css, Css::default());
3421 }
3422
3423 #[test]
3424 fn csd_stylesheet_is_platform_specific() {
3425 let mac = defaults::macos_modern_light().create_csd_stylesheet();
3426 let win = defaults::windows_11_light().create_csd_stylesheet();
3427 let lin = defaults::gnome_adwaita_light().create_csd_stylesheet();
3428 assert_ne!(mac, win);
3429 assert_ne!(win, lin);
3430 assert_ne!(mac, lin);
3431 assert!(mac.rules.as_slice().len() > win.rules.as_slice().len());
3433 }
3434
3435 #[test]
3436 fn csd_stylesheet_survives_extreme_corner_radius() {
3437 for radius in [
3438 PixelValue::px(f32::NAN),
3439 PixelValue::px(f32::INFINITY),
3440 PixelValue::px(f32::NEG_INFINITY),
3441 PixelValue::px(f32::MAX),
3442 PixelValue::px(-1.0),
3443 PixelValue::percent(f32::MAX),
3444 PixelValue::em(f32::MIN),
3445 ] {
3446 let mut style = defaults::windows_11_light();
3447 style.metrics.corner_radius = OptionPixelValue::Some(radius);
3448 let css = style.create_csd_stylesheet();
3449 assert!(
3450 !css.rules.as_slice().is_empty(),
3451 "radius {radius:?} produced no rules"
3452 );
3453 for rule in css.rules.as_slice() {
3454 assert_eq!(rule.priority, rule_priority::SYSTEM);
3455 }
3456 }
3457 }
3458
3459 #[test]
3460 fn csd_stylesheet_is_deterministic() {
3461 let style = defaults::kde_breeze_light();
3462 assert_eq!(style.create_csd_stylesheet(), style.create_csd_stylesheet());
3463 }
3464
3465 #[test]
3473 fn ricing_mode_is_deterministic_and_total() {
3474 let mode = ricing_mode();
3475 assert_eq!(mode, ricing_mode(), "ricing_mode() is not deterministic");
3476 assert!(
3477 matches!(mode, RicingMode::Off | RicingMode::Default | RicingMode::Force),
3478 "{mode:?}"
3479 );
3480 assert_eq!(RicingMode::default(), RicingMode::Default);
3481 }
3482
3483 #[test]
3484 fn ricing_enabled_is_the_inverse_of_off() {
3485 assert_eq!(ricing_enabled(), ricing_mode() != RicingMode::Off);
3486 assert_eq!(ricing_enabled(), ricing_enabled());
3487 }
3488
3489 #[test]
3490 fn detect_linux_desktop_env_is_deterministic() {
3491 let a = detect_linux_desktop_env();
3492 assert_eq!(a, detect_linux_desktop_env());
3493
3494 let blank_env = |k: &str| std::env::var(k).map(|v| v.is_empty()).unwrap_or(false);
3498 if !blank_env("XDG_CURRENT_DESKTOP") && !blank_env("DESKTOP_SESSION") {
3499 if let DesktopEnvironment::Other(ref name) = a {
3500 assert!(!name.as_str().is_empty(), "empty desktop-environment label");
3501 }
3502 }
3503 }
3504
3505 #[test]
3506 fn detect_system_language_is_a_normalized_tag() {
3507 let lang = detect_system_language();
3508 let s = lang.as_str();
3509 assert!(!s.is_empty(), "language tag must never be empty");
3510 assert!(!s.contains('.'), "{s:?} still carries an encoding suffix");
3513 assert!(!s.contains(':'), "{s:?} still carries a locale list");
3514 assert!(!s.contains('_'), "{s:?} is not BCP 47 (underscore)");
3515 assert_eq!(lang, detect_system_language(), "not deterministic");
3516 }
3517}
3518
3519#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
3525#[repr(C)]
3526pub enum Handedness {
3527 #[default]
3529 RightHanded,
3530 LeftHanded,
3532}
3533
3534