Skip to main content

cranpose_services/
accessibility_state.rs

1//! What the platform says about the assistive technology in use, for an app
2//! that speaks guidance, slows an automatic step down or drops a gesture-only
3//! path while a screen reader is on.
4
5use cranpose_core::{CompositionLocal, CompositionLocalProvider, compositionLocalOf};
6use cranpose_macros::composable;
7
8/// What the platform reports about the assistive technology in use.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
10pub struct AccessibilityState {
11    /// Whether a screen reader is on: VoiceOver on iOS, a service such as
12    /// TalkBack on Android, or a reader that connected to the app through
13    /// accesskit on the desktop. The web has no such signal and says no.
14    pub screen_reader_on: bool,
15}
16
17/// Installs what the platform reports. A backend calls this on every check
18/// and forces a root render when the answer is true, so composition reads
19/// the new state.
20pub fn set_platform_accessibility_state(state: AccessibilityState) -> bool {
21    crate::thread_state::with(|thread| thread.accessibility_state.replace(state) != state)
22}
23
24/// What the platform last reported.
25pub fn platform_accessibility_state() -> AccessibilityState {
26    crate::thread_state::with(|state| state.accessibility_state.get())
27}
28
29/// The state a composable reads: what the platform reported, unless a
30/// [`ProvideAccessibilityState`] above it says otherwise.
31pub fn local_accessibility_state() -> CompositionLocal<AccessibilityState> {
32    crate::composition_locals::cached_local(
33        |locals| &locals.accessibility_state,
34        || compositionLocalOf(platform_accessibility_state),
35    )
36}
37
38/// Gives the content below it a fixed state, for a preview or a test that
39/// wants to see the app as a screen reader user does.
40#[composable]
41pub fn ProvideAccessibilityState(state: AccessibilityState, content: impl FnOnce()) {
42    let local = local_accessibility_state();
43    CompositionLocalProvider([local.provides(state)], move || {
44        content();
45    });
46}
47
48#[cfg(test)]
49#[path = "tests/accessibility_state_tests.rs"]
50mod tests;