Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various accessibility API updates. #9989

Merged
merged 6 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bevy_a11y/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ bevy_app = { path = "../bevy_app", version = "0.12.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0-dev" }

accesskit = "0.11"
accesskit = "0.12"
72 changes: 54 additions & 18 deletions crates/bevy_a11y/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
#![allow(clippy::type_complexity)]
#![forbid(unsafe_code)]

use std::{
num::NonZeroU128,
sync::{atomic::AtomicBool, Arc},
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};

pub use accesskit;
use accesskit::{NodeBuilder, NodeId};
use accesskit::NodeBuilder;
use bevy_app::Plugin;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
prelude::{Component, Entity, Event},
schedule::SystemSet,
system::Resource,
};

Expand All @@ -30,6 +31,46 @@ pub struct ActionRequest(pub accesskit::ActionRequest);
#[derive(Resource, Default, Clone, Debug, Deref, DerefMut)]
pub struct AccessibilityRequested(Arc<AtomicBool>);

impl AccessibilityRequested {
/// Returns `true` if an access technology is active and accessibility tree
/// updates should be sent.
pub fn get(&self) -> bool {
self.load(Ordering::SeqCst)
}

/// Sets whether accessibility updates were requested by an access technology.
pub fn set(&self, value: bool) {
self.store(value, Ordering::SeqCst);
}
}

/// Resource whose value determines whether the accessibility tree is updated
/// via the ECS.
///
/// Set to `false` in cases where an external GUI library is sending
/// accessibility updates instead. Without this, the external library and ECS
/// will generate conflicting updates.
#[derive(Resource, Clone, Debug, Deref, DerefMut)]
pub struct ManageAccessibilityUpdates(bool);

impl Default for ManageAccessibilityUpdates {
fn default() -> Self {
Self(true)
}
}

impl ManageAccessibilityUpdates {
/// Returns `true` if the ECS should update the accessibility tree.
pub fn get(&self) -> bool {
self.0
}

/// Sets whether the ECS should update the accessibility tree.
pub fn set(&mut self, value: bool) {
self.0 = value;
}
}

/// Component to wrap a [`accesskit::Node`], representing this entity to the platform's
/// accessibility API.
///
Expand All @@ -47,29 +88,24 @@ impl From<NodeBuilder> for AccessibilityNode {
}
}

/// Extensions to ease integrating entities with [`AccessKit`](https://accesskit.dev).
pub trait AccessKitEntityExt {
/// Convert an entity to a stable [`NodeId`].
fn to_node_id(&self) -> NodeId;
}

impl AccessKitEntityExt for Entity {
fn to_node_id(&self) -> NodeId {
let id = NonZeroU128::new(self.to_bits() as u128 + 1);
NodeId(id.unwrap())
}
}

/// Resource representing which entity has keyboard focus, if any.
#[derive(Resource, Default, Deref, DerefMut)]
pub struct Focus(Option<Entity>);
pub struct Focus(pub Option<Entity>);

/// Set enum for the systems relating to accessibility
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum AccessibilitySystem {
/// Update the accessibility tree
Update,
}

/// Plugin managing non-GUI aspects of integrating with accessibility APIs.
pub struct AccessibilityPlugin;

