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}