1
//! Discovers system-native styling for colors, fonts, and other metrics.
2
//!
3
//! This module provides a best-effort attempt to query the host operating system
4
//! for its UI theme information. This is gated behind the **`io`** feature flag.
5
//!
6
//! **End-user customization (`AZ_RICING`):**
7
//! By default (if the `io` feature is enabled), Azul looks for an
8
//! application-specific stylesheet at `~/.config/azul/styles/<app_name>.css`
9
//! (or `%APPDATA%\azul\styles\<app_name>.css` on Windows) and applies it as
10
//! the last layer of the cascade, letting end-users "rice" any Azul app.
11
//!
12
//! The `AZ_RICING` env var has three modes (case-insensitive):
13
//!
14
//! - unset (default): load the user CSS if present; on Linux, the
15
//!   detection chain is `KDE > GNOME > riced > defaults`.
16
//! - `AZ_RICING=off` (aliases: `disabled`, `none`, `0`): skip the user
17
//!   CSS file and the riced-desktop sources (Hyprland config, pywal
18
//!   cache). Use for kiosk builds or CI runs that mustn't pick up local
19
//!   customization.
20
//! - `AZ_RICING=force` (aliases: `prefer`, `aggressive`, `1`): on Linux,
21
//!   reorder the detection chain so riced-desktop sources win over
22
//!   GNOME/KDE — useful for tiling-WM users whose `XDG_CURRENT_DESKTOP`
23
//!   still says `gnome`. The user CSS file still loads.
24

            
25
#![cfg(feature = "parser")]
26

            
27
use alloc::{
28
    boxed::Box,
29
    string::{String, ToString},
30
    vec::Vec,
31
};
32
use 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

            
45
use crate::dynamic_selector::{BoolCondition, OsVersion};
46
use core::fmt::Write;
47

            
48
// --- End-user customization mode ---
49

            
50
/// User-customization mode controlled by the `AZ_RICING` env var.
51
///
52
/// See the module-level documentation for the full description.
53
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54
#[derive(Default)]
55
pub enum RicingMode {
56
    /// `AZ_RICING=off` (or `disabled` / `none` / `0`). Skip the user
57
    /// CSS file *and* the riced-desktop sources. Vanilla detection.
58
    Off,
59
    /// Unset. Load the user CSS if present; standard detection chain
60
    /// (`KDE > GNOME > riced > defaults` on Linux).
61
    #[default]
62
    Default,
63
    /// `AZ_RICING=force` (or `prefer` / `aggressive` / `1`). Reorder
64
    /// the Linux detection chain so riced-desktop sources win over
65
    /// GNOME/KDE. The user CSS file still loads.
66
    Force,
67
}
68

            
69

            
70
/// Read the `AZ_RICING` env var and classify it. Case-insensitive.
71
/// Anything we don't recognise falls through to `Default` so a typo
72
/// degrades gracefully instead of disabling the feature silently.
73
6
#[must_use] pub fn ricing_mode() -> RicingMode {
74
6
    let Ok(raw) = std::env::var("AZ_RICING") else {
75
6
        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
6
}
83

            
84
/// True when the user CSS file at `~/.config/azul/styles/<app>.css`
85
/// should be read. False only when `AZ_RICING=off` is set.
86
3
#[must_use] pub fn ricing_enabled() -> bool {
87
3
    !matches!(ricing_mode(), RicingMode::Off)
88
3
}
89

            
90
// --- Public Data Structures ---
91
#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
92
/// Represents the detected platform.
93
#[derive(Debug, Default, Clone, PartialEq, Eq)]
94
#[repr(C, u8)]
95
pub enum Platform {
96
    Windows,
97
    MacOs,
98
    Linux(DesktopEnvironment),
99
    Android,
100
    Ios,
101
    #[default]
102
    Unknown,
103
}
104

            
105
impl Platform {
106
    /// Get the current platform at compile time.
107
    #[inline]
108
9935
    #[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
9935
        { 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
9935
    }
122
}
123
#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
124
/// Represents the detected Linux Desktop Environment.
125
#[derive(Debug, Clone, PartialEq, Eq)]
126
#[repr(C, u8)]
127
pub enum DesktopEnvironment {
128
    Gnome,
129
    Kde,
130
    Other(AzString),
131
}
132

            
133
/// The overall theme type.
134
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
135
#[repr(C)]
136
pub enum Theme {
137
    #[default]
138
    Light,
139
    Dark,
140
}
141

            
142
/// A unified collection of discovered system style properties.
143
#[derive(Debug, Clone, PartialEq)]
144
#[repr(C)]
145
pub struct SystemStyle {
146
    pub fonts: SystemFonts,
147
    pub metrics: SystemMetrics,
148
    /// Linux-specific customisation (icon theme, cursor theme, GTK theme, ...)
149
    pub linux: LinuxCustomization,
150
    pub platform: Platform,
151
    /// Focus ring / indicator visual style
152
    pub focus_visuals: FocusVisuals,
153
    /// System language/locale in BCP 47 format (e.g., "en-US", "de-DE")
154
    /// Detected from OS settings at startup
155
    pub language: AzString,
156
    /// An optional, user-provided stylesheet loaded from a conventional
157
    /// location (`~/.config/azul/styles/<app_name>.css`), allowing for
158
    /// application-specific "ricing". Only loaded when the "io" feature
159
    /// is enabled and `AZ_RICING` is not set to `off`.
160
    pub app_specific_stylesheet: Option<Box<Css>>,
161
    /// Scrollbar style information (boxed to ensure stable FFI size)
162
    pub scrollbar: Option<Box<ComputedScrollbarStyle>>,
163
    /// Global scroll physics configuration (momentum, friction, rubber-banding).
164
    /// Platform-specific defaults are applied during system style discovery.
165
    /// Applications can override this to change the "feel" of scrolling globally.
166
    pub scroll_physics: ScrollPhysics,
167
    pub theme: Theme,
168
    /// Detected OS version (e.g., Windows 11 22H2, macOS Sonoma, etc.)
169
    pub os_version: OsVersion,
170
    /// User prefers reduced motion (accessibility setting)
171
    pub prefers_reduced_motion: BoolCondition,
172
    /// User prefers high contrast (accessibility setting)
173
    pub prefers_high_contrast: BoolCondition,
174
    /// Detailed accessibility settings (superset of `prefers_reduced_motion` / `prefers_high_contrast`)
175
    pub accessibility: AccessibilitySettings,
176
    /// Which hand the user operates the device with. Touch UIs put their
177
    /// primary controls on that side so the thumb reaches them.
178
    ///
179
    /// This is INDEPENDENT of text direction: an Arabic left-hander reads
180
    /// right-to-left but still reaches with the left hand, and a
181
    /// left-handed English user reads left-to-right. Deriving one from the
182
    /// other is a bug, so they are separate settings.
183
    pub handedness: Handedness,
184
    /// Input interaction timing / distance thresholds from the OS
185
    pub input: InputMetrics,
186
    /// Text rendering / anti-aliasing hints from the OS
187
    pub text_rendering: TextRenderingHints,
188
    /// OS-level scrollbar visibility / click-behaviour preferences
189
    pub scrollbar_preferences: ScrollbarPreferences,
190
    /// Visual hints: icons in menus/buttons, toolbar style, tooltips
191
    pub visual_hints: VisualHints,
192
    /// Animation enable/disable, speed factor, focus indicator behaviour
193
    pub animation: AnimationMetrics,
194
    pub colors: SystemColors,
195
    /// Icon-specific styling options (grayscale, tinting, etc.)
196
    pub icon_style: IconStyleOptions,
197
    /// Audio feedback preferences (event sounds, input sounds)
198
    pub audio: AudioMetrics,
199
    /// FFI double-drop guard. `SystemStyle` owns two heap pointers
200
    /// (`app_specific_stylesheet`, `scrollbar`). The codegen Az wrapper
201
    /// (`AzSystemStyle`) gets an `impl Drop` -> `AzSystemStyle_delete` ->
202
    /// `drop_in_place::<SystemStyle>`, and is nested by value as
203
    /// `AzAppConfig.system_style`. Dropping an `AzAppConfig` by value
204
    /// therefore drops the real `SystemStyle` once (freeing both Boxes) and
205
    /// then re-runs `_delete` on the SAME bytes via drop-glue -> double free.
206
    /// Same class as `GlContextPtr` / `IconProviderHandle` (see core/src/icon.rs).
207
    /// The first `Drop` disarms this flag; the second sees it cleared and
208
    /// neutralizes itself (takes + forgets the already-freed Boxes) so the
209
    /// redundant drop-glue is a no-op. Defaults to `true` (own + free once).
210
    pub run_destructor: bool,
211
}
212

            
213
impl Default for SystemStyle {
214
36754
    fn default() -> Self {
215
36754
        Self {
216
36754
            fonts: SystemFonts::default(),
217
36754
            metrics: SystemMetrics::default(),
218
36754
            linux: LinuxCustomization::default(),
219
36754
            platform: Platform::default(),
220
36754
            focus_visuals: FocusVisuals::default(),
221
36754
            handedness: Handedness::default(),
222
36754
            language: AzString::default(),
223
36754
            app_specific_stylesheet: None,
224
36754
            scrollbar: None,
225
36754
            scroll_physics: ScrollPhysics::default(),
226
36754
            theme: Theme::default(),
227
36754
            os_version: OsVersion::default(),
228
36754
            prefers_reduced_motion: BoolCondition::default(),
229
36754
            prefers_high_contrast: BoolCondition::default(),
230
36754
            accessibility: AccessibilitySettings::default(),
231
36754
            input: InputMetrics::default(),
232
36754
            text_rendering: TextRenderingHints::default(),
233
36754
            scrollbar_preferences: ScrollbarPreferences::default(),
234
36754
            visual_hints: VisualHints::default(),
235
36754
            animation: AnimationMetrics::default(),
236
36754
            colors: SystemColors::default(),
237
36754
            icon_style: IconStyleOptions::default(),
238
36754
            audio: AudioMetrics::default(),
239
36754
            run_destructor: true,
240
36754
        }
241
36754
    }
242
}
243

            
244
impl Drop for SystemStyle {
245
37356
    fn drop(&mut self) {
246
        // Gate the heap frees on `run_destructor` to defuse the codegen
247
        // double-drop (see the `run_destructor` field docs). drop_in_place
248
        // runs THIS method, then the field drop-glue; so:
249
        //  * FIRST drop (flag set): disarm the flag, then let the field
250
        //    drop-glue free the two Boxes exactly once.
251
        //  * SECOND drop on the same bytes (flag cleared by the first): the
252
        //    Boxes are already freed but the fields still hold dangling
253
        //    `Some(ptr)`. Take them out (-> None) and forget the dangling
254
        //    values so the trailing drop-glue is a no-op (never derefs/frees).
255
37356
        if self.run_destructor {
256
37356
            self.run_destructor = false;
257
37356
        } else {
258
            core::mem::forget(self.app_specific_stylesheet.take());
259
            core::mem::forget(self.scrollbar.take());
260
        }
261
37356
    }
262
}
263

            
264
/// Icon-specific styling options for accessibility and theming.
265
///
266
/// These settings affect how icons are rendered, supporting accessibility
267
/// needs like reduced colors and high contrast modes.
268
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
269
#[repr(C)]
270
pub struct IconStyleOptions {
271
    /// If true, icons should be rendered in grayscale (for color-blind users
272
    /// or reduced color preference). Applies a CSS grayscale filter.
273
    pub prefer_grayscale: bool,
274
    /// Optional tint color to apply to icons. Useful for matching icons
275
    /// to the current theme or for high contrast modes.
276
    pub tint_color: OptionColorU,
277
    /// If true, icons should inherit the current text color instead of
278
    /// using their original colors. Works well with font-based icons.
279
    pub inherit_text_color: bool,
280
}
281

            
282
/// System font types that can be resolved at runtime based on OS settings.
283
/// 
284
/// This enum allows specifying semantic font roles that get resolved to
285
/// actual font families based on the current platform and user preferences.
286
/// For example, `Monospace` resolves to:
287
/// - macOS: SF Mono or Menlo
288
/// - Windows: Cascadia Mono or Consolas
289
/// - Linux: Ubuntu Mono or `DejaVu` Sans Mono
290
/// 
291
/// Font variants (bold, italic) can be combined with the base type.
292
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
293
#[repr(C)]
294
pub enum SystemFontType {
295
    /// UI font for buttons, labels, menus (SF Pro, Segoe UI, Cantarell)
296
    #[default]
297
    Ui,
298
    /// Bold variant of UI font
299
    UiBold,
300
    /// Monospace font for code (SF Mono, Consolas, Ubuntu Mono)
301
    Monospace,
302
    /// Bold variant of monospace font
303
    MonospaceBold,
304
    /// Italic variant of monospace font
305
    MonospaceItalic,
306
    /// Font for window titles
307
    Title,
308
    /// Bold variant of title font
309
    TitleBold,
310
    /// Font for menu items
311
    Menu,
312
    /// Small/caption font
313
    Small,
314
    /// Serif font for reading content (New York on macOS, Georgia on Windows)
315
    Serif,
316
    /// Bold variant of serif font
317
    SerifBold,
318
}
319

            
320

            
321
impl SystemFontType {
322
    /// Parse a `SystemFontType` from a CSS string.
323
    /// 
324
    /// Supported formats:
325
    /// - `system:ui`, `system:ui:bold`
326
    /// - `system:monospace`, `system:monospace:bold`, `system:monospace:italic`
327
    /// - `system:title`, `system:title:bold`
328
    /// - `system:menu`
329
    /// - `system:small`
330
    /// - `system:serif`, `system:serif:bold`
331
12897
    #[must_use] pub fn from_css_str(s: &str) -> Option<Self> {
332
12897
        let s = s.trim();
333
12897
        if !s.starts_with("system:") {
334
11491
            return None;
335
1406
        }
336
1406
        let rest = &s[7..]; // Skip "system:"
337
1406
        match rest {
338
1406
            "ui" => Some(Self::Ui),
339
97
            "ui:bold" => Some(Self::UiBold),
340
80
            "monospace" => Some(Self::Monospace),
341
74
            "monospace:bold" => Some(Self::MonospaceBold),
342
68
            "monospace:italic" => Some(Self::MonospaceItalic),
343
61
            "title" => Some(Self::Title),
344
56
            "title:bold" => Some(Self::TitleBold),
345
51
            "menu" => Some(Self::Menu),
346
46
            "small" => Some(Self::Small),
347
41
            "serif" => Some(Self::Serif),
348
36
            "serif:bold" => Some(Self::SerifBold),
349
31
            _ => None,
350
        }
351
12897
    }
352
    
353
    /// Get the CSS syntax for this system font type.
354
59
    #[must_use] pub const fn as_css_str(&self) -> &'static str {
355
59
        match self {
356
8
            Self::Ui => "system:ui",
357
5
            Self::UiBold => "system:ui:bold",
358
5
            Self::Monospace => "system:monospace",
359
6
            Self::MonospaceBold => "system:monospace:bold",
360
5
            Self::MonospaceItalic => "system:monospace:italic",
361
5
            Self::Title => "system:title",
362
5
            Self::TitleBold => "system:title:bold",
363
5
            Self::Menu => "system:menu",
364
5
            Self::Small => "system:small",
365
5
            Self::Serif => "system:serif",
366
5
            Self::SerifBold => "system:serif:bold",
367
        }
368
59
    }
369
    
370
    /// Returns true if this system font type implies bold weight.
371
    /// Used when resolving system fonts to pass the correct weight to fontconfig.
372
870
    #[must_use] pub const fn is_bold(&self) -> bool {
373
847
        matches!(
374
870
            self,
375
            Self::UiBold
376
                | Self::MonospaceBold
377
                | Self::TitleBold
378
                | Self::SerifBold
379
        )
380
870
    }
381
    
382
    /// Returns true if this system font type implies italic style.
383
874
    #[must_use] pub const fn is_italic(&self) -> bool {
384
874
        matches!(self, Self::MonospaceItalic)
385
874
    }
