#![cfg(feature = "parser")]
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use crate::{
corety::{AzString, OptionF32, OptionString, OptionU16},
css::Css,
parser2::{new_from_str, CssParseWarnMsg},
props::{
basic::{
color::{parse_css_color, ColorU, OptionColorU},
pixel::{PixelValue, OptionPixelValue},
},
style::scrollbar::{ComputedScrollbarStyle, OverscrollBehavior, ScrollBehavior, ScrollPhysics},
},
};
use crate::dynamic_selector::{BoolCondition, OsVersion};
use core::fmt::Write;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Default)]
pub enum RicingMode {
Off,
#[default]
Default,
Force,
}
#[must_use] pub fn ricing_mode() -> RicingMode {
let Ok(raw) = std::env::var("AZ_RICING") else {
return RicingMode::Default;
};
match raw.trim().to_ascii_lowercase().as_str() {
"off" | "disabled" | "none" | "0" | "false" => RicingMode::Off,
"force" | "prefer" | "aggressive" | "1" | "true" => RicingMode::Force,
_ => RicingMode::Default,
}
}
#[must_use] pub fn ricing_enabled() -> bool {
!matches!(ricing_mode(), RicingMode::Off)
}
#[allow(variant_size_differences)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum Platform {
Windows,
MacOs,
Linux(DesktopEnvironment),
Android,
Ios,
#[default]
Unknown,
}
impl Platform {
#[inline]
#[must_use] pub const fn current() -> Self {
#[cfg(target_os = "macos")]
{ Self::MacOs }
#[cfg(target_os = "windows")]
{ Self::Windows }
#[cfg(target_os = "linux")]
{ Self::Linux(DesktopEnvironment::Other(AzString::from_const_str("unknown"))) }
#[cfg(target_os = "android")]
{ Self::Android }
#[cfg(target_os = "ios")]
{ Self::Ios }
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux", target_os = "android", target_os = "ios")))]
{ Self::Unknown }
}
}
#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum DesktopEnvironment {
Gnome,
Kde,
Other(AzString),
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum Theme {
#[default]
Light,
Dark,
}
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct SystemStyle {
pub fonts: SystemFonts,
pub metrics: SystemMetrics,
pub linux: LinuxCustomization,
pub platform: Platform,
pub focus_visuals: FocusVisuals,
pub language: AzString,
pub app_specific_stylesheet: Option<Box<Css>>,
pub scrollbar: Option<Box<ComputedScrollbarStyle>>,
pub scroll_physics: ScrollPhysics,
pub theme: Theme,
pub os_version: OsVersion,
pub prefers_reduced_motion: BoolCondition,
pub prefers_high_contrast: BoolCondition,
pub accessibility: AccessibilitySettings,
pub input: InputMetrics,
pub text_rendering: TextRenderingHints,
pub scrollbar_preferences: ScrollbarPreferences,
pub visual_hints: VisualHints,
pub animation: AnimationMetrics,
pub colors: SystemColors,
pub icon_style: IconStyleOptions,
pub audio: AudioMetrics,
pub run_destructor: bool,
}
impl Default for SystemStyle {
fn default() -> Self {
Self {
fonts: SystemFonts::default(),
metrics: SystemMetrics::default(),
linux: LinuxCustomization::default(),
platform: Platform::default(),
focus_visuals: FocusVisuals::default(),
language: AzString::default(),
app_specific_stylesheet: None,
scrollbar: None,
scroll_physics: ScrollPhysics::default(),
theme: Theme::default(),
os_version: OsVersion::default(),
prefers_reduced_motion: BoolCondition::default(),
prefers_high_contrast: BoolCondition::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
colors: SystemColors::default(),
icon_style: IconStyleOptions::default(),
audio: AudioMetrics::default(),
run_destructor: true,
}
}
}
impl Drop for SystemStyle {
fn drop(&mut self) {
if self.run_destructor {
self.run_destructor = false;
} else {
core::mem::forget(self.app_specific_stylesheet.take());
core::mem::forget(self.scrollbar.take());
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct IconStyleOptions {
pub prefer_grayscale: bool,
pub tint_color: OptionColorU,
pub inherit_text_color: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
#[derive(Default)]
pub enum SystemFontType {
#[default]
Ui,
UiBold,
Monospace,
MonospaceBold,
MonospaceItalic,
Title,
TitleBold,
Menu,
Small,
Serif,
SerifBold,
}
impl SystemFontType {
#[must_use] pub fn from_css_str(s: &str) -> Option<Self> {
let s = s.trim();
if !s.starts_with("system:") {
return None;
}
let rest = &s[7..]; match rest {
"ui" => Some(Self::Ui),
"ui:bold" => Some(Self::UiBold),
"monospace" => Some(Self::Monospace),
"monospace:bold" => Some(Self::MonospaceBold),
"monospace:italic" => Some(Self::MonospaceItalic),
"title" => Some(Self::Title),
"title:bold" => Some(Self::TitleBold),
"menu" => Some(Self::Menu),
"small" => Some(Self::Small),
"serif" => Some(Self::Serif),
"serif:bold" => Some(Self::SerifBold),
_ => None,
}
}
#[must_use] pub const fn as_css_str(&self) -> &'static str {
match self {
Self::Ui => "system:ui",
Self::UiBold => "system:ui:bold",
Self::Monospace => "system:monospace",
Self::MonospaceBold => "system:monospace:bold",
Self::MonospaceItalic => "system:monospace:italic",
Self::Title => "system:title",
Self::TitleBold => "system:title:bold",
Self::Menu => "system:menu",
Self::Small => "system:small",
Self::Serif => "system:serif",
Self::SerifBold => "system:serif:bold",
}
}
#[must_use] pub const fn is_bold(&self) -> bool {
matches!(
self,
Self::UiBold
| Self::MonospaceBold
| Self::TitleBold
| Self::SerifBold
)
}
#[must_use] pub const fn is_italic(&self) -> bool {
matches!(self, Self::MonospaceItalic)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct AccessibilitySettings {
pub text_scale_factor: f32,
pub prefers_bold_text: bool,
pub prefers_larger_text: bool,
pub prefers_high_contrast: bool,
pub prefers_reduced_motion: bool,
pub prefers_reduced_transparency: bool,
pub screen_reader_active: bool,
pub differentiate_without_color: bool,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct SystemColors {
pub text: OptionColorU,
pub secondary_text: OptionColorU,
pub tertiary_text: OptionColorU,
pub background: OptionColorU,
pub accent: OptionColorU,
pub accent_text: OptionColorU,
pub button_face: OptionColorU,
pub button_text: OptionColorU,
pub disabled_text: OptionColorU,
pub window_background: OptionColorU,
pub under_page_background: OptionColorU,
pub selection_background: OptionColorU,
pub selection_text: OptionColorU,
pub selection_background_inactive: OptionColorU,
pub selection_text_inactive: OptionColorU,
pub link: OptionColorU,
pub separator: OptionColorU,
pub grid: OptionColorU,
pub find_highlight: OptionColorU,
pub sidebar_background: OptionColorU,
pub sidebar_selection: OptionColorU,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct SystemFonts {
pub ui_font: OptionString,
pub ui_font_size: OptionF32,
pub monospace_font: OptionString,
pub monospace_font_size: OptionF32,
pub ui_font_bold: OptionString,
pub title_font: OptionString,
pub title_font_size: OptionF32,
pub menu_font: OptionString,
pub menu_font_size: OptionF32,
pub small_font: OptionString,
pub small_font_size: OptionF32,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct SystemMetrics {
pub corner_radius: OptionPixelValue,
pub border_width: OptionPixelValue,
pub button_padding_horizontal: OptionPixelValue,
pub button_padding_vertical: OptionPixelValue,
pub titlebar: TitlebarMetrics,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(C)]
pub enum TitlebarButtonSide {
Left,
#[default]
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct TitlebarButtons {
pub has_close: bool,
pub has_minimize: bool,
pub has_maximize: bool,
pub has_fullscreen: bool,
}
impl Default for TitlebarButtons {
fn default() -> Self {
Self {
has_close: true,
has_minimize: true,
has_maximize: true,
has_fullscreen: false,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct SafeAreaInsets {
pub top: OptionPixelValue,
pub bottom: OptionPixelValue,
pub left: OptionPixelValue,
pub right: OptionPixelValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct TitlebarMetrics {
pub button_side: TitlebarButtonSide,
pub buttons: TitlebarButtons,
pub height: OptionPixelValue,
pub button_area_width: OptionPixelValue,
pub padding_horizontal: OptionPixelValue,
pub safe_area: SafeAreaInsets,
pub title_font: OptionString,
pub title_font_size: OptionF32,
pub title_font_weight: OptionU16,
}
impl Default for TitlebarMetrics {
fn default() -> Self {
Self {
button_side: TitlebarButtonSide::Right,
buttons: TitlebarButtons::default(),
height: OptionPixelValue::None,
button_area_width: OptionPixelValue::None,
padding_horizontal: OptionPixelValue::None,
safe_area: SafeAreaInsets::default(),
title_font: OptionString::None,
title_font_size: OptionF32::Some(13.0),
title_font_weight: OptionU16::Some(600), }
}
}
impl TitlebarMetrics {
#[must_use] pub fn windows() -> Self {
Self {
button_side: TitlebarButtonSide::Right,
buttons: TitlebarButtons {
has_close: true,
has_minimize: true,
has_maximize: true,
has_fullscreen: false,
},
height: OptionPixelValue::Some(PixelValue::px(32.0)),
button_area_width: OptionPixelValue::Some(PixelValue::px(138.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
safe_area: SafeAreaInsets::default(),
title_font: OptionString::Some("Segoe UI Variable Text".into()),
title_font_size: OptionF32::Some(12.0),
title_font_weight: OptionU16::Some(400), }
}
#[must_use] pub fn macos() -> Self {
Self {
button_side: TitlebarButtonSide::Left,
buttons: TitlebarButtons {
has_close: true,
has_minimize: true,
has_maximize: false, has_fullscreen: true,
},
height: OptionPixelValue::Some(PixelValue::px(28.0)),
button_area_width: OptionPixelValue::Some(PixelValue::px(78.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
safe_area: SafeAreaInsets::default(),
title_font: OptionString::Some(".SF NS".into()),
title_font_size: OptionF32::Some(13.0),
title_font_weight: OptionU16::Some(600), }
}
#[must_use] pub fn linux_gnome() -> Self {
Self {
button_side: TitlebarButtonSide::Right, buttons: TitlebarButtons {
has_close: true,
has_minimize: true,
has_maximize: true,
has_fullscreen: false,
},
height: OptionPixelValue::Some(PixelValue::px(35.0)),
button_area_width: OptionPixelValue::Some(PixelValue::px(100.0)),
padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
safe_area: SafeAreaInsets::default(),
title_font: OptionString::Some("Cantarell".into()),
title_font_size: OptionF32::Some(11.0),
title_font_weight: OptionU16::Some(700), }
}
#[must_use] pub fn ios() -> Self {
Self {
button_side: TitlebarButtonSide::Left,
buttons: TitlebarButtons {
has_close: false, has_minimize: false,
has_maximize: false,
has_fullscreen: false,
},
height: OptionPixelValue::Some(PixelValue::px(44.0)),
button_area_width: OptionPixelValue::Some(PixelValue::px(0.0)),
padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
safe_area: SafeAreaInsets {
top: OptionPixelValue::Some(PixelValue::px(47.0)),
bottom: OptionPixelValue::Some(PixelValue::px(34.0)),
left: OptionPixelValue::None,
right: OptionPixelValue::None,
},
title_font: OptionString::Some(".SFUI-Semibold".into()),
title_font_size: OptionF32::Some(17.0),
title_font_weight: OptionU16::Some(600),
}
}
#[must_use] pub fn android() -> Self {
Self {
button_side: TitlebarButtonSide::Left, buttons: TitlebarButtons {
has_close: false,
has_minimize: false,
has_maximize: false,
has_fullscreen: false,
},
height: OptionPixelValue::Some(PixelValue::px(56.0)),
button_area_width: OptionPixelValue::Some(PixelValue::px(48.0)), padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
safe_area: SafeAreaInsets::default(),
title_font: OptionString::Some("Roboto Medium".into()),
title_font_size: OptionF32::Some(20.0),
title_font_weight: OptionU16::Some(500),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct InputMetrics {
pub double_click_time_ms: u32,
pub double_click_distance_px: f32,
pub drag_threshold_px: f32,
pub caret_blink_rate_ms: u32,
pub caret_width_px: f32,
pub wheel_scroll_lines: u32,
pub hover_time_ms: u32,
}
impl Default for InputMetrics {
fn default() -> Self {
Self {
double_click_time_ms: 500,
double_click_distance_px: 4.0,
drag_threshold_px: 5.0,
caret_blink_rate_ms: 530,
caret_width_px: 1.0,
wheel_scroll_lines: 3,
hover_time_ms: 400,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum SubpixelType {
#[default]
None,
Rgb,
Bgr,
VRgb,
VBgr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct TextRenderingHints {
pub subpixel_type: SubpixelType,
pub font_smoothing_gamma: u32,
pub font_smoothing_enabled: bool,
pub increased_contrast: bool,
}
impl Default for TextRenderingHints {
fn default() -> Self {
Self {
subpixel_type: SubpixelType::None,
font_smoothing_gamma: 1000,
font_smoothing_enabled: true,
increased_contrast: false,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct FocusVisuals {
pub focus_ring_color: OptionColorU,
pub focus_border_width: OptionPixelValue,
pub focus_border_height: OptionPixelValue,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum ScrollbarVisibility {
Always,
#[default]
WhenScrolling,
Automatic,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum ScrollbarTrackClick {
JumpToPosition,
#[default]
PageUpDown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct ScrollbarPreferences {
pub visibility: ScrollbarVisibility,
pub track_click: ScrollbarTrackClick,
}
impl Default for ScrollbarPreferences {
fn default() -> Self {
Self {
visibility: ScrollbarVisibility::WhenScrolling,
track_click: ScrollbarTrackClick::PageUpDown,
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct LinuxCustomization {
pub gtk_theme: OptionString,
pub icon_theme: OptionString,
pub cursor_theme: OptionString,
pub cursor_size: u32,
pub titlebar_button_layout: OptionString,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum ToolbarStyle {
#[default]
IconsOnly,
TextOnly,
TextBesideIcon,
TextBelowIcon,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct VisualHints {
pub toolbar_style: ToolbarStyle,
pub show_button_images: bool,
pub show_menu_images: bool,
pub show_tooltips: bool,
pub flash_on_alert: bool,
}
impl Default for VisualHints {
fn default() -> Self {
Self {
toolbar_style: ToolbarStyle::IconsOnly,
show_button_images: false,
show_menu_images: true,
show_tooltips: true,
flash_on_alert: true,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum FocusBehavior {
#[default]
AlwaysVisible,
KeyboardOnly,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct AnimationMetrics {
pub animations_enabled: bool,
pub animation_duration_factor: f32,
pub focus_indicator_behavior: FocusBehavior,
}
impl Default for AnimationMetrics {
fn default() -> Self {
Self {
animations_enabled: true,
animation_duration_factor: 1.0,
focus_indicator_behavior: FocusBehavior::AlwaysVisible,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct AudioMetrics {
pub event_sounds_enabled: bool,
pub input_feedback_sounds_enabled: bool,
}
impl Default for AudioMetrics {
fn default() -> Self {
Self {
event_sounds_enabled: true,
input_feedback_sounds_enabled: false,
}
}
}
pub mod apple_fonts {
pub const SYSTEM_FONT: &str = "System Font";
pub const SF_NS_ROUNDED: &str = "SF NS Rounded";
pub const SF_COMPACT: &str = "SF Compact";
pub const SF_MONO: &str = "SF NS Mono Light";
pub const NEW_YORK: &str = "New York";
pub const SF_ARABIC: &str = "SF Arabic";
pub const SF_ARMENIAN: &str = "SF Armenian";
pub const SF_GEORGIAN: &str = "SF Georgian";
pub const SF_HEBREW: &str = "SF Hebrew";
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";
}
pub mod windows_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";
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";
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";
}
pub mod linux_fonts {
pub const CANTARELL: &str = "Cantarell";
pub const ADWAITA: &str = "Adwaita";
pub const UBUNTU: &str = "Ubuntu";
pub const UBUNTU_MONO: &str = "Ubuntu Mono";
pub const DEJAVU_SANS: &str = "DejaVu Sans";
pub const DEJAVU_SANS_MONO: &str = "DejaVu Sans Mono";
pub const DEJAVU_SERIF: &str = "DejaVu Serif";
pub const LIBERATION_SANS: &str = "Liberation Sans";
pub const LIBERATION_MONO: &str = "Liberation Mono";
pub const LIBERATION_SERIF: &str = "Liberation Serif";
pub const NOTO_SANS: &str = "Noto Sans";
pub const NOTO_MONO: &str = "Noto Sans Mono";
pub const NOTO_SERIF: &str = "Noto Serif";
pub const HACK: &str = "Hack";
pub const MONOSPACE: &str = "Monospace";
pub const SANS_SERIF: &str = "Sans";
pub const SERIF: &str = "Serif";
}
impl SystemFontType {
#[must_use] pub fn get_fallback_chain(&self, platform: &Platform) -> Vec<&'static str> {
match platform {
Platform::MacOs | Platform::Ios => self.macos_fallback_chain(),
Platform::Windows => self.windows_fallback_chain(),
Platform::Linux(_) => self.linux_fallback_chain(),
Platform::Android => self.android_fallback_chain(),
Platform::Unknown => self.generic_fallback_chain(),
}
}
fn macos_fallback_chain(self) -> Vec<&'static str> {
match self {
Self::Ui => vec![
apple_fonts::SYSTEM_FONT,
apple_fonts::HELVETICA_NEUE,
apple_fonts::LUCIDA_GRANDE,
],
Self::UiBold | Self::TitleBold => vec![
apple_fonts::HELVETICA_NEUE,
apple_fonts::LUCIDA_GRANDE,
],
Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
apple_fonts::MENLO,
apple_fonts::MONACO,
],
Self::Title | Self::Menu | Self::Small => vec![
apple_fonts::SYSTEM_FONT,
apple_fonts::HELVETICA_NEUE,
],
Self::Serif => vec![
apple_fonts::NEW_YORK,
"Georgia",
"Times New Roman",
],
Self::SerifBold => vec![
"Georgia", "Times New Roman",
],
}
}
fn windows_fallback_chain(self) -> Vec<&'static str> {
match self {
Self::Ui | Self::UiBold => vec![
windows_fonts::SEGOE_UI_VARIABLE_TEXT,
windows_fonts::SEGOE_UI,
windows_fonts::TAHOMA,
],
Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
windows_fonts::CASCADIA_MONO,
windows_fonts::CASCADIA_CODE,
windows_fonts::CONSOLAS,
windows_fonts::LUCIDA_CONSOLE,
windows_fonts::COURIER_NEW,
],
Self::Title | Self::TitleBold => vec![
windows_fonts::SEGOE_UI_VARIABLE_DISPLAY,
windows_fonts::SEGOE_UI,
],
Self::Menu => vec![
windows_fonts::SEGOE_UI,
windows_fonts::TAHOMA,
],
Self::Small => vec![
windows_fonts::SEGOE_UI,
],
Self::Serif | Self::SerifBold => vec![
"Cambria",
"Georgia",
"Times New Roman",
],
}
}
fn linux_fallback_chain(self) -> Vec<&'static str> {
match self {
Self::Ui | Self::UiBold => vec![
linux_fonts::CANTARELL,
linux_fonts::UBUNTU,
linux_fonts::NOTO_SANS,
linux_fonts::DEJAVU_SANS,
linux_fonts::LIBERATION_SANS,
linux_fonts::SANS_SERIF,
],
Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => vec![
linux_fonts::UBUNTU_MONO,
linux_fonts::HACK,
linux_fonts::NOTO_MONO,
linux_fonts::DEJAVU_SANS_MONO,
linux_fonts::LIBERATION_MONO,
linux_fonts::MONOSPACE,
],
Self::Title | Self::TitleBold | Self::Menu | Self::Small => vec![
linux_fonts::CANTARELL,
linux_fonts::UBUNTU,
linux_fonts::NOTO_SANS,
],
Self::Serif | Self::SerifBold => vec![
linux_fonts::NOTO_SERIF,
linux_fonts::DEJAVU_SERIF,
linux_fonts::LIBERATION_SERIF,
linux_fonts::SERIF,
],
}
}
fn android_fallback_chain(self) -> Vec<&'static str> {
match self {
Self::Ui | Self::UiBold | Self::Title | Self::TitleBold => vec!["Roboto", "Noto Sans"],
Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
vec!["Roboto Mono", "Droid Sans Mono", "monospace"]
}
Self::Menu | Self::Small => vec!["Roboto"],
Self::Serif | Self::SerifBold => vec!["Noto Serif", "Droid Serif", "serif"],
}
}
fn generic_fallback_chain(self) -> Vec<&'static str> {
match self {
Self::Ui | Self::UiBold | Self::Title | Self::TitleBold | Self::Menu | Self::Small => {
vec!["sans-serif"]
}
Self::Monospace | Self::MonospaceBold | Self::MonospaceItalic => {
vec!["monospace"]
}
Self::Serif | Self::SerifBold => vec!["serif"],
}
}
}
impl SystemStyle {
#[allow(clippy::too_many_lines)] #[must_use] pub fn to_json_string(&self) -> AzString {
use alloc::format;
fn opt_color(c: OptionColorU) -> alloc::string::String {
c.as_ref().map_or_else(
|| "null".into(),
|c| format!("\"#{:02x}{:02x}{:02x}{:02x}\"", c.r, c.g, c.b, c.a),
)
}
fn opt_str(s: &OptionString) -> alloc::string::String {
s.as_ref()
.map_or_else(|| "null".into(), |s| format!("\"{}\"", s.as_str()))
}
fn opt_f32(v: OptionF32) -> alloc::string::String {
v.into_option()
.map_or_else(|| "null".into(), |v| format!("{v:.2}"))
}
fn opt_u16(v: OptionU16) -> alloc::string::String {
v.into_option()
.map_or_else(|| "null".into(), |v| format!("{v}"))
}
fn opt_px(v: &OptionPixelValue) -> alloc::string::String {
v.as_ref().map_or_else(
|| "null".into(),
|v| format!("{:.1}", v.to_pixels_internal(0.0, 0.0, 0.0)),
)
}
let tm = &self.metrics.titlebar;
let inp = &self.input;
let tr = &self.text_rendering;
let acc = &self.accessibility;
let sp = &self.scrollbar_preferences;
let lnx = &self.linux;
let vh = &self.visual_hints;
let anim = &self.animation;
let audio = &self.audio;
let json = format!(
r#"{{
"theme": "{:?}",
"platform": "{:?}",
"os_version": "{:?}:{}",
"language": "{}",
"prefers_reduced_motion": {:?},
"prefers_high_contrast": {:?},
"colors": {{
"text": {},
"secondary_text": {},
"tertiary_text": {},
"background": {},
"accent": {},
"accent_text": {},
"button_face": {},
"button_text": {},
"disabled_text": {},
"window_background": {},
"under_page_background": {},
"selection_background": {},
"selection_text": {},
"selection_background_inactive": {},
"selection_text_inactive": {},
"link": {},
"separator": {},
"grid": {},
"find_highlight": {},
"sidebar_background": {},
"sidebar_selection": {}
}},
"fonts": {{
"ui_font": {},
"ui_font_size": {},
"monospace_font": {},
"title_font": {},
"menu_font": {},
"small_font": {}
}},
"titlebar": {{
"button_side": "{:?}",
"height": {},
"button_area_width": {},
"padding_horizontal": {},
"title_font": {},
"title_font_size": {},
"title_font_weight": {},
"has_close": {},
"has_minimize": {},
"has_maximize": {},
"has_fullscreen": {}
}},
"input": {{
"double_click_time_ms": {},
"double_click_distance_px": {:.1},
"drag_threshold_px": {:.1},
"caret_blink_rate_ms": {},
"caret_width_px": {:.1},
"wheel_scroll_lines": {},
"hover_time_ms": {}
}},
"text_rendering": {{
"font_smoothing_enabled": {},
"subpixel_type": "{:?}",
"font_smoothing_gamma": {},
"increased_contrast": {}
}},
"accessibility": {{
"prefers_bold_text": {},
"prefers_larger_text": {},
"text_scale_factor": {:.2},
"prefers_high_contrast": {},
"prefers_reduced_motion": {},
"prefers_reduced_transparency": {},
"screen_reader_active": {},
"differentiate_without_color": {}
}},
"scrollbar_preferences": {{
"visibility": "{:?}",
"track_click": "{:?}"
}},
"linux": {{
"gtk_theme": {},
"icon_theme": {},
"cursor_theme": {},
"cursor_size": {},
"titlebar_button_layout": {}
}},
"visual_hints": {{
"show_button_images": {},
"show_menu_images": {},
"toolbar_style": "{:?}",
"show_tooltips": {}
}},
"animation": {{
"animations_enabled": {},
"animation_duration_factor": {:.2},
"focus_indicator_behavior": "{:?}"
}},
"audio": {{
"event_sounds_enabled": {},
"input_feedback_sounds_enabled": {}
}}
}}"#,
self.theme,
self.platform,
self.os_version.os, self.os_version.version_id,
self.language.as_str(),
self.prefers_reduced_motion,
self.prefers_high_contrast,
opt_color(self.colors.text),
opt_color(self.colors.secondary_text),
opt_color(self.colors.tertiary_text),
opt_color(self.colors.background),
opt_color(self.colors.accent),
opt_color(self.colors.accent_text),
opt_color(self.colors.button_face),
opt_color(self.colors.button_text),
opt_color(self.colors.disabled_text),
opt_color(self.colors.window_background),
opt_color(self.colors.under_page_background),
opt_color(self.colors.selection_background),
opt_color(self.colors.selection_text),
opt_color(self.colors.selection_background_inactive),
opt_color(self.colors.selection_text_inactive),
opt_color(self.colors.link),
opt_color(self.colors.separator),
opt_color(self.colors.grid),
opt_color(self.colors.find_highlight),
opt_color(self.colors.sidebar_background),
opt_color(self.colors.sidebar_selection),
opt_str(&self.fonts.ui_font),
opt_f32(self.fonts.ui_font_size),
opt_str(&self.fonts.monospace_font),
opt_str(&self.fonts.title_font),
opt_str(&self.fonts.menu_font),
opt_str(&self.fonts.small_font),
tm.button_side,
opt_px(&tm.height),
opt_px(&tm.button_area_width),
opt_px(&tm.padding_horizontal),
opt_str(&tm.title_font),
opt_f32(tm.title_font_size),
opt_u16(tm.title_font_weight),
tm.buttons.has_close,
tm.buttons.has_minimize,
tm.buttons.has_maximize,
tm.buttons.has_fullscreen,
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,
tr.font_smoothing_enabled,
tr.subpixel_type,
tr.font_smoothing_gamma,
tr.increased_contrast,
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,
sp.visibility,
sp.track_click,
opt_str(&lnx.gtk_theme),
opt_str(&lnx.icon_theme),
opt_str(&lnx.cursor_theme),
lnx.cursor_size,
opt_str(&lnx.titlebar_button_layout),
vh.show_button_images,
vh.show_menu_images,
vh.toolbar_style,
vh.show_tooltips,
anim.animations_enabled,
anim.animation_duration_factor,
anim.focus_indicator_behavior,
audio.event_sounds_enabled,
audio.input_feedback_sounds_enabled,
);
AzString::from(json)
}
#[must_use] pub fn detect() -> Self {
Self::default_for_platform()
}
#[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")]
{ 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() }
}
#[inline]
#[must_use] pub fn new() -> Self {
Self::detect()
}
#[must_use] pub fn create_csd_stylesheet(&self) -> Css {
use alloc::format;
use crate::parser2::new_from_str;
let mut css = String::new();
let bg_color = self
.colors
.window_background
.as_option()
.copied()
.unwrap_or(ColorU::new_rgb(240, 240, 240));
let text_color = self
.colors
.text
.as_option()
.copied()
.unwrap_or(ColorU::new_rgb(0, 0, 0));
let accent_color = self
.colors
.accent
.as_option()
.copied()
.unwrap_or(ColorU::new_rgb(0, 120, 215));
let border_color = match self.theme {
Theme::Dark => ColorU::new_rgb(60, 60, 60),
Theme::Light => ColorU::new_rgb(200, 200, 200),
};
let corner_radius = self
.metrics
.corner_radius
.map(|px| {
use crate::props::basic::pixel::DEFAULT_FONT_SIZE;
format!("{}px", px.to_pixels_internal(1.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
})
.unwrap_or_else(|| "4px".to_string());
let _ = write!(css,
".csd-titlebar {{ width: 100%; height: 32px; background: rgb({}, {}, {}); \
border-bottom: 1px solid rgb({}, {}, {}); display: flex; flex-direction: row; \
align-items: center; justify-content: space-between; padding: 0 8px; \
cursor: grab; user-select: none; }} ",
bg_color.r, bg_color.g, bg_color.b, border_color.r, border_color.g, border_color.b,
);
let _ = write!(css,
".csd-title {{ color: rgb({}, {}, {}); font-size: 13px; flex-grow: 1; text-align: \
center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; \
user-select: none; }} ",
text_color.r, text_color.g, text_color.b,
);
css.push_str(".csd-buttons { display: flex; flex-direction: row; gap: 4px; } ");
let _ = write!(css,
".csd-button {{ width: 32px; height: 24px; border-radius: {}; background: \
transparent; color: rgb({}, {}, {}); font-size: 16px; line-height: 24px; text-align: \
center; cursor: pointer; user-select: none; }} ",
corner_radius, text_color.r, text_color.g, text_color.b,
);
let hover_color = match self.theme {
Theme::Dark => ColorU::new_rgb(60, 60, 60),
Theme::Light => ColorU::new_rgb(220, 220, 220),
};
let _ = write!(css,
".csd-button:hover {{ background: rgb({}, {}, {}); }} ",
hover_color.r, hover_color.g, hover_color.b,
);
css.push_str(
".csd-close:hover { background: rgb(232, 17, 35); color: rgb(255, 255, 255); } ",
);
match self.platform {
Platform::MacOs => {
css.push_str(".csd-buttons { position: absolute; left: 8px; } ");
css.push_str(
".csd-close { background: rgb(255, 95, 86); width: 12px; height: 12px; \
border-radius: 50%; } ",
);
css.push_str(
".csd-minimize { background: rgb(255, 189, 46); width: 12px; height: 12px; \
border-radius: 50%; } ",
);
css.push_str(
".csd-maximize { background: rgb(40, 201, 64); width: 12px; height: 12px; \
border-radius: 50%; } ",
);
}
Platform::Linux(_) => {
css.push_str(".csd-title { text-align: left; } ");
}
_ => {
}
}
let (mut parsed_css, _warnings) = new_from_str(&css);
for rule in parsed_css.rules.as_mut() {
rule.priority = crate::css::rule_priority::SYSTEM;
}
parsed_css
}
}
#[must_use] pub fn detect_linux_desktop_env() -> DesktopEnvironment {
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));
}
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));
}
if std::env::var("GNOME_DESKTOP_SESSION_ID").is_ok() {
return DesktopEnvironment::Gnome;
}
if std::env::var("KDE_FULL_SESSION").is_ok() {
return DesktopEnvironment::Kde;
}
if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
return DesktopEnvironment::Other(AzString::from_const_str("Hyprland"));
}
if std::env::var("SWAYSOCK").is_ok() {
return DesktopEnvironment::Other(AzString::from_const_str("Sway"));
}
if std::env::var("I3SOCK").is_ok() {
return DesktopEnvironment::Other(AzString::from_const_str("i3"));
}
DesktopEnvironment::Other(AzString::from_const_str("Unknown"))
}
#[must_use] pub fn detect_system_language() -> AzString {
let env_vars = ["LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"];
for var in &env_vars {
if let Ok(value) = std::env::var(var) {
let value = value.trim();
if value.is_empty() || value == "C" || value == "POSIX" {
continue;
}
let lang = value
.split('.') .next()
.unwrap_or(value)
.split(':') .next()
.unwrap_or(value);
if !lang.is_empty() {
return AzString::from(lang.replace('_', "-"));
}
}
}
AzString::from_const_str("en-US")
}
pub mod defaults {
use super::{
AccessibilitySettings, AnimationMetrics, AudioMetrics, FocusVisuals, 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,
},
};
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,
}), thumb: StyleBackgroundContent::Color(ColorU {
r: 208,
g: 208,
b: 208,
a: 255,
}), 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,
};
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,
}), thumb: StyleBackgroundContent::Color(ColorU {
r: 105,
g: 173,
b: 255,
a: 255,
}), 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,
};
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,
};
fn scrollbar_info_to_computed(info: &ScrollbarInfo) -> ComputedScrollbarStyle {
ComputedScrollbarStyle {
width: Some(info.width.clone()),
thumb_color: match info.thumb {
StyleBackgroundContent::Color(c) => Some(c),
_ => None,
},
track_color: match info.track {
StyleBackgroundContent::Color(c) => Some(c),
_ => None,
},
}
}
#[must_use] pub fn windows_11_light() -> SystemStyle {
SystemStyle {
theme: Theme::Light,
platform: Platform::Windows,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
background: OptionColorU::Some(ColorU::new_rgb(243, 243, 243)),
accent: OptionColorU::Some(ColorU::new_rgb(0, 95, 184)),
window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Segoe UI Variable Text".into()),
ui_font_size: OptionF32::Some(9.0),
monospace_font: OptionString::Some("Consolas".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
titlebar: TitlebarMetrics::windows(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_LIGHT))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::WIN_11,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::windows(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn windows_11_dark() -> SystemStyle {
SystemStyle {
theme: Theme::Dark,
platform: Platform::Windows,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
background: OptionColorU::Some(ColorU::new_rgb(32, 32, 32)),
accent: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
window_background: OptionColorU::Some(ColorU::new_rgb(25, 25, 25)),
selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Segoe UI Variable Text".into()),
ui_font_size: OptionF32::Some(9.0),
monospace_font: OptionString::Some("Consolas".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
titlebar: TitlebarMetrics::windows(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_DARK))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::WIN_11,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::windows(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn windows_7_aero() -> SystemStyle {
SystemStyle {
theme: Theme::Light,
platform: Platform::Windows,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
background: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
accent: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
selection_background: OptionColorU::Some(ColorU::new_rgb(51, 153, 255)),
selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Segoe UI".into()),
ui_font_size: OptionF32::Some(9.0),
monospace_font: OptionString::Some("Consolas".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(6.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(5.0)),
titlebar: TitlebarMetrics::windows(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::WIN_7,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::windows(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn windows_xp_luna() -> SystemStyle {
SystemStyle {
theme: Theme::Light,
platform: Platform::Windows,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
background: OptionColorU::Some(ColorU::new_rgb(236, 233, 216)),
accent: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
selection_background: OptionColorU::Some(ColorU::new_rgb(49, 106, 197)),
selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Tahoma".into()),
ui_font_size: OptionF32::Some(8.0),
monospace_font: OptionString::Some("Lucida Console".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(3.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(8.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
titlebar: TitlebarMetrics::windows(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_WINDOWS_CLASSIC))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::WIN_XP,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::windows(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn macos_modern_light() -> SystemStyle {
SystemStyle {
platform: Platform::MacOs,
theme: Theme::Light,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new(0, 0, 0, 221)),
background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
selection_background: OptionColorU::Some(ColorU::new(0, 122, 255, 128)),
selection_text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some(".SF NS".into()),
ui_font_size: OptionF32::Some(13.0),
monospace_font: OptionString::Some("Menlo".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
titlebar: TitlebarMetrics::macos(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_LIGHT))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::MACOS_SONOMA,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::macos(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn macos_modern_dark() -> SystemStyle {
SystemStyle {
platform: Platform::MacOs,
theme: Theme::Dark,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new(255, 255, 255, 221)),
background: OptionColorU::Some(ColorU::new_rgb(28, 28, 30)),
accent: OptionColorU::Some(ColorU::new_rgb(10, 132, 255)),
window_background: OptionColorU::Some(ColorU::new_rgb(44, 44, 46)),
selection_background: OptionColorU::Some(ColorU::new(10, 132, 255, 128)),
selection_text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some(".SF NS".into()),
ui_font_size: OptionF32::Some(13.0),
monospace_font: OptionString::Some("SF Mono".into()),
monospace_font_size: OptionF32::Some(12.0),
title_font: OptionString::Some(".SF NS".into()),
title_font_size: OptionF32::Some(13.0),
menu_font: OptionString::Some(".SF NS".into()),
menu_font_size: OptionF32::Some(13.0),
small_font: OptionString::Some(".SF NS".into()),
small_font_size: OptionF32::Some(11.0),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(8.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
titlebar: TitlebarMetrics::macos(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_DARK))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::MACOS_SONOMA,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::macos(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn macos_aqua() -> SystemStyle {
SystemStyle {
platform: Platform::MacOs,
theme: Theme::Light,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
background: OptionColorU::Some(ColorU::new_rgb(229, 229, 229)),
accent: OptionColorU::Some(ColorU::new_rgb(63, 128, 234)),
window_background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Lucida Grande".into()),
ui_font_size: OptionF32::Some(13.0),
monospace_font: OptionString::Some("Monaco".into()),
monospace_font_size: OptionF32::Some(12.0),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
titlebar: TitlebarMetrics::macos(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_MACOS_AQUA))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::MACOS_TIGER,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::macos(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn gnome_adwaita_light() -> SystemStyle {
SystemStyle {
platform: Platform::Linux(DesktopEnvironment::Gnome),
theme: Theme::Light,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(46, 52, 54)),
background: OptionColorU::Some(ColorU::new_rgb(249, 249, 249)),
accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
window_background: OptionColorU::Some(ColorU::new_rgb(237, 237, 237)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Cantarell".into()),
ui_font_size: OptionF32::Some(11.0),
monospace_font: OptionString::Some("Monospace".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
titlebar: TitlebarMetrics::linux_gnome(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::LINUX_6_0,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::default(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn gnome_adwaita_dark() -> SystemStyle {
SystemStyle {
platform: Platform::Linux(DesktopEnvironment::Gnome),
theme: Theme::Dark,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(238, 238, 236)),
background: OptionColorU::Some(ColorU::new_rgb(36, 36, 36)),
accent: OptionColorU::Some(ColorU::new_rgb(53, 132, 228)),
window_background: OptionColorU::Some(ColorU::new_rgb(48, 48, 48)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Cantarell".into()),
ui_font_size: OptionF32::Some(11.0),
monospace_font: OptionString::Some("Monospace".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
titlebar: TitlebarMetrics::linux_gnome(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_DARK))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::LINUX_6_0,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::default(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn gtk2_clearlooks() -> SystemStyle {
SystemStyle {
platform: Platform::Linux(DesktopEnvironment::Gnome),
theme: Theme::Light,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
background: OptionColorU::Some(ColorU::new_rgb(239, 239, 239)),
accent: OptionColorU::Some(ColorU::new_rgb(245, 121, 0)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("DejaVu Sans".into()),
ui_font_size: OptionF32::Some(10.0),
monospace_font: OptionString::Some("DejaVu Sans Mono".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(10.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
titlebar: TitlebarMetrics::linux_gnome(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_CLASSIC_LIGHT))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::LINUX_2_6,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::default(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn kde_breeze_light() -> SystemStyle {
SystemStyle {
platform: Platform::Linux(DesktopEnvironment::Kde),
theme: Theme::Light,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(31, 36, 39)),
background: OptionColorU::Some(ColorU::new_rgb(239, 240, 241)),
accent: OptionColorU::Some(ColorU::new_rgb(61, 174, 233)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Noto Sans".into()),
ui_font_size: OptionF32::Some(10.0),
monospace_font: OptionString::Some("Hack".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(4.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(6.0)),
titlebar: TitlebarMetrics::linux_gnome(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_KDE_OXYGEN))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::LINUX_6_0,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::default(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn android_material_light() -> SystemStyle {
SystemStyle {
platform: Platform::Android,
theme: Theme::Light,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
background: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
accent: OptionColorU::Some(ColorU::new_rgb(98, 0, 238)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Roboto".into()),
ui_font_size: OptionF32::Some(14.0),
monospace_font: OptionString::Some("Droid Sans Mono".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(12.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(16.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(10.0)),
titlebar: TitlebarMetrics::android(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_LIGHT))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::ANDROID_14,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::android(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn android_holo_dark() -> SystemStyle {
SystemStyle {
platform: Platform::Android,
theme: Theme::Dark,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(255, 255, 255)),
background: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
accent: OptionColorU::Some(ColorU::new_rgb(51, 181, 229)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some("Roboto".into()),
ui_font_size: OptionF32::Some(14.0),
monospace_font: OptionString::Some("Droid Sans Mono".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(2.0)),
border_width: OptionPixelValue::Some(PixelValue::px(1.0)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(12.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(8.0)),
titlebar: TitlebarMetrics::android(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_ANDROID_DARK))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::ANDROID_ICE_CREAM_SANDWICH,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::android(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
#[must_use] pub fn ios_light() -> SystemStyle {
SystemStyle {
platform: Platform::Ios,
theme: Theme::Light,
colors: SystemColors {
text: OptionColorU::Some(ColorU::new_rgb(0, 0, 0)),
background: OptionColorU::Some(ColorU::new_rgb(242, 242, 247)),
accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)),
..Default::default()
},
fonts: SystemFonts {
ui_font: OptionString::Some(".SFUI-Display-Regular".into()),
ui_font_size: OptionF32::Some(17.0),
monospace_font: OptionString::Some("Menlo".into()),
..Default::default()
},
metrics: SystemMetrics {
corner_radius: OptionPixelValue::Some(PixelValue::px(10.0)),
border_width: OptionPixelValue::Some(PixelValue::px(0.5)),
button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(20.0)),
button_padding_vertical: OptionPixelValue::Some(PixelValue::px(12.0)),
titlebar: TitlebarMetrics::ios(),
},
scrollbar: Some(Box::new(scrollbar_info_to_computed(&SCROLLBAR_IOS_LIGHT))),
app_specific_stylesheet: None,
run_destructor: true,
icon_style: IconStyleOptions::default(),
language: AzString::from_const_str("en-US"),
os_version: OsVersion::IOS_17,
prefers_reduced_motion: BoolCondition::False,
prefers_high_contrast: BoolCondition::False,
scroll_physics: ScrollPhysics::ios(),
linux: LinuxCustomization::default(),
focus_visuals: FocusVisuals::default(),
accessibility: AccessibilitySettings::default(),
input: InputMetrics::default(),
text_rendering: TextRenderingHints::default(),
scrollbar_preferences: ScrollbarPreferences::default(),
visual_hints: VisualHints::default(),
animation: AnimationMetrics::default(),
audio: AudioMetrics::default(),
}
}
}
#[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,
]
}
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()),
]
}
#[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() {
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() {
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)
);
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() {
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}", "\u{1F600}system:ui",
"systém:ui", "system:ui", "system:\u{202E}ui", "system:\u{FFFD}",
"system:ui\u{200B}", ] {
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);
let long_suffix = format!("system:ui{}", "x".repeat(1_000_000));
assert_eq!(SystemFontType::from_css_str(&long_suffix), None);
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);
}
#[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:?}");
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());
}
#[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:?}");
assert!(!(ty.is_bold() && ty.is_italic()), "{ty:?} is bold *and* italic");
}
}
#[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:?}"
);
}
}
#[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);
#[cfg(any(
target_os = "linux",
target_os = "windows",
target_os = "macos",
target_os = "android",
target_os = "ios"
))]
assert_ne!(a, Platform::Unknown);
assert_eq!(Platform::default(), Platform::Unknown);
}
#[test]
fn titlebar_metrics_have_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);
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);
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"
);
}
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());
}
#[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() {
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());
}
#[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() {
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());
}
}
#[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}");
assert!(s.contains("\"text\": \"#000000ff\""), "{s}");
assert!(s.contains("\"height\": 32.0"), "{s}");
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;
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());
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() {
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()
);
}
#[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"
);
}
const _: () = assert!(rule_priority::SYSTEM < rule_priority::AUTHOR);
}
}
#[test]
fn csd_stylesheet_uses_fallback_colors_when_the_system_reports_none() {
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);
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());
}
#[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());
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");
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");
}
}