Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui/src/accessibility.rs
6598 views
1
use crate::{
2
experimental::UiChildren,
3
prelude::{Button, Label},
4
ui_transform::UiGlobalTransform,
5
widget::{ImageNode, TextUiReader},
6
ComputedNode,
7
};
8
use bevy_a11y::AccessibilityNode;
9
use bevy_app::{App, Plugin, PostUpdate};
10
use bevy_ecs::{
11
prelude::{DetectChanges, Entity},
12
query::{Changed, Without},
13
schedule::IntoScheduleConfigs,
14
system::{Commands, Query},
15
world::Ref,
16
};
17
18
use accesskit::{Node, Rect, Role};
19
use bevy_camera::CameraUpdateSystems;
20
21
fn calc_label(
22
text_reader: &mut TextUiReader,
23
children: impl Iterator<Item = Entity>,
24
) -> Option<Box<str>> {
25
let mut name = None;
26
for child in children {
27
let values = text_reader
28
.iter(child)
29
.map(|(_, _, text, _, _)| text.into())
30
.collect::<Vec<String>>();
31
if !values.is_empty() {
32
name = Some(values.join(" "));
33
}
34
}
35
name.map(String::into_boxed_str)
36
}
37
38
fn calc_bounds(
39
mut nodes: Query<(
40
&mut AccessibilityNode,
41
Ref<ComputedNode>,
42
Ref<UiGlobalTransform>,
43
)>,
44
) {
45
for (mut accessible, node, transform) in &mut nodes {
46
if node.is_changed() || transform.is_changed() {
47
let center = transform.translation;
48
let half_size = 0.5 * node.size;
49
let min = center - half_size;
50
let max = center + half_size;
51
let bounds = Rect::new(min.x as f64, min.y as f64, max.x as f64, max.y as f64);
52
accessible.set_bounds(bounds);
53
}
54
}
55
}
56
57
fn button_changed(
58
mut commands: Commands,
59
mut query: Query<(Entity, Option<&mut AccessibilityNode>), Changed<Button>>,
60
ui_children: UiChildren,
61
mut text_reader: TextUiReader,
62
) {
63
for (entity, accessible) in &mut query {
64
let label = calc_label(&mut text_reader, ui_children.iter_ui_children(entity));
65
if let Some(mut accessible) = accessible {
66
accessible.set_role(Role::Button);
67
if let Some(name) = label {
68
accessible.set_label(name);
69
} else {
70
accessible.clear_label();
71
}
72
} else {
73
let mut node = Node::new(Role::Button);
74
if let Some(label) = label {
75
node.set_label(label);
76
}
77
commands
78
.entity(entity)
79
.try_insert(AccessibilityNode::from(node));
80
}
81
}
82
}
83
84
fn image_changed(
85
mut commands: Commands,
86
mut query: Query<
87
(Entity, Option<&mut AccessibilityNode>),
88
(Changed<ImageNode>, Without<Button>),
89
>,
90
ui_children: UiChildren,
91
mut text_reader: TextUiReader,
92
) {
93
for (entity, accessible) in &mut query {
94
let label = calc_label(&mut text_reader, ui_children.iter_ui_children(entity));
95
if let Some(mut accessible) = accessible {
96
accessible.set_role(Role::Image);
97
if let Some(label) = label {
98
accessible.set_label(label);
99
} else {
100
accessible.clear_label();
101
}
102
} else {
103
let mut node = Node::new(Role::Image);
104
if let Some(label) = label {
105
node.set_label(label);
106
}
107
commands
108
.entity(entity)
109
.try_insert(AccessibilityNode::from(node));
110
}
111
}
112
}
113
114
fn label_changed(
115
mut commands: Commands,
116
mut query: Query<(Entity, Option<&mut AccessibilityNode>), Changed<Label>>,
117
mut text_reader: TextUiReader,
118
) {
119
for (entity, accessible) in &mut query {
120
let values = text_reader
121
.iter(entity)
122
.map(|(_, _, text, _, _)| text.into())
123
.collect::<Vec<String>>();
124
let label = Some(values.join(" ").into_boxed_str());
125
if let Some(mut accessible) = accessible {
126
accessible.set_role(Role::Label);
127
if let Some(label) = label {
128
accessible.set_value(label);
129
} else {
130
accessible.clear_value();
131
}
132
} else {
133
let mut node = Node::new(Role::Label);
134
if let Some(label) = label {
135
node.set_value(label);
136
}
137
commands
138
.entity(entity)
139
.try_insert(AccessibilityNode::from(node));
140
}
141
}
142
}
143
144
/// `AccessKit` integration for `bevy_ui`.
145
pub(crate) struct AccessibilityPlugin;
146
147
impl Plugin for AccessibilityPlugin {
148
fn build(&self, app: &mut App) {
149
app.add_systems(
150
PostUpdate,
151
(
152
calc_bounds
153
.after(bevy_transform::TransformSystems::Propagate)
154
.after(CameraUpdateSystems)
155
// the listed systems do not affect calculated size
156
.ambiguous_with(crate::ui_stack_system),
157
button_changed,
158
image_changed,
159
label_changed,
160
),
161
);
162
}
163
}
164
165