386
}
387

            
388
/// Accessibility settings detected from the operating system.
389
/// 
390
/// These settings allow apps to adapt their UI for users with accessibility needs.
391
/// Detection methods:
392
/// - macOS: `UIAccessibility` APIs (isBoldTextEnabled, isReduceMotionEnabled, etc.)
393
/// - Windows: `SystemParametersInfo` (`SPI_GETHIGHCONTRAST`, `SPI_GETCLIENTAREAANIMATION`)
394
/// - Linux: gsettings (org.gnome.desktop.interface, org.gnome.desktop.a11y)
395
#[derive(Debug, Default, Clone, Copy, PartialEq)]
396
#[repr(C)]
397
pub struct AccessibilitySettings {
398
    /// Text scaling factor (1.0 = normal, 1.5 = 150%, etc.)
399
    pub text_scale_factor: f32,
400
    /// User prefers bold text for better readability
401
    /// macOS: UIAccessibility.isBoldTextEnabled
402
    /// Windows: N/A (font scaling)
403
    /// Linux: org.gnome.desktop.interface text-scaling-factor
404
    pub prefers_bold_text: bool,
405
    /// User prefers larger text
406
    /// macOS: preferredContentSizeCategory
407
    /// Windows: `SystemParametersInfo` text scale factor
408
    /// Linux: org.gnome.desktop.interface text-scaling-factor
409
    pub prefers_larger_text: bool,
410
    /// User prefers high contrast colors
411
    /// macOS: UIAccessibility.isDarkerSystemColorsEnabled
412
    /// Windows: `SPI_GETHIGHCONTRAST`
413
    /// Linux: org.gnome.desktop.a11y.interface high-contrast
414
    pub prefers_high_contrast: bool,
415
    /// User prefers reduced motion/animations
416
    /// macOS: UIAccessibility.isReduceMotionEnabled
417
    /// Windows: `SPI_GETCLIENTAREAANIMATION` (inverted)
418
    /// Linux: org.gnome.desktop.interface enable-animations (inverted)
419
    pub prefers_reduced_motion: bool,
420
    /// User prefers reduced transparency
421
    /// macOS: UIAccessibility.isReduceTransparencyEnabled
422
    /// Windows: N/A
423
    /// Linux: N/A
424
    pub prefers_reduced_transparency: bool,
425
    /// Screen reader is active (`VoiceOver`, Narrator, Orca)
426
    pub screen_reader_active: bool,
427
    /// User prefers differentiate without color
428
    /// macOS: UIAccessibility.shouldDifferentiateWithoutColor
429
    pub differentiate_without_color: bool,
430
}
431

            
432
/// Common system colors used for UI elements.
433
/// 
434
/// These colors are queried from the operating system and automatically adapt
435
/// to the current theme (light/dark mode) and accent color settings.
436
/// 
437
/// On macOS, these correspond to `NSColor` semantic colors.
438
/// On Windows, these come from `UISettings`.
439
/// On Linux/GTK, these come from the GTK theme.
440
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
441
#[repr(C)]
442
pub struct SystemColors {
443
    // === Primary semantic colors ===
444
    /// Primary text color (NSColor.textColor on macOS)
445
    pub text: OptionColorU,
446
    /// Secondary text color for less prominent text (NSColor.secondaryLabelColor)
447
    pub secondary_text: OptionColorU,
448
    /// Tertiary text color for disabled/placeholder text (NSColor.tertiaryLabelColor)
449
    pub tertiary_text: OptionColorU,
450
    /// Background color for content areas (NSColor.textBackgroundColor)
451
    pub background: OptionColorU,
452
    
453
    // === Accent colors ===
454
    /// System accent color chosen by user (NSColor.controlAccentColor on macOS)
455
    pub accent: OptionColorU,
456
    /// Text color on accent backgrounds
457
    pub accent_text: OptionColorU,
458
    
459
    // === Control colors ===
460
    /// Button/control background (NSColor.controlColor)
461
    pub button_face: OptionColorU,
462
    /// Button/control text color (NSColor.controlTextColor)
463
    pub button_text: OptionColorU,
464
    /// Disabled control text color (NSColor.disabledControlTextColor)
465
    pub disabled_text: OptionColorU,
466
    
467
    // === Window colors ===
468
    /// Window background color (NSColor.windowBackgroundColor)
469
    pub window_background: OptionColorU,
470
    /// Under-page background color (NSColor.underPageBackgroundColor)
471
    pub under_page_background: OptionColorU,
472
    
473
    // === Selection colors ===
474
    /// Selection background when window is focused (NSColor.selectedContentBackgroundColor)
475
    pub selection_background: OptionColorU,
476
    /// Selection text color when window is focused
477
    pub selection_text: OptionColorU,
478
    /// Selection background when window is NOT focused (NSColor.unemphasizedSelectedContentBackgroundColor)
479
    /// This is used for :backdrop state styling
480
    pub selection_background_inactive: OptionColorU,
481
    /// Selection text color when window is NOT focused
482
    pub selection_text_inactive: OptionColorU,
483
    
484
    // === Additional semantic colors ===
485
    /// Link color (NSColor.linkColor)
486
    pub link: OptionColorU,
487
    /// Separator/divider color (NSColor.separatorColor)
488
    pub separator: OptionColorU,
489
    /// Grid/table line color (NSColor.gridColor)
490
    pub grid: OptionColorU,
491
    /// Find/search highlight color (NSColor.findHighlightColor)
492
    pub find_highlight: OptionColorU,
493
    
494
    // === Sidebar colors (macOS-specific) ===
495
    /// Sidebar background color
496
    pub sidebar_background: OptionColorU,
497
    /// Selected row in sidebar
498
    pub sidebar_selection: OptionColorU,
499
}
500

            
501
/// Common system font settings.
502
/// 
503
/// On macOS, these are queried from `NSFont`.
504
/// On Windows, these come from `SystemParametersInfo`.
505
/// On Linux, these come from GTK/gsettings.
506
#[derive(Debug, Default, Clone, PartialEq, Eq)]
507
#[repr(C)]
508
pub struct SystemFonts {
509
    /// The primary font used for UI elements like buttons and labels.
510
    /// On macOS: SF Pro (system font)
511
    /// On Windows: Segoe UI
512
    /// On Linux: Cantarell, Ubuntu, or system default
513
    pub ui_font: OptionString,
514
    /// The default font size for UI elements, in points.
515
    pub ui_font_size: OptionF32,
516
    /// The font used for code or other monospaced text.
517
    /// On macOS: SF Mono or Menlo
518
    /// On Windows: Cascadia Mono or Consolas
519
    /// On Linux: Ubuntu Mono or `DejaVu` Sans Mono
520
    pub monospace_font: OptionString,
521
    /// Monospace font size in points
522
    pub monospace_font_size: OptionF32,
523
    /// Bold variant of the UI font (if different)
524
    pub ui_font_bold: OptionString,
525
    /// Font for window titles
526
    pub title_font: OptionString,
527
    /// Title font size in points
528
    pub title_font_size: OptionF32,
529
    /// Font for menu items
530
    pub menu_font: OptionString,
531
    /// Menu font size in points
532
    pub menu_font_size: OptionF32,
533
    /// Small/caption font for less prominent text
534
    pub small_font: OptionString,
535
    /// Small font size in points
536
    pub small_font_size: OptionF32,
537
}
538

            
539
/// Common system metrics for UI element sizing and spacing.
540
#[derive(Debug, Default, Clone, PartialEq, Eq)]
541
#[repr(C)]
542
pub struct SystemMetrics {
543
    /// The corner radius for standard elements like buttons.
544
    pub corner_radius: OptionPixelValue,
545
    /// The width of standard borders.
546
    pub border_width: OptionPixelValue,
547
    /// The horizontal (left/right) padding for buttons and similar controls.
548
    pub button_padding_horizontal: OptionPixelValue,
549
    /// The vertical (top/bottom) padding for buttons and similar controls.
550
    pub button_padding_vertical: OptionPixelValue,
551
    /// Titlebar layout information (button positions, safe areas, etc.)
552
    pub titlebar: TitlebarMetrics,
553
}
554

            
555
/// Which side of the titlebar the window control buttons are on.
556
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
557
#[repr(C)]
558
pub enum TitlebarButtonSide {
559
    /// Buttons are on the left (macOS default)
560
    Left,
561
    /// Buttons are on the right (Windows, most Linux DEs)
562
    #[default]
563
    Right,
564
}
565

            
566
/// Which window control buttons are available in the titlebar.
567
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
568
#[repr(C)]
569
pub struct TitlebarButtons {
570
    /// Close button is available
571
    pub has_close: bool,
572
    /// Minimize button is available
573
    pub has_minimize: bool,
574
    /// Maximize/zoom button is available
575
    pub has_maximize: bool,
576
    /// Fullscreen button is available (macOS green button behavior)
577
    pub has_fullscreen: bool,
578
}
579

            
580
impl Default for TitlebarButtons {
581
37394
    fn default() -> Self {
582
37394
        Self {
583
37394
            has_close: true,
584
37394
            has_minimize: true,
585
37394
            has_maximize: true,
586
37394
            has_fullscreen: false,
587
37394
        }
588
37394
    }
589
}
590

            
591
/// Safe area insets for devices with notches, rounded corners, or sensor housings.
592
/// 
593
/// On devices like iPhones with notches or Dynamic Island, the safe area
594
/// indicates regions where content should not be placed to avoid being
595
/// obscured by hardware features.
596
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
597
#[repr(C)]
598
pub struct SafeAreaInsets {
599
    /// Inset from the top edge (notch, camera housing, etc.)
600
    pub top: OptionPixelValue,
601
    /// Inset from the bottom edge (home indicator on iPhone)
602
    pub bottom: OptionPixelValue,
603
    /// Inset from the left edge (rounded corners)
604
    pub left: OptionPixelValue,
605
    /// Inset from the right edge (rounded corners)
606
    pub right: OptionPixelValue,
607
}
608

            
609
/// Metrics for titlebar layout and window chrome.
610
/// 
611
/// This provides information needed to correctly position custom titlebar
612
/// content when using `WindowDecorations::NoTitle` (expanded title mode).
613
#[derive(Debug, Clone, PartialEq, Eq)]
614
#[repr(C)]
615
pub struct TitlebarMetrics {
616
    /// Which side the window control buttons are on
617
    pub button_side: TitlebarButtonSide,
618
    /// Which buttons are available
619
    pub buttons: TitlebarButtons,
620
    /// Height of the titlebar in pixels
621
    pub height: OptionPixelValue,
622
    /// Width reserved for window control buttons (close/min/max)
623
    /// This is the space to avoid when drawing custom title text
624
    pub button_area_width: OptionPixelValue,
625
    /// Horizontal padding inside the titlebar
626
    pub padding_horizontal: OptionPixelValue,
627
    /// Safe area insets for notched/rounded displays
628
    pub safe_area: SafeAreaInsets,
629
    /// Title text font (from `SystemFonts::title_font`)
630
    pub title_font: OptionString,
631
    /// Title text font size
632
    pub title_font_size: OptionF32,
633
    /// Title text font weight (400 = normal, 600 = semibold, 700 = bold)
634
    pub title_font_weight: OptionU16,
635
}
636

            
637
impl Default for TitlebarMetrics {
638
36758
    fn default() -> Self {
639
36758
        Self {
640
36758
            button_side: TitlebarButtonSide::Right,
641
36758
            buttons: TitlebarButtons::default(),
642
36758
            // None = "not detected", like title_font above: SystemMetrics::default()
643
36758
            // must be able to represent an unknown titlebar so PixelValueOrSystem's
644
36758
            // resolve() falls back to system detection instead of these hardcoded
645
36758
            // guesses. (The concrete px values pinned the fallback path unreachable.)
646
36758
            height: OptionPixelValue::None,
647
36758
            button_area_width: OptionPixelValue::None,
648
36758
            padding_horizontal: OptionPixelValue::None,
649
36758
            safe_area: SafeAreaInsets::default(),
650
36758
            title_font: OptionString::None,
651
36758
            title_font_size: OptionF32::Some(13.0),
652
36758
            title_font_weight: OptionU16::Some(600), // Semibold
653
36758
        }
654
36758
    }
655
}
656

            
657
impl TitlebarMetrics {
658
    /// Windows-style titlebar (buttons on right)
659
59
    #[must_use] pub fn windows() -> Self {
660
59
        Self {
661
59
            button_side: TitlebarButtonSide::Right,
662
59
            buttons: TitlebarButtons {
663
59
                has_close: true,
664
59
                has_minimize: true,
665
59
                has_maximize: true,
666
59
                has_fullscreen: false,
667
59
            },
668
59
            height: OptionPixelValue::Some(PixelValue::px(32.0)),
669
59
            button_area_width: OptionPixelValue::Some(PixelValue::px(138.0)), // 3 buttons * 46px
670
59
            padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
671
59
            safe_area: SafeAreaInsets::default(),
672
59
            title_font: OptionString::Some("Segoe UI Variable Text".into()),
673
59
            title_font_size: OptionF32::Some(12.0),
674
59
            title_font_weight: OptionU16::Some(400), // Normal
675
59
        }
676
59
    }
677
    
678
    /// macOS-style titlebar (buttons on left, "traffic lights")
679
27
    #[must_use] pub fn macos() -> Self {
680
27
        Self {
681
27
            button_side: TitlebarButtonSide::Left,
682
27
            buttons: TitlebarButtons {
683
27
                has_close: true,
684
27
                has_minimize: true,
685
27
                has_maximize: false, // macOS has fullscreen instead
686
27
                has_fullscreen: true,
687
27
            },
688
27
            height: OptionPixelValue::Some(PixelValue::px(28.0)),
689
27
            button_area_width: OptionPixelValue::Some(PixelValue::px(78.0)), // 3 buttons with gaps
690
27
            padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
691
27
            safe_area: SafeAreaInsets::default(),
692
27
            title_font: OptionString::Some(".SF NS".into()),
693
27
            title_font_size: OptionF32::Some(13.0),
694
27
            title_font_weight: OptionU16::Some(600), // Semibold
695
27
        }
696
27
    }
697
    
698
    /// Linux GNOME-style titlebar (buttons on right by default)
699
237
    #[must_use] pub fn linux_gnome() -> Self {
700
237
        Self {
701
237
            button_side: TitlebarButtonSide::Right, // Default, can be changed in settings
702
237
            buttons: TitlebarButtons {
703
237
                has_close: true,
704
237
                has_minimize: true,
705
237
                has_maximize: true,
706
237
                has_fullscreen: false,
707
237
            },
708
237
            height: OptionPixelValue::Some(PixelValue::px(35.0)),
709
237
            button_area_width: OptionPixelValue::Some(PixelValue::px(100.0)),
710
237
            padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
711
237
            safe_area: SafeAreaInsets::default(),
712
237
            title_font: OptionString::Some("Cantarell".into()),
713
237
            title_font_size: OptionF32::Some(11.0),
714
237
            title_font_weight: OptionU16::Some(700), // Bold
715
237
        }
716
237
    }
717
    
718
    /// iOS-style safe area (for notched devices)
719
9
    #[must_use] pub fn ios() -> Self {
720
9
        Self {
721
9
            button_side: TitlebarButtonSide::Left,
722
9
            buttons: TitlebarButtons {
723
9
                has_close: false, // iOS apps don't have close buttons
724
9
                has_minimize: false,
725
9
                has_maximize: false,
726
9
                has_fullscreen: false,
727
9
            },
728
9
            height: OptionPixelValue::Some(PixelValue::px(44.0)),
729
9
            button_area_width: OptionPixelValue::Some(PixelValue::px(0.0)),
730
9
            padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
731
9
            safe_area: SafeAreaInsets {
732
9
                // iPhone notch safe area
733
9
                top: OptionPixelValue::Some(PixelValue::px(47.0)),
734
9
                bottom: OptionPixelValue::Some(PixelValue::px(34.0)),
735
9
                left: OptionPixelValue::None,
736
9
                right: OptionPixelValue::None,
737
9
            },
738
9
            title_font: OptionString::Some(".SFUI-Semibold".into()),
739
9
            title_font_size: OptionF32::Some(17.0),
740
9
            title_font_weight: OptionU16::Some(600),
741
9
        }
742
9
    }
743
    
744
    /// Android-style titlebar (action bar)
745
15
    #[must_use] pub fn android() -> Self {
746
15
        Self {
747
15
            button_side: TitlebarButtonSide::Left, // Back button on left
748
15
            buttons: TitlebarButtons {
749
15
                has_close: false,
750
15
                has_minimize: false,
751
15
                has_maximize: false,
752
15
                has_fullscreen: false,
753
15
            },
754
15
            height: OptionPixelValue::Some(PixelValue::px(56.0)),
755
15
            button_area_width: OptionPixelValue::Some(PixelValue::px(48.0)), // Back button
756
15
            padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
757
15
            safe_area: SafeAreaInsets::default(),
758
15
            title_font: OptionString::Some("Roboto Medium".into()),
759
15
            title_font_size: OptionF32::Some(20.0),
760
15
            title_font_weight: OptionU16::Some(500),
761
15
        }
762
15
    }