impl Plugin for AccessibilityPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.init_resource::<AccessibilityRequested>()
.init_resource::<ManageAccessibilityUpdates>()
.init_resource::<Focus>();
}
}
4 changes: 2 additions & 2 deletions crates/bevy_ui/src/accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,14 @@ fn label_changed(
.collect::<Vec<String>>();
let name = Some(values.join(" ").into_boxed_str());
if let Some(mut accessible) = accessible {
accessible.set_role(Role::LabelText);
accessible.set_role(Role::StaticText);
if let Some(name) = name {
accessible.set_name(name);
} else {
accessible.clear_name();
}
} else {
let mut node = NodeBuilder::new(Role::LabelText);
let mut node = NodeBuilder::new(Role::StaticText);
if let Some(name) = name {
node.set_name(name);
}
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_window/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ serialize = ["serde"]

[dependencies]
# bevy
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.12.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.12.0-dev" }
Expand Down
11 changes: 9 additions & 2 deletions crates/bevy_window/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//! The [`WindowPlugin`] sets up some global window-related parameters and
//! is part of the [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html).

use bevy_a11y::Focus;

mod cursor;
mod event;
mod raw_handle;
Expand Down Expand Up @@ -99,9 +101,14 @@ impl Plugin for WindowPlugin {
.add_event::<WindowThemeChanged>();

if let Some(primary_window) = &self.primary_window {
app.world
let initial_focus = app
.world
.spawn(primary_window.clone())
.insert(PrimaryWindow);
.insert(PrimaryWindow)
.id();
if let Some(mut focus) = app.world.get_resource_mut::<Focus>() {
**focus = Some(initial_focus);
}
}

match self.exit_condition {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.12.0-dev" }

# other
winit = { version = "0.28.7", default-features = false }
accesskit_winit = { version = "0.14", default-features = false }
accesskit_winit = { version = "0.15", default-features = false }
approx = { version = "0.5", default-features = false }
raw-window-handle = "0.5"

Expand Down
89 changes: 34 additions & 55 deletions crates/bevy_winit/src/accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@

use std::{
collections::VecDeque,
sync::{atomic::Ordering, Arc, Mutex},
sync::{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,
accesskit::{
ActionHandler, ActionRequest, NodeBuilder, NodeClassSet, NodeId, Role, TreeUpdate,
},
AccessibilityNode, AccessibilityRequested, AccessibilitySystem, Focus,
};
use bevy_a11y::{ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
prelude::{DetectChanges, Entity, EventReader, EventWriter},
query::With,
schedule::IntoSystemConfigs,
system::{NonSend, NonSendMut, Query, Res, ResMut, Resource},
};
use bevy_hierarchy::{Children, Parent};
use bevy_utils::{default, HashMap};
use bevy_window::{PrimaryWindow, Window, WindowClosed, WindowFocused};
use bevy_utils::HashMap;
use bevy_window::{PrimaryWindow, Window, WindowClosed};

/// Maps window entities to their `AccessKit` [`Adapter`]s.
#[derive(Default, Deref, DerefMut)]
Expand All @@ -35,34 +38,12 @@ pub struct WinitActionHandlers(pub HashMap<Entity, WinitActionHandler>);
pub struct WinitActionHandler(pub Arc<Mutex<VecDeque<ActionRequest>>>);

impl ActionHandler for WinitActionHandler {
fn do_action(&self, request: ActionRequest) {
fn do_action(&mut 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>,
Expand All @@ -86,10 +67,16 @@ fn poll_receivers(
}
}

fn should_update_accessibility_nodes(
accessibility_requested: Res<AccessibilityRequested>,
manage_accessibility_updates: Res<ManageAccessibilityUpdates>,
) -> bool {
accessibility_requested.get() && manage_accessibility_updates.get()
}

fn update_accessibility_nodes(
adapters: NonSend<AccessKitAdapters>,
focus: Res<Focus>,
accessibility_requested: Res<AccessibilityRequested>,
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
nodes: Query<(
Entity,
Expand All @@ -99,46 +86,37 @@ fn update_accessibility_nodes(
)>,
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 focus_id = (*focus).unwrap_or_else(|| primary_window_id).to_bits();
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());
if !node_entities.contains(**parent) {
root_children.push(NodeId(entity.to_bits()));
}
} else {
root_children.push(entity.to_node_id());
root_children.push(NodeId(entity.to_bits()));
}
if let Some(children) = children {
for child in children {
if node_entities.get(*child).is_ok() {
node.push_child(child.to_node_id());
if node_entities.contains(*child) {
node.push_child(NodeId(child.to_bits()));
}
}
}
to_update.push((
entity.to_node_id(),
NodeId(entity.to_bits()),
node.build(&mut NodeClassSet::lock_global()),
));
}
Expand All @@ -148,12 +126,12 @@ fn update_accessibility_nodes(
}
root.set_children(root_children);
let root = root.build(&mut NodeClassSet::lock_global());
let window_update = (primary_window_id.to_node_id(), root);
let window_update = (NodeId(primary_window_id.to_bits()), root);
to_update.insert(0, window_update);
TreeUpdate {
nodes: to_update,
focus: focus_id.map(|v| v.to_node_id()),
..default()
tree: None,
focus: NodeId(focus_id),
}
});
}
Expand All @@ -171,12 +149,13 @@ impl Plugin for AccessibilityPlugin {
.add_event::<ActionRequestWrapper>()
.add_systems(
PostUpdate,
(
handle_window_focus,
window_closed,
poll_receivers,
update_accessibility_nodes,
),
(window_closed, poll_receivers).in_set(AccessibilitySystem::Update),
)
.add_systems(
PostUpdate,
update_accessibility_nodes
.run_if(should_update_accessibility_nodes)
.in_set(AccessibilitySystem::Update),
);
}
}
13 changes: 6 additions & 7 deletions crates/bevy_winit/src/winit_windows.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#![warn(missing_docs)]
use std::sync::atomic::Ordering;

use accesskit_winit::Adapter;
use bevy_a11y::{
accesskit::{NodeBuilder, NodeClassSet, Role, Tree, TreeUpdate},
AccessKitEntityExt, AccessibilityRequested,
accesskit::{NodeBuilder, NodeClassSet, NodeId, Role, Tree, TreeUpdate},
AccessibilityRequested,
};
use bevy_ecs::entity::Entity;

Expand Down Expand Up @@ -151,17 +150,17 @@ impl WinitWindows {
root_builder.set_name(name.into_boxed_str());
let root = root_builder.build(&mut NodeClassSet::lock_global());

let accesskit_window_id = entity.to_node_id();
let accesskit_window_id = NodeId(entity.to_bits());
let handler = WinitActionHandler::default();
let accessibility_requested = (*accessibility_requested).clone();
let accessibility_requested = accessibility_requested.clone();
let adapter = Adapter::with_action_handler(
&winit_window,
move || {
accessibility_requested.store(true, Ordering::SeqCst);
accessibility_requested.set(true);
TreeUpdate {
nodes: vec![(accesskit_window_id, root)],
tree: Some(Tree::new(accesskit_window_id)),
focus: None,
focus: accesskit_window_id,
}
},
Box::new(handler.clone()),
Expand Down
Loading