incular-config 0.1.1

Renderer-independent configuration values for Incular
Documentation
//! Stable, logical application-environment values.
//!
//! These types deliberately contain no window handles or native event-loop
//! details. `incular-runtime` owns their current value and invalidation; native
//! adapters only normalize platform data into this snapshot.

use crate::{ApplicationDefaults, EdgeInsets, LocaleResolver, TextDirection};
use icu_locale::Locale;
use incular_core::{Rect, Size};

/// The platform colour preference known to the application.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Brightness {
    #[default]
    Light,
    Dark,
}

/// Flutter's `TargetPlatform`: the platform whose conventions adaptive
/// widgets follow.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TargetPlatform {
    Android,
    Fuchsia,
    Ios,
    Linux,
    MacOs,
    Windows,
}

impl TargetPlatform {
    /// Flutter's `defaultTargetPlatform` for the operating system this build
    /// targets. Platforms Flutter does not name fall back to Android, as
    /// Flutter's own default does.
    #[must_use]
    pub const fn host() -> Self {
        if cfg!(target_os = "ios") {
            Self::Ios
        } else if cfg!(target_os = "macos") {
            Self::MacOs
        } else if cfg!(target_os = "windows") {
            Self::Windows
        } else if cfg!(target_os = "linux") {
            Self::Linux
        } else if cfg!(target_os = "fuchsia") {
            Self::Fuchsia
        } else {
            Self::Android
        }
    }

    /// Whether this is macOS or iOS.
    #[must_use]
    pub const fn is_apple(self) -> bool {
        matches!(self, Self::Ios | Self::MacOs)
    }

    /// Whether this is a desktop platform (Linux, macOS or Windows).
    #[must_use]
    pub const fn is_desktop(self) -> bool {
        matches!(self, Self::Linux | Self::MacOs | Self::Windows)
    }
}

impl Default for TargetPlatform {
    fn default() -> Self {
        Self::host()
    }
}

/// Flutter's `NavigationMode`: how a user moves between controls.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum NavigationMode {
    /// Pointer or touch selects any control, and disabled controls are
    /// skipped by directional navigation.
    #[default]
    Traditional,
    /// Directional (D-pad, remote) navigation; disabled controls stay
    /// focusable so the user can learn about them.
    Directional,
}

/// The normalized sensitivity requested for a window's captured content.
///
/// The values intentionally mirror Flutter's `ContentSensitivity` policy:
/// `Sensitive` has the highest priority, followed by `AutoSensitive`, then
/// `NotSensitive`.  Platform crates consume this value without needing to
/// know anything about widget identity or the retained tree.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ContentSensitivity {
    #[default]
    NotSensitive,
    AutoSensitive,
    Sensitive,
}

impl ContentSensitivity {
    /// Returns the more protective of two requested sensitivities.
    #[must_use]
    pub const fn max(self, other: Self) -> Self {
        if self as u8 >= other as u8 {
            self
        } else {
            other
        }
    }
}

/// Stable pointer/input capabilities reported by a platform adapter.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct InputCapabilities {
    pub mouse: bool,
    pub touch: bool,
    pub keyboard: bool,
    pub stylus: bool,
    /// A trackpad has produced a native device/gesture event in this process or
    /// was reported by an authoritative platform capability query.
    pub trackpad: bool,
}

/// Flutter's `DisplayFeatureType`: what a [`DisplayFeature`] is.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum DisplayFeatureType {
    #[default]
    Unknown,
    /// A seamless fold in a flexible display.
    Fold,
    /// A physical separation between two display panels.
    Hinge,
    /// A non-functional area such as a camera notch.
    Cutout,
}

/// Flutter's `DisplayFeatureState`: the posture of a fold or hinge.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum DisplayFeatureState {
    #[default]
    Unknown,
    /// The device is flat.
    PostureFlat,
    /// The device is partly folded, like a book or a laptop.
    PostureHalfOpened,
}

/// Flutter's `DisplayFeature`: an area of the view, in logical pixels, that
/// content should avoid, such as a hinge between two screens. A fold
/// reported with zero width still separates content while half opened.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DisplayFeature {
    pub bounds: Rect,
    pub kind: DisplayFeatureType,
    pub state: DisplayFeatureState,
}

