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