Skip to main content

incular_platform/
system_environment.rs

1//! Stable operating-system environment preference contracts.
2//!
3//! The desktop shell combines these application-level preferences with
4//! per-window state (metrics, Winit theme, focus, occlusion) to create a full
5//! [`RuntimeEnvironment`]. Unsupported values are represented by `None`; when
6//! applied they intentionally reset to Incular's documented defaults rather
7//! than preserving a stale value from an earlier snapshot.
8
9use incular_config::{EdgeInsets, InputCapabilities, Locale, RuntimeEnvironment};
10use std::sync::{Arc, RwLock};
11
12/// Application-level system preferences observable by a native adapter.
13#[derive(Clone, Debug, Default, PartialEq)]
14pub struct SystemEnvironmentPreferences {
15    /// Ordered OS locale preferences. Order is significant.
16    pub locales: Option<Vec<Locale>>,
17    /// Accessibility text scaling when the platform exposes a meaningful
18    /// application text-scale value distinct from window DPI.
19    pub text_scale: Option<f32>,
20    pub reduced_motion: Option<bool>,
21    pub high_contrast: Option<bool>,
22    pub input: Option<InputCapabilities>,
23    /// Desktop platforms normally leave these unsupported (`None`), but the
24    /// contract is explicit for shells with desktop safe/view insets.
25    ///
26    /// Inset ownership: `safe_insets` is the currently usable margin and the
27    /// shell reduces covered edges while transient UI obscures them;
28    /// `view_padding` is the persistent obstruction margin the shell never
29    /// reduces under occlusion; `view_insets` is the transient occlusion
30    /// itself (for example the keyboard). Maintained safe-area padding reads
31    /// `view_padding`, never `view_insets`.
32    pub safe_insets: Option<EdgeInsets>,
33    pub view_padding: Option<EdgeInsets>,
34    pub view_insets: Option<EdgeInsets>,
35}
36
37impl SystemEnvironmentPreferences {
38    /// Applies this complete preference snapshot. Every unsupported field is
39    /// reset to the stable `RuntimeEnvironment::default()` value so a backend
40    /// losing support cannot leave old native state cached indefinitely.
41    pub fn apply_to(&self, environment: &mut RuntimeEnvironment) {
42        let defaults = RuntimeEnvironment::default();
43        environment.locales = self.locales.clone().unwrap_or(defaults.locales);
44        environment.text_scale = self.text_scale.unwrap_or(defaults.text_scale);
45        environment.reduced_motion = self.reduced_motion.unwrap_or(defaults.reduced_motion);
46        environment.high_contrast = self.high_contrast.unwrap_or(defaults.high_contrast);
47        environment.input = self.input.unwrap_or(defaults.input);
48        environment.safe_insets = self.safe_insets.unwrap_or(defaults.safe_insets);
49        environment.view_padding = self.view_padding.unwrap_or(defaults.view_padding);
50        environment.view_insets = self.view_insets.unwrap_or(defaults.view_insets);
51    }
52}
53
54/// Snapshot source used by platform adapters and deterministic tests.
55pub trait SystemEnvironmentProvider {
56    fn snapshot(&self) -> SystemEnvironmentPreferences;
57}
58
59/// Thread-safe deterministic provider for headless hosts and tests.
60#[derive(Clone, Default)]
61pub struct MemorySystemEnvironmentProvider {
62    snapshot: Arc<RwLock<SystemEnvironmentPreferences>>,
63}
64
65impl MemorySystemEnvironmentProvider {
66    #[must_use]
67    pub fn new(snapshot: SystemEnvironmentPreferences) -> Self {
68        Self {
69            snapshot: Arc::new(RwLock::new(snapshot)),
70        }
71    }
72
73    /// Atomically replaces the complete preference snapshot and returns
74    /// whether any semantic value changed.
75    pub fn replace(&self, snapshot: SystemEnvironmentPreferences) -> bool {
76        let mut current = self.snapshot.write().expect("environment provider lock");
77        if *current == snapshot {
78            return false;
79        }
80        *current = snapshot;
81        true
82    }
83}
84
85impl SystemEnvironmentProvider for MemorySystemEnvironmentProvider {
86    fn snapshot(&self) -> SystemEnvironmentPreferences {
87        self.snapshot
88            .read()
89            .expect("environment provider lock")
90            .clone()
91    }
92}
93
94/// Parses OS-provided BCP-47 locale strings through ICU4X, preserving order
95/// while dropping malformed and duplicate canonical locales.
96#[must_use]
97pub fn canonicalize_system_locales(
98    locales: impl IntoIterator<Item = impl AsRef<str>>,
99) -> Vec<Locale> {
100    let mut result = Vec::new();
101    for value in locales {
102        let Ok(locale) = value.as_ref().parse::<Locale>() else {
103            continue;
104        };
105        if !result.contains(&locale) {
106            result.push(locale);
107        }
108    }
109    result
110}