763
}
764

            
765
// ── Input interaction metrics ────────────────────────────────────────────
766

            
767
/// Input interaction timing and distance thresholds from the OS.
768
///
769
/// These values are queried from the operating system to match the user's
770
/// configured double-click speed, drag sensitivity, caret blink rate, etc.
771
///
772
/// # Platform APIs
773
/// - **macOS:** `NSEvent.doubleClickInterval`
774
/// - **Windows:** `GetDoubleClickTime()`, `GetSystemMetrics(SM_CXDOUBLECLK)`,
775
///   `GetCaretBlinkTime()`, `SystemParametersInfo(SPI_GETWHEELSCROLLLINES)`
776
/// - **Linux:** XDG Desktop Portal / gsettings
777
#[derive(Debug, Clone, Copy, PartialEq)]
778
#[repr(C)]
779
pub struct InputMetrics {
780
    /// Max milliseconds between clicks to register a double-click.
781
    pub double_click_time_ms: u32,
782
    /// Max pixels the mouse can move between clicks and still count.
783
    pub double_click_distance_px: f32,
784
    /// Pixels the mouse must move while held down before a drag starts.
785
    pub drag_threshold_px: f32,
786
    /// Caret blink rate in milliseconds (0 = no blink).
787
    pub caret_blink_rate_ms: u32,
788
    /// Width of the text caret/cursor in pixels (typically 1–2).
789
    pub caret_width_px: f32,
790
    /// Lines to scroll per mouse wheel notch.
791
    pub wheel_scroll_lines: u32,
792
    /// Milliseconds to wait before a hover triggers (e.g. tooltip delay).
793
    /// Windows: `SystemParametersInfo(SPI_GETMOUSEHOVERTIME)` — default 400.
794
    pub hover_time_ms: u32,
795
}
796

            
797
impl Default for InputMetrics {
798
37089
    fn default() -> Self {
799
37089
        Self {
800
37089
            double_click_time_ms: 500,
801
37089
            double_click_distance_px: 4.0,
802
37089
            drag_threshold_px: 5.0,
803
37089
            caret_blink_rate_ms: 530,
804
37089
            caret_width_px: 1.0,
805
37089
            wheel_scroll_lines: 3,
806
37089
            hover_time_ms: 400,
807
37089
        }
808
37089
    }
809
}
810

            
811
// ── Text rendering hints ─────────────────────────────────────────────────
812

            
813
/// Subpixel rendering layout for font smoothing.
814
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
815
#[repr(C)]
816
pub enum SubpixelType {
817
    /// No subpixel rendering (grayscale anti-aliasing only).
818
    #[default]
819
    None,
820
    /// Horizontal RGB subpixel layout (most common for LCD monitors).
821
    Rgb,
822
    /// Horizontal BGR subpixel layout.
823
    Bgr,
824
    /// Vertical RGB subpixel layout.
825
    VRgb,
826
    /// Vertical BGR subpixel layout.
827
    VBgr,
828
}
829

            
830
/// Text rendering configuration from the OS.
831
///
832
/// These hints allow the framework to match the host's font smoothing
833
/// settings for crisp, consistent text rendering.
834
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
835
#[repr(C)]
836
pub struct TextRenderingHints {
837
    /// Subpixel rendering type.
838
    pub subpixel_type: SubpixelType,
839
    /// Font smoothing gamma (1000 = default, higher = more contrast).
840
    pub font_smoothing_gamma: u32,
841
    /// Whether font smoothing (anti-aliasing) is enabled.
842
    pub font_smoothing_enabled: bool,
843
    /// User prefers increased text contrast.
844
    pub increased_contrast: bool,
845
}
846

            
847
impl Default for TextRenderingHints {
848
37089
    fn default() -> Self {
849
37089
        Self {
850
37089
            subpixel_type: SubpixelType::None,
851
37089
            font_smoothing_gamma: 1000,
852
37089
            font_smoothing_enabled: true,
853
37089
            increased_contrast: false,
854
37089
        }
855
37089
    }
856
}
857

            
858
// ── Focus ring visuals ───────────────────────────────────────────────────
859

            
860
/// Focus ring / indicator visual style.
861
///
862
/// When an element receives keyboard focus the OS typically draws a visible
863
/// ring or border.  These values come from the OS preferences.
864
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
865
#[repr(C)]
866
pub struct FocusVisuals {
867
    /// Focus ring / indicator colour.
868
    /// macOS: `NSColor.keyboardFocusIndicatorColor`
869
    pub focus_ring_color: OptionColorU,
870
    /// Width of focus border / ring.
871
    /// Windows: `SystemParametersInfo(SPI_GETFOCUSBORDERWIDTH)`
872
    pub focus_border_width: OptionPixelValue,
873
    /// Height of focus border / ring.
874
    pub focus_border_height: OptionPixelValue,
875
}
876

            
877
// ── Scrollbar preferences ────────────────────────────────────────────────
878

            
879
/// When scrollbars should be shown (OS-level preference).
880
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
881
#[repr(C)]
882
pub enum ScrollbarVisibility {
883
    /// Always show scrollbars.
884
    Always,
885
    /// Show only while scrolling, then fade out.
886
    #[default]
887
    WhenScrolling,
888
    /// Automatic: depends on input device (trackpad → overlay, mouse → always).
889
    Automatic,
890
}
891

            
892
/// What happens when clicking the scrollbar track area.
893
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
894
#[repr(C)]
895
pub enum ScrollbarTrackClick {
896
    /// Jump to the clicked position.
897
    JumpToPosition,
898
    /// Scroll by one page.
899
    #[default]
900
    PageUpDown,
901
}
902

            
903
/// OS-level scrollbar behaviour preferences.
904
///
905
/// These are separate from the CSS scrollbar *appearance* (`ComputedScrollbarStyle`).
906
/// They control *when* scrollbars appear and *how* clicking the track behaves.
907
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
908
#[repr(C)]
909
pub struct ScrollbarPreferences {
910
    /// How scrollbars should be shown.
911
    /// macOS: `NSScroller.preferredScrollerStyle`
912
    pub visibility: ScrollbarVisibility,
913
    /// What happens when clicking the scrollbar track.
914
    pub track_click: ScrollbarTrackClick,
915
}
916

            
917
impl Default for ScrollbarPreferences {
918
37089
    fn default() -> Self {
919
37089
        Self {
920
37089
            visibility: ScrollbarVisibility::WhenScrolling,
921
37089
            track_click: ScrollbarTrackClick::PageUpDown,
922
37089
        }
923
37089
    }
924
}
925

            
926
// ── Linux-specific customisation ─────────────────────────────────────────
927

            
928
/// Linux-specific customisation settings.
929
///
930
/// Read from GTK / KDE / XDG settings on Linux; `Default` (all `None` / 0)
931
/// on other platforms.
932
#[derive(Debug, Default, Clone, PartialEq, Eq)]
933
#[repr(C)]
934
pub struct LinuxCustomization {
935
    /// GTK theme name (e.g. "Adwaita", "Breeze", "Numix").
936
    pub gtk_theme: OptionString,
937
    /// Icon theme name (e.g. "Papirus", "Numix", "Breeze").
938
    pub icon_theme: OptionString,
939
    /// Cursor theme name (e.g. "`Breeze_Snow`", "DMZ-Black").
940
    pub cursor_theme: OptionString,
941
    /// Cursor size in pixels (0 = unset / use OS default).
942
    pub cursor_size: u32,
943
    /// GTK button layout string (e.g. "close,minimize,maximize:menu").
944
    /// Determines button side and order for CSD titlebars on Linux.
945
    pub titlebar_button_layout: OptionString,
946
}
947

            
948
// ── Visual hints (icons in menus / buttons / toolbar style) ──────────────
949

            
950
/// Toolbar display style (icons, text, or both).
951
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
952
#[repr(C)]
953
pub enum ToolbarStyle {
954
    /// Show only icons in toolbars.
955
    #[default]
956
    IconsOnly,
957
    /// Show only text labels in toolbars.
958
    TextOnly,
959
    /// Show text beside the icon (horizontal).
960
    TextBesideIcon,
961
    /// Show text below the icon (vertical).
962
    TextBelowIcon,
963
}
964

            
965
/// Visual hints from the OS about how icons and decorations should be shown.
966
///
967
/// These preferences differ heavily between Linux desktops (KDE vs GNOME)
968
/// and are less configurable on macOS / Windows where HIG rules apply.
969
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
970
#[repr(C)]
971
pub struct VisualHints {
972
    /// Toolbar display style.
973
    /// Linux: `org.gnome.desktop.interface toolbar-style`, KDE `ToolButtonStyle`.
974
    pub toolbar_style: ToolbarStyle,
975
    /// Show icons on push buttons?  (Common in KDE, rare in Win/Mac.)
976
    /// Linux: `org.gnome.desktop.interface buttons-have-icons`, KDE `ShowIconsOnPushButtons`.
977
    pub show_button_images: bool,
978
    /// Show icons in context menus?  (GNOME defaults off since 3.x; Win/Mac/KDE usually on.)
979
    /// Linux: `org.gnome.desktop.interface menus-have-icons`.
980
    pub show_menu_images: bool,
981
    /// Should tooltips be shown on hover?
982
    pub show_tooltips: bool,
983
    /// Flash the window taskbar entry on alert?
984
    pub flash_on_alert: bool,
985
}
986

            
987
impl Default for VisualHints {
988
37089
    fn default() -> Self {
989
37089
        Self {
990
37089
            toolbar_style: ToolbarStyle::IconsOnly,
991
37089
            show_button_images: false,
992
37089
            show_menu_images: true,
993
37089
            show_tooltips: true,
994
37089
            flash_on_alert: true,
995
37089
        }
996
37089
    }
997
}
998

            
999
// ── Animation metrics ────────────────────────────────────────────────────
/// Focus indicator behaviour (always visible vs keyboard-only).
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum FocusBehavior {
    /// Focus indicators are always visible when an element has focus.
    #[default]
    AlwaysVisible,
    /// Focus indicators are hidden until the user presses a keyboard key
    /// (Alt, Tab, arrow keys, etc.).  Windows: `SPI_GETKEYBOARDCUES`.
    KeyboardOnly,
}
/// Animation-related preferences from the OS.
///
/// These control whether UI animations (transitions, fades, slides) should
/// play and at what speed.
///
/// # Platform APIs
/// - **Windows:** `SystemParametersInfo(SPI_GETCLIENTAREAANIMATION)`,
///   `SPI_GETKEYBOARDCUES`
/// - **macOS:** `NSWorkspace.accessibilityDisplayShouldReduceMotion`
/// - **Linux:** `org.gnome.desktop.interface enable-animations`,
///   KDE `AnimationDurationFactor`
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct AnimationMetrics {
    /// Global enable/disable for UI animations.
    pub animations_enabled: bool,
    /// Animation speed factor (1.0 = normal, 0.5 = 2× faster, 2.0 = 2× slower).
    /// Primarily used in KDE.
    pub animation_duration_factor: f32,
    /// When to show focus rectangles / rings.
    pub focus_indicator_behavior: FocusBehavior,
}
impl Default for AnimationMetrics {
37089
    fn default() -> Self {
37089
        Self {
37089
            animations_enabled: true,
37089
            animation_duration_factor: 1.0,
37089
            focus_indicator_behavior: FocusBehavior::AlwaysVisible,
37089
        }
37089
    }
}
// ── Audio metrics ────────────────────────────────────────────────────────
/// Audio-feedback preferences from the OS.
///
/// Controls whether the app should make sounds on events (error pings,
/// notifications) or on input (clicks, key presses).
///
/// # Platform APIs
/// - **Windows:** `SystemParametersInfo(SPI_GETBEEP)`
/// - **macOS:** `NSSound.soundEffectAudioVolume`
/// - **Linux:** `org.gnome.desktop.sound event-sounds`,
///   `org.gnome.desktop.sound input-feedback-sounds`
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct AudioMetrics {
    /// Should the app make sounds on events?  (Error ping, notification, etc.)
    pub event_sounds_enabled: bool,
    /// Should the app make sounds on input?  (Clicks, typing feedback.)
    pub input_feedback_sounds_enabled: bool,
}
impl Default for AudioMetrics {
37089
    fn default() -> Self {
37089
        Self {
37089
            event_sounds_enabled: true,
37089
            input_feedback_sounds_enabled: false,
37089
        }
37089
    }
}
/// Apple system font family names for font fallback chains.
/// 
/// These are the canonical names for Apple's system fonts, which should
/// be used in font fallback chains for proper rendering on Apple platforms.
/// Note: The names here must match what rust-fontconfig indexes from the font metadata.
pub mod apple_fonts {
    /// System Font - Primary system font for macOS
    /// This is how rust-fontconfig indexes the SF Pro font family
    pub const SYSTEM_FONT: &str = "System Font";
    /// SF NS variants as indexed by rust-fontconfig
    pub const SF_NS_ROUNDED: &str = "SF NS Rounded";
    /// SF Compact - System font optimized for watchOS
    /// Optimized for small sizes and narrow columns
    pub const SF_COMPACT: &str = "SF Compact";
    /// SF Mono - Monospaced font used in Xcode
    /// Enables alignment between rows and columns of text
    pub const SF_MONO: &str = "SF NS Mono Light";
    /// New York - Serif font for reading
    /// Performs as traditional reading face at small sizes
    pub const NEW_YORK: &str = "New York";
    /// SF Arabic - Arabic system font
    pub const SF_ARABIC: &str = "SF Arabic";
    /// SF Armenian - Armenian system font
    pub const SF_ARMENIAN: &str = "SF Armenian";
    /// SF Georgian - Georgian system font
    pub const SF_GEORGIAN: &str = "SF Georgian";
    /// SF Hebrew - Hebrew system font with niqqud support
    pub const SF_HEBREW: &str = "SF Hebrew";
    /// Legacy macOS fonts for fallback
    pub const MENLO: &str = "Menlo";
    pub const MENLO_REGULAR: &str = "Menlo Regular";
    pub const MENLO_BOLD: &str = "Menlo Bold";
    pub const MONACO: &str = "Monaco";
    pub const LUCIDA_GRANDE: &str = "Lucida Grande";
    pub const LUCIDA_GRANDE_BOLD: &str = "Lucida Grande Bold";
    pub const HELVETICA_NEUE: &str = "Helvetica Neue";
    pub const HELVETICA_NEUE_BOLD: &str = "Helvetica Neue Bold";
}
/// Windows system font family names.
pub mod windows_fonts {
    /// Modern Windows 11 fonts
    pub const SEGOE_UI_VARIABLE: &str = "Segoe UI Variable";
    pub const SEGOE_UI_VARIABLE_TEXT: &str = "Segoe UI Variable Text";
    pub const SEGOE_UI_VARIABLE_DISPLAY: &str = "Segoe UI Variable Display";
    /// Standard Windows fonts
    pub const SEGOE_UI: &str = "Segoe UI";
    pub const CONSOLAS: &str = "Consolas";
    pub const CASCADIA_CODE: &str = "Cascadia Code";
    pub const CASCADIA_MONO: &str = "Cascadia Mono";
    /// Legacy Windows fonts
    pub const TAHOMA: &str = "Tahoma";
    pub const MS_SANS_SERIF: &str = "MS Sans Serif";
    pub const LUCIDA_CONSOLE: &str = "Lucida Console";
    pub const COURIER_NEW: &str = "Courier New";
}
/// Linux/GTK common font family names.
pub mod linux_fonts {
    /// GNOME default fonts
    pub const CANTARELL: &str = "Cantarell";
    pub const ADWAITA: &str = "Adwaita";
    /// Ubuntu fonts
    pub const UBUNTU: &str = "Ubuntu";
    pub const UBUNTU_MONO: &str = "Ubuntu Mono";
    /// `DejaVu` fonts (widely available)
    pub const DEJAVU_SANS: &str = "DejaVu Sans";
    pub const DEJAVU_SANS_MONO: &str = "DejaVu Sans Mono";
    pub const DEJAVU_SERIF: &str = "DejaVu Serif";
    /// Liberation fonts (metrically compatible with Windows fonts)
    pub const LIBERATION_SANS: &str = "Liberation Sans";
    pub const LIBERATION_MONO: &str = "Liberation Mono";
    pub const LIBERATION_SERIF: &str = "Liberation Serif";
    /// Noto fonts (broad Unicode coverage)
    pub const NOTO_SANS: &str = "Noto Sans";
    pub const NOTO_MONO: &str = "Noto Sans Mono";
    pub const NOTO_SERIF: &str = "Noto Serif";
    /// KDE default fonts
    pub const HACK: &str = "Hack";
    /// Generic fallback names
    pub const MONOSPACE: &str = "Monospace";
    pub const SANS_SERIF: &str = "Sans";
    pub const SERIF: &str = "Serif";
}
impl SystemFontType {
    /// Returns the font fallback chain for this font type on the given platform.
    /// 
    /// The returned list contains font family names in order of preference.
    /// The first available font should be used.
1266
    #[must_use] pub fn get_fallback_chain(&self, platform: &Platform) -> Vec<&'static str> {
1266
        match platform {
106
            Platform::MacOs | Platform::Ios => self.macos_fallback_chain(),
48
            Platform::Windows => self.windows_fallback_chain(),
1029
            Platform::Linux(_) => self.linux_fallback_chain(),
36
            Platform::Android => self.android_fallback_chain(),
47
            Platform::Unknown => self.generic_fallback_chain(),
        }
1266
    }
106
    fn macos_fallback_chain(self) -> Vec<&'static str> {
106
        match self {
            // Normal weight: System Font first, then Helvetica Neue.
20
            Self::Ui => vec![
20
                apple_fonts::SYSTEM_FONT,
20
                apple_fonts::HELVETICA_NEUE,
20
                apple_fonts::LUCIDA_GRANDE,
            ],
            // Bold weights: Helvetica Neue first (System Font has no Bold variant in fontconfig).
16
            Self::UiBold | Self::TitleBold => vec![
16
                apple_fonts::HELVETICA_NEUE,
16
                apple_fonts::LUCIDA_GRANDE,
            ],
            // Monospace: Menlo (has a Bold variant), then Monaco.
30
            Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
30
                apple_fonts::MENLO,
30
                apple_fonts::MONACO,
            ],
            // Title / Menu / Small: System Font then Helvetica Neue.
24
            Self::Title | Self::Menu | Self::Small => vec![
24
                apple_fonts::SYSTEM_FONT,
24
                apple_fonts::HELVETICA_NEUE,
            ],
            // Serif fonts - Georgia has bold variant
8
            Self::Serif => vec![
8
                apple_fonts::NEW_YORK,
8
                "Georgia",
8
                "Times New Roman",
            ],
8
            Self::SerifBold => vec![
8
                "Georgia", // Georgia Bold exists
8
                "Times New Roman",
            ],
        }
106
    }
48
    fn windows_fallback_chain(self) -> Vec<&'static str> {
48
        match self {
18
            Self::Ui | Self::UiBold => vec![
18
                windows_fonts::SEGOE_UI_VARIABLE_TEXT,
18
                windows_fonts::SEGOE_UI,
18
                windows_fonts::TAHOMA,
            ],
12
            Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
12
                windows_fonts::CASCADIA_MONO,
12
                windows_fonts::CASCADIA_CODE,
12
                windows_fonts::CONSOLAS,
12
                windows_fonts::LUCIDA_CONSOLE,
12
                windows_fonts::COURIER_NEW,
            ],
6
            Self::Title | Self::TitleBold => vec![
6
                windows_fonts::SEGOE_UI_VARIABLE_DISPLAY,
6
                windows_fonts::SEGOE_UI,
            ],
3
            Self::Menu => vec![
3
                windows_fonts::SEGOE_UI,
3
                windows_fonts::TAHOMA,
            ],