/// A normalized, logical window/application environment snapshot.
///
/// Insets and viewport dimensions are logical pixels. Physical dimensions are
/// retained only for resource/cache decisions; layout must use `viewport`.
#[derive(Clone, Debug, PartialEq)]
pub struct RuntimeEnvironment {
    pub viewport: Size,
    pub physical_width: u32,
    pub physical_height: u32,
    pub scale_factor: f64,
    pub brightness: Brightness,
    pub text_scale: f32,
    /// Currently usable display margin: system obstructions (notch, status
    /// bar, home indicator) minus whatever is presently covered.
    ///
    /// The shell reduces these edges while transient UI covers them, so a
    /// shown keyboard drives an obscured bottom edge toward zero. Widgets
    /// must read the current snapshot on every layout; nothing here is
    /// reconstructed from history.
    pub safe_insets: EdgeInsets,
    /// Persistent system-obstruction margin, independent of transient
    /// occlusion. Unlike [`Self::safe_insets`], the shell never reduces
    /// these edges while the keyboard or another transient surface covers
    /// them; they are the floor `SafeArea` keeps with
    /// `maintain_bottom_view_padding`.
    pub view_padding: EdgeInsets,
    /// Currently obscured area, such as the keyboard: transient occlusion
    /// the shell reports while it covers content. Keyboard avoidance (for
    /// example the material Scaffold pad) consumes these edges directly;
    /// they are never a substitute for [`Self::view_padding`].
    pub view_insets: EdgeInsets,
    /// Ordered locale preferences parsed and canonicalized by ICU4X.
    pub locales: Vec<Locale>,
    pub text_direction: TextDirection,
    pub reduced_motion: bool,
    /// Native high-contrast/accessibility preference. This is intentionally
    /// independent of [`Brightness`]; platforms that cannot observe it leave
    /// the documented default (`false`) rather than inferring it from theme.
    pub high_contrast: bool,
    /// Areas along the edges where the system consumes gestures, such as
    /// Android's back gesture (Flutter's `systemGestureInsets`).
    pub system_gesture_insets: EdgeInsets,
    /// Areas of the view that content should avoid, such as hinges and
    /// folds (Flutter's `displayFeatures`). Native adapters that cannot
    /// observe them leave this empty.
    pub display_features: Vec<DisplayFeature>,
    /// Whether times use the 24-hour clock regardless of locale.
    pub always_use_24_hour_format: bool,
    /// Whether a screen reader or similar service is driving the interface.
    pub accessible_navigation: bool,
    /// Whether the platform inverts colours; images should be inverted back.
    pub invert_colors: bool,
    /// Whether switches show on/off labels (iOS).
    pub on_off_switch_labels: bool,
    /// Whether the platform asks for bold text.
    pub bold_text: bool,
    /// Whether the platform supports `SemanticsService.announce`.
    pub supports_announce: bool,
    pub navigation_mode: NavigationMode,
    /// Whether the platform can show its own text-editing context menu.
    pub supports_showing_system_context_menu: bool,
    /// Accessibility overrides of text spacing (Flutter's
    /// `lineHeightScaleFactorOverride`, `letterSpacingOverride`,
    /// `wordSpacingOverride` and `paragraphSpacingOverride`); `None` leaves
    /// the text style's value.
    pub line_height_scale_factor_override: Option<f32>,
    pub letter_spacing_override: Option<f32>,
    pub word_spacing_override: Option<f32>,
    pub paragraph_spacing_override: Option<f32>,
    pub input: InputCapabilities,
    /// Native activation for this window. Logical widget focus is retained by
    /// the runtime separately, so an unfocused window can resume its prior
    /// text-field focus when activated again.
    pub window_focused: bool,
    /// Whether the native compositor reports this window as fully occluded.
    /// Minimized-state scheduling is handled by the window adapter separately;
    /// this flag represents only an actual occlusion signal.
    pub window_occluded: bool,
}

impl Default for RuntimeEnvironment {
    fn default() -> Self {
        let defaults = ApplicationDefaults::DEFAULT;
        Self {
            viewport: Size::ZERO,
            physical_width: 0,
            physical_height: 0,
            scale_factor: defaults.scale_factor,
            brightness: defaults.brightness,
            text_scale: defaults.text_scale,
            safe_insets: EdgeInsets::ZERO,
            view_padding: EdgeInsets::ZERO,
            view_insets: EdgeInsets::ZERO,
            locales: Vec::new(),
            text_direction: defaults.text_direction,
            reduced_motion: defaults.reduced_motion,
            high_contrast: false,
            system_gesture_insets: EdgeInsets::ZERO,
            display_features: Vec::new(),
            always_use_24_hour_format: false,
            accessible_navigation: false,
            invert_colors: false,
            on_off_switch_labels: false,
            bold_text: false,
            supports_announce: false,
            navigation_mode: NavigationMode::Traditional,
            supports_showing_system_context_menu: false,
            line_height_scale_factor_override: None,
            letter_spacing_override: None,
            word_spacing_override: None,
            paragraph_spacing_override: None,
            input: InputCapabilities::default(),
            window_focused: false,
            window_occluded: false,
        }
    }
}

impl RuntimeEnvironment {
    /// Normalizes externally supplied values without inventing platform policy.
    #[must_use]
    pub fn normalized(mut self) -> Self {
        self.scale_factor = if self.scale_factor.is_finite() && self.scale_factor > 0.0 {
            self.scale_factor
        } else {
            1.0
        };
        self.text_scale = if self.text_scale.is_finite() && self.text_scale > 0.0 {
            self.text_scale
        } else {
            1.0
        };
        self.safe_insets = self.safe_insets.normalized();
        self.view_padding = self.view_padding.normalized();
        self.view_insets = self.view_insets.normalized();
        self.system_gesture_insets = self.system_gesture_insets.normalized();
        if let Some(locale) = self.primary_locale() {
            self.text_direction = LocaleResolver::text_direction(locale);
        }
        self
    }

    #[must_use]
    pub fn primary_locale(&self) -> Option<&Locale> {
        self.locales.first()
    }

    /// Resolves application-supported locales from the platform's ordered
    /// preferences using ICU4X parent-locale fallback.
    #[must_use]
    pub fn resolve_locale(&self, supported: &[Locale]) -> Option<Locale> {
        LocaleResolver::resolve(&self.locales, supported)
    }
}