cranpose_services/
accessibility_state.rs1use std::cell::{Cell, RefCell};
6
7use cranpose_core::{CompositionLocal, CompositionLocalProvider, compositionLocalOf};
8use cranpose_macros::composable;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
12pub struct AccessibilityState {
13 pub screen_reader_on: bool,
17}
18
19thread_local! {
20 static PLATFORM_ACCESSIBILITY_STATE: Cell<AccessibilityState> =
21 const { Cell::new(AccessibilityState { screen_reader_on: false }) };
22}
23
24pub fn set_platform_accessibility_state(state: AccessibilityState) -> bool {
28 PLATFORM_ACCESSIBILITY_STATE.with(|cell| {
29 let changed = cell.get() != state;
30 cell.set(state);
31 changed
32 })
33}
34
35pub fn platform_accessibility_state() -> AccessibilityState {
37 PLATFORM_ACCESSIBILITY_STATE.with(|cell| cell.get())
38}
39
40pub fn local_accessibility_state() -> CompositionLocal<AccessibilityState> {
43 thread_local! {
44 static LOCAL: RefCell<Option<CompositionLocal<AccessibilityState>>> =
45 const { RefCell::new(None) };
46 }
47
48 LOCAL.with(|cell| {
49 let mut local = cell.borrow_mut();
50 local
51 .get_or_insert_with(|| compositionLocalOf(platform_accessibility_state))
52 .clone()
53 })
54}
55
56#[allow(non_snake_case)]
59#[composable]
60pub fn ProvideAccessibilityState(state: AccessibilityState, content: impl FnOnce()) {
61 let local = local_accessibility_state();
62 CompositionLocalProvider(vec![local.provides(state)], move || {
63 content();
64 });
65}
66
67#[cfg(test)]
68mod tests {
69 use std::{cell::RefCell, rc::Rc};
70
71 use super::*;
72 use crate::run_test_composition;
73
74 const READER_ON: AccessibilityState = AccessibilityState {
75 screen_reader_on: true,
76 };
77
78 #[test]
79 fn the_platform_state_reports_a_change_once() {
80 set_platform_accessibility_state(AccessibilityState::default());
81 assert!(set_platform_accessibility_state(READER_ON));
82 assert!(!set_platform_accessibility_state(READER_ON));
83 assert_eq!(platform_accessibility_state(), READER_ON);
84 assert!(set_platform_accessibility_state(
85 AccessibilityState::default()
86 ));
87 }
88
89 #[test]
90 fn a_composable_reads_what_the_platform_reported() {
91 set_platform_accessibility_state(READER_ON);
92 let captured = Rc::new(RefCell::new(None));
93 {
94 let captured = Rc::clone(&captured);
95 run_test_composition(move || {
96 *captured.borrow_mut() = Some(local_accessibility_state().current());
97 });
98 }
99 set_platform_accessibility_state(AccessibilityState::default());
100
101 assert_eq!(*captured.borrow(), Some(READER_ON));
102 }
103
104 #[test]
105 fn a_provider_overrides_the_platform_state() {
106 set_platform_accessibility_state(AccessibilityState::default());
107 let local = local_accessibility_state();
108 let captured = Rc::new(RefCell::new(None));
109 {
110 let captured = Rc::clone(&captured);
111 let local = local.clone();
112 run_test_composition(move || {
113 let captured = Rc::clone(&captured);
114 let local = local.clone();
115 ProvideAccessibilityState(READER_ON, move || {
116 *captured.borrow_mut() = Some(local.current());
117 });
118 });
119 }
120
121 assert_eq!(*captured.borrow(), Some(READER_ON));
122 }
123}