Skip to main content

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    TreeId, 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            tree_id: TreeId::ROOT,
91            focus: accesskit_window_id,
92        }
93    }
94}
95
96struct WinitActivationHandler(Arc<Mutex<AccessKitState>>);
97
98impl ActivationHandler for WinitActivationHandler {
99    fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
100        Some(self.0.lock().unwrap().build_initial_tree())
101    }
102}
103
104impl WinitActivationHandler {
105    pub fn new(state: Arc<Mutex<AccessKitState>>) -> Self {
106        Self(state)
107    }
108}
109
110#[derive(Clone, Default)]
111struct WinitActionHandler(Arc<Mutex<WinitActionRequestHandler>>);
112
113impl ActionHandler for WinitActionHandler {
114    fn do_action(&mut self, request: ActionRequest) {
115        let mut requests = self.0.lock().unwrap();
116        requests.push_back(request);
117    }
118}
119
120impl WinitActionHandler {
121    pub fn new(handler: Arc<Mutex<WinitActionRequestHandler>>) -> Self {
122        Self(handler)
123    }
124}
125
126struct WinitDeactivationHandler;
127
128impl DeactivationHandler for WinitDeactivationHandler {
129    fn deactivate_accessibility(&mut self) {}
130}
131
132/// Prepares accessibility for a winit window.
133pub(crate) fn prepare_accessibility_for_window(
134    event_loop: &ActiveEventLoop,
135    winit_window: &winit::window::Window,
136    entity: Entity,
137    name: String,
138    accessibility_requested: AccessibilityRequested,
139    adapters: &mut AccessKitAdapters,
140    handlers: &mut WinitActionRequestHandlers,
141) {
142    let state = AccessKitState::new(name, entity, accessibility_requested);
143    let activation_handler = WinitActivationHandler::new(Arc::clone(&state));
144
145    let action_request_handler = WinitActionRequestHandler::new();
146    let action_handler = WinitActionHandler::new(Arc::clone(&action_request_handler));
147    let deactivation_handler = WinitDeactivationHandler;
148
149    let adapter = Adapter::with_direct_handlers(
150        event_loop,
151        winit_window,
152        activation_handler,
153        action_handler,
154        deactivation_handler,
155    );
156
157    adapters.insert(entity, adapter);
158    handlers.insert(entity, action_request_handler);
159}
160
161fn window_closed(
162    mut handlers: ResMut<WinitActionRequestHandlers>,
163    mut window_closed_reader: MessageReader<WindowClosed>,
164    _non_send_marker: NonSendMarker,
165) {
166    ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
167        for WindowClosed { window, .. } in window_closed_reader.read() {
168            adapters.remove(window);
169            handlers.remove(window);
170        }
171    });
172}
173
174fn poll_receivers(
175    handlers: Res<WinitActionRequestHandlers>,
176    mut actions: MessageWriter<ActionRequestWrapper>,
177) {
178    for (_id, handler) in handlers.iter() {
179        let mut handler = handler.lock().unwrap();
180        while let Some(event) = handler.pop_front() {
181            actions.write(ActionRequestWrapper(event));
182        }
183    }
184}
185
186fn should_update_accessibility_nodes(
187    accessibility_requested: Res<AccessibilityRequested>,
188    manage_accessibility_updates: Res<ManageAccessibilityUpdates>,
189) -> bool {
190    accessibility_requested.get() && manage_accessibility_updates.get()
191}
192
193fn update_accessibility_nodes(
194    focus: Option<Res<InputFocus>>,
195    primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
196    nodes: Query<(
197        Entity,
198        &AccessibilityNode,
199        Option<&Children>,
200        Option<&ChildOf>,
201    )>,
202    node_entities: Query<Entity, With<AccessibilityNode>>,
203    _non_send_marker: NonSendMarker,
204) {
205    ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
206        let Ok((primary_window_id, primary_window)) = primary_window.single() else {
207            return;
208        };
209        let Some(adapter) = adapters.get_mut(&primary_window_id) else {
210            return;
211        };
212        let Some(focus) = focus else {
213            return;
214        };
215        if focus.is_changed() || !nodes.is_empty() {
216            // Don't panic if the focused entity does not currently exist
217            // It's probably waiting to be spawned
218            if let Some(focused_entity) = focus.get()
219                && !node_entities.contains(focused_entity)
220            {
221                return;
222            }
223
224            adapter.update_if_active(|| {
225                update_adapter(
226                    nodes,
227                    node_entities,
228                    primary_window,
229                    primary_window_id,
230                    focus,
231                )
232            });
233        }
234    });
235}
236
237fn update_adapter(
238    nodes: Query<(
239        Entity,
240        &AccessibilityNode,
241        Option<&Children>,
242        Option<&ChildOf>,
243    )>,
244    node_entities: Query<Entity, With<AccessibilityNode>>,
245    primary_window: &Window,
246    primary_window_id: Entity,
247    focus: Res<InputFocus>,
248) -> TreeUpdate {
249    let mut to_update = vec![];
250    let mut window_children = vec![];
251    for (entity, node, children, child_of) in &nodes {
252        let mut node = (**node).clone();
253        queue_node_for_update(entity, child_of, &node_entities, &mut window_children);
254        add_children_nodes(children, &node_entities, &mut node);
255        let node_id = NodeId(entity.to_bits());
256        to_update.push((node_id, node));
257    }
258    let mut window_node = Node::new(Role::Window);
259    if primary_window.focused {
260        let title = primary_window.title.clone();
261        window_node.set_label(title.into_boxed_str());
262    }
263    window_node.set_children(window_children);
264    let node_id = NodeId(primary_window_id.to_bits());
265    let window_update = (node_id, window_node);
266    to_update.insert(0, window_update);
267    TreeUpdate {
268        nodes: to_update,
269        tree: None,
270        tree_id: TreeId::ROOT,
271        focus: NodeId(focus.get().unwrap_or(primary_window_id).to_bits()),
272    }
273}
274
275#[inline]
276fn queue_node_for_update(
277    node_entity: Entity,
278    child_of: Option<&ChildOf>,
279    node_entities: &Query<Entity, With<AccessibilityNode>>,
280    window_children: &mut Vec<NodeId>,
281) {
282    let should_push = if let Some(child_of) = child_of {
283        !node_entities.contains(child_of.parent())
284    } else {
285        true
286    };
287    if should_push {
288        window_children.push(NodeId(node_entity.to_bits()));
289    }
290}
291
292#[inline]
293fn add_children_nodes(
294    children: Option<&Children>,
295    node_entities: &Query<Entity, With<AccessibilityNode>>,
296    node: &mut Node,
297) {
298    let Some(children) = children else {
299        return;
300    };
301    for child in children {
302        if node_entities.contains(*child) {
303            node.push_child(NodeId(child.to_bits()));
304        }
305    }
306}
307
308/// Implements winit-specific `AccessKit` functionality.
309pub struct AccessKitPlugin;
310
311impl Plugin for AccessKitPlugin {
312    fn build(&self, app: &mut App) {
313        app.init_resource::<WinitActionRequestHandlers>()
314            .add_message::<ActionRequestWrapper>()
315            .add_systems(
316                PostUpdate,
317                (
318                    poll_receivers,
319                    update_accessibility_nodes
320                        .run_if(should_update_accessibility_nodes)
321                        // This is unlikely to result in real conflicts,
322                        // as FocusChangeEvents only mutates internal state of InputFocus,
323                        // and update_accessibility_nodes only reads from it.
324                        // However, in case this changes in the future, this is a safer choice,
325                        // as accessibility updates could conceivably want to read focus change events.
326                        .after(bevy_input_focus::InputFocusSystems::FocusChangeEvents),
327                    window_closed
328                        .before(poll_receivers)
329                        .before(update_accessibility_nodes),
330                )
331                    .in_set(AccessibilitySystems::Update),
332            );
333    }
334}