3
            Self::Small => vec![
3
                windows_fonts::SEGOE_UI,
            ],
6
            Self::Serif | Self::SerifBold => vec![
6
                "Cambria",
6
                "Georgia",
6
                "Times New Roman",
            ],
        }
48
    }
1029
    fn linux_fallback_chain(self) -> Vec<&'static str> {
1029
        match self {
912
            Self::Ui | Self::UiBold => vec![
912
                linux_fonts::CANTARELL,
912
                linux_fonts::UBUNTU,
912
                linux_fonts::NOTO_SANS,
912
                linux_fonts::DEJAVU_SANS,
912
                linux_fonts::LIBERATION_SANS,
912
                linux_fonts::SANS_SERIF,
            ],
45
            Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
45
                linux_fonts::UBUNTU_MONO,
45
                linux_fonts::HACK,
45
                linux_fonts::NOTO_MONO,
45
                linux_fonts::DEJAVU_SANS_MONO,
45
                linux_fonts::LIBERATION_MONO,
45
                linux_fonts::MONOSPACE,
            ],
48
            Self::Title | Self::TitleBold | Self::Menu | Self::Small => vec![
48
                linux_fonts::CANTARELL,
48
                linux_fonts::UBUNTU,
48
                linux_fonts::NOTO_SANS,
            ],
24
            Self::Serif | Self::SerifBold => vec![
24
                linux_fonts::NOTO_SERIF,
24
                linux_fonts::DEJAVU_SERIF,
24
                linux_fonts::LIBERATION_SERIF,
24
                linux_fonts::SERIF,
            ],
        }
1029
    }
36
    fn android_fallback_chain(self) -> Vec<&'static str> {
36
        match self {
12
            Self::Ui | Self::UiBold | Self::Title | Self::TitleBold => vec!["Roboto", "Noto Sans"],
            Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
12
                vec!["Roboto Mono", "Droid Sans Mono", "monospace"]
            }
6
            Self::Menu | Self::Small => vec!["Roboto"],
6
            Self::Serif | Self::SerifBold => vec!["Noto Serif", "Droid Serif", "serif"],
        }
36
    }
47
    fn generic_fallback_chain(self) -> Vec<&'static str> {
47
        match self {
            Self::Ui | Self::UiBold | Self::Title | Self::TitleBold | Self::Menu | Self::Small => {
24
                vec!["sans-serif"]
            }
            Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
15
                vec!["monospace"]
            }
8
            Self::Serif | Self::SerifBold => vec!["serif"],
        }
47
    }
}
impl SystemStyle {
    /// Format the `SystemStyle` as a human-readable JSON string for debugging.
    ///
    /// This does NOT use serde — it manually formats the most important fields
    /// so that they can be verified against OS-reported values in a test script.
    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose CSS parser/formatter/dispatch table (one branch per property/variant)
23
    #[must_use] pub fn to_json_string(&self) -> AzString {
        use alloc::format;
483
        fn opt_color(c: OptionColorU) -> alloc::string::String {
483
            c.as_ref().map_or_else(
398
                || "null".into(),
85
                |c| format!("\"#{:02x}{:02x}{:02x}{:02x}\"", c.r, c.g, c.b, c.a),
            )
483
        }
230
        fn opt_str(s: &OptionString) -> alloc::string::String {
230
            s.as_ref()
230
                .map_or_else(|| "null".into(), |s| format!("\"{}\"", s.as_str()))
230
        }
46
        fn opt_f32(v: OptionF32) -> alloc::string::String {
46
            v.into_option()
46
                .map_or_else(|| "null".into(), |v| format!("{v:.2}"))
46
        }
23
        fn opt_u16(v: OptionU16) -> alloc::string::String {
23
            v.into_option()
23
                .map_or_else(|| "null".into(), |v| format!("{v}"))
23
        }
69
        fn opt_px(v: &OptionPixelValue) -> alloc::string::String {
69
            v.as_ref().map_or_else(
9
                || "null".into(),
60
                |v| format!("{:.1}", v.to_pixels_internal(0.0, 0.0, 0.0)),
            )
69
        }
23
        let tm = &self.metrics.titlebar;
23
        let inp = &self.input;
23
        let tr = &self.text_rendering;
23
        let acc = &self.accessibility;
23
        let sp = &self.scrollbar_preferences;
23
        let lnx = &self.linux;
23
        let vh = &self.visual_hints;
23
        let anim = &self.animation;
23
        let audio = &self.audio;
23
        let json = format!(
23
r#"{{
23
  "theme": "{:?}",
23
  "platform": "{:?}",
23
  "os_version": "{:?}:{}",
23
  "language": "{}",
23
  "prefers_reduced_motion": {:?},
23
  "prefers_high_contrast": {:?},
23
  "colors": {{
23
    "text": {},
23
    "secondary_text": {},
23
    "tertiary_text": {},
23
    "background": {},
23
    "accent": {},
23
    "accent_text": {},
23
    "button_face": {},
23
    "button_text": {},
23
    "disabled_text": {},
23
    "window_background": {},
23
    "under_page_background": {},
23
    "selection_background": {},
23
    "selection_text": {},
23
    "selection_background_inactive": {},
23
    "selection_text_inactive": {},
23
    "link": {},
23
    "separator": {},
23
    "grid": {},
23
    "find_highlight": {},
23
    "sidebar_background": {},
23
    "sidebar_selection": {}
23
  }},
23
  "fonts": {{
23
    "ui_font": {},
23
    "ui_font_size": {},
23
    "monospace_font": {},
23
    "title_font": {},
23
    "menu_font": {},
23
    "small_font": {}
23
  }},
23
  "titlebar": {{
23
    "button_side": "{:?}",
23
    "height": {},
23
    "button_area_width": {},
23
    "padding_horizontal": {},
23
    "title_font": {},
23
    "title_font_size": {},
23
    "title_font_weight": {},
23
    "has_close": {},
23
    "has_minimize": {},
23
    "has_maximize": {},
23
    "has_fullscreen": {}
23
  }},
23
  "input": {{
23
    "double_click_time_ms": {},
23
    "double_click_distance_px": {:.1},
23
    "drag_threshold_px": {:.1},
23
    "caret_blink_rate_ms": {},
23
    "caret_width_px": {:.1},
23
    "wheel_scroll_lines": {},
23
    "hover_time_ms": {}
23
  }},
23
  "text_rendering": {{
23
    "font_smoothing_enabled": {},
23
    "subpixel_type": "{:?}",
23
    "font_smoothing_gamma": {},
23
    "increased_contrast": {}
23
  }},
23
  "accessibility": {{
23
    "prefers_bold_text": {},
23
    "prefers_larger_text": {},
23
    "text_scale_factor": {:.2},
23
    "prefers_high_contrast": {},
23
    "prefers_reduced_motion": {},
23
    "prefers_reduced_transparency": {},
23
    "screen_reader_active": {},
23
    "differentiate_without_color": {}
23
  }},
23
  "scrollbar_preferences": {{
23
    "visibility": "{:?}",
23
    "track_click": "{:?}"
23
  }},
23
  "linux": {{
23
    "gtk_theme": {},
23
    "icon_theme": {},
23
    "cursor_theme": {},
23
    "cursor_size": {},
23
    "titlebar_button_layout": {}
23
  }},
23
  "visual_hints": {{
23
    "show_button_images": {},
23
    "show_menu_images": {},
23
    "toolbar_style": "{:?}",
23
    "show_tooltips": {}
23
  }},
23
  "animation": {{
23
    "animations_enabled": {},
23
    "animation_duration_factor": {:.2},
23
    "focus_indicator_behavior": "{:?}"
23
  }},
23
  "audio": {{
23
    "event_sounds_enabled": {},
23
    "input_feedback_sounds_enabled": {}
23
  }}
23
}}"#,
            // top-level
            self.theme,
            self.platform,
            self.os_version.os, self.os_version.version_id,
23
            self.language.as_str(),
            self.prefers_reduced_motion,
            self.prefers_high_contrast,
            // colors
23
            opt_color(self.colors.text),
23
            opt_color(self.colors.secondary_text),
23
            opt_color(self.colors.tertiary_text),
23
            opt_color(self.colors.background),
23
            opt_color(self.colors.accent),
23
            opt_color(self.colors.accent_text),
23
            opt_color(self.colors.button_face),
23
            opt_color(self.colors.button_text),
23
            opt_color(self.colors.disabled_text),
23
            opt_color(self.colors.window_background),
23
            opt_color(self.colors.under_page_background),
23
            opt_color(self.colors.selection_background),
23
            opt_color(self.colors.selection_text),
23
            opt_color(self.colors.selection_background_inactive),
23
            opt_color(self.colors.selection_text_inactive),
23
            opt_color(self.colors.link),
23
            opt_color(self.colors.separator),
23
            opt_color(self.colors.grid),
23
            opt_color(self.colors.find_highlight),
23
            opt_color(self.colors.sidebar_background),
23
            opt_color(self.colors.sidebar_selection),
            // fonts
23
            opt_str(&self.fonts.ui_font),
23
            opt_f32(self.fonts.ui_font_size),
23
            opt_str(&self.fonts.monospace_font),
23
            opt_str(&self.fonts.title_font),
23
            opt_str(&self.fonts.menu_font),
23
            opt_str(&self.fonts.small_font),
            // titlebar
            tm.button_side,
23
            opt_px(&tm.height),
23
            opt_px(&tm.button_area_width),
23
            opt_px(&tm.padding_horizontal),
23
            opt_str(&tm.title_font),
23
            opt_f32(tm.title_font_size),
23
            opt_u16(tm.title_font_weight),
            tm.buttons.has_close,
            tm.buttons.has_minimize,
            tm.buttons.has_maximize,
            tm.buttons.has_fullscreen,
            // input
            inp.double_click_time_ms,
            inp.double_click_distance_px,
            inp.drag_threshold_px,
            inp.caret_blink_rate_ms,
            inp.caret_width_px,
            inp.wheel_scroll_lines,
            inp.hover_time_ms,
            // text_rendering
            tr.font_smoothing_enabled,
            tr.subpixel_type,
            tr.font_smoothing_gamma,
            tr.increased_contrast,
            // accessibility
            acc.prefers_bold_text,
            acc.prefers_larger_text,
            acc.text_scale_factor,
            acc.prefers_high_contrast,
            acc.prefers_reduced_motion,
            acc.prefers_reduced_transparency,
            acc.screen_reader_active,
            acc.differentiate_without_color,
            // scrollbar_preferences
            sp.visibility,
            sp.track_click,
            // linux
23
            opt_str(&lnx.gtk_theme),
23
            opt_str(&lnx.icon_theme),
23
            opt_str(&lnx.cursor_theme),
            lnx.cursor_size,
23
            opt_str(&lnx.titlebar_button_layout),
            // visual_hints
            vh.show_button_images,
            vh.show_menu_images,
            vh.toolbar_style,
            vh.show_tooltips,
            // animation
            anim.animations_enabled,
            anim.animation_duration_factor,
            anim.focus_indicator_behavior,
            // audio
            audio.event_sounds_enabled,
            audio.input_feedback_sounds_enabled,
        );
23
        AzString::from(json)
23
    }
    /// Returns a platform-appropriate default system style.
    ///
    /// This returns hard-coded defaults based on the target OS. For actual
    /// runtime detection of the user's theme, colors, and fonts, use the
    /// platform discovery in `azul-dll` (called automatically by `App::create()`).
175
    #[must_use] pub fn detect() -> Self {
175
        Self::default_for_platform()
175
    }
    /// Returns hard-coded defaults for the current compile-time platform.
200
    #[must_use] pub fn default_for_platform() -> Self {
        #[cfg(target_os = "windows")]
        { defaults::windows_11_light() }
        #[cfg(target_os = "macos")]
        { defaults::macos_modern_light() }
        #[cfg(target_os = "linux")]
200
        { defaults::gnome_adwaita_light() }
        #[cfg(target_os = "android")]
        { defaults::android_material_light() }
        #[cfg(target_os = "ios")]
        { defaults::ios_light() }
        #[cfg(not(any(
            target_os = "linux",
            target_os = "windows",
            target_os = "macos",
            target_os = "android",
            target_os = "ios"
        )))]
        { Self::default() }
200
    }
    /// Alias for `detect` - kept for internal compatibility, not exposed in FFI.
    #[inline]
2
    #[must_use] pub fn new() -> Self {
2
        Self::detect()
2
    }
    /// Create a CSS stylesheet for CSD (Client-Side Decorations) titlebar
    ///
    /// This generates CSS rules for the CSD titlebar using system colors,
    /// fonts, and metrics to match the native platform look. Returned rules
    /// carry `rule_priority::SYSTEM`.
28
    #[must_use] pub fn create_csd_stylesheet(&self) -> Css {
        use alloc::format;
        use crate::parser2::new_from_str;
        // Build CSS string from SystemStyle
28
        let mut css = String::new();
        // Get system colors with fallbacks
28
        let bg_color = self
28
            .colors
28
            .window_background
28
            .as_option()
28
            .copied()
28
            .unwrap_or(ColorU::new_rgb(240, 240, 240));
28
        let text_color = self
28
            .colors
28
            .text
28
            .as_option()
28
            .copied()
28
            .unwrap_or(ColorU::new_rgb(0, 0, 0));
28
        let accent_color = self
28
            .colors
28
            .accent
28
            .as_option()
28
            .copied()
28
            .unwrap_or(ColorU::new_rgb(0, 120, 215));
28
        let border_color = match self.theme {
4
            Theme::Dark => ColorU::new_rgb(60, 60, 60),
24
            Theme::Light => ColorU::new_rgb(200, 200, 200),
        };
        // Get system metrics with fallbacks
28
        let corner_radius = self
28
            .metrics
28
            .corner_radius
28
            .map(|px| {
                use crate::props::basic::pixel::DEFAULT_FONT_SIZE;
26
                format!("{}px", px.to_pixels_internal(1.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
26
            })
28
            .unwrap_or_else(|| "4px".to_string());
        // Titlebar container
28
        let _ = write!(css,
28
            ".csd-titlebar {{ width: 100%; height: 32px; background: rgb({}, {}, {}); \
28
             border-bottom: 1px solid rgb({}, {}, {}); display: flex; flex-direction: row; \
28
             align-items: center; justify-content: space-between; padding: 0 8px; \
28
             cursor: grab; user-select: none; }} ",
            bg_color.r, bg_color.g, bg_color.b, border_color.r, border_color.g, border_color.b,
        );
        // Title text
28
        let _ = write!(css,
28
            ".csd-title {{ color: rgb({}, {}, {}); font-size: 13px; flex-grow: 1; text-align: \
28
             center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; \
28
             user-select: none; }} ",
            text_color.r, text_color.g, text_color.b,
        );
        // Button container
28
        css.push_str(".csd-buttons { display: flex; flex-direction: row; gap: 4px; } ");
        // Buttons
28
        let _ = write!(css,
28
            ".csd-button {{ width: 32px; height: 24px; border-radius: {}; background: \
28
             transparent; color: rgb({}, {}, {}); font-size: 16px; line-height: 24px; text-align: \
28
             center; cursor: pointer; user-select: none; }} ",
            corner_radius, text_color.r, text_color.g, text_color.b,
        );
        // Button hover state
28
        let hover_color = match self.theme {
4
            Theme::Dark => ColorU::new_rgb(60, 60, 60),
24
            Theme::Light => ColorU::new_rgb(220, 220, 220),
        };
28
        let _ = write!(css,
28
            ".csd-button:hover {{ background: rgb({}, {}, {}); }} ",
            hover_color.r, hover_color.g, hover_color.b,
        );
        // Close button hover (red on all platforms)
28
        css.push_str(
28
            ".csd-close:hover { background: rgb(232, 17, 35); color: rgb(255, 255, 255); } ",
        );
        // Platform-specific button styling
28
        match self.platform {
4
            Platform::MacOs => {
4
                // macOS traffic light buttons (left side)
4
                css.push_str(".csd-buttons { position: absolute; left: 8px; } ");
4
                css.push_str(
4
                    ".csd-close { background: rgb(255, 95, 86); width: 12px; height: 12px; \
4
                     border-radius: 50%; } ",
4
                );
4
                css.push_str(
4
                    ".csd-minimize { background: rgb(255, 189, 46); width: 12px; height: 12px; \
4
                     border-radius: 50%; } ",
4
                );
4
                css.push_str(
4
                    ".csd-maximize { background: rgb(40, 201, 64); width: 12px; height: 12px; \
4
                     border-radius: 50%; } ",
4
                );
4
            }
7
            Platform::Linux(_) => {
7
                // Linux - title on left, buttons on right
7
                css.push_str(".csd-title { text-align: left; } ");
7
            }
17
            _ => {
17
                // Windows and others - standard layout
17
            }
        }
        // Parse CSS string into a Css.
28
        let (mut parsed_css, _warnings) = new_from_str(&css);
        // Tag every rule as system-level so author CSS overrides win.
191
        for rule in parsed_css.rules.as_mut() {
191
            rule.priority = crate::css::rule_priority::SYSTEM;
191
        }
28
        parsed_css
28
    }
}
/// Detect the Linux desktop environment from environment variables.
///
/// Checks `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, and specific env markers
/// to identify GNOME, KDE, XFCE, Cinnamon, MATE, Hyprland, Sway, i3, etc.
2
#[must_use] pub fn detect_linux_desktop_env() -> DesktopEnvironment {
    // Check XDG_CURRENT_DESKTOP first (most reliable)
