mirror of
https://github.com/bevyengine/bevy
synced 2024-12-22 19:13:08 +00:00
d5d355ae1f
# Objective Replace instances of ```rust for x in collection.iter{_mut}() { ``` with ```rust for x in &{mut} collection { ``` This also changes CI to no longer suppress this lint. Note that since this lint only shows up when using clippy in pedantic mode, it was probably unnecessary to suppress this lint in the first place.
182 lines
6.4 KiB
Rust
182 lines
6.4 KiB
Rust
//! Helpers for mapping window entities to accessibility types
|
|
|
|
use std::{
|
|
collections::VecDeque,
|
|
sync::{atomic::Ordering, Arc, Mutex},
|
|
};
|
|
|
|
use accesskit_winit::Adapter;
|
|
use bevy_a11y::ActionRequest as ActionRequestWrapper;
|
|
use bevy_a11y::{
|
|
accesskit::{ActionHandler, ActionRequest, NodeBuilder, NodeClassSet, Role, TreeUpdate},
|
|
AccessKitEntityExt, AccessibilityNode, AccessibilityRequested, Focus,
|
|
};
|
|
use bevy_app::{App, Plugin, PostUpdate};
|
|
use bevy_derive::{Deref, DerefMut};
|
|
use bevy_ecs::{
|
|
prelude::{DetectChanges, Entity, EventReader, EventWriter},
|
|
query::With,
|
|
system::{NonSend, NonSendMut, Query, Res, ResMut, Resource},
|
|
};
|
|
use bevy_hierarchy::{Children, Parent};
|
|
use bevy_utils::{default, HashMap};
|
|
use bevy_window::{PrimaryWindow, Window, WindowClosed, WindowFocused};
|
|
|
|
/// Maps window entities to their `AccessKit` [`Adapter`]s.
|
|
#[derive(Default, Deref, DerefMut)]
|
|
pub struct AccessKitAdapters(pub HashMap<Entity, Adapter>);
|
|
|
|
/// Maps window entities to their respective [`WinitActionHandler`]s.
|
|
#[derive(Resource, Default, Deref, DerefMut)]
|
|
pub struct WinitActionHandlers(pub HashMap<Entity, WinitActionHandler>);
|
|
|
|
/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel.
|
|
#[derive(Clone, Default, Deref, DerefMut)]
|
|
pub struct WinitActionHandler(pub Arc<Mutex<VecDeque<ActionRequest>>>);
|
|
|
|
impl ActionHandler for WinitActionHandler {
|
|
fn do_action(&self, request: ActionRequest) {
|
|
let mut requests = self.0.lock().unwrap();
|
|
requests.push_back(request);
|
|
}
|
|
}
|
|
|
|
fn handle_window_focus(
|
|
focus: Res<Focus>,
|
|
adapters: NonSend<AccessKitAdapters>,
|
|
mut focused: EventReader<WindowFocused>,
|
|
) {
|
|
for event in focused.read() {
|
|
if let Some(adapter) = adapters.get(&event.window) {
|
|
adapter.update_if_active(|| {
|
|
let focus_id = (*focus).unwrap_or_else(|| event.window);
|
|
TreeUpdate {
|
|
focus: if event.focused {
|
|
Some(focus_id.to_node_id())
|
|
} else {
|
|
None
|
|
},
|
|
..default()
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn window_closed(
|
|
mut adapters: NonSendMut<AccessKitAdapters>,
|
|
mut receivers: ResMut<WinitActionHandlers>,
|
|
mut events: EventReader<WindowClosed>,
|
|
) {
|
|
for WindowClosed { window, .. } in events.read() {
|
|
adapters.remove(window);
|
|
receivers.remove(window);
|
|
}
|
|
}
|
|
|
|
fn poll_receivers(
|
|
handlers: Res<WinitActionHandlers>,
|
|
mut actions: EventWriter<ActionRequestWrapper>,
|
|
) {
|
|
for (_id, handler) in handlers.iter() {
|
|
let mut handler = handler.lock().unwrap();
|
|
while let Some(event) = handler.pop_front() {
|
|
actions.send(ActionRequestWrapper(event));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_accessibility_nodes(
|
|
adapters: NonSend<AccessKitAdapters>,
|
|
focus: Res<Focus>,
|
|
accessibility_requested: Res<AccessibilityRequested>,
|
|
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
|
|
nodes: Query<(
|
|
Entity,
|
|
&AccessibilityNode,
|
|
Option<&Children>,
|
|
Option<&Parent>,
|
|
)>,
|
|
node_entities: Query<Entity, With<AccessibilityNode>>,
|
|
) {
|
|
if !accessibility_requested.load(Ordering::SeqCst) {
|
|
return;
|
|
}
|
|
if let Ok((primary_window_id, primary_window)) = primary_window.get_single() {
|
|
if let Some(adapter) = adapters.get(&primary_window_id) {
|
|
let should_run = focus.is_changed() || !nodes.is_empty();
|
|
if should_run {
|
|
adapter.update_if_active(|| {
|
|
let mut to_update = vec![];
|
|
let mut has_focus = false;
|
|
let mut name = None;
|
|
if primary_window.focused {
|
|
has_focus = true;
|
|
let title = primary_window.title.clone();
|
|
name = Some(title.into_boxed_str());
|
|
}
|
|
let focus_id = if has_focus {
|
|
(*focus).or_else(|| Some(primary_window_id))
|
|
} else {
|
|
None
|
|
};
|
|
let mut root_children = vec![];
|
|
for (entity, node, children, parent) in &nodes {
|
|
let mut node = (**node).clone();
|
|
if let Some(parent) = parent {
|
|
if node_entities.get(**parent).is_err() {
|
|
root_children.push(entity.to_node_id());
|
|
}
|
|
} else {
|
|
root_children.push(entity.to_node_id());
|
|
}
|
|
if let Some(children) = children {
|
|
for child in children {
|
|
if node_entities.get(*child).is_ok() {
|
|
node.push_child(child.to_node_id());
|
|
}
|
|
}
|
|
}
|
|
to_update.push((
|
|
entity.to_node_id(),
|
|
node.build(&mut NodeClassSet::lock_global()),
|
|
));
|
|
}
|
|
let mut root = NodeBuilder::new(Role::Window);
|
|
if let Some(name) = name {
|
|
root.set_name(name);
|
|
}
|
|
root.set_children(root_children);
|
|
let root = root.build(&mut NodeClassSet::lock_global());
|
|
let window_update = (primary_window_id.to_node_id(), root);
|
|
to_update.insert(0, window_update);
|
|
TreeUpdate {
|
|
nodes: to_update,
|
|
focus: focus_id.map(|v| v.to_node_id()),
|
|
..default()
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Implements winit-specific `AccessKit` functionality.
|
|
pub struct AccessibilityPlugin;
|
|
|
|
impl Plugin for AccessibilityPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.init_non_send_resource::<AccessKitAdapters>()
|
|
.init_resource::<WinitActionHandlers>()
|
|
.add_event::<ActionRequestWrapper>()
|
|
.add_systems(
|
|
PostUpdate,
|
|
(
|
|
handle_window_focus,
|
|
window_closed,
|
|
poll_receivers,
|
|
update_accessibility_nodes,
|
|
),
|
|
);
|
|
}
|
|
}
|