bevy_winit/
accessibility.rs

1//! Helpers for mapping window entities to accessibility types
2
3use alloc::{collections::VecDeque, sync::Arc};
4use bevy_input_focus::InputFocus;
5use core::cell::RefCell;
6use std::sync::Mutex;
7use winit::event_loop::ActiveEventLoop;
8
9use accesskit::{
10    ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree,
11    TreeUpdate,
12};
13use accesskit_winit::Adapter;
14use bevy_a11y::{
15    AccessibilityNode, AccessibilityRequested, AccessibilitySystems,
16    ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates,
17};
18use bevy_app::{App, Plugin, PostUpdate};
19use bevy_derive::{Deref, DerefMut};
20use bevy_ecs::{entity::EntityHashMap, prelude::*, system::NonSendMarker};
21use bevy_window::{PrimaryWindow, Window, WindowClosed};
22
23thread_local! {
24    /// Temporary storage of access kit adapter data to replace usage of `!Send` resources. This will be replaced with proper
25    /// storage of `!Send` data after issue #17667 is complete.
26    pub static ACCESS_KIT_ADAPTERS: RefCell<AccessKitAdapters> = const { RefCell::new(AccessKitAdapters::new()) };
27}
28
29/// Maps window entities to their `AccessKit` [`Adapter`]s.
30#[derive(Default, Deref, DerefMut)]
31pub struct AccessKitAdapters(pub EntityHashMap<Adapter>);
32
33impl AccessKitAdapters {
34    /// Creates a new empty `AccessKitAdapters`.
35    pub const fn new() -> Self {
36        Self(EntityHashMap::new())
37    }
38}
39
40/// Maps window entities to their respective [`ActionRequest`]s.
41#[derive(Resource, Default, Deref, DerefMut)]
42pub struct WinitActionRequestHandlers(pub EntityHashMap<Arc<Mutex<WinitActionRequestHandler>>>);
43
44/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel.
45#[derive(Clone, Default, Deref, DerefMut)]
46pub struct WinitActionRequestHandler(pub VecDeque<ActionRequest>);
47
48impl WinitActionRequestHandler {
49    fn new() -> Arc<Mutex<Self>> {
50        Arc::new(Mutex::new(Self(VecDeque::new())))
51    }
52}
53
54struct AccessKitState {
55    name: String,
56    entity: Entity,
57    requested: AccessibilityRequested,
58}
59
60impl AccessKitState {
61    fn new(
62        name: impl Into<String>,
63        entity: Entity,
64        requested: AccessibilityRequested,
65    ) -> Arc<Mutex<Self>> {
66        let name = name.into();
67
68        Arc::new(Mutex::new(Self {
69            name,
70            entity,
71            requested,
72        }))
73    }
74
75    fn build_root(&mut self) -> Node {
76        let mut node = Node::new(Role::Window);
77        node.set_label(self.name.clone());
78        node
79    }
80
81    fn build_initial_tree(&mut self) -> TreeUpdate {
82        let root = self.build_root();
83        let accesskit_window_id = NodeId(self.entity.to_bits());
84        let tree = Tree::new(accesskit_window_id);
85        self.requested.set(true);
86
87        TreeUpdate {
88            nodes: vec![(accesskit_window_id, root)],
89            tree: Some(tree),
90            focus: accesskit_window_id,
91        }
92    }
93}
94
95struct WinitActivationHandler(Arc<Mutex<AccessKitState>>);
96
97impl ActivationHandler for WinitActivationHandler {
98    fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
99        Some(self.0.lock().unwrap().build_initial_tree())
100    }
101}
102
103impl WinitActivationHandler {
104    pub fn new(state: Arc<Mutex<AccessKitState>>) -> Self {
105        Self(state)
106    }
107}
108
109#[derive(Clone, Default)]
110struct WinitActionHandler(Arc<Mutex<WinitActionRequestHandler>>);
111
112impl ActionHandler for WinitActionHandler {
113    fn do_action(&mut self, request: ActionRequest) {
114        let mut requests = self.0.lock().unwrap();
115        requests.push_back(request);
116    }
117}
118
119impl WinitActionHandler {
120    pub fn new(handler: Arc<Mutex<WinitActionRequestHandler>>) -> Self {
121        Self(handler)
122    }
123}
124
125struct WinitDeactivationHandler;
126
127impl DeactivationHandler for WinitDeactivationHandler {
128    fn deactivate_accessibility(&mut self) {}
129}
130
131/// Prepares accessibility for a winit window.
132pub(crate) fn prepare_accessibility_for_window(
133    event_loop: &ActiveEventLoop,
134    winit_window: &winit::window::Window,
135    entity: Entity,
136    name: String,
137    accessibility_requested: AccessibilityRequested,
138    adapters: &mut AccessKitAdapters,
139    handlers: &mut WinitActionRequestHandlers,
140) {
141    let state = AccessKitState::new(name, entity, accessibility_requested);
142    let activation_handler = WinitActivationHandler::new(Arc::clone(&state));
143
144    let action_request_handler = WinitActionRequestHandler::new();
145    let action_handler = WinitActionHandler::new(Arc::clone(&action_request_handler));
146    let deactivation_handler = WinitDeactivationHandler;
147
148    let adapter = Adapter::with_direct_handlers(
149        event_loop,
150        winit_window,
151        activation_handler,
152        action_handler,
153        deactivation_handler,
154    );
155
156    adapters.insert(entity, adapter);
157    handlers.insert(entity, action_request_handler);
158}
159
160fn window_closed(
161    mut handlers: ResMut<WinitActionRequestHandlers>,
162    mut window_closed_reader: MessageReader<WindowClosed>,
163    _non_send_marker: NonSendMarker,
164) {
165    ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
166        for WindowClosed { window, .. } in window_closed_reader.read() {
167            adapters.remove(window);
168            handlers.remove(window);
169        }
170    });
171}
172
173fn poll_receivers(
174    handlers: Res<WinitActionRequestHandlers>,
175    mut actions: MessageWriter<ActionRequestWrapper>,
176) {
177    for (_id, handler) in handlers.iter() {
178        let mut handler = handler.lock().unwrap();
179        while let Some(event) = handler.pop_front() {
180            actions.write(ActionRequestWrapper(event));
181        }
182    }
183}
184
185fn should_update_accessibility_nodes(
186    accessibility_requested: Res<AccessibilityRequested>,
187    manage_accessibility_updates: Res<ManageAccessibilityUpdates>,
188) -> bool {
189    accessibility_requested.get() && manage_accessibility_updates.get()
190}
191
192fn update_accessibility_nodes(
193    focus: Option<Res<InputFocus>>,
194    primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
195    nodes: Query<(
196        Entity,
197        &AccessibilityNode,
198        Option<&Children>,
199        Option<&ChildOf>,
200    )>,
201    node_entities: Query<Entity, With<AccessibilityNode>>,
202    _non_send_marker: NonSendMarker,
203) {
204    ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
205        let Ok((primary_window_id, primary_window)) = primary_window.single() else {
206            return;
207        };
208        let Some(adapter) = adapters.get_mut(&primary_window_id) else {
209            return;
210        };
211        let Some(focus) = focus else {
212            return;
213        };
214        if focus.is_changed() || !nodes.is_empty() {
215            // Don't panic if the focused entity does not currently exist
216            // It's probably waiting to be spawned
217            if let Some(focused_entity) = focus.0
218                && !node_entities.contains(focused_entity)
219            {
220                return;
221            }
222
223            adapter.update_if_active(|| {
224                update_adapter(
225                    nodes,
226                    node_entities,
227                    primary_window,
228                    primary_window_id,
229                    focus,
230                )
231            });
232        }
233    });
234}
235
236fn update_adapter(
237    nodes: Query<(
238        Entity,
239        &AccessibilityNode,
240        Option<&Children>,
241        Option<&ChildOf>,
242    )>,
243    node_entities: Query<Entity, With<AccessibilityNode>>,
244    primary_window: &Window,
245    primary_window_id: Entity,
246    focus: Res<InputFocus>,
247) -> TreeUpdate {
248    let mut to_update = vec![];
249    let mut window_children = vec![];
250    for (entity, node, children, child_of) in &nodes {
251        let mut node = (**node).clone();
252        queue_node_for_update(entity, child_of, &node_entities, &mut window_children);
253        add_children_nodes(children, &node_entities, &mut node);
254        let node_id = NodeId(entity.to_bits());
255        to_update.push((node_id, node));
256    }
257    let mut window_node = Node::new(Role::Window);
258    if primary_window.focused {
259        let title = primary_window.title.clone();
260        window_node.set_label(title.into_boxed_str());
261    }
262    window_node.set_children(window_children);
263    let node_id = NodeId(primary_window_id.to_bits());
264    let window_update = (node_id, window_node);
265    to_update.insert(0, window_update);
266    TreeUpdate {
267        nodes: to_update,
268        tree: None,
269        focus: NodeId(focus.0.unwrap_or(primary_window_id).to_bits()),
270    }
271}
272
273#[inline]
274fn queue_node_for_update(
275    node_entity: Entity,
276    child_of: Option<&ChildOf>,
277    node_entities: &Query<Entity, With<AccessibilityNode>>,
278    window_children: &mut Vec<NodeId>,
279) {
280    let should_push = if let Some(child_of) = child_of {
281        !node_entities.contains(child_of.parent())
282    } else {
283        true
284    };
285    if should_push {
286        window_children.push(NodeId(node_entity.to_bits()));
287    }
288}
289
290#[inline]
291fn add_children_nodes(
292    children: Option<&Children>,
293    node_entities: &Query<Entity, With<AccessibilityNode>>,
294    node: &mut Node,
295) {
296    let Some(children) = children else {
297        return;
298    };
299    for child in children {
300        if node_entities.contains(*child) {
301            node.push_child(NodeId(child.to_bits()));
302        }
303    }
304}
305
306/// Implements winit-specific `AccessKit` functionality.
307pub struct AccessKitPlugin;
308
309impl Plugin for AccessKitPlugin {
310    fn build(&self, app: &mut App) {
311        app.init_resource::<WinitActionRequestHandlers>()
312            .add_message::<ActionRequestWrapper>()
313            .add_systems(
314                PostUpdate,
315                (
316                    poll_receivers,
317                    update_accessibility_nodes.run_if(should_update_accessibility_nodes),
318                    window_closed
319                        .before(poll_receivers)
320                        .before(update_accessibility_nodes),
321                )
322                    .in_set(AccessibilitySystems::Update),
323            );
324    }
325}