2
    if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") {
        let desktop_lower = desktop.to_lowercase();
        if desktop_lower.contains("gnome") {
            return DesktopEnvironment::Gnome;
        }
        if desktop_lower.contains("kde") || desktop_lower.contains("plasma") {
            return DesktopEnvironment::Kde;
        }
        if desktop_lower.contains("xfce") {
            return DesktopEnvironment::Other(AzString::from_const_str("XFCE"));
        }
        if desktop_lower.contains("unity") {
            return DesktopEnvironment::Other(AzString::from_const_str("Unity"));
        }
        if desktop_lower.contains("cinnamon") {
            return DesktopEnvironment::Other(AzString::from_const_str("Cinnamon"));
        }
        if desktop_lower.contains("mate") {
            return DesktopEnvironment::Other(AzString::from_const_str("MATE"));
        }
        if desktop_lower.contains("lxde") || desktop_lower.contains("lxqt") {
            return DesktopEnvironment::Other(AzString::from(desktop.to_uppercase()));
        }
        if desktop_lower.contains("budgie") {
            return DesktopEnvironment::Other(AzString::from_const_str("Budgie"));
        }
        if desktop_lower.contains("pantheon") {
            return DesktopEnvironment::Other(AzString::from_const_str("Pantheon"));
        }
        if desktop_lower.contains("deepin") {
            return DesktopEnvironment::Other(AzString::from_const_str("Deepin"));
        }
        if desktop_lower.contains("hyprland") {
            return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
        }
        if desktop_lower.contains("sway") {
            return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
        }
        if desktop_lower.contains("i3") {
            return DesktopEnvironment::Other(AzString::from_const_str("i3"));
        }
        return DesktopEnvironment::Other(AzString::from(desktop));
2
    }
    // Check DESKTOP_SESSION as fallback
2
    if let Ok(session) = std::env::var("DESKTOP_SESSION") {
        let session_lower = session.to_lowercase();
        if session_lower.contains("gnome") {
            return DesktopEnvironment::Gnome;
        }
        if session_lower.contains("plasma") || session_lower.contains("kde") {
            return DesktopEnvironment::Kde;
        }
        if session_lower.contains("xfce") {
            return DesktopEnvironment::Other(AzString::from_const_str("XFCE"));
        }
        if session_lower.contains("cinnamon") {
            return DesktopEnvironment::Other(AzString::from_const_str("Cinnamon"));
        }
        return DesktopEnvironment::Other(AzString::from(session));
2
    }
    // Check for specific environment markers
2
    if std::env::var("GNOME_DESKTOP_SESSION_ID").is_ok() {
        return DesktopEnvironment::Gnome;
2
    }
2
    if std::env::var("KDE_FULL_SESSION").is_ok() {
        return DesktopEnvironment::Kde;
2
    }
2
    if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
        return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
2
    }
2
    if std::env::var("SWAYSOCK").is_ok() {
        return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
2
    }
2
    if std::env::var("I3SOCK").is_ok() {
        return DesktopEnvironment::Other(AzString::from_const_str("i3"));
2
    }
2
    DesktopEnvironment::Other(AzString::from_const_str("Unknown"))
2
}
/// Detect the system language as a BCP 47 tag.
///
/// Checks `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, and `LANG` in priority order.
/// Returns `"en-US"` if detection fails. For runtime detection via native
/// OS APIs, the platform discovery in `azul-dll` overrides this.
2
#[must_use] pub fn detect_system_language() -> AzString {
2
    let env_vars = ["LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"];
8
    for var in &env_vars {
8
        if let Ok(value) = std::env::var(var) {
2
            let value = value.trim();
2
            if value.is_empty() || value == "C" || value == "POSIX" {
                continue;
2
            }
            // Parse locale format: "de_DE.UTF-8" or "de_DE" or "de"
2
            let lang = value
2
                .split('.')  // Remove .UTF-8 suffix
2
                .next()
2
                .unwrap_or(value)
2
                .split(':')  // LANGUAGE can be "de:en_US:en"
2
                .next()
2
                .unwrap_or(value);
2
            if !lang.is_empty() {
2
                return AzString::from(lang.replace('_', "-"));
            }
6
        }
    }
    AzString::from_const_str("en-US")
2
}
pub mod defaults {
    //! A collection of hard-coded system style defaults that mimic the appearance
    //! of various operating systems and desktop environments.
    //!
    //! These are used as a
    //! fallback when the "io" feature is disabled, ensuring deterministic styles
    //! for testing and environments where system calls are not desired.
    use super::{
        AccessibilitySettings, AnimationMetrics, AudioMetrics, FocusVisuals, Handedness,
        InputMetrics, LinuxCustomization, ScrollbarPreferences, TextRenderingHints, VisualHints,
    };
    use crate::{
        corety::{AzString, OptionF32, OptionString},
        dynamic_selector::{BoolCondition, OsVersion},
        props::{
            basic::{
                color::{ColorU, OptionColorU},
                pixel::{PixelValue, OptionPixelValue},
            },
            layout::{
                dimensions::LayoutWidth,
                spacing::{LayoutPaddingLeft, LayoutPaddingRight},
            },
            style::{
                background::StyleBackgroundContent,
                scrollbar::{
                    ComputedScrollbarStyle, OverflowScrolling, OverscrollBehavior, ScrollBehavior,
                    ScrollPhysics, ScrollbarInfo,
                    SCROLLBAR_ANDROID_DARK, SCROLLBAR_ANDROID_LIGHT, SCROLLBAR_CLASSIC_DARK,
                    SCROLLBAR_CLASSIC_LIGHT, SCROLLBAR_IOS_DARK, SCROLLBAR_IOS_LIGHT,
                    SCROLLBAR_MACOS_DARK, SCROLLBAR_MACOS_LIGHT, SCROLLBAR_WINDOWS_DARK,
                    SCROLLBAR_WINDOWS_LIGHT,
                },
            },
        },
        system::{
            DesktopEnvironment, Platform, SystemColors, SystemFonts, SystemMetrics, SystemStyle,
            Theme, IconStyleOptions, TitlebarMetrics,
        },
    };
    // --- Custom Scrollbar Style Constants for Nostalgia ---
    /// A scrollbar style mimicking the classic Windows 95/98/2000/XP look.
    pub const SCROLLBAR_WINDOWS_CLASSIC: ScrollbarInfo = ScrollbarInfo {
        width: LayoutWidth::Px(PixelValue::const_px(17)),
        padding_left: LayoutPaddingLeft {
            inner: PixelValue::const_px(0),
        },
        padding_right: LayoutPaddingRight {
            inner: PixelValue::const_px(0),
        },
        track: StyleBackgroundContent::Color(ColorU {
            r: 223,
            g: 223,
            b: 223,
            a: 255,
        }), // Scrollbar trough color
        thumb: StyleBackgroundContent::Color(ColorU {
            r: 208,
            g: 208,
            b: 208,
            a: 255,
        }), // Button face color
        button: StyleBackgroundContent::Color(ColorU {
            r: 208,
            g: 208,
            b: 208,
            a: 255,
        }),
        corner: StyleBackgroundContent::Color(ColorU {
            r: 223,
            g: 223,
            b: 223,
            a: 255,
        }),
        resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
        clip_to_container_border: false,
        scroll_behavior: ScrollBehavior::Auto,
        overscroll_behavior_x: OverscrollBehavior::None,
        overscroll_behavior_y: OverscrollBehavior::None,
        overflow_scrolling: OverflowScrolling::Auto,
    };
    /// A scrollbar style mimicking the macOS "Aqua" theme from the early 2000s.
    pub const SCROLLBAR_MACOS_AQUA: ScrollbarInfo = ScrollbarInfo {
        width: LayoutWidth::Px(PixelValue::const_px(15)),
        padding_left: LayoutPaddingLeft {
            inner: PixelValue::const_px(0),
        },
        padding_right: LayoutPaddingRight {
            inner: PixelValue::const_px(0),
        },
        track: StyleBackgroundContent::Color(ColorU {
            r: 238,
            g: 238,
            b: 238,
            a: 128,
        }), // Translucent track
        thumb: StyleBackgroundContent::Color(ColorU {
            r: 105,
            g: 173,
            b: 255,
            a: 255,
        }), // "Gel" blue
        button: StyleBackgroundContent::Color(ColorU {
            r: 105,
            g: 173,
            b: 255,
            a: 255,
        }),
        corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
        resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
        clip_to_container_border: true,
        scroll_behavior: ScrollBehavior::Smooth,
        overscroll_behavior_x: OverscrollBehavior::Auto,
        overscroll_behavior_y: OverscrollBehavior::Auto,
        overflow_scrolling: OverflowScrolling::Auto,
    };
    /// A scrollbar style mimicking the KDE Oxygen theme.
    pub const SCROLLBAR_KDE_OXYGEN: ScrollbarInfo = ScrollbarInfo {
        width: LayoutWidth::Px(PixelValue::const_px(14)),
        padding_left: LayoutPaddingLeft {
            inner: PixelValue::const_px(2),
        },
        padding_right: LayoutPaddingRight {
            inner: PixelValue::const_px(2),
        },
        track: StyleBackgroundContent::Color(ColorU {
            r: 242,
            g: 242,
            b: 242,
            a: 255,
        }),
        thumb: StyleBackgroundContent::Color(ColorU {
            r: 177,
            g: 177,
            b: 177,
            a: 255,
        }),
        button: StyleBackgroundContent::Color(ColorU {
            r: 216,
            g: 216,
            b: 216,
            a: 255,
        }),
        corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
        resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
        clip_to_container_border: false,
        scroll_behavior: ScrollBehavior::Auto,
        overscroll_behavior_x: OverscrollBehavior::Auto,
        overscroll_behavior_y: OverscrollBehavior::Auto,
        overflow_scrolling: OverflowScrolling::Auto,
    };
    /// Helper to convert a detailed `ScrollbarInfo` into the simplified `ComputedScrollbarStyle`.
335
    fn scrollbar_info_to_computed(info: &ScrollbarInfo) -> ComputedScrollbarStyle {
        ComputedScrollbarStyle {
335
            width: Some(info.width.clone()),
335
            thumb_color: match info.thumb {
335
                StyleBackgroundContent::Color(c) => Some(c),
                _ => None,
            },
335
            track_color: match info.track {
335
                StyleBackgroundContent::Color(c) => Some(c),
                _ => None,
            },
        }
335
    }
    // --- Windows Styles ---
    /// Windows 11 light mode defaults (Segoe UI Variable, `WinUI` 3 colors).
27
    #[must_use] pub fn windows_11_light() -> SystemStyle {
27
        SystemStyle {
27
            theme: Theme::Light,
27
            platform: Platform::Windows,
27
            colors: SystemColors {
27
                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
27
                background: OptionColorU::Some(ColorU::new_rgb(243, 243, 243)),
27
                accent: OptionColorU::Some(ColorU::new_rgb(0, 95, 184)),
27
                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
27
                selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
27
                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
27
                ..Default::default()
27
            },
27
            fonts: SystemFonts {
27
                ui_font: OptionString::Some("Segoe UI Variable Text".into()),
27
                ui_font_size: OptionF32::Some(9.0),
27
                monospace_font: OptionString::Some("Consolas".into()),
27
                ..Default::default()
27
            },
27
            metrics: SystemMetrics {
27
                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
27
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
27
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
27
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
27
                titlebar: TitlebarMetrics::windows(),
27
            },
27
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_LIGHT))),
27
            app_specific_stylesheet: None,
27
            run_destructor: true,
27
            icon_style: IconStyleOptions::default(),
27
            language: AzString::from_const_str("en-US"),
27
            os_version: OsVersion::WIN_11,
27
            prefers_reduced_motion: BoolCondition::False,
27
            prefers_high_contrast: BoolCondition::False,
27
            scroll_physics: ScrollPhysics::windows(),
27
            linux: LinuxCustomization::default(),
27
            focus_visuals: FocusVisuals::default(),
27
            handedness: Handedness::default(),
27
            accessibility: AccessibilitySettings::default(),
27
            input: InputMetrics::default(),
27
            text_rendering: TextRenderingHints::default(),
27
            scrollbar_preferences: ScrollbarPreferences::default(),
27
            visual_hints: VisualHints::default(),
27
            animation: AnimationMetrics::default(),
27
            audio: AudioMetrics::default(),
27
        }
27
    }
    /// Windows 11 dark mode defaults (Segoe UI Variable, `WinUI` 3 dark colors).
7
    #[must_use] pub fn windows_11_dark() -> SystemStyle {
7
        SystemStyle {
7
            theme: Theme::Dark,
7
            platform: Platform::Windows,
7
            colors: SystemColors {
7
                text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
7
                background: OptionColorU::Some(ColorU::new_rgb(32, 32, 32)),
7
                accent: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
7
                window_background: OptionColorU::Some(ColorU::new_rgb(25, 25, 25)),
7
                selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
7
                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
7
                ..Default::default()
7
            },
7
            fonts: SystemFonts {
7
                ui_font: OptionString::Some("Segoe UI Variable Text".into()),
7
                ui_font_size: OptionF32::Some(9.0),
7
                monospace_font: OptionString::Some("Consolas".into()),
7
                ..Default::default()
7
            },
7
            metrics: SystemMetrics {
7
                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
7
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
7
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
7
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
7
                titlebar: TitlebarMetrics::windows(),
7
            },
7
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_DARK))),
7
            app_specific_stylesheet: None,
7
            run_destructor: true,
7
            icon_style: IconStyleOptions::default(),
7
            language: AzString::from_const_str("en-US"),
7
            os_version: OsVersion::WIN_11,
7
            prefers_reduced_motion: BoolCondition::False,
7
            prefers_high_contrast: BoolCondition::False,
7
            scroll_physics: ScrollPhysics::windows(),
7
            linux: LinuxCustomization::default(),
7
            focus_visuals: FocusVisuals::default(),
7
            handedness: Handedness::default(),
7
            accessibility: AccessibilitySettings::default(),
7
            input: InputMetrics::default(),
7
            text_rendering: TextRenderingHints::default(),
7
            scrollbar_preferences: ScrollbarPreferences::default(),
7
            visual_hints: VisualHints::default(),
7
            animation: AnimationMetrics::default(),
7
            audio: AudioMetrics::default(),
7
        }
7
    }
    /// Windows 7 Aero theme defaults (Segoe UI, classic Aero colors).
11
    #[must_use] pub fn windows_7_aero() -> SystemStyle {
11
        SystemStyle {
11
            theme: Theme::Light,
11
            platform: Platform::Windows,
11
            colors: SystemColors {
11
                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
11
                background: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
11
                accent: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
11
                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
11
                selection_background: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
11
                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
11
                ..Default::default()
11
            },
11
            fonts: SystemFonts {
11
                ui_font: OptionString::Some("Segoe UI".into()),
11
                ui_font_size: OptionF32::Some(9.0),
11
                monospace_font: OptionString::Some("Consolas".into()),
11
                ..Default::default()
11
            },
11
            metrics: SystemMetrics {
11
                corner_radius: OptionPixelValue::Some(PixelValue::px(6.0)),
11
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
11
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
11
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(5.0)),
11
                titlebar: TitlebarMetrics::windows(),
11
            },
11
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
11
            app_specific_stylesheet: None,
11
            run_destructor: true,
11
            icon_style: IconStyleOptions::default(),
11
            language: AzString::from_const_str("en-US"),
11
            os_version: OsVersion::WIN_7,
11
            prefers_reduced_motion: BoolCondition::False,
11
            prefers_high_contrast: BoolCondition::False,
11
            scroll_physics: ScrollPhysics::windows(),
11
            linux: LinuxCustomization::default(),
11
            focus_visuals: FocusVisuals::default(),
11
            handedness: Handedness::default(),
11
            accessibility: AccessibilitySettings::default(),
11
            input: InputMetrics::default(),
11
            text_rendering: TextRenderingHints::default(),
11
            scrollbar_preferences: ScrollbarPreferences::default(),
11
            visual_hints: VisualHints::default(),
11
            animation: AnimationMetrics::default(),
11
            audio: AudioMetrics::default(),
11
        }
11
    }
    /// Windows XP Luna theme defaults (Tahoma, classic Luna blue).
11
    #[must_use] pub fn windows_xp_luna() -> SystemStyle {
11
        SystemStyle {
11
            theme: Theme::Light,
11
            platform: Platform::Windows,
11
            colors: SystemColors {
11
                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
11
                background: OptionColorU::Some(ColorU::new_rgb(236, 233, 216)),
11
                accent: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
11
                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
11
                selection_background: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
11
                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
11
                ..Default::default()
11
            },
11
            fonts: SystemFonts {
11
                ui_font: OptionString::Some("Tahoma".into()),
11
                ui_font_size: OptionF32::Some(8.0),
11
                monospace_font: OptionString::Some("Lucida Console".into()),
11
                ..Default::default()
11
            },
11
            metrics: SystemMetrics {
11
                corner_radius: OptionPixelValue::Some(PixelValue::px(3.0)),
11
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
11
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
11
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
11
                titlebar: TitlebarMetrics::windows(),
11
            },
11
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_CLASSIC))),
11
            app_specific_stylesheet: None,
11
            run_destructor: true,
11
            icon_style: IconStyleOptions::default(),
11
            language: AzString::from_const_str("en-US"),
11
            os_version: OsVersion::WIN_XP,
11
            prefers_reduced_motion: BoolCondition::False,
11
            prefers_high_contrast: BoolCondition::False,
11
            scroll_physics: ScrollPhysics::windows(),
11
            linux: LinuxCustomization::default(),
11
            focus_visuals: FocusVisuals::default(),
11
            handedness: Handedness::default(),
11
            accessibility: AccessibilitySettings::default(),
11
            input: InputMetrics::default(),
11
            text_rendering: TextRenderingHints::default(),
