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 input: InputMetrics,
178 pub text_rendering: TextRenderingHints,
180 pub scrollbar_preferences: ScrollbarPreferences,
182 pub visual_hints: VisualHints,
184 pub animation: AnimationMetrics,
186 pub colors: SystemColors,
187 pub icon_style: IconStyleOptions,
189 pub audio: AudioMetrics,
191 pub run_destructor: bool,
203}
204
205impl Default for SystemStyle {
206 fn default() -> Self {
207 Self {
208 fonts: SystemFonts::default(),
209 metrics: SystemMetrics::default(),
210 linux: LinuxCustomization::default(),
211 platform: Platform::default(),
212 focus_visuals: FocusVisuals::default(),
213 language: AzString::default(),
214 app_specific_stylesheet: None,
215 scrollbar: None,
216 scroll_physics: ScrollPhysics::default(),
217 theme: Theme::default(),
218 os_version: OsVersion::default(),
219 prefers_reduced_motion: BoolCondition::default(),
220 prefers_high_contrast: BoolCondition::default(),
221 accessibility: AccessibilitySettings::default(),
222 input: InputMetrics::default(),
223 text_rendering: TextRenderingHints::default(),
224 scrollbar_preferences: ScrollbarPreferences::default(),
225 visual_hints: VisualHints::default(),
226 animation: AnimationMetrics::default(),
227 colors: SystemColors::default(),
228 icon_style: IconStyleOptions::default(),
229 audio: AudioMetrics::default(),
230 run_destructor: true,
231 }
232 }
233}
234
235impl Drop for SystemStyle {
236 fn drop(&mut self) {
237 if self.run_destructor {
247 self.run_destructor = false;
248 } else {
249 core::mem::forget(self.app_specific_stylesheet.take());
250 core::mem::forget(self.scrollbar.take());
251 }
252 }
253}
254
255#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
260#[repr(C)]
261pub struct IconStyleOptions {
262 pub prefer_grayscale: bool,
265 pub tint_color: OptionColorU,
268 pub inherit_text_color: bool,
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
284#[repr(C)]
285#[derive(Default)]
286pub enum SystemFontType {
287 #[default]
289 Ui,
290 UiBold,
292 Monospace,
294 MonospaceBold,
296 MonospaceItalic,
298 Title,
300 TitleBold,
302 Menu,
304 Small,
306 Serif,
308 SerifBold,
310}
311
312
313impl SystemFontType {
314 #[must_use] pub fn from_css_str(s: &str) -> Option<Self> {
324 let s = s.trim();
325 if !s.starts_with("system:") {
326 return None;
327 }
328 let rest = &s[7..]; match rest {
330 "ui" => Some(Self::Ui),
331 "ui:bold" => Some(Self::UiBold),
332 "monospace" => Some(Self::Monospace),
333 "monospace:bold" => Some(Self::MonospaceBold),
334 "monospace:italic" => Some(Self::MonospaceItalic),
335 "title" => Some(Self::Title),
336 "title:bold" => Some(Self::TitleBold),
337 "menu" => Some(Self::Menu),
338 "small" => Some(Self::Small),
339 "serif" => Some(Self::Serif),
340 "serif:bold" => Some(Self::SerifBold),
341 _ => None,
342 }
343 }
344
345 #[must_use] pub const fn as_css_str(&self) -> &'static str {
347 match self {
348 Self::Ui => "system:ui",
349 Self::UiBold => "system:ui:bold",
350 Self::Monospace => "system:monospace",
351 Self::MonospaceBold => "system:monospace:bold",
352 Self::MonospaceItalic => "system:monospace:italic",
353 Self::Title => "system:title",
354 Self::TitleBold => "system:title:bold",
355 Self::Menu => "system:menu",
356 Self::Small => "system:small",
357 Self::Serif => "system:serif",
358 Self::SerifBold => "system:serif:bold",
359 }
360 }
361
362 #[must_use] pub const fn is_bold(&self) -> bool {
365 matches!(
366 self,
367 Self::UiBold
368 | Self::MonospaceBold
369 | Self::TitleBold
370 | Self::SerifBold
371 )
372 }
373
374 #[must_use] pub const fn is_italic(&self) -> bool {
376 matches!(self, Self::MonospaceItalic)
377 }
378}
379
380#[derive(Debug, Default, Clone, Copy, PartialEq)]
388#[repr(C)]
389pub struct AccessibilitySettings {
390 pub text_scale_factor: f32,
392 pub prefers_bold_text: bool,
397 pub prefers_larger_text: bool,
402 pub prefers_high_contrast: bool,
407 pub prefers_reduced_motion: bool,
412 pub prefers_reduced_transparency: bool,
417 pub screen_reader_active: bool,
419 pub differentiate_without_color: bool,
422}
423
424#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
433#[repr(C)]
434pub struct SystemColors {
435 pub text: OptionColorU,
438 pub secondary_text: OptionColorU,
440 pub tertiary_text: OptionColorU,
442 pub background: OptionColorU,
444
445 pub accent: OptionColorU,
448 pub accent_text: OptionColorU,
450
451 pub button_face: OptionColorU,
454 pub button_text: OptionColorU,
456 pub disabled_text: OptionColorU,
458
459 pub window_background: OptionColorU,
462 pub under_page_background: OptionColorU,
464
465 pub selection_background: OptionColorU,
468 pub selection_text: OptionColorU,
470 pub selection_background_inactive: OptionColorU,
473 pub selection_text_inactive: OptionColorU,
475
476 pub link: OptionColorU,
479 pub separator: OptionColorU,
481 pub grid: OptionColorU,
483 pub find_highlight: OptionColorU,
485
486 pub sidebar_background: OptionColorU,
489 pub sidebar_selection: OptionColorU,
491}
492
493#[derive(Debug, Default, Clone, PartialEq, Eq)]
499#[repr(C)]
500pub struct SystemFonts {
501 pub ui_font: OptionString,
506 pub ui_font_size: OptionF32,
508 pub monospace_font: OptionString,
513 pub monospace_font_size: OptionF32,
515 pub ui_font_bold: OptionString,
517 pub title_font: OptionString,
519 pub title_font_size: OptionF32,
521 pub menu_font: OptionString,
523 pub menu_font_size: OptionF32,
525 pub small_font: OptionString,
527 pub small_font_size: OptionF32,
529}
530
531#[derive(Debug, Default, Clone, PartialEq, Eq)]
533#[repr(C)]
534pub struct SystemMetrics {
535 pub corner_radius: OptionPixelValue,
537 pub border_width: OptionPixelValue,
539 pub button_padding_horizontal: OptionPixelValue,
541 pub button_padding_vertical: OptionPixelValue,
543 pub titlebar: TitlebarMetrics,
545}
546
547#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
549#[repr(C)]
550pub enum TitlebarButtonSide {
551 Left,
553 #[default]
555 Right,
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
560#[repr(C)]
561pub struct TitlebarButtons {
562 pub has_close: bool,
564 pub has_minimize: bool,
566 pub has_maximize: bool,
568 pub has_fullscreen: bool,
570}
571
572impl Default for TitlebarButtons {
573 fn default() -> Self {
574 Self {
575 has_close: true,
576 has_minimize: true,
577 has_maximize: true,
578 has_fullscreen: false,
579 }
580 }
581}
582
583#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
589#[repr(C)]
590pub struct SafeAreaInsets {
591 pub top: OptionPixelValue,
593 pub bottom: OptionPixelValue,
595 pub left: OptionPixelValue,
597 pub right: OptionPixelValue,
599}
600
601#[derive(Debug, Clone, PartialEq, Eq)]
606#[repr(C)]
607pub struct TitlebarMetrics {
608 pub button_side: TitlebarButtonSide,
610 pub buttons: TitlebarButtons,
612 pub height: OptionPixelValue,
614 pub button_area_width: OptionPixelValue,
617 pub padding_horizontal: OptionPixelValue,
619 pub safe_area: SafeAreaInsets,
621 pub title_font: OptionString,
623 pub title_font_size: OptionF32,
625 pub title_font_weight: OptionU16,
627}
628
629impl Default for TitlebarMetrics {
630 fn default() -> Self {
631 Self {
632 button_side: TitlebarButtonSide::Right,
633 buttons: TitlebarButtons::default(),
634 height: OptionPixelValue::None,
639 button_area_width: OptionPixelValue::None,
640 padding_horizontal: OptionPixelValue::None,
641 safe_area: SafeAreaInsets::default(),
642 title_font: OptionString::None,
643 title_font_size: OptionF32::Some(13.0),
644 title_font_weight: OptionU16::Some(600), }
646 }
647}
648
649impl TitlebarMetrics {
650 #[must_use] pub fn windows() -> Self {
652 Self {
653 button_side: TitlebarButtonSide::Right,
654 buttons: TitlebarButtons {
655 has_close: true,
656 has_minimize: true,
657 has_maximize: true,
658 has_fullscreen: false,
659 },
660 height: OptionPixelValue::Some(PixelValue::px(32.0)),
661 button_area_width: OptionPixelValue::Some(PixelValue::px(138.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
663 safe_area: SafeAreaInsets::default(),
664 title_font: OptionString::Some("Segoe UI Variable Text".into()),
665 title_font_size: OptionF32::Some(12.0),
666 title_font_weight: OptionU16::Some(400), }
668 }
669
670 #[must_use] pub fn macos() -> Self {
672 Self {
673 button_side: TitlebarButtonSide::Left,
674 buttons: TitlebarButtons {
675 has_close: true,
676 has_minimize: true,
677 has_maximize: false, has_fullscreen: true,
679 },
680 height: OptionPixelValue::Some(PixelValue::px(28.0)),
681 button_area_width: OptionPixelValue::Some(PixelValue::px(78.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
683 safe_area: SafeAreaInsets::default(),
684 title_font: OptionString::Some(".SF NS".into()),
685 title_font_size: OptionF32::Some(13.0),
686 title_font_weight: OptionU16::Some(600), }
688 }
689
690 #[must_use] pub fn linux_gnome() -> Self {
692 Self {
693 button_side: TitlebarButtonSide::Right, buttons: TitlebarButtons {
695 has_close: true,
696 has_minimize: true,
697 has_maximize: true,
698 has_fullscreen: false,
699 },
700 height: OptionPixelValue::Some(PixelValue::px(35.0)),
701 button_area_width: OptionPixelValue::Some(PixelValue::px(100.0)),
702 padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
703 safe_area: SafeAreaInsets::default(),
704 title_font: OptionString::Some("Cantarell".into()),
705 title_font_size: OptionF32::Some(11.0),
706 title_font_weight: OptionU16::Some(700), }
708 }
709
710 #[must_use] pub fn ios() -> Self {
712 Self {
713 button_side: TitlebarButtonSide::Left,
714 buttons: TitlebarButtons {
715 has_close: false, has_minimize: false,
717 has_maximize: false,
718 has_fullscreen: false,
719 },
720 height: OptionPixelValue::Some(PixelValue::px(44.0)),
721 button_area_width: OptionPixelValue::Some(PixelValue::px(0.0)),
722 padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
723 safe_area: SafeAreaInsets {
724 top: OptionPixelValue::Some(PixelValue::px(47.0)),
726 bottom: OptionPixelValue::Some(PixelValue::px(34.0)),
727 left: OptionPixelValue::None,
728 right: OptionPixelValue::None,
729 },
730 title_font: OptionString::Some(".SFUI-Semibold".into()),
731 title_font_size: OptionF32::Some(17.0),
732 title_font_weight: OptionU16::Some(600),
733 }
734 }
735
736 #[must_use] pub fn android() -> Self {
738 Self {
739 button_side: TitlebarButtonSide::Left, buttons: TitlebarButtons {
741 has_close: false,
742 has_minimize: false,
743 has_maximize: false,
744 has_fullscreen: false,
745 },
746 height: OptionPixelValue::Some(PixelValue::px(56.0)),
747 button_area_width: OptionPixelValue::Some(PixelValue::px(48.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
749 safe_area: SafeAreaInsets::default(),
750 title_font: OptionString::Some("Roboto Medium".into()),
751 title_font_size: OptionF32::Some(20.0),
752 title_font_weight: OptionU16::Some(500),
753 }
754 }
755}
756
757#[derive(Debug, Clone, Copy, PartialEq)]
770#[repr(C)]
771pub struct InputMetrics {
772 pub double_click_time_ms: u32,
774 pub double_click_distance_px: f32,
776 pub drag_threshold_px: f32,
778 pub caret_blink_rate_ms: u32,
780 pub caret_width_px: f32,
782 pub wheel_scroll_lines: u32,
784 pub hover_time_ms: u32,
787}
788
789impl Default for InputMetrics {
790 fn default() -> Self {
791 Self {
792 double_click_time_ms: 500,
793 double_click_distance_px: 4.0,
794 drag_threshold_px: 5.0,
795 caret_blink_rate_ms: 530,
796 caret_width_px: 1.0,
797 wheel_scroll_lines: 3,
798 hover_time_ms: 400,
799 }
800 }
801}
802
803#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
807#[repr(C)]
808pub enum SubpixelType {
809 #[default]
811 None,
812 Rgb,
814 Bgr,
816 VRgb,
818 VBgr,
820}
821
822#[derive(Debug, Clone, Copy, PartialEq, Eq)]
827#[repr(C)]
828pub struct TextRenderingHints {
829 pub subpixel_type: SubpixelType,
831 pub font_smoothing_gamma: u32,
833 pub font_smoothing_enabled: bool,
835 pub increased_contrast: bool,
837}
838
839impl Default for TextRenderingHints {
840 fn default() -> Self {
841 Self {
842 subpixel_type: SubpixelType::None,
843 font_smoothing_gamma: 1000,
844 font_smoothing_enabled: true,
845 increased_contrast: false,
846 }
847 }
848}
849
850#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
857#[repr(C)]
858pub struct FocusVisuals {
859 pub focus_ring_color: OptionColorU,
862 pub focus_border_width: OptionPixelValue,
865 pub focus_border_height: OptionPixelValue,
867}
868
869#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
873#[repr(C)]
874pub enum ScrollbarVisibility {
875 Always,
877 #[default]
879 WhenScrolling,
880 Automatic,
882}
883
884#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
886#[repr(C)]
887pub enum ScrollbarTrackClick {
888 JumpToPosition,
890 #[default]
892 PageUpDown,
893}
894
895#[derive(Debug, Clone, Copy, PartialEq, Eq)]
900#[repr(C)]
901pub struct ScrollbarPreferences {
902 pub visibility: ScrollbarVisibility,
905 pub track_click: ScrollbarTrackClick,
907}
908
909impl Default for ScrollbarPreferences {
910 fn default() -> Self {
911 Self {
912 visibility: ScrollbarVisibility::WhenScrolling,
913 track_click: ScrollbarTrackClick::PageUpDown,
914 }
915 }
916}
917
918#[derive(Debug, Default, Clone, PartialEq, Eq)]
925#[repr(C)]
926pub struct LinuxCustomization {
927 pub gtk_theme: OptionString,
929 pub icon_theme: OptionString,
931 pub cursor_theme: OptionString,
933 pub cursor_size: u32,
935 pub titlebar_button_layout: OptionString,
938}
939
940#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
944#[repr(C)]
945pub enum ToolbarStyle {
946 #[default]
948 IconsOnly,
949 TextOnly,
951 TextBesideIcon,
953 TextBelowIcon,
955}
956
957#[derive(Debug, Clone, Copy, PartialEq, Eq)]
962#[repr(C)]
963pub struct VisualHints {
964 pub toolbar_style: ToolbarStyle,
967 pub show_button_images: bool,
970 pub show_menu_images: bool,
973 pub show_tooltips: bool,
975 pub flash_on_alert: bool,
977}
978
979impl Default for VisualHints {
980 fn default() -> Self {
981 Self {
982 toolbar_style: ToolbarStyle::IconsOnly,
983 show_button_images: false,
984 show_menu_images: true,
985 show_tooltips: true,
986 flash_on_alert: true,
987 }
988 }
989}
990
991#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
995#[repr(C)]
996pub enum FocusBehavior {
997 #[default]
999 AlwaysVisible,
1000 KeyboardOnly,
1003}
1004
1005#[derive(Debug, Clone, Copy, PartialEq)]
1017#[repr(C)]
1018pub struct AnimationMetrics {
1019 pub animations_enabled: bool,
1021 pub animation_duration_factor: f32,
1024 pub focus_indicator_behavior: FocusBehavior,
1026}
1027
1028impl Default for AnimationMetrics {
1029 fn default() -> Self {
1030 Self {
1031 animations_enabled: true,
1032 animation_duration_factor: 1.0,
1033 focus_indicator_behavior: FocusBehavior::AlwaysVisible,
1034 }
1035 }
1036}
1037
1038#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1051#[repr(C)]
1052pub struct AudioMetrics {
1053 pub event_sounds_enabled: bool,
1055 pub input_feedback_sounds_enabled: bool,
1057}
1058
1059impl Default for AudioMetrics {
1060 fn default() -> Self {
1061 Self {
1062 event_sounds_enabled: true,
1063 input_feedback_sounds_enabled: false,
1064 }
1065 }
1066}
1067
1068pub mod apple_fonts {
1074 pub const SYSTEM_FONT: &str = "System Font";
1077
1078 pub const SF_NS_ROUNDED: &str = "SF NS Rounded";
1080
1081 pub const SF_COMPACT: &str = "SF Compact";
1084
1085 pub const SF_MONO: &str = "SF NS Mono Light";
1088
1089 pub const NEW_YORK: &str = "New York";
1092
1093 pub const SF_ARABIC: &str = "SF Arabic";
1095
1096 pub const SF_ARMENIAN: &str = "SF Armenian";
1098
1099 pub const SF_GEORGIAN: &str = "SF Georgian";
1101
1102 pub const SF_HEBREW: &str = "SF Hebrew";
1104
1105 pub const MENLO: &str = "Menlo";
1107 pub const MENLO_REGULAR: &str = "Menlo Regular";
1108 pub const MENLO_BOLD: &str = "Menlo Bold";
1109 pub const MONACO: &str = "Monaco";
1110 pub const LUCIDA_GRANDE: &str = "Lucida Grande";
1111 pub const LUCIDA_GRANDE_BOLD: &str = "Lucida Grande Bold";
1112 pub const HELVETICA_NEUE: &str = "Helvetica Neue";
1113 pub const HELVETICA_NEUE_BOLD: &str = "Helvetica Neue Bold";
1114}
1115
1116pub mod windows_fonts {
1118 pub const SEGOE_UI_VARIABLE: &str = "Segoe UI Variable";
1120 pub const SEGOE_UI_VARIABLE_TEXT: &str = "Segoe UI Variable Text";
1121 pub const SEGOE_UI_VARIABLE_DISPLAY: &str = "Segoe UI Variable Display";
1122
1123 pub const SEGOE_UI: &str = "Segoe UI";
1125 pub const CONSOLAS: &str = "Consolas";
1126 pub const CASCADIA_CODE: &str = "Cascadia Code";
1127 pub const CASCADIA_MONO: &str = "Cascadia Mono";
1128
1129 pub const TAHOMA: &str = "Tahoma";
1131 pub const MS_SANS_SERIF: &str = "MS Sans Serif";
1132 pub const LUCIDA_CONSOLE: &str = "Lucida Console";
1133 pub const COURIER_NEW: &str = "Courier New";
1134}
1135
1136pub mod linux_fonts {
1138 pub const CANTARELL: &str = "Cantarell";
1140 pub const ADWAITA: &str = "Adwaita";
1141
1142 pub const UBUNTU: &str = "Ubuntu";
1144 pub const UBUNTU_MONO: &str = "Ubuntu Mono";
1145
1146 pub const DEJAVU_SANS: &str = "DejaVu Sans";
1148 pub const DEJAVU_SANS_MONO: &str = "DejaVu Sans Mono";
1149 pub const DEJAVU_SERIF: &str = "DejaVu Serif";
1150
1151 pub const LIBERATION_SANS: &str = "Liberation Sans";
1153 pub const LIBERATION_MONO: &str = "Liberation Mono";
1154 pub const LIBERATION_SERIF: &str = "Liberation Serif";
1155
1156 pub const NOTO_SANS: &str = "Noto Sans";
1158 pub const NOTO_MONO: &str = "Noto Sans Mono";
1159 pub const NOTO_SERIF: &str = "Noto Serif";
1160
1161 pub const HACK: &str = "Hack";
1163
1164 pub const MONOSPACE: &str = "Monospace";
1166 pub const SANS_SERIF: &str = "Sans";
1167 pub const SERIF: &str = "Serif";
1168}
1169
1170impl SystemFontType {
1171 #[must_use] pub fn get_fallback_chain(&self, platform: &Platform) -> Vec<&'static str> {
1176 match platform {
1177 Platform::MacOs | Platform::Ios => self.macos_fallback_chain(),
1178 Platform::Windows => self.windows_fallback_chain(),
1179 Platform::Linux(_) => self.linux_fallback_chain(),
1180 Platform::Android => self.android_fallback_chain(),
1181 Platform::Unknown => self.generic_fallback_chain(),
1182 }
1183 }
1184
1185 fn macos_fallback_chain(self) -> Vec<&'static str> {
1186 match self {
1187 Self::Ui => vec![
1189 apple_fonts::SYSTEM_FONT,
1190 apple_fonts::HELVETICA_NEUE,
1191 apple_fonts::LUCIDA_GRANDE,
1192 ],
1193 Self::UiBold | Self::TitleBold => vec![
1195 apple_fonts::HELVETICA_NEUE,
1196 apple_fonts::LUCIDA_GRANDE,
1197 ],
1198 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
1200 apple_fonts::MENLO,
1201 apple_fonts::MONACO,
1202 ],
1203 Self::Title | Self::Menu | Self::Small => vec![
1205 apple_fonts::SYSTEM_FONT,
1206 apple_fonts::HELVETICA_NEUE,
1207 ],
1208 Self::Serif => vec![
1210 apple_fonts::NEW_YORK,
1211 "Georgia",
1212 "Times New Roman",
1213 ],
1214 Self::SerifBold => vec![
1215 "Georgia", "Times New Roman",
1217 ],
1218 }
1219 }
1220
1221 fn windows_fallback_chain(self) -> Vec<&'static str> {
1222 match self {
1223 Self::Ui | Self::UiBold => vec![
1224 windows_fonts::SEGOE_UI_VARIABLE_TEXT,
1225 windows_fonts::SEGOE_UI,
1226 windows_fonts::TAHOMA,
1227 ],
1228 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
1229 windows_fonts::CASCADIA_MONO,
1230 windows_fonts::CASCADIA_CODE,
1231 windows_fonts::CONSOLAS,
1232 windows_fonts::LUCIDA_CONSOLE,
1233 windows_fonts::COURIER_NEW,
1234 ],
1235 Self::Title | Self::TitleBold => vec![
1236 windows_fonts::SEGOE_UI_VARIABLE_DISPLAY,
1237 windows_fonts::SEGOE_UI,
1238 ],
1239 Self::Menu => vec![
1240 windows_fonts::SEGOE_UI,
1241 windows_fonts::TAHOMA,
1242 ],
1243 Self::Small => vec![
1244 windows_fonts::SEGOE_UI,
1245 ],
1246 Self::Serif | Self::SerifBold => vec![
1247 "Cambria",
1248 "Georgia",
1249 "Times New Roman",
1250 ],
1251 }
1252 }
1253
1254 fn linux_fallback_chain(self) -> Vec<&'static str> {
1255 match self {
1256 Self::Ui | Self::UiBold => vec![
1257 linux_fonts::CANTARELL,
1258 linux_fonts::UBUNTU,
1259 linux_fonts::NOTO_SANS,
1260 linux_fonts::DEJAVU_SANS,
1261 linux_fonts::LIBERATION_SANS,
1262 linux_fonts::SANS_SERIF,
1263 ],
1264 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
1265 linux_fonts::UBUNTU_MONO,
1266 linux_fonts::HACK,
1267 linux_fonts::NOTO_MONO,
1268 linux_fonts::DEJAVU_SANS_MONO,
1269 linux_fonts::LIBERATION_MONO,
1270 linux_fonts::MONOSPACE,
1271 ],
1272 Self::Title | Self::TitleBold | Self::Menu | Self::Small => vec![
1273 linux_fonts::CANTARELL,
1274 linux_fonts::UBUNTU,
1275 linux_fonts::NOTO_SANS,
1276 ],
1277 Self::Serif | Self::SerifBold => vec![
1278 linux_fonts::NOTO_SERIF,
1279 linux_fonts::DEJAVU_SERIF,
1280 linux_fonts::LIBERATION_SERIF,
1281 linux_fonts::SERIF,
1282 ],
1283 }
1284 }
1285
1286 fn android_fallback_chain(self) -> Vec<&'static str> {
1287 match self {
1288 Self::Ui | Self::UiBold | Self::Title | Self::TitleBold => vec!["Roboto", "Noto Sans"],
1289 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
1290 vec!["Roboto Mono", "Droid Sans Mono", "monospace"]
1291 }
1292 Self::Menu | Self::Small => vec!["Roboto"],
1293 Self::Serif | Self::SerifBold => vec!["Noto Serif", "Droid Serif", "serif"],
1294 }
1295 }
1296
1297 fn generic_fallback_chain(self) -> Vec<&'static str> {
1298 match self {
1299 Self::Ui | Self::UiBold | Self::Title | Self::TitleBold | Self::Menu | Self::Small => {
1300 vec!["sans-serif"]
1301 }
1302 Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
1303 vec!["monospace"]
1304 }
1305 Self::Serif | Self::SerifBold => vec!["serif"],
1306 }
1307 }
1308}
1309
1310impl SystemStyle {
1311
1312 #[allow(clippy::too_many_lines)] #[must_use] pub fn to_json_string(&self) -> AzString {
1318 use alloc::format;
1319
1320 fn opt_color(c: OptionColorU) -> alloc::string::String {
1321 c.as_ref().map_or_else(
1322 || "null".into(),
1323 |c| format!("\"#{:02x}{:02x}{:02x}{:02x}\"", c.r, c.g, c.b, c.a),
1324 )
1325 }
1326 fn opt_str(s: &OptionString) -> alloc::string::String {
1327 s.as_ref()
1328 .map_or_else(|| "null".into(), |s| format!("\"{}\"", s.as_str()))
1329 }
1330 fn opt_f32(v: OptionF32) -> alloc::string::String {
1331 v.into_option()
1332 .map_or_else(|| "null".into(), |v| format!("{v:.2}"))
1333 }
1334 fn opt_u16(v: OptionU16) -> alloc::string::String {
1335 v.into_option()
1336 .map_or_else(|| "null".into(), |v| format!("{v}"))
1337 }
1338 fn opt_px(v: &OptionPixelValue) -> alloc::string::String {
1339 v.as_ref().map_or_else(
1340 || "null".into(),
1341 |v| format!("{:.1}", v.to_pixels_internal(0.0, 0.0, 0.0)),
1342 )
1343 }
1344
1345 let tm = &self.metrics.titlebar;
1346 let inp = &self.input;
1347 let tr = &self.text_rendering;
1348 let acc = &self.accessibility;
1349 let sp = &self.scrollbar_preferences;
1350 let lnx = &self.linux;
1351 let vh = &self.visual_hints;
1352 let anim = &self.animation;
1353 let audio = &self.audio;
1354
1355 let json = format!(
1356r#"{{
1357 "theme": "{:?}",
1358 "platform": "{:?}",
1359 "os_version": "{:?}:{}",
1360 "language": "{}",
1361 "prefers_reduced_motion": {:?},
1362 "prefers_high_contrast": {:?},
1363 "colors": {{
1364 "text": {},
1365 "secondary_text": {},
1366 "tertiary_text": {},
1367 "background": {},
1368 "accent": {},
1369 "accent_text": {},
1370 "button_face": {},
1371 "button_text": {},
1372 "disabled_text": {},
1373 "window_background": {},
1374 "under_page_background": {},
1375 "selection_background": {},
1376 "selection_text": {},
1377 "selection_background_inactive": {},
1378 "selection_text_inactive": {},
1379 "link": {},
1380 "separator": {},
1381 "grid": {},
1382 "find_highlight": {},
1383 "sidebar_background": {},
1384 "sidebar_selection": {}
1385 }},
1386 "fonts": {{
1387 "ui_font": {},
1388 "ui_font_size": {},
1389 "monospace_font": {},
1390 "title_font": {},
1391 "menu_font": {},
1392 "small_font": {}
1393 }},
1394 "titlebar": {{
1395 "button_side": "{:?}",
1396 "height": {},
1397 "button_area_width": {},
1398 "padding_horizontal": {},
1399 "title_font": {},
1400 "title_font_size": {},
1401 "title_font_weight": {},
1402 "has_close": {},
1403 "has_minimize": {},
1404 "has_maximize": {},
1405 "has_fullscreen": {}
1406 }},
1407 "input": {{
1408 "double_click_time_ms": {},
1409 "double_click_distance_px": {:.1},
1410 "drag_threshold_px": {:.1},
1411 "caret_blink_rate_ms": {},
1412 "caret_width_px": {:.1},
1413 "wheel_scroll_lines": {},
1414 "hover_time_ms": {}
1415 }},
1416 "text_rendering": {{
1417 "font_smoothing_enabled": {},
1418 "subpixel_type": "{:?}",
1419 "font_smoothing_gamma": {},
1420 "increased_contrast": {}
1421 }},
1422 "accessibility": {{
1423 "prefers_bold_text": {},
1424 "prefers_larger_text": {},
1425 "text_scale_factor": {:.2},
1426 "prefers_high_contrast": {},
1427 "prefers_reduced_motion": {},
1428 "prefers_reduced_transparency": {},
1429 "screen_reader_active": {},
1430 "differentiate_without_color": {}
1431 }},
1432 "scrollbar_preferences": {{
1433 "visibility": "{:?}",
1434 "track_click": "{:?}"
1435 }},
1436 "linux": {{
1437 "gtk_theme": {},
1438 "icon_theme": {},
1439 "cursor_theme": {},
1440 "cursor_size": {},
1441 "titlebar_button_layout": {}
1442 }},
1443 "visual_hints": {{
1444 "show_button_images": {},
1445 "show_menu_images": {},
1446 "toolbar_style": "{:?}",
1447 "show_tooltips": {}
1448 }},
1449 "animation": {{
1450 "animations_enabled": {},
1451 "animation_duration_factor": {:.2},
1452 "focus_indicator_behavior": "{:?}"
1453 }},
1454 "audio": {{
1455 "event_sounds_enabled": {},
1456 "input_feedback_sounds_enabled": {}
1457 }}
1458}}"#,
1459 self.theme,
1461 self.platform,
1462 self.os_version.os, self.os_version.version_id,
1463 self.language.as_str(),
1464 self.prefers_reduced_motion,
1465 self.prefers_high_contrast,
1466 opt_color(self.colors.text),
1468 opt_color(self.colors.secondary_text),
1469 opt_color(self.colors.tertiary_text),
1470 opt_color(self.colors.background),
1471 opt_color(self.colors.accent),
1472 opt_color(self.colors.accent_text),
1473 opt_color(self.colors.button_face),
1474 opt_color(self.colors.button_text),
1475 opt_color(self.colors.disabled_text),
1476 opt_color(self.colors.window_background),
1477 opt_color(self.colors.under_page_background),
1478 opt_color(self.colors.selection_background),
1479 opt_color(self.colors.selection_text),
1480 opt_color(self.colors.selection_background_inactive),
1481 opt_color(self.colors.selection_text_inactive),
1482 opt_color(self.colors.link),
1483 opt_color(self.colors.separator),
1484 opt_color(self.colors.grid),
1485 opt_color(self.colors.find_highlight),
1486 opt_color(self.colors.sidebar_background),
1487 opt_color(self.colors.sidebar_selection),
1488 opt_str(&self.fonts.ui_font),
1490 opt_f32(self.fonts.ui_font_size),
1491 opt_str(&self.fonts.monospace_font),
1492 opt_str(&self.fonts.title_font),
1493 opt_str(&self.fonts.menu_font),
1494 opt_str(&self.fonts.small_font),
1495 tm.button_side,
1497 opt_px(&tm.height),
1498 opt_px(&tm.button_area_width),
1499 opt_px(&tm.padding_horizontal),
1500 opt_str(&tm.title_font),
1501 opt_f32(tm.title_font_size),
1502 opt_u16(tm.title_font_weight),
1503 tm.buttons.has_close,
1504 tm.buttons.has_minimize,
1505 tm.buttons.has_maximize,
1506 tm.buttons.has_fullscreen,
1507 inp.double_click_time_ms,
1509 inp.double_click_distance_px,
1510 inp.drag_threshold_px,
1511 inp.caret_blink_rate_ms,
1512 inp.caret_width_px,
1513 inp.wheel_scroll_lines,
1514 inp.hover_time_ms,
1515 tr.font_smoothing_enabled,
1517 tr.subpixel_type,
1518 tr.font_smoothing_gamma,
1519 tr.increased_contrast,
1520 acc.prefers_bold_text,
1522 acc.prefers_larger_text,
1523 acc.text_scale_factor,
1524 acc.prefers_high_contrast,
1525 acc.prefers_reduced_motion,
1526 acc.prefers_reduced_transparency,
1527 acc.screen_reader_active,
1528 acc.differentiate_without_color,
1529 sp.visibility,
1531 sp.track_click,
1532 opt_str(&lnx.gtk_theme),
1534 opt_str(&lnx.icon_theme),
1535 opt_str(&lnx.cursor_theme),
1536 lnx.cursor_size,
1537 opt_str(&lnx.titlebar_button_layout),
1538 vh.show_button_images,
1540 vh.show_menu_images,
1541 vh.toolbar_style,
1542 vh.show_tooltips,
1543 anim.animations_enabled,
1545 anim.animation_duration_factor,
1546 anim.focus_indicator_behavior,
1547 audio.event_sounds_enabled,
1549 audio.input_feedback_sounds_enabled,
1550 );
1551
1552 AzString::from(json)
1553 }
1554
1555 #[must_use] pub fn detect() -> Self {
1561 Self::default_for_platform()
1562 }
1563
1564 #[must_use] pub fn default_for_platform() -> Self {
1566 #[cfg(target_os = "windows")]
1567 { defaults::windows_11_light() }
1568 #[cfg(target_os = "macos")]
1569 { defaults::macos_modern_light() }
1570 #[cfg(target_os = "linux")]
1571 { defaults::gnome_adwaita_light() }
1572 #[cfg(target_os = "android")]
1573 { defaults::android_material_light() }
1574 #[cfg(target_os = "ios")]
1575 { defaults::ios_light() }
1576 #[cfg(not(any(
1577 target_os = "linux",
1578 target_os = "windows",
1579 target_os = "macos",
1580 target_os = "android",
1581 target_os = "ios"
1582 )))]
1583 { Self::default() }
1584 }
1585
1586 #[inline]
1588 #[must_use] pub fn new() -> Self {
1589 Self::detect()
1590 }
1591
1592 #[must_use] pub fn create_csd_stylesheet(&self) -> Css {
1598 use alloc::format;
1599
1600 use crate::parser2::new_from_str;
1601
1602 let mut css = String::new();
1604
1605 let bg_color = self
1607 .colors
1608 .window_background
1609 .as_option()
1610 .copied()
1611 .unwrap_or(ColorU::new_rgb(240, 240, 240));
1612 let text_color = self
1613 .colors
1614 .text
1615 .as_option()
1616 .copied()
1617 .unwrap_or(ColorU::new_rgb(0, 0, 0));
1618 let accent_color = self
1619 .colors
1620 .accent
1621 .as_option()
1622 .copied()
1623 .unwrap_or(ColorU::new_rgb(0, 120, 215));
1624 let border_color = match self.theme {
1625 Theme::Dark => ColorU::new_rgb(60, 60, 60),
1626 Theme::Light => ColorU::new_rgb(200, 200, 200),
1627 };
1628
1629 let corner_radius = self
1631 .metrics
1632 .corner_radius
1633 .map(|px| {
1634 use crate::props::basic::pixel::DEFAULT_FONT_SIZE;
1635 format!("{}px", px.to_pixels_internal(1.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
1636 })
1637 .unwrap_or_else(|| "4px".to_string());
1638
1639 let _ = write!(css,
1641 ".csd-titlebar {{ width: 100%; height: 32px; background: rgb({}, {}, {}); \
1642 border-bottom: 1px solid rgb({}, {}, {}); display: flex; flex-direction: row; \
1643 align-items: center; justify-content: space-between; padding: 0 8px; \
1644 cursor: grab; user-select: none; }} ",
1645 bg_color.r, bg_color.g, bg_color.b, border_color.r, border_color.g, border_color.b,
1646 );
1647
1648 let _ = write!(css,
1650 ".csd-title {{ color: rgb({}, {}, {}); font-size: 13px; flex-grow: 1; text-align: \
1651 center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; \
1652 user-select: none; }} ",
1653 text_color.r, text_color.g, text_color.b,
1654 );
1655
1656 css.push_str(".csd-buttons { display: flex; flex-direction: row; gap: 4px; } ");
1658
1659 let _ = write!(css,
1661 ".csd-button {{ width: 32px; height: 24px; border-radius: {}; background: \
1662 transparent; color: rgb({}, {}, {}); font-size: 16px; line-height: 24px; text-align: \
1663 center; cursor: pointer; user-select: none; }} ",
1664 corner_radius, text_color.r, text_color.g, text_color.b,
1665 );
1666
1667 let hover_color = match self.theme {
1669 Theme::Dark => ColorU::new_rgb(60, 60, 60),
1670 Theme::Light => ColorU::new_rgb(220, 220, 220),
1671 };
1672 let _ = write!(css,
1673 ".csd-button:hover {{ background: rgb({}, {}, {}); }} ",
1674 hover_color.r, hover_color.g, hover_color.b,
1675 );
1676
1677 css.push_str(
1679 ".csd-close:hover { background: rgb(232, 17, 35); color: rgb(255, 255, 255); } ",
1680 );
1681
1682 match self.platform {
1684 Platform::MacOs => {
1685 css.push_str(".csd-buttons { position: absolute; left: 8px; } ");
1687 css.push_str(
1688 ".csd-close { background: rgb(255, 95, 86); width: 12px; height: 12px; \
1689 border-radius: 50%; } ",
1690 );
1691 css.push_str(
1692 ".csd-minimize { background: rgb(255, 189, 46); width: 12px; height: 12px; \
1693 border-radius: 50%; } ",
1694 );
1695 css.push_str(
1696 ".csd-maximize { background: rgb(40, 201, 64); width: 12px; height: 12px; \
1697 border-radius: 50%; } ",
1698 );
1699 }
1700 Platform::Linux(_) => {
1701 css.push_str(".csd-title { text-align: left; } ");
1703 }
1704 _ => {
1705 }
1707 }
1708
1709 let (mut parsed_css, _warnings) = new_from_str(&css);
1711 for rule in parsed_css.rules.as_mut() {
1713 rule.priority = crate::css::rule_priority::SYSTEM;
1714 }
1715 parsed_css
1716 }
1717}
1718
1719#[must_use] pub fn detect_linux_desktop_env() -> DesktopEnvironment {
1724 if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") {
1726 let desktop_lower = desktop.to_lowercase();
1727 if desktop_lower.contains("gnome") {
1728 return DesktopEnvironment::Gnome;
1729 }
1730 if desktop_lower.contains("kde") || desktop_lower.contains("plasma") {
1731 return DesktopEnvironment::Kde;
1732 }
1733 if desktop_lower.contains("xfce") {
1734 return DesktopEnvironment::Other(AzString::from_const_str("XFCE"));
1735 }
1736 if desktop_lower.contains("unity") {
1737 return DesktopEnvironment::Other(AzString::from_const_str("Unity"));
1738 }
1739 if desktop_lower.contains("cinnamon") {
1740 return DesktopEnvironment::Other(AzString::from_const_str("Cinnamon"));
1741 }
1742 if desktop_lower.contains("mate") {
1743 return DesktopEnvironment::Other(AzString::from_const_str("MATE"));
1744 }
1745 if desktop_lower.contains("lxde") || desktop_lower.contains("lxqt") {
1746 return DesktopEnvironment::Other(AzString::from(desktop.to_uppercase()));
1747 }
1748 if desktop_lower.contains("budgie") {
1749 return DesktopEnvironment::Other(AzString::from_const_str("Budgie"));
1750 }
1751 if desktop_lower.contains("pantheon") {
1752 return DesktopEnvironment::Other(AzString::from_const_str("Pantheon"));
1753 }
1754 if desktop_lower.contains("deepin") {
1755 return DesktopEnvironment::Other(AzString::from_const_str("Deepin"));
1756 }
1757 if desktop_lower.contains("hyprland") {
1758 return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
1759 }
1760 if desktop_lower.contains("sway") {
1761 return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
1762 }
1763 if desktop_lower.contains("i3") {
1764 return DesktopEnvironment::Other(AzString::from_const_str("i3"));
1765 }
1766 return DesktopEnvironment::Other(AzString::from(desktop));
1767 }
1768
1769 if let Ok(session) = std::env::var("DESKTOP_SESSION") {
1771 let session_lower = session.to_lowercase();
1772 if session_lower.contains("gnome") {
1773 return DesktopEnvironment::Gnome;
1774 }
1775 if session_lower.contains("plasma") || session_lower.contains("kde") {
1776 return DesktopEnvironment::Kde;
1777 }
1778 if session_lower.contains("xfce") {
1779 return DesktopEnvironment::Other(AzString::from_const_str("XFCE"));
1780 }
1781 if session_lower.contains("cinnamon") {
1782 return DesktopEnvironment::Other(AzString::from_const_str("Cinnamon"));
1783 }
1784 return DesktopEnvironment::Other(AzString::from(session));
1785 }
1786
1787 if std::env::var("GNOME_DESKTOP_SESSION_ID").is_ok() {
1789 return DesktopEnvironment::Gnome;
1790 }
1791 if std::env::var("KDE_FULL_SESSION").is_ok() {
1792 return DesktopEnvironment::Kde;
1793 }
1794 if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
1795 return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
1796 }
1797 if std::env::var("SWAYSOCK").is_ok() {
1798 return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
1799 }
1800 if std::env::var("I3SOCK").is_ok() {
1801 return DesktopEnvironment::Other(AzString::from_const_str("i3"));
1802 }
1803
1804 DesktopEnvironment::Other(AzString::from_const_str("Unknown"))
1805}
1806
1807#[must_use] pub fn detect_system_language() -> AzString {
1813 let env_vars = ["LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"];
1814 for var in &env_vars {
1815 if let Ok(value) = std::env::var(var) {
1816 let value = value.trim();
1817 if value.is_empty() || value == "C" || value == "POSIX" {
1818 continue;
1819 }
1820 let lang = value
1822 .split('.') .next()
1824 .unwrap_or(value)
1825 .split(':') .next()
1827 .unwrap_or(value);
1828 if !lang.is_empty() {
1829 return AzString::from(lang.replace('_', "-"));
1830 }
1831 }
1832 }
1833 AzString::from_const_str("en-US")
1834}
1835
1836pub mod defaults {
1837 use super::{
1845 AccessibilitySettings, AnimationMetrics, AudioMetrics, FocusVisuals, InputMetrics,
1846 LinuxCustomization, ScrollbarPreferences, TextRenderingHints, VisualHints,
1847 };
1848 use crate::{
1849 corety::{AzString, OptionF32, OptionString},
1850 dynamic_selector::{BoolCondition, OsVersion},
1851 props::{
1852 basic::{
1853 color::{ColorU, OptionColorU},
1854 pixel::{PixelValue, OptionPixelValue},
1855 },
1856 layout::{
1857 dimensions::LayoutWidth,
1858 spacing::{LayoutPaddingLeft, LayoutPaddingRight},
1859 },
1860 style::{
1861 background::StyleBackgroundContent,
1862 scrollbar::{
1863 ComputedScrollbarStyle, OverflowScrolling, OverscrollBehavior, ScrollBehavior,
1864 ScrollPhysics, ScrollbarInfo,
1865 SCROLLBAR_ANDROID_DARK, SCROLLBAR_ANDROID_LIGHT, SCROLLBAR_CLASSIC_DARK,
1866 SCROLLBAR_CLASSIC_LIGHT, SCROLLBAR_IOS_DARK, SCROLLBAR_IOS_LIGHT,
1867 SCROLLBAR_MACOS_DARK, SCROLLBAR_MACOS_LIGHT, SCROLLBAR_WINDOWS_DARK,
1868 SCROLLBAR_WINDOWS_LIGHT,
1869 },
1870 },
1871 },
1872 system::{
1873 DesktopEnvironment, Platform, SystemColors, SystemFonts, SystemMetrics, SystemStyle,
1874 Theme, IconStyleOptions, TitlebarMetrics,
1875 },
1876 };
1877
1878 pub const SCROLLBAR_WINDOWS_CLASSIC: ScrollbarInfo = ScrollbarInfo {
1882 width: LayoutWidth::Px(PixelValue::const_px(17)),
1883 padding_left: LayoutPaddingLeft {
1884 inner: PixelValue::const_px(0),
1885 },
1886 padding_right: LayoutPaddingRight {
1887 inner: PixelValue::const_px(0),
1888 },
1889 track: StyleBackgroundContent::Color(ColorU {
1890 r: 223,
1891 g: 223,
1892 b: 223,
1893 a: 255,
1894 }), thumb: StyleBackgroundContent::Color(ColorU {
1896 r: 208,
1897 g: 208,
1898 b: 208,
1899 a: 255,
1900 }), button: StyleBackgroundContent::Color(ColorU {
1902 r: 208,
1903 g: 208,
1904 b: 208,
1905 a: 255,
1906 }),
1907 corner: StyleBackgroundContent::Color(ColorU {
1908 r: 223,
1909 g: 223,
1910 b: 223,
1911 a: 255,
1912 }),
1913 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1914 clip_to_container_border: false,
1915 scroll_behavior: ScrollBehavior::Auto,
1916 overscroll_behavior_x: OverscrollBehavior::None,
1917 overscroll_behavior_y: OverscrollBehavior::None,
1918 overflow_scrolling: OverflowScrolling::Auto,
1919 };
1920
1921 pub const SCROLLBAR_MACOS_AQUA: ScrollbarInfo = ScrollbarInfo {
1923 width: LayoutWidth::Px(PixelValue::const_px(15)),
1924 padding_left: LayoutPaddingLeft {
1925 inner: PixelValue::const_px(0),
1926 },
1927 padding_right: LayoutPaddingRight {
1928 inner: PixelValue::const_px(0),
1929 },
1930 track: StyleBackgroundContent::Color(ColorU {
1931 r: 238,
1932 g: 238,
1933 b: 238,
1934 a: 128,
1935 }), thumb: StyleBackgroundContent::Color(ColorU {
1937 r: 105,
1938 g: 173,
1939 b: 255,
1940 a: 255,
1941 }), button: StyleBackgroundContent::Color(ColorU {
1943 r: 105,
1944 g: 173,
1945 b: 255,
1946 a: 255,
1947 }),
1948 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1949 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1950 clip_to_container_border: true,
1951 scroll_behavior: ScrollBehavior::Smooth,
1952 overscroll_behavior_x: OverscrollBehavior::Auto,
1953 overscroll_behavior_y: OverscrollBehavior::Auto,
1954 overflow_scrolling: OverflowScrolling::Auto,
1955 };
1956
1957 pub const SCROLLBAR_KDE_OXYGEN: ScrollbarInfo = ScrollbarInfo {
1959 width: LayoutWidth::Px(PixelValue::const_px(14)),
1960 padding_left: LayoutPaddingLeft {
1961 inner: PixelValue::const_px(2),
1962 },
1963 padding_right: LayoutPaddingRight {
1964 inner: PixelValue::const_px(2),
1965 },
1966 track: StyleBackgroundContent::Color(ColorU {
1967 r: 242,
1968 g: 242,
1969 b: 242,
1970 a: 255,
1971 }),
1972 thumb: StyleBackgroundContent::Color(ColorU {
1973 r: 177,
1974 g: 177,
1975 b: 177,
1976 a: 255,
1977 }),
1978 button: StyleBackgroundContent::Color(ColorU {
1979 r: 216,
1980 g: 216,
1981 b: 216,
1982 a: 255,
1983 }),
1984 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1985 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1986 clip_to_container_border: false,
1987 scroll_behavior: ScrollBehavior::Auto,
1988 overscroll_behavior_x: OverscrollBehavior::Auto,
1989 overscroll_behavior_y: OverscrollBehavior::Auto,
1990 overflow_scrolling: OverflowScrolling::Auto,
1991 };
1992
1993 fn scrollbar_info_to_computed(info: &ScrollbarInfo) -> ComputedScrollbarStyle {
1995 ComputedScrollbarStyle {
1996 width: Some(info.width.clone()),
1997 thumb_color: match info.thumb {
1998 StyleBackgroundContent::Color(c) => Some(c),
1999 _ => None,
2000 },
2001 track_color: match info.track {
2002 StyleBackgroundContent::Color(c) => Some(c),
2003 _ => None,
2004 },
2005 }
2006 }
2007
2008 #[must_use] pub fn windows_11_light() -> SystemStyle {
2012 SystemStyle {
2013 theme: Theme::Light,
2014 platform: Platform::Windows,
2015 colors: SystemColors {
2016 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2017 background: OptionColorU::Some(ColorU::new_rgb(243, 243, 243)),
2018 accent: OptionColorU::Some(ColorU::new_rgb(0, 95, 184)),
2019 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2020 selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2021 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2022 ..Default::default()
2023 },
2024 fonts: SystemFonts {
2025 ui_font: OptionString::Some("Segoe UI Variable Text".into()),
2026 ui_font_size: OptionF32::Some(9.0),
2027 monospace_font: OptionString::Some("Consolas".into()),
2028 ..Default::default()
2029 },
2030 metrics: SystemMetrics {
2031 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2032 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2033 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2034 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2035 titlebar: TitlebarMetrics::windows(),
2036 },
2037 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_LIGHT))),
2038 app_specific_stylesheet: None,
2039 run_destructor: true,
2040 icon_style: IconStyleOptions::default(),
2041 language: AzString::from_const_str("en-US"),
2042 os_version: OsVersion::WIN_11,
2043 prefers_reduced_motion: BoolCondition::False,
2044 prefers_high_contrast: BoolCondition::False,
2045 scroll_physics: ScrollPhysics::windows(),
2046 linux: LinuxCustomization::default(),
2047 focus_visuals: FocusVisuals::default(),
2048 accessibility: AccessibilitySettings::default(),
2049 input: InputMetrics::default(),
2050 text_rendering: TextRenderingHints::default(),
2051 scrollbar_preferences: ScrollbarPreferences::default(),
2052 visual_hints: VisualHints::default(),
2053 animation: AnimationMetrics::default(),
2054 audio: AudioMetrics::default(),
2055 }
2056 }
2057
2058 #[must_use] pub fn windows_11_dark() -> SystemStyle {
2060 SystemStyle {
2061 theme: Theme::Dark,
2062 platform: Platform::Windows,
2063 colors: SystemColors {
2064 text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2065 background: OptionColorU::Some(ColorU::new_rgb(32, 32, 32)),
2066 accent: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2067 window_background: OptionColorU::Some(ColorU::new_rgb(25, 25, 25)),
2068 selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
2069 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2070 ..Default::default()
2071 },
2072 fonts: SystemFonts {
2073 ui_font: OptionString::Some("Segoe UI Variable Text".into()),
2074 ui_font_size: OptionF32::Some(9.0),
2075 monospace_font: OptionString::Some("Consolas".into()),
2076 ..Default::default()
2077 },
2078 metrics: SystemMetrics {
2079 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2080 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2081 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2082 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2083 titlebar: TitlebarMetrics::windows(),
2084 },
2085 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_DARK))),
2086 app_specific_stylesheet: None,
2087 run_destructor: true,
2088 icon_style: IconStyleOptions::default(),
2089 language: AzString::from_const_str("en-US"),
2090 os_version: OsVersion::WIN_11,
2091 prefers_reduced_motion: BoolCondition::False,
2092 prefers_high_contrast: BoolCondition::False,
2093 scroll_physics: ScrollPhysics::windows(),
2094 linux: LinuxCustomization::default(),
2095 focus_visuals: FocusVisuals::default(),
2096 accessibility: AccessibilitySettings::default(),
2097 input: InputMetrics::default(),
2098 text_rendering: TextRenderingHints::default(),
2099 scrollbar_preferences: ScrollbarPreferences::default(),
2100 visual_hints: VisualHints::default(),
2101 animation: AnimationMetrics::default(),
2102 audio: AudioMetrics::default(),
2103 }
2104 }
2105
2106 #[must_use] pub fn windows_7_aero() -> SystemStyle {
2108 SystemStyle {
2109 theme: Theme::Light,
2110 platform: Platform::Windows,
2111 colors: SystemColors {
2112 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2113 background: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
2114 accent: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
2115 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2116 selection_background: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
2117 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2118 ..Default::default()
2119 },
2120 fonts: SystemFonts {
2121 ui_font: OptionString::Some("Segoe UI".into()),
2122 ui_font_size: OptionF32::Some(9.0),
2123 monospace_font: OptionString::Some("Consolas".into()),
2124 ..Default::default()
2125 },
2126 metrics: SystemMetrics {
2127 corner_radius: OptionPixelValue::Some(PixelValue::px(6.0)),
2128 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2129 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
2130 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(5.0)),
2131 titlebar: TitlebarMetrics::windows(),
2132 },
2133 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
2134 app_specific_stylesheet: None,
2135 run_destructor: true,
2136 icon_style: IconStyleOptions::default(),
2137 language: AzString::from_const_str("en-US"),
2138 os_version: OsVersion::WIN_7,
2139 prefers_reduced_motion: BoolCondition::False,
2140 prefers_high_contrast: BoolCondition::False,
2141 scroll_physics: ScrollPhysics::windows(),
2142 linux: LinuxCustomization::default(),
2143 focus_visuals: FocusVisuals::default(),
2144 accessibility: AccessibilitySettings::default(),
2145 input: InputMetrics::default(),
2146 text_rendering: TextRenderingHints::default(),
2147 scrollbar_preferences: ScrollbarPreferences::default(),
2148 visual_hints: VisualHints::default(),
2149 animation: AnimationMetrics::default(),
2150 audio: AudioMetrics::default(),
2151 }
2152 }
2153
2154 #[must_use] pub fn windows_xp_luna() -> SystemStyle {
2156 SystemStyle {
2157 theme: Theme::Light,
2158 platform: Platform::Windows,
2159 colors: SystemColors {
2160 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2161 background: OptionColorU::Some(ColorU::new_rgb(236, 233, 216)),
2162 accent: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
2163 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2164 selection_background: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
2165 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2166 ..Default::default()
2167 },
2168 fonts: SystemFonts {
2169 ui_font: OptionString::Some("Tahoma".into()),
2170 ui_font_size: OptionF32::Some(8.0),
2171 monospace_font: OptionString::Some("Lucida Console".into()),
2172 ..Default::default()
2173 },
2174 metrics: SystemMetrics {
2175 corner_radius: OptionPixelValue::Some(PixelValue::px(3.0)),
2176 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2177 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
2178 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
2179 titlebar: TitlebarMetrics::windows(),
2180 },
2181 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_CLASSIC))),
2182 app_specific_stylesheet: None,
2183 run_destructor: true,
2184 icon_style: IconStyleOptions::default(),
2185 language: AzString::from_const_str("en-US"),
2186 os_version: OsVersion::WIN_XP,
2187 prefers_reduced_motion: BoolCondition::False,
2188 prefers_high_contrast: BoolCondition::False,
2189 scroll_physics: ScrollPhysics::windows(),
2190 linux: LinuxCustomization::default(),
2191 focus_visuals: FocusVisuals::default(),
2192 accessibility: AccessibilitySettings::default(),
2193 input: InputMetrics::default(),
2194 text_rendering: TextRenderingHints::default(),
2195 scrollbar_preferences: ScrollbarPreferences::default(),
2196 visual_hints: VisualHints::default(),
2197 animation: AnimationMetrics::default(),
2198 audio: AudioMetrics::default(),
2199 }
2200 }
2201
2202 #[must_use] pub fn macos_modern_light() -> SystemStyle {
2206 SystemStyle {
2207 platform: Platform::MacOs,
2208 theme: Theme::Light,
2209 colors: SystemColors {
2210 text: OptionColorU::Some(ColorU::new(0, 0, 0, 221)),
2211 background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
2212 accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
2213 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2214 selection_background: OptionColorU::Some(ColorU::new(0, 122, 255, 128)),
2216 selection_text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2217 ..Default::default()
2218 },
2219 fonts: SystemFonts {
2220 ui_font: OptionString::Some(".SF NS".into()),
2221 ui_font_size: OptionF32::Some(13.0),
2222 monospace_font: OptionString::Some("Menlo".into()),
2223 ..Default::default()
2224 },
2225 metrics: SystemMetrics {
2226 corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
2227 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2228 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2229 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2230 titlebar: TitlebarMetrics::macos(),
2231 },
2232 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_LIGHT))),
2233 app_specific_stylesheet: None,
2234 run_destructor: true,
2235 icon_style: IconStyleOptions::default(),
2236 language: AzString::from_const_str("en-US"),
2237 os_version: OsVersion::MACOS_SONOMA,
2238 prefers_reduced_motion: BoolCondition::False,
2239 prefers_high_contrast: BoolCondition::False,
2240 scroll_physics: ScrollPhysics::macos(),
2241 linux: LinuxCustomization::default(),
2242 focus_visuals: FocusVisuals::default(),
2243 accessibility: AccessibilitySettings::default(),
2244 input: InputMetrics::default(),
2245 text_rendering: TextRenderingHints::default(),
2246 scrollbar_preferences: ScrollbarPreferences::default(),
2247 visual_hints: VisualHints::default(),
2248 animation: AnimationMetrics::default(),
2249 audio: AudioMetrics::default(),
2250 }
2251 }
2252
2253 #[must_use] pub fn macos_modern_dark() -> SystemStyle {
2255 SystemStyle {
2256 platform: Platform::MacOs,
2257 theme: Theme::Dark,
2258 colors: SystemColors {
2259 text: OptionColorU::Some(ColorU::new(255, 255, 255, 221)),
2260 background: OptionColorU::Some(ColorU::new_rgb(28, 28, 30)),
2261 accent: OptionColorU::Some(ColorU::new_rgb(10, 132, 255)),
2262 window_background: OptionColorU::Some(ColorU::new_rgb(44, 44, 46)),
2263 selection_background: OptionColorU::Some(ColorU::new(10, 132, 255, 128)),
2265 selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2266 ..Default::default()
2267 },
2268 fonts: SystemFonts {
2269 ui_font: OptionString::Some(".SF NS".into()),
2270 ui_font_size: OptionF32::Some(13.0),
2271 monospace_font: OptionString::Some("SF Mono".into()),
2272 monospace_font_size: OptionF32::Some(12.0),
2273 title_font: OptionString::Some(".SF NS".into()),
2274 title_font_size: OptionF32::Some(13.0),
2275 menu_font: OptionString::Some(".SF NS".into()),
2276 menu_font_size: OptionF32::Some(13.0),
2277 small_font: OptionString::Some(".SF NS".into()),
2278 small_font_size: OptionF32::Some(11.0),
2279 ..Default::default()
2280 },
2281 metrics: SystemMetrics {
2282 corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
2283 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2284 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2285 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2286 titlebar: TitlebarMetrics::macos(),
2287 },
2288 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_DARK))),
2289 app_specific_stylesheet: None,
2290 run_destructor: true,
2291 icon_style: IconStyleOptions::default(),
2292 language: AzString::from_const_str("en-US"),
2293 os_version: OsVersion::MACOS_SONOMA,
2294 prefers_reduced_motion: BoolCondition::False,
2295 prefers_high_contrast: BoolCondition::False,
2296 scroll_physics: ScrollPhysics::macos(),
2297 linux: LinuxCustomization::default(),
2298 focus_visuals: FocusVisuals::default(),
2299 accessibility: AccessibilitySettings::default(),
2300 input: InputMetrics::default(),
2301 text_rendering: TextRenderingHints::default(),
2302 scrollbar_preferences: ScrollbarPreferences::default(),
2303 visual_hints: VisualHints::default(),
2304 animation: AnimationMetrics::default(),
2305 audio: AudioMetrics::default(),
2306 }
2307 }
2308
2309 #[must_use] pub fn macos_aqua() -> SystemStyle {
2311 SystemStyle {
2312 platform: Platform::MacOs,
2313 theme: Theme::Light,
2314 colors: SystemColors {
2315 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2316 background: OptionColorU::Some(ColorU::new_rgb(229, 229, 229)),
2317 accent: OptionColorU::Some(ColorU::new_rgb(63, 128, 234)),
2318 window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2319 ..Default::default()
2320 },
2321 fonts: SystemFonts {
2322 ui_font: OptionString::Some("Lucida Grande".into()),
2323 ui_font_size: OptionF32::Some(13.0),
2324 monospace_font: OptionString::Some("Monaco".into()),
2325 monospace_font_size: OptionF32::Some(12.0),
2326 ..Default::default()
2327 },
2328 metrics: SystemMetrics {
2329 corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
2330 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2331 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2332 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2333 titlebar: TitlebarMetrics::macos(),
2334 },
2335 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_AQUA))),
2336 app_specific_stylesheet: None,
2337 run_destructor: true,
2338 icon_style: IconStyleOptions::default(),
2339 language: AzString::from_const_str("en-US"),
2340 os_version: OsVersion::MACOS_TIGER,
2341 prefers_reduced_motion: BoolCondition::False,
2342 prefers_high_contrast: BoolCondition::False,
2343 scroll_physics: ScrollPhysics::macos(),
2344 linux: LinuxCustomization::default(),
2345 focus_visuals: FocusVisuals::default(),
2346 accessibility: AccessibilitySettings::default(),
2347 input: InputMetrics::default(),
2348 text_rendering: TextRenderingHints::default(),
2349 scrollbar_preferences: ScrollbarPreferences::default(),
2350 visual_hints: VisualHints::default(),
2351 animation: AnimationMetrics::default(),
2352 audio: AudioMetrics::default(),
2353 }
2354 }
2355
2356 #[must_use] pub fn gnome_adwaita_light() -> SystemStyle {
2360 SystemStyle {
2361 platform: Platform::Linux(DesktopEnvironment::Gnome),
2362 theme: Theme::Light,
2363 colors: SystemColors {
2364 text: OptionColorU::Some(ColorU::new_rgb(46, 52, 54)),
2365 background: OptionColorU::Some(ColorU::new_rgb(249, 249, 249)),
2366 accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
2367 window_background: OptionColorU::Some(ColorU::new_rgb(237, 237, 237)),
2368 ..Default::default()
2369 },
2370 fonts: SystemFonts {
2371 ui_font: OptionString::Some("Cantarell".into()),
2372 ui_font_size: OptionF32::Some(11.0),
2373 monospace_font: OptionString::Some("Monospace".into()),
2374 ..Default::default()
2375 },
2376 metrics: SystemMetrics {
2377 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2378 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2379 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2380 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2381 titlebar: TitlebarMetrics::linux_gnome(),
2382 },
2383 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
2384 app_specific_stylesheet: None,
2385 run_destructor: true,
2386 icon_style: IconStyleOptions::default(),
2387 language: AzString::from_const_str("en-US"),
2388 os_version: OsVersion::LINUX_6_0,
2389 prefers_reduced_motion: BoolCondition::False,
2390 prefers_high_contrast: BoolCondition::False,
2391 scroll_physics: ScrollPhysics::default(),
2392 linux: LinuxCustomization::default(),
2393 focus_visuals: FocusVisuals::default(),
2394 accessibility: AccessibilitySettings::default(),
2395 input: InputMetrics::default(),
2396 text_rendering: TextRenderingHints::default(),
2397 scrollbar_preferences: ScrollbarPreferences::default(),
2398 visual_hints: VisualHints::default(),
2399 animation: AnimationMetrics::default(),
2400 audio: AudioMetrics::default(),
2401 }
2402 }
2403
2404 #[must_use] pub fn gnome_adwaita_dark() -> SystemStyle {
2406 SystemStyle {
2407 platform: Platform::Linux(DesktopEnvironment::Gnome),
2408 theme: Theme::Dark,
2409 colors: SystemColors {
2410 text: OptionColorU::Some(ColorU::new_rgb(238, 238, 236)),
2411 background: OptionColorU::Some(ColorU::new_rgb(36, 36, 36)),
2412 accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
2413 window_background: OptionColorU::Some(ColorU::new_rgb(48, 48, 48)),
2414 ..Default::default()
2415 },
2416 fonts: SystemFonts {
2417 ui_font: OptionString::Some("Cantarell".into()),
2418 ui_font_size: OptionF32::Some(11.0),
2419 monospace_font: OptionString::Some("Monospace".into()),
2420 ..Default::default()
2421 },
2422 metrics: SystemMetrics {
2423 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2424 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2425 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2426 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2427 titlebar: TitlebarMetrics::linux_gnome(),
2428 },
2429 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_DARK))),
2430 app_specific_stylesheet: None,
2431 run_destructor: true,
2432 icon_style: IconStyleOptions::default(),
2433 language: AzString::from_const_str("en-US"),
2434 os_version: OsVersion::LINUX_6_0,
2435 prefers_reduced_motion: BoolCondition::False,
2436 prefers_high_contrast: BoolCondition::False,
2437 scroll_physics: ScrollPhysics::default(),
2438 linux: LinuxCustomization::default(),
2439 focus_visuals: FocusVisuals::default(),
2440 accessibility: AccessibilitySettings::default(),
2441 input: InputMetrics::default(),
2442 text_rendering: TextRenderingHints::default(),
2443 scrollbar_preferences: ScrollbarPreferences::default(),
2444 visual_hints: VisualHints::default(),
2445 animation: AnimationMetrics::default(),
2446 audio: AudioMetrics::default(),
2447 }
2448 }
2449
2450 #[must_use] pub fn gtk2_clearlooks() -> SystemStyle {
2452 SystemStyle {
2453 platform: Platform::Linux(DesktopEnvironment::Gnome),
2454 theme: Theme::Light,
2455 colors: SystemColors {
2456 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2457 background: OptionColorU::Some(ColorU::new_rgb(239, 239, 239)),
2458 accent: OptionColorU::Some(ColorU::new_rgb(245, 121, 0)),
2459 ..Default::default()
2460 },
2461 fonts: SystemFonts {
2462 ui_font: OptionString::Some("DejaVu Sans".into()),
2463 ui_font_size: OptionF32::Some(10.0),
2464 monospace_font: OptionString::Some("DejaVu Sans Mono".into()),
2465 ..Default::default()
2466 },
2467 metrics: SystemMetrics {
2468 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2469 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2470 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
2471 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2472 titlebar: TitlebarMetrics::linux_gnome(),
2473 },
2474 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
2475 app_specific_stylesheet: None,
2476 run_destructor: true,
2477 icon_style: IconStyleOptions::default(),
2478 language: AzString::from_const_str("en-US"),
2479 os_version: OsVersion::LINUX_2_6,
2480 prefers_reduced_motion: BoolCondition::False,
2481 prefers_high_contrast: BoolCondition::False,
2482 scroll_physics: ScrollPhysics::default(),
2483 linux: LinuxCustomization::default(),
2484 focus_visuals: FocusVisuals::default(),
2485 accessibility: AccessibilitySettings::default(),
2486 input: InputMetrics::default(),
2487 text_rendering: TextRenderingHints::default(),
2488 scrollbar_preferences: ScrollbarPreferences::default(),
2489 visual_hints: VisualHints::default(),
2490 animation: AnimationMetrics::default(),
2491 audio: AudioMetrics::default(),
2492 }
2493 }
2494
2495 #[must_use] pub fn kde_breeze_light() -> SystemStyle {
2497 SystemStyle {
2498 platform: Platform::Linux(DesktopEnvironment::Kde),
2499 theme: Theme::Light,
2500 colors: SystemColors {
2501 text: OptionColorU::Some(ColorU::new_rgb(31, 36, 39)),
2502 background: OptionColorU::Some(ColorU::new_rgb(239, 240, 241)),
2503 accent: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
2504 ..Default::default()
2505 },
2506 fonts: SystemFonts {
2507 ui_font: OptionString::Some("Noto Sans".into()),
2508 ui_font_size: OptionF32::Some(10.0),
2509 monospace_font: OptionString::Some("Hack".into()),
2510 ..Default::default()
2511 },
2512 metrics: SystemMetrics {
2513 corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
2514 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2515 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2516 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
2517 titlebar: TitlebarMetrics::linux_gnome(),
2518 },
2519 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_KDE_OXYGEN))),
2520 app_specific_stylesheet: None,
2521 run_destructor: true,
2522 icon_style: IconStyleOptions::default(),
2523 language: AzString::from_const_str("en-US"),
2524 os_version: OsVersion::LINUX_6_0,
2525 prefers_reduced_motion: BoolCondition::False,
2526 prefers_high_contrast: BoolCondition::False,
2527 scroll_physics: ScrollPhysics::default(),
2528 linux: LinuxCustomization::default(),
2529 focus_visuals: FocusVisuals::default(),
2530 accessibility: AccessibilitySettings::default(),
2531 input: InputMetrics::default(),
2532 text_rendering: TextRenderingHints::default(),
2533 scrollbar_preferences: ScrollbarPreferences::default(),
2534 visual_hints: VisualHints::default(),
2535 animation: AnimationMetrics::default(),
2536 audio: AudioMetrics::default(),
2537 }
2538 }
2539
2540 #[must_use] pub fn android_material_light() -> SystemStyle {
2544 SystemStyle {
2545 platform: Platform::Android,
2546 theme: Theme::Light,
2547 colors: SystemColors {
2548 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2549 background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2550 accent: OptionColorU::Some(ColorU::new_rgb(98, 0, 238)),
2551 ..Default::default()
2552 },
2553 fonts: SystemFonts {
2554 ui_font: OptionString::Some("Roboto".into()),
2555 ui_font_size: OptionF32::Some(14.0),
2556 monospace_font: OptionString::Some("Droid Sans Mono".into()),
2557 ..Default::default()
2558 },
2559 metrics: SystemMetrics {
2560 corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
2561 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2562 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
2563 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(10.0)),
2564 titlebar: TitlebarMetrics::android(),
2565 },
2566 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_LIGHT))),
2567 app_specific_stylesheet: None,
2568 run_destructor: true,
2569 icon_style: IconStyleOptions::default(),
2570 language: AzString::from_const_str("en-US"),
2571 os_version: OsVersion::ANDROID_14,
2572 prefers_reduced_motion: BoolCondition::False,
2573 prefers_high_contrast: BoolCondition::False,
2574 scroll_physics: ScrollPhysics::android(),
2575 linux: LinuxCustomization::default(),
2576 focus_visuals: FocusVisuals::default(),
2577 accessibility: AccessibilitySettings::default(),
2578 input: InputMetrics::default(),
2579 text_rendering: TextRenderingHints::default(),
2580 scrollbar_preferences: ScrollbarPreferences::default(),
2581 visual_hints: VisualHints::default(),
2582 animation: AnimationMetrics::default(),
2583 audio: AudioMetrics::default(),
2584 }
2585 }
2586
2587 #[must_use] pub fn android_holo_dark() -> SystemStyle {
2589 SystemStyle {
2590 platform: Platform::Android,
2591 theme: Theme::Dark,
2592 colors: SystemColors {
2593 text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
2594 background: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2595 accent: OptionColorU::Some(ColorU::new_rgb(51, 181, 229)),
2596 ..Default::default()
2597 },
2598 fonts: SystemFonts {
2599 ui_font: OptionString::Some("Roboto".into()),
2600 ui_font_size: OptionF32::Some(14.0),
2601 monospace_font: OptionString::Some("Droid Sans Mono".into()),
2602 ..Default::default()
2603 },
2604 metrics: SystemMetrics {
2605 corner_radius: OptionPixelValue::Some(PixelValue::px(2.0)),
2606 border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
2607 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
2608 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
2609 titlebar: TitlebarMetrics::android(),
2610 },
2611 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_DARK))),
2612 app_specific_stylesheet: None,
2613 run_destructor: true,
2614 icon_style: IconStyleOptions::default(),
2615 language: AzString::from_const_str("en-US"),
2616 os_version: OsVersion::ANDROID_ICE_CREAM_SANDWICH,
2617 prefers_reduced_motion: BoolCondition::False,
2618 prefers_high_contrast: BoolCondition::False,
2619 scroll_physics: ScrollPhysics::android(),
2620 linux: LinuxCustomization::default(),
2621 focus_visuals: FocusVisuals::default(),
2622 accessibility: AccessibilitySettings::default(),
2623 input: InputMetrics::default(),
2624 text_rendering: TextRenderingHints::default(),
2625 scrollbar_preferences: ScrollbarPreferences::default(),
2626 visual_hints: VisualHints::default(),
2627 animation: AnimationMetrics::default(),
2628 audio: AudioMetrics::default(),
2629 }
2630 }
2631
2632 #[must_use] pub fn ios_light() -> SystemStyle {
2634 SystemStyle {
2635 platform: Platform::Ios,
2636 theme: Theme::Light,
2637 colors: SystemColors {
2638 text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
2639 background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
2640 accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
2641 ..Default::default()
2642 },
2643 fonts: SystemFonts {
2644 ui_font: OptionString::Some(".SFUI-Display-Regular".into()),
2645 ui_font_size: OptionF32::Some(17.0),
2646 monospace_font: OptionString::Some("Menlo".into()),
2647 ..Default::default()
2648 },
2649 metrics: SystemMetrics {
2650 corner_radius: OptionPixelValue::Some(PixelValue::px(10.0)),
2651 border_width: OptionPixelValue::Some(PixelValue::px(0.5)),
2652 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(20.0)),
2653 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(12.0)),
2654 titlebar: TitlebarMetrics::ios(),
2655 },
2656 scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_IOS_LIGHT))),
2657 app_specific_stylesheet: None,
2658 run_destructor: true,
2659 icon_style: IconStyleOptions::default(),
2660 language: AzString::from_const_str("en-US"),
2661 os_version: OsVersion::IOS_17,
2662 prefers_reduced_motion: BoolCondition::False,
2663 prefers_high_contrast: BoolCondition::False,
2664 scroll_physics: ScrollPhysics::ios(),
2665 linux: LinuxCustomization::default(),
2666 focus_visuals: FocusVisuals::default(),
2667 accessibility: AccessibilitySettings::default(),
2668 input: InputMetrics::default(),
2669 text_rendering: TextRenderingHints::default(),
2670 scrollbar_preferences: ScrollbarPreferences::default(),
2671 visual_hints: VisualHints::default(),
2672 animation: AnimationMetrics::default(),
2673 audio: AudioMetrics::default(),
2674 }
2675 }
2676}
2677
2678#[cfg(test)]
2679mod autotest_generated {
2680 use super::*;
2681 use crate::css::rule_priority;
2682
2683 const ALL_FONT_TYPES: [SystemFontType; 11] = [
2684 SystemFontType::Ui,
2685 SystemFontType::UiBold,
2686 SystemFontType::Monospace,
2687 SystemFontType::MonospaceBold,
2688 SystemFontType::MonospaceItalic,
2689 SystemFontType::Title,
2690 SystemFontType::TitleBold,
2691 SystemFontType::Menu,
2692 SystemFontType::Small,
2693 SystemFontType::Serif,
2694 SystemFontType::SerifBold,
2695 ];
2696
2697 fn all_platforms() -> Vec<Platform> {
2698 vec![
2699 Platform::Windows,
2700 Platform::MacOs,
2701 Platform::Linux(DesktopEnvironment::Gnome),
2702 Platform::Linux(DesktopEnvironment::Kde),
2703 Platform::Linux(DesktopEnvironment::Other(AzString::from_const_str("Hyprland"))),
2704 Platform::Android,
2705 Platform::Ios,
2706 Platform::Unknown,
2707 ]
2708 }
2709
2710 fn all_default_styles() -> Vec<(&'static str, SystemStyle)> {
2712 vec![
2713 ("windows_11_light", defaults::windows_11_light()),
2714 ("windows_11_dark", defaults::windows_11_dark()),
2715 ("windows_7_aero", defaults::windows_7_aero()),
2716 ("windows_xp_luna", defaults::windows_xp_luna()),
2717 ("macos_modern_light", defaults::macos_modern_light()),
2718 ("macos_modern_dark", defaults::macos_modern_dark()),
2719 ("macos_aqua", defaults::macos_aqua()),
2720 ("gnome_adwaita_light", defaults::gnome_adwaita_light()),
2721 ("gnome_adwaita_dark", defaults::gnome_adwaita_dark()),
2722 ("gtk2_clearlooks", defaults::gtk2_clearlooks()),
2723 ("kde_breeze_light", defaults::kde_breeze_light()),
2724 ("android_material_light", defaults::android_material_light()),
2725 ("android_holo_dark", defaults::android_holo_dark()),
2726 ("ios_light", defaults::ios_light()),
2727 ]
2728 }
2729
2730 #[test]
2733 fn from_css_str_valid_minimal() {
2734 assert_eq!(SystemFontType::from_css_str("system:ui"), Some(SystemFontType::Ui));
2735 assert_eq!(
2736 SystemFontType::from_css_str("system:monospace:italic"),
2737 Some(SystemFontType::MonospaceItalic)
2738 );
2739 }
2740
2741 #[test]
2742 fn from_css_str_empty_input_returns_none() {
2743 assert_eq!(SystemFontType::from_css_str(""), None);
2744 }
2745
2746 #[test]
2747 fn from_css_str_whitespace_only_returns_none() {
2748 for s in [" ", "\t\n", "\r\n\r\n", "\t \t \n"] {
2749 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2750 }
2751 }
2752
2753 #[test]
2754 fn from_css_str_prefix_only_is_none_and_does_not_panic_on_slice() {
2755 assert_eq!(SystemFontType::from_css_str("system:"), None);
2757 assert_eq!(SystemFontType::from_css_str(" system: "), None);
2758 assert_eq!(SystemFontType::from_css_str("system::"), None);
2759 }
2760
2761 #[test]
2762 fn from_css_str_garbage_returns_none() {
2763 for s in [
2764 ";;;",
2765 "{}{}",
2766 "\0\u{1}\u{2}\u{7f}",
2767 "system",
2768 "systemui",
2769 "system;ui",
2770 "system:ui:",
2771 ":system:ui",
2772 "font-family: system:ui;",
2773 "\\system:ui",
2774 "system:ui\0",
2775 "system:\u{0}ui",
2776 ] {
2777 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2778 }
2779 }
2780
2781 #[test]
2782 fn from_css_str_leading_trailing_junk() {
2783 assert_eq!(SystemFontType::from_css_str(" system:ui "), Some(SystemFontType::Ui));
2785 assert_eq!(
2786 SystemFontType::from_css_str("\t\nsystem:monospace\r\n"),
2787 Some(SystemFontType::Monospace)
2788 );
2789 assert_eq!(SystemFontType::from_css_str("system:ui;garbage"), None);
2791 assert_eq!(SystemFontType::from_css_str("garbage system:ui"), None);
2792 assert_eq!(SystemFontType::from_css_str("system:ui system:ui"), None);
2793 assert_eq!(SystemFontType::from_css_str("system: ui"), None);
2794 assert_eq!(SystemFontType::from_css_str("system:ui:bold:extra"), None);
2795 }
2796
2797 #[test]
2798 fn from_css_str_is_case_sensitive() {
2799 for s in ["SYSTEM:UI", "System:Ui", "system:UI", "System:ui", "sYsTeM:ui"] {
2802 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2803 }
2804 }
2805
2806 #[test]
2807 fn from_css_str_boundary_numbers() {
2808 for s in [
2809 "0",
2810 "-0",
2811 "9223372036854775807",
2812 "-9223372036854775808",
2813 "NaN",
2814 "inf",
2815 "-inf",
2816 "1e400",
2817 "system:0",
2818 "system:-1",
2819 "system:NaN",
2820 "system:inf",
2821 "system:9223372036854775807",
2822 ] {
2823 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2824 }
2825 }
2826
2827 #[test]
2828 fn from_css_str_unicode_does_not_panic() {
2829 for s in [
2830 "\u{1F600}",
2831 "system:\u{1F600}",
2832 "system:ui\u{0301}", "\u{1F600}system:ui",
2834 "systém:ui", "system:ui", "system:\u{202E}ui", "system:\u{FFFD}",
2838 "system:ui\u{200B}", ] {
2840 assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
2841 }
2842 }
2843
2844 #[test]
2845 fn from_css_str_extremely_long_input_does_not_hang() {
2846 let long = format!("system:{}", "u".repeat(1_000_000));
2847 assert_eq!(SystemFontType::from_css_str(&long), None);
2848
2849 let long_suffix = format!("system:ui{}", "x".repeat(1_000_000));
2851 assert_eq!(SystemFontType::from_css_str(&long_suffix), None);
2852
2853 let padded = format!("{}system:ui{}", " ".repeat(100_000), " ".repeat(100_000));
2855 assert_eq!(SystemFontType::from_css_str(&padded), Some(SystemFontType::Ui));
2856 }
2857
2858 #[test]
2859 fn from_css_str_deeply_nested_input_does_not_stack_overflow() {
2860 let nested = format!("system:{}{}", "(".repeat(10_000), ")".repeat(10_000));
2861 assert_eq!(SystemFontType::from_css_str(&nested), None);
2862
2863 let brackets = format!("system:{}", "[".repeat(10_000));
2864 assert_eq!(SystemFontType::from_css_str(&brackets), None);
2865 }
2866
2867 #[test]
2870 fn font_type_css_str_round_trips() {
2871 for ty in ALL_FONT_TYPES {
2872 let s = ty.as_css_str();
2873 assert_eq!(SystemFontType::from_css_str(s), Some(ty), "round-trip of {ty:?}");
2874 assert_eq!(
2876 SystemFontType::from_css_str(&format!(" {s}\t")),
2877 Some(ty),
2878 "padded round-trip of {ty:?}"
2879 );
2880 }
2881 }
2882
2883 #[test]
2884 fn font_type_css_str_is_well_formed_and_unique() {
2885 let mut seen: Vec<&'static str> = Vec::new();
2886 for ty in ALL_FONT_TYPES {
2887 let s = ty.as_css_str();
2888 assert!(s.starts_with("system:"), "{ty:?} -> {s:?}");
2889 assert!(s.len() > "system:".len(), "{ty:?} has an empty keyword");
2890 assert_eq!(s.trim(), s, "{ty:?} -> {s:?} has surrounding whitespace");
2891 assert!(s.is_ascii(), "{ty:?} -> {s:?} is not ASCII");
2892 seen.push(s);
2893 }
2894 seen.sort_unstable();
2895 assert!(
2896 seen.windows(2).all(|w| w[0] != w[1]),
2897 "as_css_str() is not injective: {seen:?}"
2898 );
2899 }
2900
2901 #[test]
2902 fn font_type_default_is_ui() {
2903 let d = SystemFontType::default();
2904 assert_eq!(d, SystemFontType::Ui);
2905 assert_eq!(d.as_css_str(), "system:ui");
2906 assert!(!d.is_bold());
2907 assert!(!d.is_italic());
2908 }
2909
2910 #[test]
2913 fn is_bold_matches_exactly_the_bold_variants() {
2914 assert!(SystemFontType::UiBold.is_bold());
2915 assert!(SystemFontType::MonospaceBold.is_bold());
2916 assert!(SystemFontType::TitleBold.is_bold());
2917 assert!(SystemFontType::SerifBold.is_bold());
2918
2919 assert!(!SystemFontType::Ui.is_bold());
2920 assert!(!SystemFontType::Monospace.is_bold());
2921 assert!(!SystemFontType::MonospaceItalic.is_bold());
2922 assert!(!SystemFontType::Title.is_bold());
2923 assert!(!SystemFontType::Menu.is_bold());
2924 assert!(!SystemFontType::Small.is_bold());
2925 assert!(!SystemFontType::Serif.is_bold());
2926 }
2927
2928 #[test]
2929 fn is_italic_matches_exactly_the_italic_variant() {
2930 assert!(SystemFontType::MonospaceItalic.is_italic());
2931 for ty in ALL_FONT_TYPES {
2932 if ty != SystemFontType::MonospaceItalic {
2933 assert!(!ty.is_italic(), "{ty:?} must not be italic");
2934 }
2935 }
2936 }
2937
2938 #[test]
2939 fn predicates_agree_with_the_css_keyword() {
2940 for ty in ALL_FONT_TYPES {
2941 let s = ty.as_css_str();
2942 assert_eq!(ty.is_bold(), s.ends_with(":bold"), "{ty:?} -> {s:?}");
2943 assert_eq!(ty.is_italic(), s.ends_with(":italic"), "{ty:?} -> {s:?}");
2944 assert!(!(ty.is_bold() && ty.is_italic()), "{ty:?} is bold *and* italic");
2946 }
2947 }
2948
2949 #[test]
2952 fn fallback_chains_are_non_empty_and_deduplicated() {
2953 for platform in all_platforms() {
2954 for ty in ALL_FONT_TYPES {
2955 let chain = ty.get_fallback_chain(&platform);
2956 assert!(!chain.is_empty(), "{ty:?} on {platform:?} has an empty chain");
2957 assert!(
2958 chain.iter().all(|f| !f.trim().is_empty()),
2959 "{ty:?} on {platform:?} has a blank family: {chain:?}"
2960 );
2961 let mut sorted = chain.clone();
2962 sorted.sort_unstable();
2963 assert!(
2964 sorted.windows(2).all(|w| w[0] != w[1]),
2965 "{ty:?} on {platform:?} lists a duplicate family: {chain:?}"
2966 );
2967 }
2968 }
2969 }
2970
2971 #[test]
2972 fn fallback_chain_is_deterministic() {
2973 for platform in all_platforms() {
2974 for ty in ALL_FONT_TYPES {
2975 assert_eq!(
2976 ty.get_fallback_chain(&platform),
2977 ty.get_fallback_chain(&platform),
2978 "{ty:?} on {platform:?} is not deterministic"
2979 );
2980 }
2981 }
2982 }
2983
2984 #[test]
2985 fn ios_shares_the_macos_fallback_chain() {
2986 for ty in ALL_FONT_TYPES {
2987 assert_eq!(
2988 ty.get_fallback_chain(&Platform::Ios),
2989 ty.get_fallback_chain(&Platform::MacOs),
2990 "{ty:?}"
2991 );
2992 }
2993 }
2994
2995 #[test]
2996 fn linux_fallback_chain_ignores_the_desktop_environment() {
2997 let gnome = Platform::Linux(DesktopEnvironment::Gnome);
2998 let kde = Platform::Linux(DesktopEnvironment::Kde);
2999 let other = Platform::Linux(DesktopEnvironment::Other(AzString::from_const_str("")));
3000 for ty in ALL_FONT_TYPES {
3001 let a = ty.get_fallback_chain(&gnome);
3002 assert_eq!(a, ty.get_fallback_chain(&kde), "{ty:?}");
3003 assert_eq!(a, ty.get_fallback_chain(&other), "{ty:?}");
3004 }
3005 }
3006
3007 #[test]
3008 fn unknown_platform_falls_back_to_generic_css_families() {
3009 for ty in ALL_FONT_TYPES {
3010 let chain = ty.get_fallback_chain(&Platform::Unknown);
3011 assert_eq!(chain.len(), 1, "{ty:?} -> {chain:?}");
3012 let expected = if ty.is_italic() || matches!(
3013 ty,
3014 SystemFontType::Monospace | SystemFontType::MonospaceBold
3015 ) {
3016 "monospace"
3017 } else if matches!(ty, SystemFontType::Serif | SystemFontType::SerifBold) {
3018 "serif"
3019 } else {
3020 "sans-serif"
3021 };
3022 assert_eq!(chain[0], expected, "{ty:?}");
3023 }
3024 }
3025
3026 #[test]
3027 fn monospace_variants_share_one_chain_per_platform() {
3028 for platform in all_platforms() {
3029 let base = SystemFontType::Monospace.get_fallback_chain(&platform);
3030 assert_eq!(
3031 SystemFontType::MonospaceBold.get_fallback_chain(&platform),
3032 base,
3033 "{platform:?}"
3034 );
3035 assert_eq!(
3036 SystemFontType::MonospaceItalic.get_fallback_chain(&platform),
3037 base,
3038 "{platform:?}"
3039 );
3040 }
3041 }
3042
3043 #[test]
3046 fn platform_current_is_deterministic_and_matches_target_os() {
3047 let a = Platform::current();
3048 assert_eq!(a, Platform::current());
3049
3050 #[cfg(target_os = "linux")]
3051 assert!(matches!(a, Platform::Linux(_)), "{a:?}");
3052 #[cfg(target_os = "windows")]
3053 assert_eq!(a, Platform::Windows);
3054 #[cfg(target_os = "macos")]
3055 assert_eq!(a, Platform::MacOs);
3056 #[cfg(target_os = "android")]
3057 assert_eq!(a, Platform::Android);
3058 #[cfg(target_os = "ios")]
3059 assert_eq!(a, Platform::Ios);
3060
3061 #[cfg(any(
3063 target_os = "linux",
3064 target_os = "windows",
3065 target_os = "macos",
3066 target_os = "android",
3067 target_os = "ios"
3068 ))]
3069 assert_ne!(a, Platform::Unknown);
3070
3071 assert_eq!(Platform::default(), Platform::Unknown);
3073 }
3074
3075 #[test]
3078 fn titlebar_metrics_have_sane_geometry() {
3079 let all = [
3084 ("windows", TitlebarMetrics::windows()),
3085 ("macos", TitlebarMetrics::macos()),
3086 ("linux_gnome", TitlebarMetrics::linux_gnome()),
3087 ("ios", TitlebarMetrics::ios()),
3088 ("android", TitlebarMetrics::android()),
3089 ];
3090 for (name, tm) in all {
3091 let height = tm
3092 .height
3093 .as_ref()
3094 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3095 .expect("titlebar height must be set");
3096 assert!(height.is_finite() && height > 0.0, "{name}: height {height}");
3097
3098 let button_area = tm
3099 .button_area_width
3100 .as_ref()
3101 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3102 .expect("button area width must be set");
3103 assert!(
3104 button_area.is_finite() && button_area >= 0.0,
3105 "{name}: button_area_width {button_area}"
3106 );
3107
3108 let padding = tm
3109 .padding_horizontal
3110 .as_ref()
3111 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3112 .expect("padding must be set");
3113 assert!(padding.is_finite() && padding >= 0.0, "{name}: padding {padding}");
3114
3115 let size = tm.title_font_size.into_option().expect("font size must be set");
3116 assert!(size.is_finite() && size > 0.0, "{name}: font size {size}");
3117
3118 let weight = tm.title_font_weight.into_option().expect("font weight must be set");
3119 assert!((100..=900).contains(&weight), "{name}: weight {weight}");
3120 }
3121 }
3122
3123 #[test]
3124 fn titlebar_metrics_match_their_platform_conventions() {
3125 let win = TitlebarMetrics::windows();
3126 assert_eq!(win.button_side, TitlebarButtonSide::Right);
3127 assert!(win.buttons.has_close && win.buttons.has_minimize && win.buttons.has_maximize);
3128 assert!(!win.buttons.has_fullscreen);
3129
3130 let mac = TitlebarMetrics::macos();
3132 assert_eq!(mac.button_side, TitlebarButtonSide::Left);
3133 assert!(mac.buttons.has_fullscreen);
3134 assert!(!mac.buttons.has_maximize);
3135
3136 assert_eq!(TitlebarMetrics::linux_gnome().button_side, TitlebarButtonSide::Right);
3137
3138 for (name, tm) in [("ios", TitlebarMetrics::ios()), ("android", TitlebarMetrics::android())] {
3140 let b = tm.buttons;
3141 assert!(
3142 !b.has_close && !b.has_minimize && !b.has_maximize && !b.has_fullscreen,
3143 "{name} must not expose window controls"
3144 );
3145 }
3146
3147 let ios = TitlebarMetrics::ios();
3149 assert!(ios.safe_area.top.is_some());
3150 assert!(ios.safe_area.bottom.is_some());
3151 assert_eq!(TitlebarMetrics::windows().safe_area, SafeAreaInsets::default());
3152 }
3153
3154 #[test]
3157 fn system_style_new_detect_and_default_for_platform_agree() {
3158 let a = SystemStyle::new();
3159 let b = SystemStyle::detect();
3160 let c = SystemStyle::default_for_platform();
3161 assert_eq!(a, b);
3162 assert_eq!(b, c);
3163 }
3164
3165 #[test]
3166 fn system_style_constructors_arm_the_ffi_drop_guard() {
3167 assert!(SystemStyle::default().run_destructor);
3170 assert!(SystemStyle::new().run_destructor);
3171 assert!(SystemStyle::detect().run_destructor);
3172 for (name, style) in all_default_styles() {
3173 assert!(style.run_destructor, "{name} does not own its heap pointers");
3174 assert!(style.clone().run_destructor, "clone of {name} lost the guard");
3175 }
3176 }
3177
3178 #[test]
3179 fn system_style_default_is_empty_but_valid() {
3180 let d = SystemStyle::default();
3181 assert_eq!(d.platform, Platform::Unknown);
3182 assert_eq!(d.theme, Theme::Light);
3183 assert!(d.app_specific_stylesheet.is_none());
3184 assert!(d.scrollbar.is_none());
3185 assert!(d.language.as_str().is_empty());
3186 assert!(d.colors.text.is_none());
3187 }
3188
3189 #[test]
3192 fn default_styles_are_fully_populated() {
3193 for (name, style) in all_default_styles() {
3194 assert!(style.colors.text.is_some(), "{name}: no text color");
3195 assert!(style.colors.background.is_some(), "{name}: no background color");
3196 assert!(style.colors.accent.is_some(), "{name}: no accent color");
3197 assert!(style.fonts.ui_font.is_some(), "{name}: no UI font");
3198 assert!(style.fonts.monospace_font.is_some(), "{name}: no monospace font");
3199 assert!(!style.language.as_str().is_empty(), "{name}: empty language");
3200 assert_ne!(style.platform, Platform::Unknown, "{name}: unknown platform");
3201
3202 let size = style.fonts.ui_font_size.into_option().expect("ui font size");
3203 assert!(size.is_finite() && size > 0.0, "{name}: ui font size {size}");
3204
3205 let radius = style
3206 .metrics
3207 .corner_radius
3208 .as_ref()
3209 .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
3210 .expect("corner radius");
3211 assert!(radius.is_finite() && radius >= 0.0, "{name}: corner radius {radius}");
3212 }
3213 }
3214
3215 #[test]
3216 fn default_styles_carry_a_fully_resolved_scrollbar() {
3217 for (name, style) in all_default_styles() {
3220 let sb = style.scrollbar.as_ref().unwrap_or_else(|| panic!("{name}: no scrollbar"));
3221 assert!(sb.width.is_some(), "{name}: scrollbar width lost");
3222 assert!(sb.thumb_color.is_some(), "{name}: thumb color lost");
3223 assert!(sb.track_color.is_some(), "{name}: track color lost");
3224 }
3225 }
3226
3227 #[test]
3228 fn light_and_dark_default_styles_differ() {
3229 assert_ne!(defaults::windows_11_light(), defaults::windows_11_dark());
3230 assert_ne!(defaults::macos_modern_light(), defaults::macos_modern_dark());
3231 assert_ne!(defaults::gnome_adwaita_light(), defaults::gnome_adwaita_dark());
3232 assert_ne!(defaults::android_material_light(), defaults::android_holo_dark());
3233
3234 assert_eq!(defaults::windows_11_dark().theme, Theme::Dark);
3235 assert_eq!(defaults::macos_modern_dark().theme, Theme::Dark);
3236 assert_eq!(defaults::gnome_adwaita_dark().theme, Theme::Dark);
3237 assert_eq!(defaults::android_holo_dark().theme, Theme::Dark);
3238
3239 assert_eq!(defaults::kde_breeze_light().platform, Platform::Linux(DesktopEnvironment::Kde));
3240 assert_eq!(defaults::ios_light().platform, Platform::Ios);
3241 }
3242
3243 #[test]
3244 fn default_style_constructors_are_deterministic() {
3245 for _ in 0..3 {
3246 assert_eq!(defaults::windows_xp_luna(), defaults::windows_xp_luna());
3247 assert_eq!(defaults::macos_aqua(), defaults::macos_aqua());
3248 assert_eq!(defaults::gtk2_clearlooks(), defaults::gtk2_clearlooks());
3249 assert_eq!(defaults::windows_7_aero(), defaults::windows_7_aero());
3250 }
3251 }
3252
3253 #[test]
3256 fn to_json_string_has_balanced_braces_for_every_default() {
3257 let mut styles = all_default_styles();
3258 styles.push(("default", SystemStyle::default()));
3259 for (name, style) in styles {
3260 let json = style.to_json_string();
3261 let s = json.as_str();
3262 assert!(s.starts_with('{'), "{name}: does not start with '{{'");
3263 assert!(s.ends_with('}'), "{name}: does not end with '}}'");
3264 let open = s.chars().filter(|c| *c == '{').count();
3265 let close = s.chars().filter(|c| *c == '}').count();
3266 assert_eq!(open, close, "{name}: unbalanced braces");
3267 for key in [
3268 "\"theme\"",
3269 "\"platform\"",
3270 "\"colors\"",
3271 "\"fonts\"",
3272 "\"titlebar\"",
3273 "\"input\"",
3274 "\"accessibility\"",
3275 "\"audio\"",
3276 ] {
3277 assert!(s.contains(key), "{name}: missing {key}");
3278 }
3279 }
3280 }
3281
3282 #[test]
3283 fn to_json_string_reports_known_values() {
3284 let json = defaults::windows_11_light().to_json_string();
3285 let s = json.as_str();
3286 assert!(s.contains("\"theme\": \"Light\""), "{s}");
3287 assert!(s.contains("\"platform\": \"Windows\""), "{s}");
3288 assert!(s.contains("\"language\": \"en-US\""), "{s}");
3289 assert!(s.contains("\"text\": \"#000000ff\""), "{s}");
3291 assert!(s.contains("\"height\": 32.0"), "{s}");
3293 assert!(s.contains("\"grid\": null"), "{s}");
3295 }
3296
3297 #[test]
3298 fn to_json_string_survives_nan_and_infinite_metrics() {
3299 let mut style = SystemStyle::default();
3300 style.accessibility.text_scale_factor = f32::NAN;
3301 style.animation.animation_duration_factor = f32::INFINITY;
3302 style.input.double_click_distance_px = f32::NEG_INFINITY;
3303 style.input.drag_threshold_px = f32::MAX;
3304 style.input.caret_width_px = f32::MIN_POSITIVE;
3305 style.input.double_click_time_ms = u32::MAX;
3306 style.input.caret_blink_rate_ms = u32::MAX;
3307 style.input.wheel_scroll_lines = u32::MAX;
3308 style.input.hover_time_ms = u32::MAX;
3309 style.text_rendering.font_smoothing_gamma = u32::MAX;
3310 style.linux.cursor_size = u32::MAX;
3311
3312 let json = style.to_json_string();
3314 let s = json.as_str();
3315 assert!(!s.is_empty());
3316 assert!(s.contains(&format!("\"cursor_size\": {}", u32::MAX)), "{s}");
3317 assert!(s.contains(&format!("\"double_click_time_ms\": {}", u32::MAX)), "{s}");
3318 }
3319
3320 #[test]
3321 fn to_json_string_survives_extreme_pixel_metrics() {
3322 let mut style = SystemStyle::default();
3323 style.metrics.titlebar.height = OptionPixelValue::Some(PixelValue::px(f32::NAN));
3324 style.metrics.titlebar.button_area_width =
3325 OptionPixelValue::Some(PixelValue::px(f32::INFINITY));
3326 style.metrics.titlebar.padding_horizontal =
3327 OptionPixelValue::Some(PixelValue::px(f32::NEG_INFINITY));
3328 style.metrics.titlebar.title_font_size = OptionF32::Some(f32::MAX);
3329 style.metrics.titlebar.title_font_weight = OptionU16::Some(u16::MAX);
3330
3331 let json = style.to_json_string();
3332 assert!(!json.as_str().is_empty());
3333
3334 let nan_px = PixelValue::px(f32::NAN).to_pixels_internal(0.0, 0.0, 0.0);
3337 assert_eq!(nan_px, 0.0);
3338 assert!(PixelValue::px(f32::INFINITY)
3339 .to_pixels_internal(0.0, 0.0, 0.0)
3340 .is_finite());
3341 assert!(PixelValue::px(f32::NEG_INFINITY)
3342 .to_pixels_internal(0.0, 0.0, 0.0)
3343 .is_finite());
3344 }
3345
3346 #[test]
3347 fn to_json_string_survives_hostile_strings() {
3348 let mut style = SystemStyle::default();
3351 style.language = AzString::from("\"\\\n\t\u{1F600}");
3352 style.fonts.ui_font = OptionString::Some(AzString::from("a\"b\\c"));
3353 style.linux.gtk_theme = OptionString::Some(AzString::from("\u{202E}evil"));
3354
3355 let json = style.to_json_string();
3356 let s = json.as_str();
3357 assert!(!s.is_empty());
3358 assert!(s.contains("\"language\":"), "{s}");
3359 }
3360
3361 #[test]
3362 fn to_json_string_is_deterministic() {
3363 let style = defaults::gnome_adwaita_dark();
3364 assert_eq!(style.to_json_string(), style.to_json_string());
3365 assert_ne!(
3366 defaults::gnome_adwaita_dark().to_json_string(),
3367 defaults::gnome_adwaita_light().to_json_string()
3368 );
3369 }
3370
3371 #[test]
3374 fn csd_stylesheet_rules_all_carry_system_priority() {
3375 let mut styles = all_default_styles();
3376 styles.push(("default", SystemStyle::default()));
3377 for (name, style) in styles {
3378 let css = style.create_csd_stylesheet();
3379 let rules = css.rules.as_slice();
3380 assert!(!rules.is_empty(), "{name}: produced no rules");
3381 for rule in rules {
3382 assert_eq!(
3383 rule.priority,
3384 rule_priority::SYSTEM,
3385 "{name}: rule escaped the SYSTEM layer"
3386 );
3387 }
3388 const _: () = assert!(rule_priority::SYSTEM < rule_priority::AUTHOR);
3390 }
3391 }
3392
3393 #[test]
3394 fn csd_stylesheet_uses_fallback_colors_when_the_system_reports_none() {
3395 let css = SystemStyle::default().create_csd_stylesheet();
3397 assert!(!css.rules.as_slice().is_empty());
3398 assert_ne!(css, Css::default());
3399 }
3400
3401 #[test]
3402 fn csd_stylesheet_is_platform_specific() {
3403 let mac = defaults::macos_modern_light().create_csd_stylesheet();
3404 let win = defaults::windows_11_light().create_csd_stylesheet();
3405 let lin = defaults::gnome_adwaita_light().create_csd_stylesheet();
3406 assert_ne!(mac, win);
3407 assert_ne!(win, lin);
3408 assert_ne!(mac, lin);
3409 assert!(mac.rules.as_slice().len() > win.rules.as_slice().len());
3411 }
3412
3413 #[test]
3414 fn csd_stylesheet_survives_extreme_corner_radius() {
3415 for radius in [
3416 PixelValue::px(f32::NAN),
3417 PixelValue::px(f32::INFINITY),
3418 PixelValue::px(f32::NEG_INFINITY),
3419 PixelValue::px(f32::MAX),
3420 PixelValue::px(-1.0),
3421 PixelValue::percent(f32::MAX),
3422 PixelValue::em(f32::MIN),
3423 ] {
3424 let mut style = defaults::windows_11_light();
3425 style.metrics.corner_radius = OptionPixelValue::Some(radius);
3426 let css = style.create_csd_stylesheet();
3427 assert!(
3428 !css.rules.as_slice().is_empty(),
3429 "radius {radius:?} produced no rules"
3430 );
3431 for rule in css.rules.as_slice() {
3432 assert_eq!(rule.priority, rule_priority::SYSTEM);
3433 }
3434 }
3435 }
3436
3437 #[test]
3438 fn csd_stylesheet_is_deterministic() {
3439 let style = defaults::kde_breeze_light();
3440 assert_eq!(style.create_csd_stylesheet(), style.create_csd_stylesheet());
3441 }
3442
3443 #[test]
3451 fn ricing_mode_is_deterministic_and_total() {
3452 let mode = ricing_mode();
3453 assert_eq!(mode, ricing_mode(), "ricing_mode() is not deterministic");
3454 assert!(
3455 matches!(mode, RicingMode::Off | RicingMode::Default | RicingMode::Force),
3456 "{mode:?}"
3457 );
3458 assert_eq!(RicingMode::default(), RicingMode::Default);
3459 }
3460
3461 #[test]
3462 fn ricing_enabled_is_the_inverse_of_off() {
3463 assert_eq!(ricing_enabled(), ricing_mode() != RicingMode::Off);
3464 assert_eq!(ricing_enabled(), ricing_enabled());
3465 }
3466
3467 #[test]
3468 fn detect_linux_desktop_env_is_deterministic() {
3469 let a = detect_linux_desktop_env();
3470 assert_eq!(a, detect_linux_desktop_env());
3471
3472 let blank_env = |k: &str| std::env::var(k).map(|v| v.is_empty()).unwrap_or(false);
3476 if !blank_env("XDG_CURRENT_DESKTOP") && !blank_env("DESKTOP_SESSION") {
3477 if let DesktopEnvironment::Other(ref name) = a {
3478 assert!(!name.as_str().is_empty(), "empty desktop-environment label");
3479 }
3480 }
3481 }
3482
3483 #[test]
3484 fn detect_system_language_is_a_normalized_tag() {
3485 let lang = detect_system_language();
3486 let s = lang.as_str();
3487 assert!(!s.is_empty(), "language tag must never be empty");
3488 assert!(!s.contains('.'), "{s:?} still carries an encoding suffix");
3491 assert!(!s.contains(':'), "{s:?} still carries a locale list");
3492 assert!(!s.contains('_'), "{s:?} is not BCP 47 (underscore)");
3493 assert_eq!(lang, detect_system_language(), "not deterministic");
3494 }
3495}