//! Helpers for mapping window entities to accessibility types use alloc::{collections::VecDeque, sync::Arc}; use bevy_input_focus::InputFocus; use std::sync::Mutex; use accesskit::{ ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree, TreeUpdate, }; use accesskit_winit::Adapter; use bevy_a11y::{ AccessibilityNode, AccessibilityRequested, AccessibilitySystem, ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates, }; use bevy_app::{App, Plugin, PostUpdate}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, entity::EntityHashMap, prelude::{Entity, EventReader, EventWriter}, query::With, schedule::IntoSystemConfigs, system::{NonSendMut, Query, Res, ResMut, Resource}, }; use bevy_hierarchy::{Children, Parent}; use bevy_window::{PrimaryWindow, Window, WindowClosed}; /// Maps window entities to their `AccessKit` [`Adapter`]s. #[derive(Default, Deref, DerefMut)] pub struct AccessKitAdapters(pub EntityHashMap); /// Maps window entities to their respective [`ActionRequest`]s. #[derive(Resource, Default, Deref, DerefMut)] pub struct WinitActionRequestHandlers(pub EntityHashMap>>); /// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel. #[derive(Clone, Default, Deref, DerefMut)] pub struct WinitActionRequestHandler(pub VecDeque); impl WinitActionRequestHandler { fn new() -> Arc> { Arc::new(Mutex::new(Self(VecDeque::new()))) } } struct AccessKitState { name: String, entity: Entity, requested: AccessibilityRequested, } impl AccessKitState { fn new( name: impl Into, entity: Entity, requested: AccessibilityRequested, ) -> Arc> { let name = name.into(); Arc::new(Mutex::new(Self { name, entity, requested, })) } fn build_root(&mut self) -> Node { let mut node = Node::new(Role::Window); node.set_label(self.name.clone()); node } fn build_initial_tree(&mut self) -> TreeUpdate { let root = self.build_root(); let accesskit_window_id = NodeId(self.entity.to_bits()); let mut tree = Tree::new(accesskit_window_id); tree.app_name = Some(self.name.clone()); self.requested.set(true); TreeUpdate { nodes: vec![(accesskit_window_id, root)], tree: Some(tree), focus: accesskit_window_id, } } } struct WinitActivationHandler(Arc>); impl ActivationHandler for WinitActivationHandler { fn request_initial_tree(&mut self) -> Option { Some(self.0.lock().unwrap().build_initial_tree()) } } impl WinitActivationHandler { pub fn new(state: Arc>) -> Self { Self(state) } } #[derive(Clone, Default)] struct WinitActionHandler(Arc>); impl ActionHandler for WinitActionHandler { fn do_action(&mut self, request: ActionRequest) { let mut requests = self.0.lock().unwrap(); requests.push_back(request); } } impl WinitActionHandler { pub fn new(handler: Arc>) -> Self { Self(handler) } } struct WinitDeactivationHandler; impl DeactivationHandler for WinitDeactivationHandler { fn deactivate_accessibility(&mut self) {} } /// Prepares accessibility for a winit window. pub(crate) fn prepare_accessibility_for_window( winit_window: &winit::window::Window, entity: Entity, name: String, accessibility_requested: AccessibilityRequested, adapters: &mut AccessKitAdapters, handlers: &mut WinitActionRequestHandlers, ) { let state = AccessKitState::new(name, entity, accessibility_requested); let activation_handler = WinitActivationHandler::new(Arc::clone(&state)); let action_request_handler = WinitActionRequestHandler::new(); let action_handler = WinitActionHandler::new(Arc::clone(&action_request_handler)); let deactivation_handler = WinitDeactivationHandler; let adapter = Adapter::with_direct_handlers( winit_window, activation_handler, action_handler, deactivation_handler, ); adapters.insert(entity, adapter); handlers.insert(entity, action_request_handler); } fn window_closed( mut adapters: NonSendMut, mut handlers: ResMut, mut events: EventReader, ) { for WindowClosed { window, .. } in events.read() { adapters.remove(window); handlers.remove(window); } } fn poll_receivers( handlers: Res, mut actions: EventWriter, ) { for (_id, handler) in handlers.iter() { let mut handler = handler.lock().unwrap(); while let Some(event) = handler.pop_front() { actions.send(ActionRequestWrapper(event)); } } } fn should_update_accessibility_nodes( accessibility_requested: Res, manage_accessibility_updates: Res, ) -> bool { accessibility_requested.get() && manage_accessibility_updates.get() } fn update_accessibility_nodes( mut adapters: NonSendMut, focus: Res, primary_window: Query<(Entity, &Window), With>, nodes: Query<( Entity, &AccessibilityNode, Option<&Children>, Option<&Parent>, )>, node_entities: Query>, ) { let Ok((primary_window_id, primary_window)) = primary_window.get_single() else { return; }; let Some(adapter) = adapters.get_mut(&primary_window_id) else { return; }; if focus.is_changed() || !nodes.is_empty() { adapter.update_if_active(|| { update_adapter( nodes, node_entities, primary_window, primary_window_id, focus, ) }); } } fn update_adapter( nodes: Query<( Entity, &AccessibilityNode, Option<&Children>, Option<&Parent>, )>, node_entities: Query>, primary_window: &Window, primary_window_id: Entity, focus: Res, ) -> TreeUpdate { let mut to_update = vec![]; let mut window_children = vec![]; for (entity, node, children, parent) in &nodes { let mut node = (**node).clone(); queue_node_for_update(entity, parent, &node_entities, &mut window_children); add_children_nodes(children, &node_entities, &mut node); let node_id = NodeId(entity.to_bits()); to_update.push((node_id, node)); } let mut window_node = Node::new(Role::Window); if primary_window.focused { let title = primary_window.title.clone(); window_node.set_label(title.into_boxed_str()); } window_node.set_children(window_children); let node_id = NodeId(primary_window_id.to_bits()); let window_update = (node_id, window_node); to_update.insert(0, window_update); TreeUpdate { nodes: to_update, tree: None, focus: NodeId(focus.0.unwrap_or(primary_window_id).to_bits()), } } #[inline] fn queue_node_for_update( node_entity: Entity, parent: Option<&Parent>, node_entities: &Query>, window_children: &mut Vec, ) { let should_push = if let Some(parent) = parent { !node_entities.contains(parent.get()) } else { true }; if should_push { window_children.push(NodeId(node_entity.to_bits())); } } #[inline] fn add_children_nodes( children: Option<&Children>, node_entities: &Query>, node: &mut Node, ) { let Some(children) = children else { return; }; for child in children { if node_entities.contains(*child) { node.push_child(NodeId(child.to_bits())); } } } /// Implements winit-specific `AccessKit` functionality. pub struct AccessKitPlugin; impl Plugin for AccessKitPlugin { fn build(&self, app: &mut App) { app.init_non_send_resource::() .init_resource::() .add_event::() .add_systems( PostUpdate, ( poll_receivers, update_accessibility_nodes.run_if(should_update_accessibility_nodes), window_closed .before(poll_receivers) .before(update_accessibility_nodes), ) .in_set(AccessibilitySystem::Update), ); } }