11
            scrollbar_preferences: ScrollbarPreferences::default(),
11
            visual_hints: VisualHints::default(),
11
            animation: AnimationMetrics::default(),
11
            audio: AudioMetrics::default(),
11
        }
11
    }
    // --- macOS Styles ---
    /// Modern macOS light mode defaults (SF Pro, rounded corners).
7
    #[must_use] pub fn macos_modern_light() -> SystemStyle {
7
        SystemStyle {
7
            platform: Platform::MacOs,
7
            theme: Theme::Light,
7
            colors: SystemColors {
7
                text: OptionColorU::Some(ColorU::new(0, 0, 0, 221)),
7
                background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
7
                accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
7
                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
7
                // Default macOS selection uses accent color with transparency
7
                selection_background: OptionColorU::Some(ColorU::new(0, 122, 255, 128)),
7
                selection_text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
7
                ..Default::default()
7
            },
7
            fonts: SystemFonts {
7
                ui_font: OptionString::Some(".SF NS".into()),
7
                ui_font_size: OptionF32::Some(13.0),
7
                monospace_font: OptionString::Some("Menlo".into()),
7
                ..Default::default()
7
            },
7
            metrics: SystemMetrics {
7
                corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
7
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
7
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
7
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
7
                titlebar: TitlebarMetrics::macos(),
7
            },
7
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_LIGHT))),
7
            app_specific_stylesheet: None,
7
            run_destructor: true,
7
            icon_style: IconStyleOptions::default(),
7
            language: AzString::from_const_str("en-US"),
7
            os_version: OsVersion::MACOS_SONOMA,
7
            prefers_reduced_motion: BoolCondition::False,
7
            prefers_high_contrast: BoolCondition::False,
7
            scroll_physics: ScrollPhysics::macos(),
7
            linux: LinuxCustomization::default(),
7
            focus_visuals: FocusVisuals::default(),
7
            handedness: Handedness::default(),
7
            accessibility: AccessibilitySettings::default(),
7
            input: InputMetrics::default(),
7
            text_rendering: TextRenderingHints::default(),
7
            scrollbar_preferences: ScrollbarPreferences::default(),
7
            visual_hints: VisualHints::default(),
7
            animation: AnimationMetrics::default(),
7
            audio: AudioMetrics::default(),
7
        }
7
    }
    /// Modern macOS dark mode defaults (SF Pro, dark background).
7
    #[must_use] pub fn macos_modern_dark() -> SystemStyle {
7
        SystemStyle {
7
            platform: Platform::MacOs,
7
            theme: Theme::Dark,
7
            colors: SystemColors {
7
                text: OptionColorU::Some(ColorU::new(255, 255, 255, 221)),
7
                background: OptionColorU::Some(ColorU::new_rgb(28, 28, 30)),
7
                accent: OptionColorU::Some(ColorU::new_rgb(10, 132, 255)),
7
                window_background: OptionColorU::Some(ColorU::new_rgb(44, 44, 46)),
7
                // Default macOS selection uses accent color with transparency
7
                selection_background: OptionColorU::Some(ColorU::new(10, 132, 255, 128)),
7
                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
7
                ..Default::default()
7
            },
7
            fonts: SystemFonts {
7
                ui_font: OptionString::Some(".SF NS".into()),
7
                ui_font_size: OptionF32::Some(13.0),
7
                monospace_font: OptionString::Some("SF Mono".into()),
7
                monospace_font_size: OptionF32::Some(12.0),
7
                title_font: OptionString::Some(".SF NS".into()),
7
                title_font_size: OptionF32::Some(13.0),
7
                menu_font: OptionString::Some(".SF NS".into()),
7
                menu_font_size: OptionF32::Some(13.0),
7
                small_font: OptionString::Some(".SF NS".into()),
7
                small_font_size: OptionF32::Some(11.0),
7
                ..Default::default()
7
            },
7
            metrics: SystemMetrics {
7
                corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
7
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
7
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
7
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
7
                titlebar: TitlebarMetrics::macos(),
7
            },
7
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_DARK))),
7
            app_specific_stylesheet: None,
7
            run_destructor: true,
7
            icon_style: IconStyleOptions::default(),
7
            language: AzString::from_const_str("en-US"),
7
            os_version: OsVersion::MACOS_SONOMA,
7
            prefers_reduced_motion: BoolCondition::False,
7
            prefers_high_contrast: BoolCondition::False,
7
            scroll_physics: ScrollPhysics::macos(),
7
            linux: LinuxCustomization::default(),
7
            focus_visuals: FocusVisuals::default(),
7
            handedness: Handedness::default(),
7
            accessibility: AccessibilitySettings::default(),
7
            input: InputMetrics::default(),
7
            text_rendering: TextRenderingHints::default(),
7
            scrollbar_preferences: ScrollbarPreferences::default(),
7
            visual_hints: VisualHints::default(),
7
            animation: AnimationMetrics::default(),
7
            audio: AudioMetrics::default(),
7
        }
7
    }
    /// Classic macOS Aqua theme defaults (Lucida Grande, gel scrollbars).
11
    #[must_use] pub fn macos_aqua() -> SystemStyle {
11
        SystemStyle {
11
            platform: Platform::MacOs,
11
            theme: Theme::Light,
11
            colors: SystemColors {
11
                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
11
                background: OptionColorU::Some(ColorU::new_rgb(229, 229, 229)),
11
                accent: OptionColorU::Some(ColorU::new_rgb(63, 128, 234)),
11
                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
11
                ..Default::default()
11
            },
11
            fonts: SystemFonts {
11
                ui_font: OptionString::Some("Lucida Grande".into()),
11
                ui_font_size: OptionF32::Some(13.0),
11
                monospace_font: OptionString::Some("Monaco".into()),
11
                monospace_font_size: OptionF32::Some(12.0),
11
                ..Default::default()
11
            },
11
            metrics: SystemMetrics {
11
                corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
11
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
11
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
11
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
11
                titlebar: TitlebarMetrics::macos(),
11
            },
11
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_AQUA))),
11
            app_specific_stylesheet: None,
11
            run_destructor: true,
11
            icon_style: IconStyleOptions::default(),
11
            language: AzString::from_const_str("en-US"),
11
            os_version: OsVersion::MACOS_TIGER,
11
            prefers_reduced_motion: BoolCondition::False,
11
            prefers_high_contrast: BoolCondition::False,
11
            scroll_physics: ScrollPhysics::macos(),
11
            linux: LinuxCustomization::default(),
11
            focus_visuals: FocusVisuals::default(),
11
            handedness: Handedness::default(),
11
            accessibility: AccessibilitySettings::default(),
11
            input: InputMetrics::default(),
11
            text_rendering: TextRenderingHints::default(),
11
            scrollbar_preferences: ScrollbarPreferences::default(),
11
            visual_hints: VisualHints::default(),
11
            animation: AnimationMetrics::default(),
11
            audio: AudioMetrics::default(),
11
        }
11
    }
    // --- Linux Styles ---
    /// GNOME Adwaita light theme defaults (Cantarell font).
208
    #[must_use] pub fn gnome_adwaita_light() -> SystemStyle {
208
        SystemStyle {
208
            platform: Platform::Linux(DesktopEnvironment::Gnome),
208
            theme: Theme::Light,
208
            colors: SystemColors {
208
                text: OptionColorU::Some(ColorU::new_rgb(46, 52, 54)),
208
                background: OptionColorU::Some(ColorU::new_rgb(249, 249, 249)),
208
                accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
208
                window_background: OptionColorU::Some(ColorU::new_rgb(237, 237, 237)),
208
                ..Default::default()
208
            },
208
            fonts: SystemFonts {
208
                ui_font: OptionString::Some("Cantarell".into()),
208
                ui_font_size: OptionF32::Some(11.0),
208
                monospace_font: OptionString::Some("Monospace".into()),
208
                ..Default::default()
208
            },
208
            metrics: SystemMetrics {
208
                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
208
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
208
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
208
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
208
                titlebar: TitlebarMetrics::linux_gnome(),
208
            },
208
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
208
            app_specific_stylesheet: None,
208
            run_destructor: true,
208
            icon_style: IconStyleOptions::default(),
208
            language: AzString::from_const_str("en-US"),
208
            os_version: OsVersion::LINUX_6_0,
208
            prefers_reduced_motion: BoolCondition::False,
208
            prefers_high_contrast: BoolCondition::False,
208
            scroll_physics: ScrollPhysics::default(),
208
            linux: LinuxCustomization::default(),
208
            focus_visuals: FocusVisuals::default(),
208
            handedness: Handedness::default(),
208
            accessibility: AccessibilitySettings::default(),
208
            input: InputMetrics::default(),
208
            text_rendering: TextRenderingHints::default(),
208
            scrollbar_preferences: ScrollbarPreferences::default(),
208
            visual_hints: VisualHints::default(),
208
            animation: AnimationMetrics::default(),
208
            audio: AudioMetrics::default(),
208
        }
208
    }
    /// GNOME Adwaita dark theme defaults (Cantarell font, dark background).
9
    #[must_use] pub fn gnome_adwaita_dark() -> SystemStyle {
9
        SystemStyle {
9
            platform: Platform::Linux(DesktopEnvironment::Gnome),
9
            theme: Theme::Dark,
9
            colors: SystemColors {
9
                text: OptionColorU::Some(ColorU::new_rgb(238, 238, 236)),
9
                background: OptionColorU::Some(ColorU::new_rgb(36, 36, 36)),
9
                accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
9
                window_background: OptionColorU::Some(ColorU::new_rgb(48, 48, 48)),
9
                ..Default::default()
9
            },
9
            fonts: SystemFonts {
9
                ui_font: OptionString::Some("Cantarell".into()),
9
                ui_font_size: OptionF32::Some(11.0),
9
                monospace_font: OptionString::Some("Monospace".into()),
9
                ..Default::default()
9
            },
9
            metrics: SystemMetrics {
9
                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
9
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
9
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
9
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
9
                titlebar: TitlebarMetrics::linux_gnome(),
9
            },
9
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_DARK))),
9
            app_specific_stylesheet: None,
9
            run_destructor: true,
9
            icon_style: IconStyleOptions::default(),
9
            language: AzString::from_const_str("en-US"),
9
            os_version: OsVersion::LINUX_6_0,
9
            prefers_reduced_motion: BoolCondition::False,
9
            prefers_high_contrast: BoolCondition::False,
9
            scroll_physics: ScrollPhysics::default(),
9
            linux: LinuxCustomization::default(),
9
            focus_visuals: FocusVisuals::default(),
9
            handedness: Handedness::default(),
9
            accessibility: AccessibilitySettings::default(),
9
            input: InputMetrics::default(),
9
            text_rendering: TextRenderingHints::default(),
9
            scrollbar_preferences: ScrollbarPreferences::default(),
9
            visual_hints: VisualHints::default(),
9
            animation: AnimationMetrics::default(),
9
            audio: AudioMetrics::default(),
9
        }
9
    }
    /// GTK2 Clearlooks theme defaults (`DejaVu` Sans, orange accent).
11
    #[must_use] pub fn gtk2_clearlooks() -> SystemStyle {
11
        SystemStyle {
11
            platform: Platform::Linux(DesktopEnvironment::Gnome),
11
            theme: Theme::Light,
11
            colors: SystemColors {
11
                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
11
                background: OptionColorU::Some(ColorU::new_rgb(239, 239, 239)),
11
                accent: OptionColorU::Some(ColorU::new_rgb(245, 121, 0)),
11
                ..Default::default()
11
            },
11
            fonts: SystemFonts {
11
                ui_font: OptionString::Some("DejaVu Sans".into()),
11
                ui_font_size: OptionF32::Some(10.0),
11
                monospace_font: OptionString::Some("DejaVu Sans Mono".into()),
11
                ..Default::default()
11
            },
11
            metrics: SystemMetrics {
11
                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
11
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
11
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
11
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
11
                titlebar: TitlebarMetrics::linux_gnome(),
11
            },
11
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
11
            app_specific_stylesheet: None,
11
            run_destructor: true,
11
            icon_style: IconStyleOptions::default(),
11
            language: AzString::from_const_str("en-US"),
11
            os_version: OsVersion::LINUX_2_6,
11
            prefers_reduced_motion: BoolCondition::False,
11
            prefers_high_contrast: BoolCondition::False,
11
            scroll_physics: ScrollPhysics::default(),
11
            linux: LinuxCustomization::default(),
11
            focus_visuals: FocusVisuals::default(),
11
            handedness: Handedness::default(),
11
            accessibility: AccessibilitySettings::default(),
11
            input: InputMetrics::default(),
11
            text_rendering: TextRenderingHints::default(),
11
            scrollbar_preferences: ScrollbarPreferences::default(),
11
            visual_hints: VisualHints::default(),
11
            animation: AnimationMetrics::default(),
11
            audio: AudioMetrics::default(),
11
        }
11
    }
    /// KDE Breeze light theme defaults (Noto Sans, Oxygen scrollbars).
7
    #[must_use] pub fn kde_breeze_light() -> SystemStyle {
7
        SystemStyle {
7
            platform: Platform::Linux(DesktopEnvironment::Kde),
7
            theme: Theme::Light,
7
            colors: SystemColors {
7
                text: OptionColorU::Some(ColorU::new_rgb(31, 36, 39)),
7
                background: OptionColorU::Some(ColorU::new_rgb(239, 240, 241)),
7
                accent: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
7
                ..Default::default()
7
            },
7
            fonts: SystemFonts {
7
                ui_font: OptionString::Some("Noto Sans".into()),
7
                ui_font_size: OptionF32::Some(10.0),
7
                monospace_font: OptionString::Some("Hack".into()),
7
                ..Default::default()
7
            },
7
            metrics: SystemMetrics {
7
                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
7
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
7
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
7
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
7
                titlebar: TitlebarMetrics::linux_gnome(),
7
            },
7
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_KDE_OXYGEN))),
7
            app_specific_stylesheet: None,
7
            run_destructor: true,
7
            icon_style: IconStyleOptions::default(),
7
            language: AzString::from_const_str("en-US"),
7
            os_version: OsVersion::LINUX_6_0,
7
            prefers_reduced_motion: BoolCondition::False,
7
            prefers_high_contrast: BoolCondition::False,
7
            scroll_physics: ScrollPhysics::default(),
7
            linux: LinuxCustomization::default(),
7
            focus_visuals: FocusVisuals::default(),
7
            handedness: Handedness::default(),
7
            accessibility: AccessibilitySettings::default(),
7
            input: InputMetrics::default(),
7
            text_rendering: TextRenderingHints::default(),
7
            scrollbar_preferences: ScrollbarPreferences::default(),
7
            visual_hints: VisualHints::default(),
7
            animation: AnimationMetrics::default(),
7
            audio: AudioMetrics::default(),
7
        }
7
    }
    // --- Mobile Styles ---
    /// Android Material Design light theme defaults (Roboto font).
6
    #[must_use] pub fn android_material_light() -> SystemStyle {
6
        SystemStyle {
6
            platform: Platform::Android,
6
            theme: Theme::Light,
6
            colors: SystemColors {
6
                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
6
                background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
6
                accent: OptionColorU::Some(ColorU::new_rgb(98, 0, 238)),
6
                ..Default::default()
6
            },
6
            fonts: SystemFonts {
6
                ui_font: OptionString::Some("Roboto".into()),
6
                ui_font_size: OptionF32::Some(14.0),
6
                monospace_font: OptionString::Some("Droid Sans Mono".into()),
6
                ..Default::default()
6
            },
6
            metrics: SystemMetrics {
6
                corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
6
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
6
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
6
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(10.0)),
6
                titlebar: TitlebarMetrics::android(),
6
            },
6
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_LIGHT))),
6
            app_specific_stylesheet: None,
6
            run_destructor: true,
6
            icon_style: IconStyleOptions::default(),
6
            language: AzString::from_const_str("en-US"),
6
            os_version: OsVersion::ANDROID_14,
6
            prefers_reduced_motion: BoolCondition::False,
6
            prefers_high_contrast: BoolCondition::False,
6
            scroll_physics: ScrollPhysics::android(),
6
            linux: LinuxCustomization::default(),
6
            focus_visuals: FocusVisuals::default(),
6
            handedness: Handedness::default(),
6
            accessibility: AccessibilitySettings::default(),
6
            input: InputMetrics::default(),
6
            text_rendering: TextRenderingHints::default(),
6
            scrollbar_preferences: ScrollbarPreferences::default(),
6
            visual_hints: VisualHints::default(),
6
            animation: AnimationMetrics::default(),
6
            audio: AudioMetrics::default(),
6
        }
6
    }
    /// Android Holo dark theme defaults (Roboto font, dark background).
7
    #[must_use] pub fn android_holo_dark() -> SystemStyle {
7
        SystemStyle {
7
            platform: Platform::Android,
7
            theme: Theme::Dark,
7
            colors: SystemColors {
7
                text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
7
                background: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
7
                accent: OptionColorU::Some(ColorU::new_rgb(51, 181, 229)),
7
                ..Default::default()
7
            },
7
            fonts: SystemFonts {
7
                ui_font: OptionString::Some("Roboto".into()),
7
                ui_font_size: OptionF32::Some(14.0),
7
                monospace_font: OptionString::Some("Droid Sans Mono".into()),
7
                ..Default::default()
7
            },
7
            metrics: SystemMetrics {
7
                corner_radius: OptionPixelValue::Some(PixelValue::px(2.0)),
7
                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
7
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
7
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
7
                titlebar: TitlebarMetrics::android(),
7
            },
7
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_DARK))),
7
            app_specific_stylesheet: None,
7
            run_destructor: true,
