Skip to main content

teksilo_core/
environment.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4use crate::styles::Theme;
5
6/// Layout direction for RTL/LTR support.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum LayoutDirection {
9    #[default]
10    LeftToRight,
11    RightToLeft,
12}
13
14/// Whether an assistive technology is driving the app, as reported by the
15/// platform.
16///
17/// Lives here rather than in `teksilo-platform` because `Environment` is a
18/// `teksilo-core` type and core cannot name platform's
19/// `AccessibilityPreferences`; the platform layer pushes the value in exactly
20/// as it pushes [`Environment::prefers_reduced_motion`].
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
22pub enum ScreenReaderState {
23    /// The platform does not expose the state, or has not been asked yet. The
24    /// default — behaviour is unchanged from before the field existed.
25    #[default]
26    Unknown,
27    /// No assistive technology is attached.
28    Inactive,
29    /// An assistive technology is attached and reading the tree.
30    Active,
31}
32
33/// Whether the platform's touch *exploration* mode is on — VoiceOver on iOS,
34/// TalkBack's "Explore by touch" on Android, Narrator touch mode on Windows.
35///
36/// While it is on, a touch is a *probe*: the first tap announces what is under
37/// the finger and a second tap activates it. Gesture recognition must step
38/// aside for it, which is why this is an environment flag and not a preference
39/// a widget reads case by case.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
41pub enum ExploreByTouch {
42    /// Explore-by-touch is off, or the platform does not report it. The
43    /// default, and today's behaviour.
44    #[default]
45    Off,
46    /// Follow [`ScreenReaderState`]: on when a screen reader is active.
47    Auto,
48    /// Explore-by-touch is on regardless of the screen-reader state.
49    On,
50}
51
52/// Environment data that flows down the widget tree.
53/// Subtrees can override parts of the environment.
54#[derive(Debug, Clone)]
55pub struct Environment {
56    pub theme: Theme,
57    pub layout_direction: LayoutDirection,
58    pub scale_factor: f32,
59    pub prefers_high_contrast: bool,
60    pub prefers_reduced_motion: bool,
61    pub prefers_large_text: bool,
62    /// The OS's stated preference for a touch-first UI (Windows tablet mode,
63    /// a convertible in slate posture), or `None` when the platform does not
64    /// report one.
65    ///
66    /// Nothing writes it and nothing reads it yet: it is the input to a
67    /// density policy the framework does not act on — see
68    /// [`WidgetTree::set_density_policy`](crate::WidgetTree::set_density_policy).
69    pub prefers_touch: Option<bool>,
70    /// Whether an assistive technology is attached. See [`ScreenReaderState`].
71    pub screen_reader: ScreenReaderState,
72    /// Whether the platform's touch-exploration mode is on. See
73    /// [`ExploreByTouch`].
74    pub explore_by_touch: ExploreByTouch,
75}
76
77impl Environment {
78    pub fn new(theme: Theme) -> Self {
79        Self {
80            theme,
81            layout_direction: LayoutDirection::default(),
82            scale_factor: 1.0,
83            prefers_high_contrast: false,
84            prefers_reduced_motion: false,
85            prefers_large_text: false,
86            prefers_touch: None,
87            screen_reader: ScreenReaderState::default(),
88            explore_by_touch: ExploreByTouch::default(),
89        }
90    }
91
92    /// Apply a theme override function, returning a new Environment with the
93    /// modified theme while preserving all other fields.
94    pub fn with_theme_override(&self, f: &dyn Fn(&mut Theme)) -> Self {
95        let mut env = self.clone();
96        f(&mut env.theme);
97        env
98    }
99}
100
101impl Default for Environment {
102    fn default() -> Self {
103        Self::new(crate::presets::intui::light())
104    }
105}
106
107/// A stored theme override closure for a widget node.
108/// When present on a node, its subtree sees a modified theme.
109pub(crate) struct ThemeOverride {
110    pub func: Box<dyn Fn(&mut Theme)>,
111}
112
113impl std::fmt::Debug for ThemeOverride {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        f.write_str("ThemeOverride(..)")
116    }
117}