incular-platform 0.1.1

Shared platform abstractions for Incular
Documentation
//! Stable operating-system environment preference contracts.
//!
//! The desktop shell combines these application-level preferences with
//! per-window state (metrics, Winit theme, focus, occlusion) to create a full
//! [`RuntimeEnvironment`]. Unsupported values are represented by `None`; when
//! applied they intentionally reset to Incular's documented defaults rather
//! than preserving a stale value from an earlier snapshot.

use incular_config::{EdgeInsets, InputCapabilities, Locale, RuntimeEnvironment};
use std::sync::{Arc, RwLock};

/// Application-level system preferences observable by a native adapter.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SystemEnvironmentPreferences {
    /// Ordered OS locale preferences. Order is significant.
    pub locales: Option<Vec<Locale>>,
    /// Accessibility text scaling when the platform exposes a meaningful
    /// application text-scale value distinct from window DPI.
    pub text_scale: Option<f32>,
    pub reduced_motion: Option<bool>,
    pub high_contrast: Option<bool>,
    pub always_use_24_hour_format: Option<bool>,
    pub accessible_navigation: Option<bool>,
    pub invert_colors: Option<bool>,
    pub on_off_switch_labels: Option<bool>,
    pub bold_text: Option<bool>,
    pub input: Option<InputCapabilities>,
    /// Desktop platforms normally leave these unsupported (`None`), but the
    /// contract is explicit for shells with desktop safe/view insets.
    ///
    /// Inset ownership: `safe_insets` is the currently usable margin and the
    /// shell reduces covered edges while transient UI obscures them;
    /// `view_padding` is the persistent obstruction margin the shell never
    /// reduces under occlusion; `view_insets` is the transient occlusion
    /// itself (for example the keyboard). Maintained safe-area padding reads
    /// `view_padding`, never `view_insets`.
    pub safe_insets: Option<EdgeInsets>,
    pub view_padding: Option<EdgeInsets>,
    pub view_insets: Option<EdgeInsets>,
}

impl SystemEnvironmentPreferences {
    /// Applies this complete preference snapshot. Every unsupported field is
    /// reset to the stable `RuntimeEnvironment::default()` value so a backend
    /// losing support cannot leave old native state cached indefinitely.
    pub fn apply_to(&self, environment: &mut RuntimeEnvironment) {
        let defaults = RuntimeEnvironment::default();
        environment.locales = self.locales.clone().unwrap_or(defaults.locales);
        environment.text_scale = self.text_scale.unwrap_or(defaults.text_scale);
        environment.reduced_motion = self.reduced_motion.unwrap_or(defaults.reduced_motion);
        environment.high_contrast = self.high_contrast.unwrap_or(defaults.high_contrast);
        environment.always_use_24_hour_format = self
            .always_use_24_hour_format
            .unwrap_or(defaults.always_use_24_hour_format);
        environment.accessible_navigation = self
            .accessible_navigation
            .unwrap_or(defaults.accessible_navigation);
        environment.invert_colors = self.invert_colors.unwrap_or(defaults.invert_colors);
        environment.on_off_switch_labels = self
            .on_off_switch_labels
            .unwrap_or(defaults.on_off_switch_labels);
        environment.bold_text = self.bold_text.unwrap_or(defaults.bold_text);
        environment.input = self.input.unwrap_or(defaults.input);
        environment.safe_insets = self.safe_insets.unwrap_or(defaults.safe_insets);
        environment.view_padding = self.view_padding.unwrap_or(defaults.view_padding);
        environment.view_insets = self.view_insets.unwrap_or(defaults.view_insets);
    }
}

/// Snapshot source used by platform adapters and deterministic tests.
pub trait SystemEnvironmentProvider {
    fn snapshot(&self) -> SystemEnvironmentPreferences;
}

/// Thread-safe deterministic provider for headless hosts and tests.
#[derive(Clone, Default)]
pub struct MemorySystemEnvironmentProvider {
    snapshot: Arc<RwLock<SystemEnvironmentPreferences>>,
}

impl MemorySystemEnvironmentProvider {
    #[must_use]
    pub fn new(snapshot: SystemEnvironmentPreferences) -> Self {
        Self {
            snapshot: Arc::new(RwLock::new(snapshot)),
        }
    }

    /// Atomically replaces the complete preference snapshot and returns
    /// whether any semantic value changed.
    pub fn replace(&self, snapshot: SystemEnvironmentPreferences) -> bool {
        let mut current = self.snapshot.write().expect("environment provider lock");
        if *current == snapshot {
            return false;
        }
        *current = snapshot;
        true
    }
}

impl SystemEnvironmentProvider for MemorySystemEnvironmentProvider {
    fn snapshot(&self) -> SystemEnvironmentPreferences {
        self.snapshot
            .read()
            .expect("environment provider lock")
            .clone()
    }
}

/// Parses OS-provided BCP-47 locale strings through ICU4X, preserving order
/// while dropping malformed and duplicate canonical locales.
#[must_use]
pub fn canonicalize_system_locales(
    locales: impl IntoIterator<Item = impl AsRef<str>>,
) -> Vec<Locale> {
    let mut result = Vec::new();
    for value in locales {
        let Ok(locale) = value.as_ref().parse::<Locale>() else {
            continue;
        };
        if !result.contains(&locale) {
            result.push(locale);
        }
    }
    result
}