7
            icon_style: IconStyleOptions::default(),
7
            language: AzString::from_const_str("en-US"),
7
            os_version: OsVersion::ANDROID_ICE_CREAM_SANDWICH,
7
            prefers_reduced_motion: BoolCondition::False,
7
            prefers_high_contrast: BoolCondition::False,
7
            scroll_physics: ScrollPhysics::android(),
7
            linux: LinuxCustomization::default(),
7
            focus_visuals: FocusVisuals::default(),
7
            handedness: Handedness::default(),
7
            accessibility: AccessibilitySettings::default(),
7
            input: InputMetrics::default(),
7
            text_rendering: TextRenderingHints::default(),
7
            scrollbar_preferences: ScrollbarPreferences::default(),
7
            visual_hints: VisualHints::default(),
7
            animation: AnimationMetrics::default(),
7
            audio: AudioMetrics::default(),
7
        }
7
    }
    /// iOS light theme defaults (SF UI font, rounded corners).
6
    #[must_use] pub fn ios_light() -> SystemStyle {
6
        SystemStyle {
6
            platform: Platform::Ios,
6
            theme: Theme::Light,
6
            colors: SystemColors {
6
                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
6
                background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
6
                accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
6
                ..Default::default()
6
            },
6
            fonts: SystemFonts {
6
                ui_font: OptionString::Some(".SFUI-Display-Regular".into()),
6
                ui_font_size: OptionF32::Some(17.0),
6
                monospace_font: OptionString::Some("Menlo".into()),
6
                ..Default::default()
6
            },
6
            metrics: SystemMetrics {
6
                corner_radius: OptionPixelValue::Some(PixelValue::px(10.0)),
6
                border_width: OptionPixelValue::Some(PixelValue::px(0.5)),
6
                button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(20.0)),
6
                button_padding_vertical: OptionPixelValue::Some(PixelValue::px(12.0)),
6
                titlebar: TitlebarMetrics::ios(),
6
            },
6
            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_IOS_LIGHT))),
6
            app_specific_stylesheet: None,
6
            run_destructor: true,
6
            icon_style: IconStyleOptions::default(),
6
            language: AzString::from_const_str("en-US"),
6
            os_version: OsVersion::IOS_17,
6
            prefers_reduced_motion: BoolCondition::False,
6
            prefers_high_contrast: BoolCondition::False,
6
            scroll_physics: ScrollPhysics::ios(),
6
            linux: LinuxCustomization::default(),
6
            focus_visuals: FocusVisuals::default(),
6
            handedness: Handedness::default(),
6
            accessibility: AccessibilitySettings::default(),
6
            input: InputMetrics::default(),
6
            text_rendering: TextRenderingHints::default(),
6
            scrollbar_preferences: ScrollbarPreferences::default(),
6
            visual_hints: VisualHints::default(),
6
            animation: AnimationMetrics::default(),
6
            audio: AudioMetrics::default(),
6
        }
