bevy_a11y/lib.rs
1#![forbid(unsafe_code)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc(
4 html_logo_url = "https://bevy.org/assets/icon.png",
5 html_favicon_url = "https://bevy.org/assets/icon.png"
6)]
7#![no_std]
8
9//! Reusable accessibility primitives
10//!
11//! This crate provides accessibility integration for the engine. It exposes the
12//! [`AccessibilityPlugin`]. This plugin integrates `AccessKit`, a Rust crate
13//! providing OS-agnostic accessibility primitives, with Bevy's ECS.
14//!
15//! ## Some notes on utility
16//!
17//! While this crate defines useful types for accessibility, it does not
18//! actually power accessibility features in Bevy.
19//!
20//! Instead, it helps other interfaces coordinate their approach to
21//! accessibility. Binary authors should add the [`AccessibilityPlugin`], while
22//! library maintainers may use the [`AccessibilityRequested`] and
23//! [`ManageAccessibilityUpdates`] resources.
24//!
25//! The [`AccessibilityNode`] component is useful in both cases. It helps
26//! describe an entity in terms of its accessibility factors through an
27//! `AccessKit` "node".
28//!
29//! Typical UI concepts, like buttons, checkboxes, and textboxes, are easily
30//! described by this component, though, technically, it can represent any kind
31//! of Bevy [`Entity`].
32//!
33//! ## This crate no longer re-exports `AccessKit`
34//!
35//! As of Bevy version 0.15, [the `accesskit` crate][accesskit_crate] is no
36//! longer re-exported from this crate.[^accesskit_node_confusion] If you need
37//! to use `AccessKit` yourself, you'll have to add it as a separate dependency
38//! in your project's `Cargo.toml`.
39//!
40//! Make sure to use the same version of the `accesskit` crate as Bevy.
41//! Otherwise, you may experience errors similar to: "Perhaps two different
42//! versions of crate `accesskit` are being used?"
43//!
44//! [accesskit_crate]: https://crates.io/crates/accesskit
45//! [`Entity`]: bevy_ecs::entity::Entity
46//!
47//! <!--
48//! note: multi-line footnotes need to be indented like this!
49//!
50//! please do not remove the indentation, or the second paragraph will display
51//! at the end of the module docs, **before** the footnotes...
52//! -->
53//!
54//! [^accesskit_node_confusion]: Some users were confused about `AccessKit`'s
55//! `Node` type, sometimes thinking it was Bevy UI's primary way to define
56//! nodes!
57//!
58//! For this reason, its re-export was removed by default. Users who need
59//! its types can instead manually depend on the `accesskit` crate.
60
61#[cfg(feature = "std")]
62extern crate std;
63
64extern crate alloc;
65
66use alloc::sync::Arc;
67use core::sync::atomic::{AtomicBool, Ordering};
68
69use accesskit::Node;
70use bevy_app::Plugin;
71use bevy_derive::{Deref, DerefMut};
72use bevy_ecs::{component::Component, message::Message, resource::Resource, schedule::SystemSet};
73
74#[cfg(feature = "bevy_reflect")]
75use {
76 bevy_ecs::reflect::ReflectResource, bevy_reflect::std_traits::ReflectDefault,
77 bevy_reflect::Reflect,
78};
79
80#[cfg(feature = "serialize")]
81use serde::{Deserialize, Serialize};
82
83#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
84use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
85
86/// Wrapper struct for [`accesskit::ActionRequest`].
87///
88/// This newtype is required to use `ActionRequest` as a Bevy `Event`.
89#[derive(Message, Deref, DerefMut)]
90#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
91pub struct ActionRequest(pub accesskit::ActionRequest);
92
93/// Tracks whether an assistive technology has requested accessibility
94/// information.
95///
96/// This type is a [`Resource`] initialized by the
97/// [`AccessibilityPlugin`]. It may be useful if a third-party plugin needs to
98/// conditionally integrate with `AccessKit`.
99///
100/// In other words, this resource represents whether accessibility providers
101/// are "turned on" or "turned off" across an entire Bevy `App`.
102///
103/// By default, it is set to `false`, indicating that nothing has requested
104/// accessibility information yet.
105///
106/// [`Resource`]: bevy_ecs::resource::Resource
107#[derive(Resource, Default, Clone, Debug, Deref, DerefMut)]
108#[cfg_attr(
109 feature = "bevy_reflect",
110 derive(Reflect),
111 reflect(Default, Clone, Resource)
112)]
113pub struct AccessibilityRequested(Arc<AtomicBool>);
114
115impl AccessibilityRequested {
116 /// Checks if any assistive technology has requested accessibility
117 /// information.
118 ///
119 /// If so, this method returns `true`, indicating that accessibility tree
120 /// updates should be sent.
121 pub fn get(&self) -> bool {
122 self.load(Ordering::SeqCst)
123 }
124
125 /// Sets the app's preference for sending accessibility updates.
126 ///
127 /// If the `value` argument is `true`, this method requests that the app,
128 /// including both Bevy and third-party interfaces, provides updates to
129 /// accessibility information.
130 ///
131 /// Setting with `false` requests that the entire app stops providing these
132 /// updates.
133 pub fn set(&self, value: bool) {
134 self.store(value, Ordering::SeqCst);
135 }
136}
137
138/// Determines whether Bevy's ECS updates the accessibility tree.
139///
140/// This [`Resource`] tells Bevy internals whether it should be handling
141/// `AccessKit` updates (`true`), or if something else is doing that (`false`).
142///
143/// It defaults to `true`. So, by default, Bevy is configured to maintain the
144/// `AccessKit` tree.
145///
146/// Set to `false` in cases where an external GUI library is sending
147/// accessibility updates instead. When this option is set inconsistently with
148/// that requirement, the external library and ECS will generate conflicting
149/// updates.
150///
151/// [`Resource`]: bevy_ecs::resource::Resource
152#[derive(Resource, Clone, Debug, Deref, DerefMut)]
153#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
154#[cfg_attr(
155 feature = "bevy_reflect",
156 derive(Reflect),
157 reflect(Resource, Clone, Default)
158)]
159#[cfg_attr(
160 all(feature = "bevy_reflect", feature = "serialize"),
161 reflect(Serialize, Deserialize)
162)]
163pub struct ManageAccessibilityUpdates(bool);
164
165impl Default for ManageAccessibilityUpdates {
166 fn default() -> Self {
167 Self(true)
168 }
169}
170
171impl ManageAccessibilityUpdates {
172 /// Returns `true` if Bevy's ECS should update the accessibility tree.
173 pub fn get(&self) -> bool {
174 self.0
175 }
176
177 /// Sets whether Bevy's ECS should update the accessibility tree.
178 pub fn set(&mut self, value: bool) {
179 self.0 = value;
180 }
181}
182
183/// Represents an entity to `AccessKit` through an [`accesskit::Node`].
184///
185/// Platform-specific accessibility APIs utilize `AccessKit` nodes in their
186/// accessibility frameworks. So, this component acts as a translation between
187/// "Bevy entity" and "platform-agnostic accessibility element".
188///
189/// ## Organization in the `AccessKit` Accessibility Tree
190///
191/// `AccessKit` allows users to form a "tree of nodes" providing accessibility
192/// information. That tree is **not** Bevy's ECS!
193///
194/// To explain, let's say this component is added to an entity, `E`.
195///
196/// ### Parent and Child
197///
198/// If `E` has a parent, `P`, and `P` also has this `AccessibilityNode`
199/// component, then `E`'s `AccessKit` node will be a child of `P`'s `AccessKit`
200/// node.
201///
202/// Resulting `AccessKit` tree:
203/// - P
204/// - E
205///
206/// In other words, parent-child relationships are maintained, but only if both
207/// have this component.
208///
209/// ### On the Window
210///
211/// If `E` doesn't have a parent, or if the immediate parent doesn't have an
212/// `AccessibilityNode`, its `AccessKit` node will be an immediate child of the
213/// primary window.
214///
215/// Resulting `AccessKit` tree:
216/// - Primary window
217/// - E
218///
219/// When there's no `AccessKit`-compatible parent, the child lacks hierarchical
220/// information in `AccessKit`. As such, it is placed directly under the
221/// primary window on the `AccessKit` tree.
222///
223/// This behavior may or may not be intended, so please utilize
224/// `AccessibilityNode`s with care.
225#[derive(Component, Clone, Deref, DerefMut)]
226#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
227pub struct AccessibilityNode(
228 /// A representation of this component's entity to `AccessKit`.
229 ///
230 /// Note that, with its parent struct acting as just a newtype, users are
231 /// intended to directly update this field.
232 pub Node,
233);
234
235impl From<Node> for AccessibilityNode {
236 /// Converts an [`accesskit::Node`] into the Bevy Engine
237 /// [`AccessibilityNode`] newtype.
238 ///
239 /// Doing so allows it to be inserted onto Bevy entities, representing Bevy
240 /// entities in the `AccessKit` tree.
241 fn from(node: Node) -> Self {
242 Self(node)
243 }
244}
245
246/// A system set relating to accessibility.
247///
248/// Helps run accessibility updates all at once.
249#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
250#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
251#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
252#[cfg_attr(
253 all(feature = "bevy_reflect", feature = "serialize"),
254 reflect(Serialize, Deserialize, Clone)
255)]
256pub enum AccessibilitySystems {
257 /// Update the accessibility tree.
258 Update,
259}
260
261/// Deprecated alias for [`AccessibilitySystems`].
262#[deprecated(since = "0.17.0", note = "Renamed to `AccessibilitySystems`.")]
263pub type AccessibilitySystem = AccessibilitySystems;
264
265/// Plugin managing integration with accessibility APIs.
266///
267/// Note that it doesn't handle GUI aspects of this integration, instead
268/// providing helpful resources for other interfaces to utilize.
269///
270/// ## Behavior
271///
272/// This plugin's main role is to initialize the [`AccessibilityRequested`] and
273/// [`ManageAccessibilityUpdates`] resources to their default values, meaning:
274///
275/// - no assistive technologies have requested accessibility information yet,
276/// and
277/// - Bevy's ECS will manage updates to the accessibility tree.
278#[derive(Default)]
279pub struct AccessibilityPlugin;
280
281impl Plugin for AccessibilityPlugin {
282 fn build(&self, app: &mut bevy_app::App) {
283 app.init_resource::<AccessibilityRequested>()
284 .init_resource::<ManageAccessibilityUpdates>()
285 .allow_ambiguous_component::<AccessibilityNode>();
286 }
287}