Skip to main content

azul_css/
system.rs

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//! **Application-Specific Ricing:**
7//! By default (if the `io` feature is enabled), Azul will look for an application-specific
8//! stylesheet at `~/.config/azul/styles/<app_name>.css` (or `%APPDATA%\azul\styles\<app_name>.css`
9//! on Windows). This allows end-users to override and "rice" any Azul application.
10//! This behavior can be disabled by setting the `AZUL_DISABLE_RICING` environment variable.
11//!
12//! **Linux Customization Easter Egg:**
13//! Linux users can set the `AZUL_SMOKE_AND_MIRRORS` environment variable to force Azul to
14//! skip standard GNOME/KDE detection and prioritize discovery methods for "riced" desktops
15//! (like parsing Hyprland configs or `pywal` caches), leaning into the car "ricing" subculture
16//! where a flashy appearance is paramount.
17
18#![cfg(feature = "parser")]
19
20use alloc::{
21    boxed::Box,
22    string::{String, ToString},
23    vec::Vec,
24};
25#[cfg(feature = "io")]
26use core::time::Duration;
27
28use crate::{
29    corety::{AzString, OptionF32, OptionString},
30    css::Stylesheet,
31    parser2::{new_from_str, CssParseWarnMsg},
32    props::{
33        basic::{
34            color::{parse_css_color, ColorU, OptionColorU},
35            pixel::{PixelValue, OptionPixelValue},
36        },
37        style::scrollbar::{ComputedScrollbarStyle, OverscrollBehavior, ScrollBehavior},
38    },
39};
40
41// --- Public Data Structures ---
42
43/// Represents the detected platform.
44#[derive(Debug, Default, Clone, PartialEq, Eq)]
45#[repr(C, u8)]
46pub enum Platform {
47    Windows,
48    MacOs,
49    Linux(DesktopEnvironment),
50    Android,
51    Ios,
52    #[default]
53    Unknown,
54}
55
56/// Represents the detected Linux Desktop Environment.
57#[derive(Debug, Clone, PartialEq, Eq)]
58#[repr(C, u8)]
59pub enum DesktopEnvironment {
60    Gnome,
61    Kde,
62    Other(AzString),
63}
64
65/// The overall theme type.
66#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
67#[repr(C)]
68pub enum Theme {
69    #[default]
70    Light,
71    Dark,
72}
73
74/// A unified collection of discovered system style properties.
75#[derive(Debug, Default, Clone, PartialEq)]
76#[repr(C)]
77pub struct SystemStyle {
78    pub theme: Theme,
79    pub platform: Platform,
80    pub colors: SystemColors,
81    pub fonts: SystemFonts,
82    pub metrics: SystemMetrics,
83    /// System language/locale in BCP 47 format (e.g., "en-US", "de-DE")
84    /// Detected from OS settings at startup
85    pub language: AzString,
86    /// An optional, user-provided stylesheet loaded from a conventional
87    /// location (`~/.config/azul/styles/<app_name>.css`), allowing for
88    /// application-specific "ricing". This is only loaded when the "io"
89    /// feature is enabled and not disabled by the `AZUL_DISABLE_RICING` env var.
90    pub app_specific_stylesheet: Option<Box<Stylesheet>>,
91    /// Icon-specific styling options (grayscale, tinting, etc.)
92    pub icon_style: IconStyleOptions,
93    /// Scrollbar style information (boxed to ensure stable FFI size)
94    pub scrollbar: Option<Box<ComputedScrollbarStyle>>,
95}
96
97/// Icon-specific styling options for accessibility and theming.
98///
99/// These settings affect how icons are rendered, supporting accessibility
100/// needs like reduced colors and high contrast modes.
101#[derive(Debug, Default, Clone, PartialEq)]
102#[repr(C)]
103pub struct IconStyleOptions {
104    /// If true, icons should be rendered in grayscale (for color-blind users
105    /// or reduced color preference). Applies a CSS grayscale filter.
106    pub prefer_grayscale: bool,
107    /// Optional tint color to apply to icons. Useful for matching icons
108    /// to the current theme or for high contrast modes.
109    pub tint_color: OptionColorU,
110    /// If true, icons should inherit the current text color instead of
111    /// using their original colors. Works well with font-based icons.
112    pub inherit_text_color: bool,
113}
114
115/// Common system colors used for UI elements.
116#[derive(Debug, Default, Clone, PartialEq)]
117#[repr(C)]
118pub struct SystemColors {
119    pub text: OptionColorU,
120    pub background: OptionColorU,
121    pub accent: OptionColorU,
122    pub accent_text: OptionColorU,
123    pub button_face: OptionColorU,
124    pub button_text: OptionColorU,
125    pub window_background: OptionColorU,
126    pub selection_background: OptionColorU,
127    pub selection_text: OptionColorU,
128}
129
130/// Common system font settings.
131#[derive(Debug, Default, Clone, PartialEq)]
132#[repr(C)]
133pub struct SystemFonts {
134    /// The primary font used for UI elements like buttons and labels.
135    pub ui_font: OptionString,
136    /// The default font size for UI elements, in points.
137    pub ui_font_size: OptionF32,
138    /// The font used for code or other monospaced text.
139    pub monospace_font: OptionString,
140}
141
142/// Common system metrics for UI element sizing and spacing.
143#[derive(Debug, Default, Clone, PartialEq)]
144#[repr(C)]
145pub struct SystemMetrics {
146    /// The corner radius for standard elements like buttons.
147    pub corner_radius: OptionPixelValue,
148    /// The width of standard borders.
149    pub border_width: OptionPixelValue,
150}
151
152impl SystemStyle {
153    /// Discovers the system's UI style, and loads an optional app-specific stylesheet.
154    ///
155    /// If the "io" feature is enabled, this function may be slow as it can
156    /// involve running external commands and reading files.
157    ///
158    /// If the "io" feature is disabled, this returns a hard-coded, deterministic
159    /// style based on the target operating system.
160    pub fn new() -> Self {
161        // Step 1: Get the base style (either from I/O or hardcoded defaults).
162        let mut style = {
163            #[cfg(feature = "io")]
164            {
165                #[cfg(target_os = "linux")]
166                {
167                    discover_linux_style()
168                }
169                #[cfg(target_os = "windows")]
170                {
171                    discover_windows_style()
172                }
173                #[cfg(target_os = "macos")]
174                {
175                    discover_macos_style()
176                }
177                #[cfg(target_os = "android")]
178                {
179                    defaults::android_material_light()
180                }
181                #[cfg(target_os = "ios")]
182                {
183                    defaults::ios_light()
184                }
185                #[cfg(not(any(
186                    target_os = "linux",
187                    target_os = "windows",
188                    target_os = "macos",
189                    target_os = "android",
190                    target_os = "ios"
191                )))]
192                {
193                    Self::default()
194                } // Fallback for unknown OS
195            }
196            #[cfg(not(feature = "io"))]
197            {
198                // Return hard-coded defaults based on compile-time target
199                #[cfg(target_os = "windows")]
200                {
201                    defaults::windows_11_light()
202                }
203                #[cfg(target_os = "macos")]
204                {
205                    defaults::macos_modern_light()
206                }
207                #[cfg(target_os = "linux")]
208                {
209                    defaults::gnome_adwaita_light()
210                }
211                #[cfg(target_os = "android")]
212                {
213                    defaults::android_material_light()
214                }
215                #[cfg(target_os = "ios")]
216                {
217                    defaults::ios_light()
218                }
219                #[cfg(not(any(
220                    target_os = "linux",
221                    target_os = "windows",
222                    target_os = "macos",
223                    target_os = "android",
224                    target_os = "ios"
225                )))]
226                {
227                    Self::default()
228                }
229            }
230        };
231
232        // Step 2: Check for the opt-out env var for app-specific styling.
233        #[cfg(feature = "io")]
234        {
235            if std::env::var("AZUL_DISABLE_RICING").is_ok() {
236                return style; // User explicitly disabled it.
237            }
238
239            // Step 3: Try to load the app-specific stylesheet.
240            if let Some(stylesheet) = load_app_specific_stylesheet() {
241                style.app_specific_stylesheet = Some(Box::new(stylesheet));
242            }
243        }
244
245        style
246    }
247
248    /// Create a CSS stylesheet for CSD (Client-Side Decorations) titlebar
249    ///
250    /// This generates CSS rules for the CSD titlebar using system colors,
251    /// fonts, and metrics to match the native platform look.
252    pub fn create_csd_stylesheet(&self) -> Stylesheet {
253        use alloc::format;
254
255        use crate::parser2::new_from_str;
256
257        // Build CSS string from SystemStyle
258        let mut css = String::new();
259
260        // Get system colors with fallbacks
261        let bg_color = self
262            .colors
263            .window_background
264            .as_option()
265            .copied()
266            .unwrap_or(ColorU::new_rgb(240, 240, 240));
267        let text_color = self
268            .colors
269            .text
270            .as_option()
271            .copied()
272            .unwrap_or(ColorU::new_rgb(0, 0, 0));
273        let accent_color = self
274            .colors
275            .accent
276            .as_option()
277            .copied()
278            .unwrap_or(ColorU::new_rgb(0, 120, 215));
279        let border_color = match self.theme {
280            Theme::Dark => ColorU::new_rgb(60, 60, 60),
281            Theme::Light => ColorU::new_rgb(200, 200, 200),
282        };
283
284        // Get system metrics with fallbacks
285        let corner_radius = self
286            .metrics
287            .corner_radius
288            .map(|px| {
289                use crate::props::basic::pixel::DEFAULT_FONT_SIZE;
290                format!("{}px", px.to_pixels_internal(1.0, DEFAULT_FONT_SIZE))
291            })
292            .unwrap_or_else(|| "4px".to_string());
293
294        // Titlebar container
295        css.push_str(&format!(
296            ".csd-titlebar {{ width: 100%; height: 32px; background: rgb({}, {}, {}); \
297             border-bottom: 1px solid rgb({}, {}, {}); display: flex; flex-direction: row; \
298             align-items: center; justify-content: space-between; padding: 0 8px; }} ",
299            bg_color.r, bg_color.g, bg_color.b, border_color.r, border_color.g, border_color.b,
300        ));
301
302        // Title text
303        css.push_str(&format!(
304            ".csd-title {{ color: rgb({}, {}, {}); font-size: 13px; flex-grow: 1; text-align: \
305             center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }} ",
306            text_color.r, text_color.g, text_color.b,
307        ));
308
309        // Button container
310        css.push_str(".csd-buttons { display: flex; flex-direction: row; gap: 4px; } ");
311
312        // Buttons
313        css.push_str(&format!(
314            ".csd-button {{ width: 32px; height: 24px; border-radius: {}; background: \
315             transparent; color: rgb({}, {}, {}); font-size: 16px; line-height: 24px; text-align: \
316             center; cursor: pointer; user-select: none; }} ",
317            corner_radius, text_color.r, text_color.g, text_color.b,
318        ));
319
320        // Button hover state
321        let hover_color = match self.theme {
322            Theme::Dark => ColorU::new_rgb(60, 60, 60),
323            Theme::Light => ColorU::new_rgb(220, 220, 220),
324        };
325        css.push_str(&format!(
326            ".csd-button:hover {{ background: rgb({}, {}, {}); }} ",
327            hover_color.r, hover_color.g, hover_color.b,
328        ));
329
330        // Close button hover (red on all platforms)
331        css.push_str(
332            ".csd-close:hover { background: rgb(232, 17, 35); color: rgb(255, 255, 255); } ",
333        );
334
335        // Platform-specific button styling
336        match self.platform {
337            Platform::MacOs => {
338                // macOS traffic light buttons (left side)
339                css.push_str(".csd-buttons { position: absolute; left: 8px; } ");
340                css.push_str(
341                    ".csd-close { background: rgb(255, 95, 86); width: 12px; height: 12px; \
342                     border-radius: 50%; } ",
343                );
344                css.push_str(
345                    ".csd-minimize { background: rgb(255, 189, 46); width: 12px; height: 12px; \
346                     border-radius: 50%; } ",
347                );
348                css.push_str(
349                    ".csd-maximize { background: rgb(40, 201, 64); width: 12px; height: 12px; \
350                     border-radius: 50%; } ",
351                );
352            }
353            Platform::Linux(_) => {
354                // Linux - title on left, buttons on right
355                css.push_str(".csd-title { text-align: left; } ");
356            }
357            _ => {
358                // Windows and others - standard layout
359            }
360        }
361
362        // Parse CSS string into Stylesheet
363        let (mut parsed_css, _warnings) = new_from_str(&css);
364
365        // Return first stylesheet (should always exist)
366        if !parsed_css.stylesheets.is_empty() {
367            parsed_css.stylesheets.into_library_owned_vec().remove(0)
368        } else {
369            Stylesheet::default()
370        }
371    }
372}
373
374// -- Platform-Specific Implementations (with I/O) --
375
376#[cfg(feature = "io")]
377fn discover_linux_style() -> SystemStyle {
378    // Check for the easter egg env var. If it's set, we skip straight to the "riced"
379    // discovery, embracing the smoke and mirrors of a custom desktop.
380    if std::env::var("AZUL_SMOKE_AND_MIRRORS").is_err() {
381        // If the env var is NOT set, try the normal desktop environments first.
382        if let Ok(kde_style) = discover_kde_style() {
383            return kde_style;
384        }
385        if let Ok(gnome_style) = discover_gnome_style() {
386            return gnome_style;
387        }
388    }
389
390    // This also acts as a fallback for non-GNOME/KDE environments.
391    if let Ok(riced_style) = discover_riced_style() {
392        return riced_style;
393    }
394
395    // Absolute fallback if nothing can be determined.
396    defaults::gnome_adwaita_light()
397}
398
399#[cfg(feature = "io")]
400fn discover_gnome_style() -> Result<SystemStyle, ()> {
401    let theme_name = run_command_with_timeout(
402        "gsettings",
403        &["get", "org.gnome.desktop.interface", "gtk-theme"],
404        Duration::from_secs(1),
405    )?;
406    let theme_name = theme_name.trim().trim_matches('\'');
407
408    let color_scheme = run_command_with_timeout(
409        "gsettings",
410        &["get", "org.gnome.desktop.interface", "color-scheme"],
411        Duration::from_secs(1),
412    )
413    .unwrap_or_default();
414    let theme = if color_scheme.contains("prefer-dark") {
415        Theme::Dark
416    } else {
417        Theme::Light
418    };
419
420    let ui_font = run_command_with_timeout(
421        "gsettings",
422        &["get", "org.gnome.desktop.interface", "font-name"],
423        Duration::from_secs(1),
424    )
425    .ok();
426    let monospace_font = run_command_with_timeout(
427        "gsettings",
428        &["get", "org.gnome.desktop.interface", "monospace-font-name"],
429        Duration::from_secs(1),
430    )
431    .ok();
432
433    let mut style = if theme == Theme::Dark {
434        defaults::gnome_adwaita_dark()
435    } else {
436        defaults::gnome_adwaita_light()
437    };
438
439    style.platform = Platform::Linux(DesktopEnvironment::Gnome);
440    style.language = detect_system_language();
441    if let Some(font) = ui_font {
442        style.fonts.ui_font = OptionString::Some(font.trim().trim_matches('\'').to_string().into());
443    }
444    if let Some(font) = monospace_font {
445        style.fonts.monospace_font =
446            OptionString::Some(font.trim().trim_matches('\'').to_string().into());
447    }
448
449    Ok(style)
450}
451
452#[cfg(feature = "io")]
453fn discover_kde_style() -> Result<SystemStyle, ()> {
454    // Check for kreadconfig5. If it doesn't exist, we're likely not on KDE Plasma 5+.
455    run_command_with_timeout("kreadconfig5", &["--version"], Duration::from_secs(1))?;
456
457    // Get the color scheme name to determine light/dark theme.
458    let scheme_name = run_command_with_timeout(
459        "kreadconfig5",
460        &["--group", "General", "--key", "ColorScheme"],
461        Duration::from_secs(1),
462    )
463    .unwrap_or_default();
464    let theme = if scheme_name.to_lowercase().contains("dark") {
465        Theme::Dark
466    } else {
467        Theme::Light
468    };
469
470    // Start with the appropriate Breeze default.
471    let mut style = if theme == Theme::Dark {
472        // NOTE: A specific "breeze_dark" default could be added for more accuracy.
473        defaults::gnome_adwaita_dark()
474    } else {
475        defaults::kde_breeze_light()
476    };
477    style.platform = Platform::Linux(DesktopEnvironment::Kde);
478    style.language = detect_system_language();
479
480    // Get the UI font. The format is "Font Name,Size,-1,5,50,0,0,0,0,0"
481    if let Ok(font_str) = run_command_with_timeout(
482        "kreadconfig5",
483        &["--group", "General", "--key", "font"],
484        Duration::from_secs(1),
485    ) {
486        let mut parts = font_str.trim().split(',');
487        if let Some(font_name) = parts.next() {
488            style.fonts.ui_font = OptionString::Some(font_name.to_string().into());
489        }
490        if let Some(font_size_str) = parts.next() {
491            if let Ok(size) = font_size_str.parse::<f32>() {
492                style.fonts.ui_font_size = OptionF32::Some(size);
493            }
494        }
495    }
496
497    // Get the monospace font.
498    if let Ok(font_str) = run_command_with_timeout(
499        "kreadconfig5",
500        &["--group", "General", "--key", "fixed"],
501        Duration::from_secs(1),
502    ) {
503        if let Some(font_name) = font_str.trim().split(',').next() {
504            style.fonts.monospace_font = OptionString::Some(font_name.to_string().into());
505        }
506    }
507
508    // Get the accent color (active titlebar color). Format is "R,G,B".
509    if let Ok(color_str) = run_command_with_timeout(
510        "kreadconfig5",
511        &["--group", "WM", "--key", "activeBackground"],
512        Duration::from_secs(1),
513    ) {
514        let rgb: Vec<Result<u8, _>> = color_str
515            .trim()
516            .split(',')
517            .map(|c| c.parse::<u8>())
518            .collect();
519        if rgb.len() == 3 {
520            if let (Ok(r), Ok(g), Ok(b)) = (&rgb[0], &rgb[1], &rgb[2]) {
521                style.colors.accent = OptionColorU::Some(ColorU::new_rgb(*r, *g, *b));
522            }
523        }
524    }
525
526    Ok(style)
527}
528
529#[cfg(feature = "io")]
530/// Attempts to discover styling from common "ricing" tools and window manager configs.
531fn discover_riced_style() -> Result<SystemStyle, ()> {
532    // We can confirm we're in a specific WM environment if needed.
533    // For example, Hyprland sets this variable.
534    let is_hyprland = std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok();
535    if !is_hyprland {
536        // This function could be expanded to check for sway, i3, etc.
537        // For now, we'll only proceed if we have a strong hint.
538        return Err(());
539    }
540
541    let mut style = SystemStyle {
542        platform: Platform::Linux(DesktopEnvironment::Other("Tiling WM".into())),
543        // Start with a generic dark theme, as it's common for riced setups.
544        ..defaults::gnome_adwaita_dark()
545    };
546    style.language = detect_system_language();
547
548    // Strategy 3: Check for a `pywal` cache first, as it's a great source for colors.
549    let home_dir = std::env::var("HOME").unwrap_or_default();
550    let wal_cache_path = format!("{}/.cache/wal/colors.json", home_dir);
551    if let Ok(json_content) = std::fs::read_to_string(wal_cache_path) {
552        if let Ok(json) = serde_json::from_str::<serde_json::Value>(&json_content) {
553            let colors = &json["colors"];
554            style.colors.background = colors["color0"]
555                .as_str()
556                .and_then(|s| parse_css_color(s).ok())
557                .map(OptionColorU::Some)
558                .unwrap_or(OptionColorU::None);
559            style.colors.text = colors["color7"]
560                .as_str()
561                .and_then(|s| parse_css_color(s).ok())
562                .map(OptionColorU::Some)
563                .unwrap_or(OptionColorU::None);
564            style.colors.accent = colors["color4"]
565                .as_str()
566                .and_then(|s| parse_css_color(s).ok())
567                .map(OptionColorU::Some)
568                .unwrap_or(OptionColorU::None);
569            style.theme = Theme::Dark; // Wal is often used with dark themes.
570        }
571    }
572
573    // Strategy 2: Parse hyprland.conf for specifics like borders and radius.
574    let hypr_conf_path = format!("{}/.config/hypr/hyprland.conf", home_dir);
575    if let Ok(conf_content) = std::fs::read_to_string(hypr_conf_path) {
576        for line in conf_content.lines() {
577            let line = line.trim();
578            if line.starts_with('#') || !line.contains('=') {
579                continue;
580            }
581            let mut parts = line.splitn(2, '=').map(|s| s.trim());
582            let key = parts.next();
583            let value = parts.next();
584
585            if let (Some(k), Some(v)) = (key, value) {
586                match k {
587                    "rounding" => {
588                        if let Ok(px) = v.parse::<f32>() {
589                            style.metrics.corner_radius = OptionPixelValue::Some(PixelValue::px(px));
590                        }
591                    }
592                    "border_size" => {
593                        if let Ok(px) = v.parse::<f32>() {
594                            style.metrics.border_width = OptionPixelValue::Some(PixelValue::px(px));
595                        }
596                    }
597                    // Use the active border as the accent color if `wal` didn't provide one.
598                    "col.active_border" if style.colors.accent.is_none() => {
599                        // Hyprland format is "rgba(RRGGBBAA)" or "rgb(RRGGBB)"
600                        if let Some(hex_str) = v.split_whitespace().last() {
601                            if let Ok(color) = parse_css_color(&format!("#{}", hex_str)) {
602                                style.colors.accent = OptionColorU::Some(color);
603                            }
604                        }
605                    }
606                    _ => {}
607                }
608            }
609        }
610    }
611
612    // Strategy 1: Finally, try to get the GTK font as a sensible default for UI text.
613    if let Ok(font_str) = run_command_with_timeout(
614        "gsettings",
615        &["get", "org.gnome.desktop.interface", "font-name"],
616        Duration::from_secs(1),
617    ) {
618        if let Some(font_name) = font_str.trim().trim_matches('\'').split(' ').next() {
619            style.fonts.ui_font = OptionString::Some(font_name.to_string().into());
620        }
621    }
622
623    Ok(style)
624}
625
626#[cfg(feature = "io")]
627fn discover_windows_style() -> SystemStyle {
628    let mut style = defaults::windows_11_light(); // Start with a modern default
629    style.platform = Platform::Windows;
630    style.language = detect_system_language();
631
632    let theme_val = run_command_with_timeout(
633        "reg",
634        &[
635            "query",
636            r"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
637            "/v",
638            "AppsUseLightTheme",
639        ],
640        Duration::from_secs(1),
641    );
642    if let Ok(output) = theme_val {
643        if output.contains("0x0") {
644            style = defaults::windows_11_dark();
645        }
646    }
647
648    let accent_val = run_command_with_timeout(
649        "reg",
650        &[
651            "query",
652            r"HKCU\Software\Microsoft\Windows\DWM",
653            "/v",
654            "AccentColor",
655        ],
656        Duration::from_secs(1),
657    );
658    if let Ok(output) = accent_val {
659        if let Some(hex_str) = output.split_whitespace().last() {
660            if let Ok(hex_val) = u32::from_str_radix(hex_str.trim_start_matches("0x"), 16) {
661                let a = (hex_val >> 24) as u8;
662                let b = (hex_val >> 16) as u8;
663                let g = (hex_val >> 8) as u8;
664                let r = hex_val as u8;
665                style.colors.accent = OptionColorU::Some(ColorU::new(r, g, b, a));
666            }
667        }
668    }
669
670    style
671}
672
673#[cfg(feature = "io")]
674fn discover_macos_style() -> SystemStyle {
675    let mut style = defaults::macos_modern_light();
676    style.platform = Platform::MacOs;
677    style.language = detect_system_language();
678
679    let theme_val = run_command_with_timeout(
680        "defaults",
681        &["read", "-g", "AppleInterfaceStyle"],
682        Duration::from_secs(1),
683    );
684    if theme_val.is_ok() {
685        style = defaults::macos_modern_dark();
686    }
687
688    style
689}
690
691#[cfg(feature = "io")]
692fn discover_android_style() -> SystemStyle {
693    // On-device detection is complex; return a modern default.
694    defaults::android_material_light()
695}
696
697#[cfg(feature = "io")]
698fn discover_ios_style() -> SystemStyle {
699    // On-device detection is complex; return a modern default.
700    defaults::ios_light()
701}
702
703// -- Helper Functions (IO-dependent) --
704
705#[cfg(feature = "io")]
706/// A simple helper to run a command and get its stdout, with a timeout.
707fn run_command_with_timeout(program: &str, args: &[&str], timeout: Duration) -> Result<String, ()> {
708    use std::{
709        process::{Command, Stdio},
710        thread,
711    };
712
713    let mut child = Command::new(program)
714        .args(args)
715        .stdout(Stdio::piped())
716        .stderr(Stdio::piped())
717        .spawn()
718        .map_err(|_| ())?;
719
720    let (tx, rx) = std::sync::mpsc::channel();
721
722    let child_thread = thread::spawn(move || {
723        let output = child.wait_with_output();
724        tx.send(output).ok();
725    });
726
727    match rx.recv_timeout(timeout) {
728        Ok(Ok(output)) if output.status.success() => {
729            Ok(String::from_utf8(output.stdout).unwrap_or_default())
730        }
731        _ => {
732            // Ensure the child process is killed on timeout
733            // This part is tricky without a more robust process management library
734            child_thread.join().ok(); // Wait for the thread to finish
735            Err(())
736        }
737    }
738}
739
740/// Loads an application-specific stylesheet from a conventional path.
741///
742/// Looks for `<config_dir>/azul/styles/<exe_name>.css`.
743/// Returns `None` if the file doesn't exist, can't be read, or is empty.
744#[cfg(feature = "io")]
745/// Loads an application-specific stylesheet from a conventional path.
746///
747/// Looks for `<config_dir>/azul/styles/<exe_name>.css`.
748/// Returns `None` if the file doesn't exist, can't be read, or is empty.
749#[cfg(feature = "io")]
750fn load_app_specific_stylesheet() -> Option<Stylesheet> {
751    // Get the name of the currently running executable.
752    let exe_path = std::env::current_exe().ok()?;
753    let exe_name = exe_path.file_name()?.to_str()?;
754
755    // Use `dirs-next` to find the conventional config directory for the current platform.
756    // This correctly handles Linux ($XDG_CONFIG_HOME, ~/.config),
757    // macOS (~/Library/Application Support), and Windows (%APPDATA%).
758    let config_dir = dirs_next::config_dir()?;
759
760    let css_path = config_dir
761        .join("azul")
762        .join("styles")
763        .join(format!("{}.css", exe_name));
764
765    // If the file doesn't exist or can't be read, `ok()` will gracefully convert the error
766    // to `None`, which will then be returned by the function.
767    let css_content = std::fs::read_to_string(css_path).ok()?;
768
769    if css_content.trim().is_empty() {
770        return None;
771    }
772
773    let (mut css, _warnings) = new_from_str(&css_content);
774
775    // The parser returns a `Css` which contains a `Vec<Stylesheet>`.
776    // For an app-specific theme file, we are only interested in the first stylesheet.
777    if !css.stylesheets.is_empty() {
778        let mut owned_vec = css.stylesheets.into_library_owned_vec();
779        Some(owned_vec.remove(0))
780    } else {
781        None
782    }
783}
784
785// -- Language Detection Functions --
786
787/// Detect the system language and return a BCP 47 language tag.
788/// Falls back to "en-US" if detection fails.
789#[cfg(feature = "io")]
790pub fn detect_system_language() -> AzString {
791    #[cfg(target_os = "windows")]
792    {
793        detect_language_windows()
794    }
795    #[cfg(target_os = "macos")]
796    {
797        detect_language_macos()
798    }
799    #[cfg(target_os = "linux")]
800    {
801        detect_language_linux()
802    }
803    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
804    {
805        AzString::from_const_str("en-US")
806    }
807}
808
809/// Detect language on Windows using PowerShell
810#[cfg(all(feature = "io", target_os = "windows"))]
811fn detect_language_windows() -> AzString {
812    // Try to get the system UI culture via PowerShell
813    if let Ok(output) = run_command_with_timeout(
814        "powershell",
815        &["-Command", "(Get-Culture).Name"],
816        Duration::from_secs(2),
817    ) {
818        let lang = output.trim();
819        if !lang.is_empty() && lang.contains('-') {
820            return AzString::from(lang.to_string());
821        }
822    }
823
824    // Fallback: try registry
825    if let Ok(output) = run_command_with_timeout(
826        "reg",
827        &[
828            "query",
829            r"HKCU\Control Panel\International",
830            "/v",
831            "LocaleName",
832        ],
833        Duration::from_secs(1),
834    ) {
835        // Parse registry output: "LocaleName    REG_SZ    de-DE"
836        for line in output.lines() {
837            if line.contains("LocaleName") {
838                if let Some(lang) = line.split_whitespace().last() {
839                    let lang = lang.trim();
840                    if !lang.is_empty() {
841                        return AzString::from(lang.to_string());
842                    }
843                }
844            }
845        }
846    }
847
848    AzString::from_const_str("en-US")
849}
850
851/// Detect language on macOS using defaults command
852#[cfg(all(feature = "io", target_os = "macos"))]
853fn detect_language_macos() -> AzString {
854    // Try AppleLocale first (more specific)
855    if let Ok(output) = run_command_with_timeout(
856        "defaults",
857        &["read", "-g", "AppleLocale"],
858        Duration::from_secs(1),
859    ) {
860        let locale = output.trim();
861        if !locale.is_empty() {
862            // Convert underscore to hyphen: "de_DE" -> "de-DE"
863            return AzString::from(locale.replace('_', "-"));
864        }
865    }
866
867    // Fallback: try AppleLanguages array
868    if let Ok(output) = run_command_with_timeout(
869        "defaults",
870        &["read", "-g", "AppleLanguages"],
871        Duration::from_secs(1),
872    ) {
873        // Output is a plist array, extract first language
874        // Example: "(\n    \"de-DE\",\n    \"en-US\"\n)"
875        for line in output.lines() {
876            let trimmed = line
877                .trim()
878                .trim_matches(|c| c == '"' || c == ',' || c == '(' || c == ')');
879            if !trimmed.is_empty() && trimmed.contains('-') {
880                return AzString::from(trimmed.to_string());
881            }
882        }
883    }
884
885    AzString::from_const_str("en-US")
886}
887
888/// Detect language on Linux using environment variables
889#[cfg(all(feature = "io", target_os = "linux"))]
890fn detect_language_linux() -> AzString {
891    // Check LANGUAGE, LANG, LC_ALL, LC_MESSAGES in order of priority
892    let env_vars = ["LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"];
893
894    for var in &env_vars {
895        if let Ok(value) = std::env::var(var) {
896            let value = value.trim();
897            if value.is_empty() || value == "C" || value == "POSIX" {
898                continue;
899            }
900
901            // Parse locale format: "de_DE.UTF-8" or "de_DE" or "de"
902            let lang = value
903                .split('.')  // Remove .UTF-8 suffix
904                .next()
905                .unwrap_or(value)
906                .replace('_', "-"); // Convert to BCP 47
907
908            if !lang.is_empty() {
909                return AzString::from(lang);
910            }
911        }
912    }
913
914    AzString::from_const_str("en-US")
915}
916
917/// Default language when io feature is disabled
918#[cfg(not(feature = "io"))]
919pub fn detect_system_language() -> AzString {
920    AzString::from_const_str("en-US")
921}
922
923pub mod defaults {
924    //! A collection of hard-coded system style defaults that mimic the appearance
925    //! of various operating systems and desktop environments. These are used as a
926    //! fallback when the "io" feature is disabled, ensuring deterministic styles
927    //! for testing and environments where system calls are not desired.
928
929    use crate::{
930        corety::{AzString, OptionF32, OptionString},
931        props::{
932            basic::{
933                color::{ColorU, OptionColorU},
934                pixel::{PixelValue, OptionPixelValue},
935            },
936            layout::{
937                dimensions::LayoutWidth,
938                spacing::{LayoutPaddingLeft, LayoutPaddingRight},
939            },
940            style::{
941                background::StyleBackgroundContent,
942                scrollbar::{
943                    ComputedScrollbarStyle, OverscrollBehavior, ScrollBehavior, ScrollbarInfo,
944                    SCROLLBAR_ANDROID_DARK, SCROLLBAR_ANDROID_LIGHT, SCROLLBAR_CLASSIC_DARK,
945                    SCROLLBAR_CLASSIC_LIGHT, SCROLLBAR_IOS_DARK, SCROLLBAR_IOS_LIGHT,
946                    SCROLLBAR_MACOS_DARK, SCROLLBAR_MACOS_LIGHT, SCROLLBAR_WINDOWS_DARK,
947                    SCROLLBAR_WINDOWS_LIGHT,
948                },
949            },
950        },
951        system::{
952            DesktopEnvironment, Platform, SystemColors, SystemFonts, SystemMetrics, SystemStyle,
953            Theme, IconStyleOptions,
954        },
955    };
956
957    // --- Custom Scrollbar Style Constants for Nostalgia ---
958
959    /// A scrollbar style mimicking the classic Windows 95/98/2000/XP look.
960    pub const SCROLLBAR_WINDOWS_CLASSIC: ScrollbarInfo = ScrollbarInfo {
961        width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(17)),
962        padding_left: LayoutPaddingLeft {
963            inner: crate::props::basic::pixel::PixelValue::const_px(0),
964        },
965        padding_right: LayoutPaddingRight {
966            inner: crate::props::basic::pixel::PixelValue::const_px(0),
967        },
968        track: StyleBackgroundContent::Color(ColorU {
969            r: 223,
970            g: 223,
971            b: 223,
972            a: 255,
973        }), // Scrollbar trough color
974        thumb: StyleBackgroundContent::Color(ColorU {
975            r: 208,
976            g: 208,
977            b: 208,
978            a: 255,
979        }), // Button face color
980        button: StyleBackgroundContent::Color(ColorU {
981            r: 208,
982            g: 208,
983            b: 208,
984            a: 255,
985        }),
986        corner: StyleBackgroundContent::Color(ColorU {
987            r: 223,
988            g: 223,
989            b: 223,
990            a: 255,
991        }),
992        resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
993        clip_to_container_border: false,
994        scroll_behavior: ScrollBehavior::Auto,
995        overscroll_behavior_x: OverscrollBehavior::None,
996        overscroll_behavior_y: OverscrollBehavior::None,
997    };
998
999    /// A scrollbar style mimicking the macOS "Aqua" theme from the early 2000s.
1000    pub const SCROLLBAR_MACOS_AQUA: ScrollbarInfo = ScrollbarInfo {
1001        width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(15)),
1002        padding_left: LayoutPaddingLeft {
1003            inner: crate::props::basic::pixel::PixelValue::const_px(0),
1004        },
1005        padding_right: LayoutPaddingRight {
1006            inner: crate::props::basic::pixel::PixelValue::const_px(0),
1007        },
1008        track: StyleBackgroundContent::Color(ColorU {
1009            r: 238,
1010            g: 238,
1011            b: 238,
1012            a: 128,
1013        }), // Translucent track
1014        thumb: StyleBackgroundContent::Color(ColorU {
1015            r: 105,
1016            g: 173,
1017            b: 255,
1018            a: 255,
1019        }), // "Gel" blue
1020        button: StyleBackgroundContent::Color(ColorU {
1021            r: 105,
1022            g: 173,
1023            b: 255,
1024            a: 255,
1025        }),
1026        corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1027        resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1028        clip_to_container_border: true,
1029        scroll_behavior: ScrollBehavior::Smooth,
1030        overscroll_behavior_x: OverscrollBehavior::Auto,
1031        overscroll_behavior_y: OverscrollBehavior::Auto,
1032    };
1033
1034    /// A scrollbar style mimicking the KDE Oxygen theme.
1035    pub const SCROLLBAR_KDE_OXYGEN: ScrollbarInfo = ScrollbarInfo {
1036        width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(14)),
1037        padding_left: LayoutPaddingLeft {
1038            inner: crate::props::basic::pixel::PixelValue::const_px(2),
1039        },
1040        padding_right: LayoutPaddingRight {
1041            inner: crate::props::basic::pixel::PixelValue::const_px(2),
1042        },
1043        track: StyleBackgroundContent::Color(ColorU {
1044            r: 242,
1045            g: 242,
1046            b: 242,
1047            a: 255,
1048        }),
1049        thumb: StyleBackgroundContent::Color(ColorU {
1050            r: 177,
1051            g: 177,
1052            b: 177,
1053            a: 255,
1054        }),
1055        button: StyleBackgroundContent::Color(ColorU {
1056            r: 216,
1057            g: 216,
1058            b: 216,
1059            a: 255,
1060        }),
1061        corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1062        resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
1063        clip_to_container_border: false,
1064        scroll_behavior: ScrollBehavior::Auto,
1065        overscroll_behavior_x: OverscrollBehavior::Auto,
1066        overscroll_behavior_y: OverscrollBehavior::Auto,
1067    };
1068
1069    /// Helper to convert a detailed `ScrollbarInfo` into the simplified `ComputedScrollbarStyle`.
1070    fn scrollbar_info_to_computed(info: &ScrollbarInfo) -> ComputedScrollbarStyle {
1071        ComputedScrollbarStyle {
1072            width: Some(info.width),
1073            thumb_color: match info.thumb {
1074                StyleBackgroundContent::Color(c) => Some(c),
1075                _ => None,
1076            },
1077            track_color: match info.track {
1078                StyleBackgroundContent::Color(c) => Some(c),
1079                _ => None,
1080            },
1081        }
1082    }
1083
1084    // --- Windows Styles ---
1085
1086    pub fn windows_11_light() -> SystemStyle {
1087        SystemStyle {
1088            theme: Theme::Light,
1089            platform: Platform::Windows,
1090            colors: SystemColors {
1091                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
1092                background: OptionColorU::Some(ColorU::new_rgb(243, 243, 243)),
1093                accent: OptionColorU::Some(ColorU::new_rgb(0, 95, 184)),
1094                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1095                selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
1096                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1097                ..Default::default()
1098            },
1099            fonts: SystemFonts {
1100                ui_font: OptionString::Some("Segoe UI Variable Text".into()),
1101                ui_font_size: OptionF32::Some(9.0),
1102                monospace_font: OptionString::Some("Consolas".into()),
1103            },
1104            metrics: SystemMetrics {
1105                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
1106                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1107            },
1108            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_LIGHT))),
1109            app_specific_stylesheet: None,
1110            icon_style: IconStyleOptions::default(),
1111            language: AzString::from_const_str("en-US"),
1112        }
1113    }
1114
1115    pub fn windows_11_dark() -> SystemStyle {
1116        SystemStyle {
1117            theme: Theme::Dark,
1118            platform: Platform::Windows,
1119            colors: SystemColors {
1120                text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1121                background: OptionColorU::Some(ColorU::new_rgb(32, 32, 32)),
1122                accent: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
1123                window_background: OptionColorU::Some(ColorU::new_rgb(25, 25, 25)),
1124                selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
1125                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1126                ..Default::default()
1127            },
1128            fonts: SystemFonts {
1129                ui_font: OptionString::Some("Segoe UI Variable Text".into()),
1130                ui_font_size: OptionF32::Some(9.0),
1131                monospace_font: OptionString::Some("Consolas".into()),
1132            },
1133            metrics: SystemMetrics {
1134                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
1135                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1136            },
1137            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_DARK))),
1138            app_specific_stylesheet: None,
1139            icon_style: IconStyleOptions::default(),
1140            language: AzString::from_const_str("en-US"),
1141        }
1142    }
1143
1144    pub fn windows_7_aero() -> SystemStyle {
1145        SystemStyle {
1146            theme: Theme::Light,
1147            platform: Platform::Windows,
1148            colors: SystemColors {
1149                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
1150                background: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
1151                accent: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
1152                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1153                selection_background: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
1154                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1155                ..Default::default()
1156            },
1157            fonts: SystemFonts {
1158                ui_font: OptionString::Some("Segoe UI".into()),
1159                ui_font_size: OptionF32::Some(9.0),
1160                monospace_font: OptionString::Some("Consolas".into()),
1161            },
1162            metrics: SystemMetrics {
1163                corner_radius: OptionPixelValue::Some(PixelValue::px(6.0)),
1164                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1165            },
1166            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
1167            app_specific_stylesheet: None,
1168            icon_style: IconStyleOptions::default(),
1169            language: AzString::from_const_str("en-US"),
1170        }
1171    }
1172
1173    pub fn windows_xp_luna() -> SystemStyle {
1174        SystemStyle {
1175            theme: Theme::Light,
1176            platform: Platform::Windows,
1177            colors: SystemColors {
1178                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
1179                background: OptionColorU::Some(ColorU::new_rgb(236, 233, 216)),
1180                accent: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
1181                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1182                selection_background: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
1183                selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1184                ..Default::default()
1185            },
1186            fonts: SystemFonts {
1187                ui_font: OptionString::Some("Tahoma".into()),
1188                ui_font_size: OptionF32::Some(8.0),
1189                monospace_font: OptionString::Some("Lucida Console".into()),
1190            },
1191            metrics: SystemMetrics {
1192                corner_radius: OptionPixelValue::Some(PixelValue::px(3.0)),
1193                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1194            },
1195            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_CLASSIC))),
1196            app_specific_stylesheet: None,
1197            icon_style: IconStyleOptions::default(),
1198            language: AzString::from_const_str("en-US"),
1199        }
1200    }
1201
1202    // --- macOS Styles ---
1203
1204    pub fn macos_modern_light() -> SystemStyle {
1205        SystemStyle {
1206            platform: Platform::MacOs,
1207            theme: Theme::Light,
1208            colors: SystemColors {
1209                text: OptionColorU::Some(ColorU::new(0, 0, 0, 221)),
1210                background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
1211                accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
1212                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1213                ..Default::default()
1214            },
1215            fonts: SystemFonts {
1216                ui_font: OptionString::Some(".SF NS".into()),
1217                ui_font_size: OptionF32::Some(13.0),
1218                monospace_font: OptionString::Some("Menlo".into()),
1219            },
1220            metrics: SystemMetrics {
1221                corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
1222                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1223            },
1224            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_LIGHT))),
1225            app_specific_stylesheet: None,
1226            icon_style: IconStyleOptions::default(),
1227            language: AzString::from_const_str("en-US"),
1228        }
1229    }
1230
1231    pub fn macos_modern_dark() -> SystemStyle {
1232        SystemStyle {
1233            platform: Platform::MacOs,
1234            theme: Theme::Dark,
1235            colors: SystemColors {
1236                text: OptionColorU::Some(ColorU::new(255, 255, 255, 221)),
1237                background: OptionColorU::Some(ColorU::new_rgb(28, 28, 30)),
1238                accent: OptionColorU::Some(ColorU::new_rgb(10, 132, 255)),
1239                window_background: OptionColorU::Some(ColorU::new_rgb(44, 44, 46)),
1240                ..Default::default()
1241            },
1242            fonts: SystemFonts {
1243                ui_font: OptionString::Some(".SF NS".into()),
1244                ui_font_size: OptionF32::Some(13.0),
1245                monospace_font: OptionString::Some("Menlo".into()),
1246            },
1247            metrics: SystemMetrics {
1248                corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
1249                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1250            },
1251            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_DARK))),
1252            app_specific_stylesheet: None,
1253            icon_style: IconStyleOptions::default(),
1254            language: AzString::from_const_str("en-US"),
1255        }
1256    }
1257
1258    pub fn macos_aqua() -> SystemStyle {
1259        SystemStyle {
1260            platform: Platform::MacOs,
1261            theme: Theme::Light,
1262            colors: SystemColors {
1263                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
1264                background: OptionColorU::Some(ColorU::new_rgb(229, 229, 229)),
1265                accent: OptionColorU::Some(ColorU::new_rgb(63, 128, 234)),
1266                window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1267                ..Default::default()
1268            },
1269            fonts: SystemFonts {
1270                ui_font: OptionString::Some("Lucida Grande".into()),
1271                ui_font_size: OptionF32::Some(13.0),
1272                monospace_font: OptionString::Some("Monaco".into()),
1273            },
1274            metrics: SystemMetrics {
1275                corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
1276                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1277            },
1278            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_AQUA))),
1279            app_specific_stylesheet: None,
1280            icon_style: IconStyleOptions::default(),
1281            language: AzString::from_const_str("en-US"),
1282        }
1283    }
1284
1285    // --- Linux Styles ---
1286
1287    pub fn gnome_adwaita_light() -> SystemStyle {
1288        SystemStyle {
1289            platform: Platform::Linux(DesktopEnvironment::Gnome),
1290            theme: Theme::Light,
1291            colors: SystemColors {
1292                text: OptionColorU::Some(ColorU::new_rgb(46, 52, 54)),
1293                background: OptionColorU::Some(ColorU::new_rgb(249, 249, 249)),
1294                accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
1295                window_background: OptionColorU::Some(ColorU::new_rgb(237, 237, 237)),
1296                ..Default::default()
1297            },
1298            fonts: SystemFonts {
1299                ui_font: OptionString::Some("Cantarell".into()),
1300                ui_font_size: OptionF32::Some(11.0),
1301                monospace_font: OptionString::Some("Monospace".into()),
1302            },
1303            metrics: SystemMetrics {
1304                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
1305                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1306            },
1307            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
1308            app_specific_stylesheet: None,
1309            icon_style: IconStyleOptions::default(),
1310            language: AzString::from_const_str("en-US"),
1311        }
1312    }
1313
1314    pub fn gnome_adwaita_dark() -> SystemStyle {
1315        SystemStyle {
1316            platform: Platform::Linux(DesktopEnvironment::Gnome),
1317            theme: Theme::Dark,
1318            colors: SystemColors {
1319                text: OptionColorU::Some(ColorU::new_rgb(238, 238, 236)),
1320                background: OptionColorU::Some(ColorU::new_rgb(36, 36, 36)),
1321                accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
1322                window_background: OptionColorU::Some(ColorU::new_rgb(48, 48, 48)),
1323                ..Default::default()
1324            },
1325            fonts: SystemFonts {
1326                ui_font: OptionString::Some("Cantarell".into()),
1327                ui_font_size: OptionF32::Some(11.0),
1328                monospace_font: OptionString::Some("Monospace".into()),
1329            },
1330            metrics: SystemMetrics {
1331                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
1332                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1333            },
1334            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_DARK))),
1335            app_specific_stylesheet: None,
1336            icon_style: IconStyleOptions::default(),
1337            language: AzString::from_const_str("en-US"),
1338        }
1339    }
1340
1341    pub fn gtk2_clearlooks() -> SystemStyle {
1342        SystemStyle {
1343            platform: Platform::Linux(DesktopEnvironment::Gnome),
1344            theme: Theme::Light,
1345            colors: SystemColors {
1346                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
1347                background: OptionColorU::Some(ColorU::new_rgb(239, 239, 239)),
1348                accent: OptionColorU::Some(ColorU::new_rgb(245, 121, 0)),
1349                ..Default::default()
1350            },
1351            fonts: SystemFonts {
1352                ui_font: OptionString::Some("DejaVu Sans".into()),
1353                ui_font_size: OptionF32::Some(10.0),
1354                monospace_font: OptionString::Some("DejaVu Sans Mono".into()),
1355            },
1356            metrics: SystemMetrics {
1357                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
1358                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1359            },
1360            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
1361            app_specific_stylesheet: None,
1362            icon_style: IconStyleOptions::default(),
1363            language: AzString::from_const_str("en-US"),
1364        }
1365    }
1366
1367    pub fn kde_breeze_light() -> SystemStyle {
1368        SystemStyle {
1369            platform: Platform::Linux(DesktopEnvironment::Kde),
1370            theme: Theme::Light,
1371            colors: SystemColors {
1372                text: OptionColorU::Some(ColorU::new_rgb(31, 36, 39)),
1373                background: OptionColorU::Some(ColorU::new_rgb(239, 240, 241)),
1374                accent: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
1375                ..Default::default()
1376            },
1377            fonts: SystemFonts {
1378                ui_font: OptionString::Some("Noto Sans".into()),
1379                ui_font_size: OptionF32::Some(10.0),
1380                monospace_font: OptionString::Some("Hack".into()),
1381            },
1382            metrics: SystemMetrics {
1383                corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
1384                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1385            },
1386            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_KDE_OXYGEN))),
1387            app_specific_stylesheet: None,
1388            icon_style: IconStyleOptions::default(),
1389            language: AzString::from_const_str("en-US"),
1390        }
1391    }
1392
1393    // --- Mobile Styles ---
1394
1395    pub fn android_material_light() -> SystemStyle {
1396        SystemStyle {
1397            platform: Platform::Android,
1398            theme: Theme::Light,
1399            colors: SystemColors {
1400                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
1401                background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1402                accent: OptionColorU::Some(ColorU::new_rgb(98, 0, 238)),
1403                ..Default::default()
1404            },
1405            fonts: SystemFonts {
1406                ui_font: OptionString::Some("Roboto".into()),
1407                ui_font_size: OptionF32::Some(14.0),
1408                monospace_font: OptionString::Some("Droid Sans Mono".into()),
1409            },
1410            metrics: SystemMetrics {
1411                corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
1412                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1413            },
1414            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_LIGHT))),
1415            app_specific_stylesheet: None,
1416            icon_style: IconStyleOptions::default(),
1417            language: AzString::from_const_str("en-US"),
1418        }
1419    }
1420
1421    pub fn android_holo_dark() -> SystemStyle {
1422        SystemStyle {
1423            platform: Platform::Android,
1424            theme: Theme::Dark,
1425            colors: SystemColors {
1426                text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
1427                background: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
1428                accent: OptionColorU::Some(ColorU::new_rgb(51, 181, 229)),
1429                ..Default::default()
1430            },
1431            fonts: SystemFonts {
1432                ui_font: OptionString::Some("Roboto".into()),
1433                ui_font_size: OptionF32::Some(14.0),
1434                monospace_font: OptionString::Some("Droid Sans Mono".into()),
1435            },
1436            metrics: SystemMetrics {
1437                corner_radius: OptionPixelValue::Some(PixelValue::px(2.0)),
1438                border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
1439            },
1440            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_DARK))),
1441            app_specific_stylesheet: None,
1442            icon_style: IconStyleOptions::default(),
1443            language: AzString::from_const_str("en-US"),
1444        }
1445    }
1446
1447    pub fn ios_light() -> SystemStyle {
1448        SystemStyle {
1449            platform: Platform::Ios,
1450            theme: Theme::Light,
1451            colors: SystemColors {
1452                text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
1453                background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
1454                accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
1455                ..Default::default()
1456            },
1457            fonts: SystemFonts {
1458                ui_font: OptionString::Some(".SFUI-Display-Regular".into()),
1459                ui_font_size: OptionF32::Some(17.0),
1460                monospace_font: OptionString::Some("Menlo".into()),
1461            },
1462            metrics: SystemMetrics {
1463                corner_radius: OptionPixelValue::Some(PixelValue::px(10.0)),
1464                border_width: OptionPixelValue::Some(PixelValue::px(0.5)),
1465            },
1466            scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_IOS_LIGHT))),
1467            app_specific_stylesheet: None,
1468            icon_style: IconStyleOptions::default(),
1469            language: AzString::from_const_str("en-US"),
1470        }
1471    }
1472}