6
    }
}
#[cfg(test)]
mod autotest_generated {
    use super::*;
    use crate::css::rule_priority;
    const ALL_FONT_TYPES: [SystemFontType; 11] = [
        SystemFontType::Ui,
        SystemFontType::UiBold,
        SystemFontType::Monospace,
        SystemFontType::MonospaceBold,
        SystemFontType::MonospaceItalic,
        SystemFontType::Title,
        SystemFontType::TitleBold,
        SystemFontType::Menu,
        SystemFontType::Small,
        SystemFontType::Serif,
        SystemFontType::SerifBold,
    ];
    fn all_platforms() -> Vec<Platform> {
        vec![
            Platform::Windows,
            Platform::MacOs,
            Platform::Linux(DesktopEnvironment::Gnome),
            Platform::Linux(DesktopEnvironment::Kde),
            Platform::Linux(DesktopEnvironment::Other(AzString::from_const_str("Hyprland"))),
            Platform::Android,
            Platform::Ios,
            Platform::Unknown,
        ]
    }
    /// Every hard-coded default style, so the smoke tests can sweep all of them.
    fn all_default_styles() -> Vec<(&'static str, SystemStyle)> {
        vec![
            ("windows_11_light", defaults::windows_11_light()),
            ("windows_11_dark", defaults::windows_11_dark()),
            ("windows_7_aero", defaults::windows_7_aero()),
            ("windows_xp_luna", defaults::windows_xp_luna()),
            ("macos_modern_light", defaults::macos_modern_light()),
            ("macos_modern_dark", defaults::macos_modern_dark()),
            ("macos_aqua", defaults::macos_aqua()),
            ("gnome_adwaita_light", defaults::gnome_adwaita_light()),
            ("gnome_adwaita_dark", defaults::gnome_adwaita_dark()),
            ("gtk2_clearlooks", defaults::gtk2_clearlooks()),
            ("kde_breeze_light", defaults::kde_breeze_light()),
            ("android_material_light", defaults::android_material_light()),
            ("android_holo_dark", defaults::android_holo_dark()),
            ("ios_light", defaults::ios_light()),
        ]
    }
    // ── SystemFontType::from_css_str — parser ────────────────────────────
    #[test]
    fn from_css_str_valid_minimal() {
        assert_eq!(SystemFontType::from_css_str("system:ui"), Some(SystemFontType::Ui));
        assert_eq!(
            SystemFontType::from_css_str("system:monospace:italic"),
            Some(SystemFontType::MonospaceItalic)
        );
    }
    #[test]
    fn from_css_str_empty_input_returns_none() {
        assert_eq!(SystemFontType::from_css_str(""), None);
    }
    #[test]
    fn from_css_str_whitespace_only_returns_none() {
        for s in ["   ", "\t\n", "\r\n\r\n", "\t \t \n"] {
            assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
        }
    }
    #[test]
    fn from_css_str_prefix_only_is_none_and_does_not_panic_on_slice() {
        // Exactly 7 bytes: `&s[7..]` slices right at the end of the string.
        assert_eq!(SystemFontType::from_css_str("system:"), None);
        assert_eq!(SystemFontType::from_css_str("  system:  "), None);
        assert_eq!(SystemFontType::from_css_str("system::"), None);
    }
    #[test]
    fn from_css_str_garbage_returns_none() {
        for s in [
            ";;;",
            "{}{}",
            "\0\u{1}\u{2}\u{7f}",
            "system",
            "systemui",
            "system;ui",
            "system:ui:",
            ":system:ui",
            "font-family: system:ui;",
            "\\system:ui",
            "system:ui\0",
            "system:\u{0}ui",
        ] {
            assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
        }
    }
    #[test]
    fn from_css_str_leading_trailing_junk() {
        // Surrounding ASCII whitespace is trimmed …
        assert_eq!(SystemFontType::from_css_str("  system:ui  "), Some(SystemFontType::Ui));
        assert_eq!(
            SystemFontType::from_css_str("\t\nsystem:monospace\r\n"),
            Some(SystemFontType::Monospace)
        );
        // … but any other junk is rejected outright.
        assert_eq!(SystemFontType::from_css_str("system:ui;garbage"), None);
        assert_eq!(SystemFontType::from_css_str("garbage system:ui"), None);
        assert_eq!(SystemFontType::from_css_str("system:ui system:ui"), None);
        assert_eq!(SystemFontType::from_css_str("system: ui"), None);
        assert_eq!(SystemFontType::from_css_str("system:ui:bold:extra"), None);
    }
    #[test]
    fn from_css_str_is_case_sensitive() {
        // Documents current behaviour: unlike `AZ_RICING`, the font keyword
        // is matched case-sensitively, so upper/mixed case is rejected.
        for s in ["SYSTEM:UI", "System:Ui", "system:UI", "System:ui", "sYsTeM:ui"] {
            assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
        }
    }
    #[test]
    fn from_css_str_boundary_numbers() {
        for s in [
            "0",
            "-0",
            "9223372036854775807",
            "-9223372036854775808",
            "NaN",
            "inf",
            "-inf",
            "1e400",
            "system:0",
            "system:-1",
            "system:NaN",
            "system:inf",
            "system:9223372036854775807",
        ] {
            assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
        }
    }
    #[test]
    fn from_css_str_unicode_does_not_panic() {
        for s in [
            "\u{1F600}",
            "system:\u{1F600}",
            "system:ui\u{0301}",          // combining acute accent
            "\u{1F600}system:ui",
            "systém:ui",                   // non-ASCII inside the prefix
            "system:ui",              // fullwidth latin
            "system:\u{202E}ui",           // right-to-left override
            "system:\u{FFFD}",
            "system:ui\u{200B}",           // zero-width space (not trimmed)
        ] {
            assert_eq!(SystemFontType::from_css_str(s), None, "input {s:?}");
        }
    }
    #[test]
    fn from_css_str_extremely_long_input_does_not_hang() {
        let long = format!("system:{}", "u".repeat(1_000_000));
        assert_eq!(SystemFontType::from_css_str(&long), None);
        // A valid keyword with a megabyte of trailing junk is still invalid.
        let long_suffix = format!("system:ui{}", "x".repeat(1_000_000));
        assert_eq!(SystemFontType::from_css_str(&long_suffix), None);
        // A megabyte of whitespace around a valid keyword still parses.
        let padded = format!("{}system:ui{}", " ".repeat(100_000), " ".repeat(100_000));
        assert_eq!(SystemFontType::from_css_str(&padded), Some(SystemFontType::Ui));
    }
    #[test]
    fn from_css_str_deeply_nested_input_does_not_stack_overflow() {
        let nested = format!("system:{}{}", "(".repeat(10_000), ")".repeat(10_000));
        assert_eq!(SystemFontType::from_css_str(&nested), None);
        let brackets = format!("system:{}", "[".repeat(10_000));
        assert_eq!(SystemFontType::from_css_str(&brackets), None);
    }
    // ── SystemFontType round-trip / getters ──────────────────────────────
    #[test]
    fn font_type_css_str_round_trips() {
        for ty in ALL_FONT_TYPES {
            let s = ty.as_css_str();
            assert_eq!(SystemFontType::from_css_str(s), Some(ty), "round-trip of {ty:?}");
            // Padding must not change the decoded value.
            assert_eq!(
                SystemFontType::from_css_str(&format!("  {s}\t")),
                Some(ty),
                "padded round-trip of {ty:?}"
            );
        }
    }
    #[test]
    fn font_type_css_str_is_well_formed_and_unique() {
        let mut seen: Vec<&'static str> = Vec::new();
        for ty in ALL_FONT_TYPES {
            let s = ty.as_css_str();
            assert!(s.starts_with("system:"), "{ty:?} -> {s:?}");
            assert!(s.len() > "system:".len(), "{ty:?} has an empty keyword");
            assert_eq!(s.trim(), s, "{ty:?} -> {s:?} has surrounding whitespace");
            assert!(s.is_ascii(), "{ty:?} -> {s:?} is not ASCII");
            seen.push(s);
        }
        seen.sort_unstable();
        assert!(
            seen.windows(2).all(|w| w[0] != w[1]),
            "as_css_str() is not injective: {seen:?}"
        );
    }
    #[test]
    fn font_type_default_is_ui() {
        let d = SystemFontType::default();
        assert_eq!(d, SystemFontType::Ui);
        assert_eq!(d.as_css_str(), "system:ui");
        assert!(!d.is_bold());
        assert!(!d.is_italic());
    }
    // ── SystemFontType::is_bold / is_italic — predicates ─────────────────
    #[test]
    fn is_bold_matches_exactly_the_bold_variants() {
        assert!(SystemFontType::UiBold.is_bold());
        assert!(SystemFontType::MonospaceBold.is_bold());
        assert!(SystemFontType::TitleBold.is_bold());
        assert!(SystemFontType::SerifBold.is_bold());
        assert!(!SystemFontType::Ui.is_bold());
        assert!(!SystemFontType::Monospace.is_bold());
        assert!(!SystemFontType::MonospaceItalic.is_bold());
        assert!(!SystemFontType::Title.is_bold());
        assert!(!SystemFontType::Menu.is_bold());
        assert!(!SystemFontType::Small.is_bold());
        assert!(!SystemFontType::Serif.is_bold());
    }
    #[test]
    fn is_italic_matches_exactly_the_italic_variant() {
        assert!(SystemFontType::MonospaceItalic.is_italic());
        for ty in ALL_FONT_TYPES {
            if ty != SystemFontType::MonospaceItalic {
                assert!(!ty.is_italic(), "{ty:?} must not be italic");
            }
        }
    }
    #[test]
    fn predicates_agree_with_the_css_keyword() {
        for ty in ALL_FONT_TYPES {
            let s = ty.as_css_str();
            assert_eq!(ty.is_bold(), s.ends_with(":bold"), "{ty:?} -> {s:?}");
            assert_eq!(ty.is_italic(), s.ends_with(":italic"), "{ty:?} -> {s:?}");
            // No variant is both bold and italic.
            assert!(!(ty.is_bold() && ty.is_italic()), "{ty:?} is bold *and* italic");
        }
    }
    // ── SystemFontType::get_fallback_chain (+ private per-OS chains) ─────
    #[test]
    fn fallback_chains_are_non_empty_and_deduplicated() {
        for platform in all_platforms() {
            for ty in ALL_FONT_TYPES {
                let chain = ty.get_fallback_chain(&platform);
                assert!(!chain.is_empty(), "{ty:?} on {platform:?} has an empty chain");
                assert!(
                    chain.iter().all(|f| !f.trim().is_empty()),
                    "{ty:?} on {platform:?} has a blank family: {chain:?}"
                );
                let mut sorted = chain.clone();
                sorted.sort_unstable();
                assert!(
                    sorted.windows(2).all(|w| w[0] != w[1]),
                    "{ty:?} on {platform:?} lists a duplicate family: {chain:?}"
                );
            }
        }
    }
    #[test]
    fn fallback_chain_is_deterministic() {
        for platform in all_platforms() {
            for ty in ALL_FONT_TYPES {
                assert_eq!(
                    ty.get_fallback_chain(&platform),
                    ty.get_fallback_chain(&platform),
                    "{ty:?} on {platform:?} is not deterministic"
                );
            }
        }
    }
    #[test]
    fn ios_shares_the_macos_fallback_chain() {
        for ty in ALL_FONT_TYPES {
            assert_eq!(
                ty.get_fallback_chain(&Platform::Ios),
                ty.get_fallback_chain(&Platform::MacOs),
                "{ty:?}"
            );
        }
    }
    #[test]
    fn linux_fallback_chain_ignores_the_desktop_environment() {
        let gnome = Platform::Linux(DesktopEnvironment::Gnome);
        let kde = Platform::Linux(DesktopEnvironment::Kde);
        let other = Platform::Linux(DesktopEnvironment::Other(AzString::from_const_str("")));
        for ty in ALL_FONT_TYPES {
            let a = ty.get_fallback_chain(&gnome);
            assert_eq!(a, ty.get_fallback_chain(&kde), "{ty:?}");
            assert_eq!(a, ty.get_fallback_chain(&other), "{ty:?}");
        }
    }
    #[test]
    fn unknown_platform_falls_back_to_generic_css_families() {
        for ty in ALL_FONT_TYPES {
            let chain = ty.get_fallback_chain(&Platform::Unknown);
            assert_eq!(chain.len(), 1, "{ty:?} -> {chain:?}");
            let expected = if ty.is_italic() || matches!(
                ty,
                SystemFontType::Monospace | SystemFontType::MonospaceBold
            ) {
                "monospace"
            } else if matches!(ty, SystemFontType::Serif | SystemFontType::SerifBold) {
                "serif"
            } else {
                "sans-serif"
            };
            assert_eq!(chain[0], expected, "{ty:?}");
        }
    }
    #[test]
    fn monospace_variants_share_one_chain_per_platform() {
        for platform in all_platforms() {
            let base = SystemFontType::Monospace.get_fallback_chain(&platform);
            assert_eq!(
                SystemFontType::MonospaceBold.get_fallback_chain(&platform),
                base,
                "{platform:?}"
            );
            assert_eq!(
                SystemFontType::MonospaceItalic.get_fallback_chain(&platform),
                base,
                "{platform:?}"
            );
        }
    }
    // ── Platform::current ────────────────────────────────────────────────
    #[test]
    fn platform_current_is_deterministic_and_matches_target_os() {
        let a = Platform::current();
        assert_eq!(a, Platform::current());
        #[cfg(target_os = "linux")]
        assert!(matches!(a, Platform::Linux(_)), "{a:?}");
        #[cfg(target_os = "windows")]
        assert_eq!(a, Platform::Windows);
        #[cfg(target_os = "macos")]
        assert_eq!(a, Platform::MacOs);
        #[cfg(target_os = "android")]
        assert_eq!(a, Platform::Android);
        #[cfg(target_os = "ios")]
        assert_eq!(a, Platform::Ios);
        // `current()` never reports the fallback on a supported OS.
        #[cfg(any(
            target_os = "linux",
            target_os = "windows",
            target_os = "macos",
            target_os = "android",
            target_os = "ios"
        ))]
        assert_ne!(a, Platform::Unknown);
        // Default is the "we don't know" variant, not the compiled-for one.
        assert_eq!(Platform::default(), Platform::Unknown);
    }
    // ── TitlebarMetrics constructors ─────────────────────────────────────
    #[test]
    fn titlebar_metrics_have_sane_geometry() {
        // NB: TitlebarMetrics::default() is deliberately the "unknown" variant (all-None,
        // so SystemMetrics::default() can represent "not detected" and resolve() falls
        // back) — it is NOT a rendering profile, so it is excluded here. The platform
        // constructors below are the ones that must carry concrete, sane geometry.
        let all = [
            ("windows", TitlebarMetrics::windows()),
            ("macos", TitlebarMetrics::macos()),
            ("linux_gnome", TitlebarMetrics::linux_gnome()),
            ("ios", TitlebarMetrics::ios()),
            ("android", TitlebarMetrics::android()),
        ];
        for (name, tm) in all {
            let height = tm
                .height
                .as_ref()
                .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
                .expect("titlebar height must be set");
            assert!(height.is_finite() && height > 0.0, "{name}: height {height}");
            let button_area = tm
                .button_area_width
                .as_ref()
                .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
                .expect("button area width must be set");
            assert!(
                button_area.is_finite() && button_area >= 0.0,
                "{name}: button_area_width {button_area}"
            );
            let padding = tm
                .padding_horizontal
                .as_ref()
                .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
                .expect("padding must be set");
            assert!(padding.is_finite() && padding >= 0.0, "{name}: padding {padding}");
            let size = tm.title_font_size.into_option().expect("font size must be set");
            assert!(size.is_finite() && size > 0.0, "{name}: font size {size}");
            let weight = tm.title_font_weight.into_option().expect("font weight must be set");
            assert!((100..=900).contains(&weight), "{name}: weight {weight}");
        }
    }
    #[test]
    fn titlebar_metrics_match_their_platform_conventions() {
        let win = TitlebarMetrics::windows();
        assert_eq!(win.button_side, TitlebarButtonSide::Right);
        assert!(win.buttons.has_close && win.buttons.has_minimize && win.buttons.has_maximize);
        assert!(!win.buttons.has_fullscreen);
        // macOS: traffic lights on the left, zoom replaced by fullscreen.
        let mac = TitlebarMetrics::macos();
        assert_eq!(mac.button_side, TitlebarButtonSide::Left);
        assert!(mac.buttons.has_fullscreen);
        assert!(!mac.buttons.has_maximize);
        assert_eq!(TitlebarMetrics::linux_gnome().button_side, TitlebarButtonSide::Right);
        // Mobile: no window controls at all.
        for (name, tm) in [("ios", TitlebarMetrics::ios()), ("android", TitlebarMetrics::android())] {
            let b = tm.buttons;
            assert!(
                !b.has_close && !b.has_minimize && !b.has_maximize && !b.has_fullscreen,
                "{name} must not expose window controls"
            );
        }
        // Only iOS declares a notch safe area.
        let ios = TitlebarMetrics::ios();
        assert!(ios.safe_area.top.is_some());
        assert!(ios.safe_area.bottom.is_some());
        assert_eq!(TitlebarMetrics::windows().safe_area, SafeAreaInsets::default());
    }
    // ── SystemStyle::new / detect / default_for_platform ─────────────────
    #[test]
    fn system_style_new_detect_and_default_for_platform_agree() {
        let a = SystemStyle::new();
        let b = SystemStyle::detect();
        let c = SystemStyle::default_for_platform();
        assert_eq!(a, b);
        assert_eq!(b, c);
    }
    #[test]
    fn system_style_constructors_arm_the_ffi_drop_guard() {
        // `run_destructor` is the double-drop guard; every freshly built style
        // (and every clone of one) must own its heap pointers.
        assert!(SystemStyle::default().run_destructor);
        assert!(SystemStyle::new().run_destructor);
        assert!(SystemStyle::detect().run_destructor);
        for (name, style) in all_default_styles() {
            assert!(style.run_destructor, "{name} does not own its heap pointers");
            assert!(style.clone().run_destructor, "clone of {name} lost the guard");
        }
    }
    #[test]
    fn system_style_default_is_empty_but_valid() {
        let d = SystemStyle::default();
        assert_eq!(d.platform, Platform::Unknown);
        assert_eq!(d.theme, Theme::Light);
        assert!(d.app_specific_stylesheet.is_none());
        assert!(d.scrollbar.is_none());
        assert!(d.language.as_str().is_empty());
        assert!(d.colors.text.is_none());
    }
    // ── defaults::* (+ the private scrollbar_info_to_computed helper) ─────
    #[test]
    fn default_styles_are_fully_populated() {
        for (name, style) in all_default_styles() {
            assert!(style.colors.text.is_some(), "{name}: no text color");
            assert!(style.colors.background.is_some(), "{name}: no background color");
            assert!(style.colors.accent.is_some(), "{name}: no accent color");
            assert!(style.fonts.ui_font.is_some(), "{name}: no UI font");
            assert!(style.fonts.monospace_font.is_some(), "{name}: no monospace font");
            assert!(!style.language.as_str().is_empty(), "{name}: empty language");
            assert_ne!(style.platform, Platform::Unknown, "{name}: unknown platform");
            let size = style.fonts.ui_font_size.into_option().expect("ui font size");
            assert!(size.is_finite() && size > 0.0, "{name}: ui font size {size}");
            let radius = style
                .metrics
                .corner_radius
                .as_ref()
                .map(|p| p.to_pixels_internal(0.0, 0.0, 0.0))
                .expect("corner radius");
            assert!(radius.is_finite() && radius >= 0.0, "{name}: corner radius {radius}");
        }
    }
    #[test]
    fn default_styles_carry_a_fully_resolved_scrollbar() {
        // Exercises the private `scrollbar_info_to_computed` helper: every
        // built-in ScrollbarInfo uses solid colors, so nothing may map to None.
        for (name, style) in all_default_styles() {
            let sb = style.scrollbar.as_ref().unwrap_or_else(|| panic!("{name}: no scrollbar"));
            assert!(sb.width.is_some(), "{name}: scrollbar width lost");
            assert!(sb.thumb_color.is_some(), "{name}: thumb color lost");
            assert!(sb.track_color.is_some(), "{name}: track color lost");
        }
    }
    #[test]
    fn light_and_dark_default_styles_differ() {
        assert_ne!(defaults::windows_11_light(), defaults::windows_11_dark());
        assert_ne!(defaults::macos_modern_light(), defaults::macos_modern_dark());
        assert_ne!(defaults::gnome_adwaita_light(), defaults::gnome_adwaita_dark());
        assert_ne!(defaults::android_material_light(), defaults::android_holo_dark());
        assert_eq!(defaults::windows_11_dark().theme, Theme::Dark);
        assert_eq!(defaults::macos_modern_dark().theme, Theme::Dark);
        assert_eq!(defaults::gnome_adwaita_dark().theme, Theme::Dark);
        assert_eq!(defaults::android_holo_dark().theme, Theme::Dark);
        assert_eq!(defaults::kde_breeze_light().platform, Platform::Linux(DesktopEnvironment::Kde));
        assert_eq!(defaults::ios_light().platform, Platform::Ios);
    }
    #[test]
    fn default_style_constructors_are_deterministic() {
        for _ in 0..3 {
            assert_eq!(defaults::windows_xp_luna(), defaults::windows_xp_luna());
            assert_eq!(defaults::macos_aqua(), defaults::macos_aqua());
            assert_eq!(defaults::gtk2_clearlooks(), defaults::gtk2_clearlooks());
            assert_eq!(defaults::windows_7_aero(), defaults::windows_7_aero());
        }
    }
    // ── SystemStyle::to_json_string ──────────────────────────────────────
    #[test]
    fn to_json_string_has_balanced_braces_for_every_default() {
        let mut styles = all_default_styles();
        styles.push(("default", SystemStyle::default()));
        for (name, style) in styles {
            let json = style.to_json_string();
            let s = json.as_str();
            assert!(s.starts_with('{'), "{name}: does not start with '{{'");
            assert!(s.ends_with('}'), "{name}: does not end with '}}'");
            let open = s.chars().filter(|c| *c == '{').count();
            let close = s.chars().filter(|c| *c == '}').count();
            assert_eq!(open, close, "{name}: unbalanced braces");
            for key in [
                "\"theme\"",
                "\"platform\"",
                "\"colors\"",
                "\"fonts\"",
                "\"titlebar\"",
                "\"input\"",
                "\"accessibility\"",
                "\"audio\"",
            ] {
                assert!(s.contains(key), "{name}: missing {key}");
            }
        }
    }
    #[test]
    fn to_json_string_reports_known_values() {
        let json = defaults::windows_11_light().to_json_string();
        let s = json.as_str();
        assert!(s.contains("\"theme\": \"Light\""), "{s}");
        assert!(s.contains("\"platform\": \"Windows\""), "{s}");
        assert!(s.contains("\"language\": \"en-US\""), "{s}");
        // text = rgb(0,0,0) -> "#000000ff" (alpha is included)
        assert!(s.contains("\"text\": \"#000000ff\""), "{s}");
        // Windows titlebar height is 32px, formatted with one decimal.
        assert!(s.contains("\"height\": 32.0"), "{s}");
        // Unset colors serialize as JSON null, not as an empty string.
        assert!(s.contains("\"grid\": null"), "{s}");
    }
    #[test]
    fn to_json_string_survives_nan_and_infinite_metrics() {
        let mut style = SystemStyle::default();
        style.accessibility.text_scale_factor = f32::NAN;
        style.animation.animation_duration_factor = f32::INFINITY;
        style.input.double_click_distance_px = f32::NEG_INFINITY;
        style.input.drag_threshold_px = f32::MAX;
        style.input.caret_width_px = f32::MIN_POSITIVE;
        style.input.double_click_time_ms = u32::MAX;
        style.input.caret_blink_rate_ms = u32::MAX;
        style.input.wheel_scroll_lines = u32::MAX;
        style.input.hover_time_ms = u32::MAX;
        style.text_rendering.font_smoothing_gamma = u32::MAX;
        style.linux.cursor_size = u32::MAX;
        // Must not panic; the extreme values are formatted, not truncated away.
        let json = style.to_json_string();
        let s = json.as_str();
        assert!(!s.is_empty());
        assert!(s.contains(&format!("\"cursor_size\": {}", u32::MAX)), "{s}");
        assert!(s.contains(&format!("\"double_click_time_ms\": {}", u32::MAX)), "{s}");
    }
    #[test]
    fn to_json_string_survives_extreme_pixel_metrics() {
        let mut style = SystemStyle::default();
        style.metrics.titlebar.height = OptionPixelValue::Some(PixelValue::px(f32::NAN));
        style.metrics.titlebar.button_area_width =
            OptionPixelValue::Some(PixelValue::px(f32::INFINITY));
        style.metrics.titlebar.padding_horizontal =
            OptionPixelValue::Some(PixelValue::px(f32::NEG_INFINITY));
        style.metrics.titlebar.title_font_size = OptionF32::Some(f32::MAX);
        style.metrics.titlebar.title_font_weight = OptionU16::Some(u16::MAX);
        let json = style.to_json_string();
        assert!(!json.as_str().is_empty());
        // PixelValue stores fixed-point isize, so NaN saturates to 0 and the
        // infinities saturate to the isize bounds — the JSON stays finite.
        let nan_px = PixelValue::px(f32::NAN).to_pixels_internal(0.0, 0.0, 0.0);
        assert_eq!(nan_px, 0.0);
        assert!(PixelValue::px(f32::INFINITY)
            .to_pixels_internal(0.0, 0.0, 0.0)
            .is_finite());
        assert!(PixelValue::px(f32::NEG_INFINITY)
            .to_pixels_internal(0.0, 0.0, 0.0)
            .is_finite());
    }
    #[test]
    fn to_json_string_survives_hostile_strings() {
        // Quote / backslash / newline / unicode in an OS-reported string must
        // not panic the formatter.
        let mut style = SystemStyle::default();
        style.language = AzString::from("\"\\\n\t\u{1F600}");
        style.fonts.ui_font = OptionString::Some(AzString::from("a\"b\\c"));
        style.linux.gtk_theme = OptionString::Some(AzString::from("\u{202E}evil"));
        let json = style.to_json_string();
        let s = json.as_str();
        assert!(!s.is_empty());
        assert!(s.contains("\"language\":"), "{s}");
    }
    #[test]
    fn to_json_string_is_deterministic() {
        let style = defaults::gnome_adwaita_dark();
        assert_eq!(style.to_json_string(), style.to_json_string());
        assert_ne!(
            defaults::gnome_adwaita_dark().to_json_string(),
            defaults::gnome_adwaita_light().to_json_string()
        );
    }
    // ── SystemStyle::create_csd_stylesheet ───────────────────────────────
    #[test]
    fn csd_stylesheet_rules_all_carry_system_priority() {
        let mut styles = all_default_styles();
        styles.push(("default", SystemStyle::default()));
        for (name, style) in styles {
            let css = style.create_csd_stylesheet();
            let rules = css.rules.as_slice();
            assert!(!rules.is_empty(), "{name}: produced no rules");
            for rule in rules {
                assert_eq!(
                    rule.priority,
                    rule_priority::SYSTEM,
                    "{name}: rule escaped the SYSTEM layer"
                );
            }
            // System rules must lose against author CSS.
            const _: () = assert!(rule_priority::SYSTEM < rule_priority::AUTHOR);
        }
    }
    #[test]
    fn csd_stylesheet_uses_fallback_colors_when_the_system_reports_none() {
        // All colors unset -> the hard-coded fallbacks must still produce CSS.
        let css = SystemStyle::default().create_csd_stylesheet();
        assert!(!css.rules.as_slice().is_empty());
        assert_ne!(css, Css::default());
    }
    #[test]
    fn csd_stylesheet_is_platform_specific() {
        let mac = defaults::macos_modern_light().create_csd_stylesheet();
        let win = defaults::windows_11_light().create_csd_stylesheet();
        let lin = defaults::gnome_adwaita_light().create_csd_stylesheet();
        assert_ne!(mac, win);
        assert_ne!(win, lin);
        assert_ne!(mac, lin);
        // macOS appends the traffic-light rules on top of the shared ones.
        assert!(mac.rules.as_slice().len() > win.rules.as_slice().len());
    }
    #[test]
    fn csd_stylesheet_survives_extreme_corner_radius() {
        for radius in [
            PixelValue::px(f32::NAN),
            PixelValue::px(f32::INFINITY),
            PixelValue::px(f32::NEG_INFINITY),
            PixelValue::px(f32::MAX),
            PixelValue::px(-1.0),
            PixelValue::percent(f32::MAX),
            PixelValue::em(f32::MIN),
        ] {
            let mut style = defaults::windows_11_light();
            style.metrics.corner_radius = OptionPixelValue::Some(radius);
            let css = style.create_csd_stylesheet();
            assert!(
                !css.rules.as_slice().is_empty(),
                "radius {radius:?} produced no rules"
            );
            for rule in css.rules.as_slice() {
                assert_eq!(rule.priority, rule_priority::SYSTEM);
            }
        }
    }
    #[test]
    fn csd_stylesheet_is_deterministic() {
        let style = defaults::kde_breeze_light();
        assert_eq!(style.create_csd_stylesheet(), style.create_csd_stylesheet());
    }
    // ── AZ_RICING / environment probes ───────────────────────────────────
    //
    // These functions read process-global environment variables. The tests
    // below deliberately do NOT mutate the environment (`set_var` races with
    // every other test thread in the same binary), so they assert the
    // invariants that must hold for *any* ambient environment.
    #[test]
    fn ricing_mode_is_deterministic_and_total() {
        let mode = ricing_mode();
        assert_eq!(mode, ricing_mode(), "ricing_mode() is not deterministic");
        assert!(
            matches!(mode, RicingMode::Off | RicingMode::Default | RicingMode::Force),
            "{mode:?}"
        );
        assert_eq!(RicingMode::default(), RicingMode::Default);
    }
    #[test]
    fn ricing_enabled_is_the_inverse_of_off() {
        assert_eq!(ricing_enabled(), ricing_mode() != RicingMode::Off);
        assert_eq!(ricing_enabled(), ricing_enabled());
    }
    #[test]
    fn detect_linux_desktop_env_is_deterministic() {
        let a = detect_linux_desktop_env();
        assert_eq!(a, detect_linux_desktop_env());
        // Unless the ambient env explicitly sets an *empty* desktop string
        // (which the function forwards verbatim), the `Other` label is either
        // a const name or the non-empty env value.
        let blank_env = |k: &str| std::env::var(k).map(|v| v.is_empty()).unwrap_or(false);
        if !blank_env("XDG_CURRENT_DESKTOP") && !blank_env("DESKTOP_SESSION") {
            if let DesktopEnvironment::Other(ref name) = a {
                assert!(!name.as_str().is_empty(), "empty desktop-environment label");
            }
        }
    }
    #[test]
    fn detect_system_language_is_a_normalized_tag() {
        let lang = detect_system_language();
        let s = lang.as_str();
        assert!(!s.is_empty(), "language tag must never be empty");
        // The encoding suffix (".UTF-8"), the LANGUAGE list separator (':')
        // and the POSIX underscore must all be normalized away.
        assert!(!s.contains('.'), "{s:?} still carries an encoding suffix");
        assert!(!s.contains(':'), "{s:?} still carries a locale list");
        assert!(!s.contains('_'), "{s:?} is not BCP 47 (underscore)");
        assert_eq!(lang, detect_system_language(), "not deterministic");
    }
}
/// Which hand operates the device (see [`SystemStyle::handedness`]).
///
/// INDEPENDENT of text direction: an Arabic left-hander reads right-to-left
/// but still reaches with the left hand, and a left-handed English user
/// reads left-to-right. Deriving one from the other is a bug.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(C)]
pub enum Handedness {
    /// Primary touch controls on the right (the default).
    #[default]
    RightHanded,
    /// Primary touch controls on the left.
    LeftHanded,
}
// NOTE: no explicit `is_left_handed()` here - the codegen already emits an
// `isLeftHanded()` predicate for every enum variant, and a hand-written one
// collides with it (PHP refuses the duplicate, other bindings get ambiguous
// dispatch). Match on the variant instead.