2023-03-18 01:45:34 +00:00
|
|
|
use crate::{
|
|
|
|
prelude::{Button, Label},
|
|
|
|
Node, UiImage,
|
|
|
|
};
|
2023-03-01 22:45:04 +00:00
|
|
|
use bevy_a11y::{
|
|
|
|
accesskit::{NodeBuilder, Rect, Role},
|
|
|
|
AccessibilityNode,
|
|
|
|
};
|
2023-05-23 23:50:48 +00:00
|
|
|
use bevy_app::{App, Plugin, PostUpdate};
|
2023-03-01 22:45:04 +00:00
|
|
|
use bevy_ecs::{
|
2023-05-08 20:49:55 +00:00
|
|
|
prelude::{DetectChanges, Entity},
|
|
|
|
query::{Changed, Without},
|
2023-05-23 23:50:48 +00:00
|
|
|
schedule::IntoSystemConfigs,
|
2023-03-01 22:45:04 +00:00
|
|
|
system::{Commands, Query},
|
2023-05-08 20:49:55 +00:00
|
|
|
world::Ref,
|
2023-03-01 22:45:04 +00:00
|
|
|
};
|
|
|
|
use bevy_hierarchy::Children;
|
|
|
|
use bevy_render::prelude::Camera;
|
|
|
|
use bevy_text::Text;
|
|
|
|
use bevy_transform::prelude::GlobalTransform;
|
|
|
|
|
|
|
|
fn calc_name(texts: &Query<&Text>, children: &Children) -> Option<Box<str>> {
|
|
|
|
let mut name = None;
|
2023-09-19 03:35:22 +00:00
|
|
|
for child in children {
|
2023-03-01 22:45:04 +00:00
|
|
|
if let Ok(text) = texts.get(*child) {
|
|
|
|
let values = text
|
|
|
|
.sections
|
|
|
|
.iter()
|
|
|
|
.map(|v| v.value.to_string())
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
name = Some(values.join(" "));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
name.map(|v| v.into_boxed_str())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn calc_bounds(
|
|
|
|
camera: Query<(&Camera, &GlobalTransform)>,
|
2023-05-08 20:49:55 +00:00
|
|
|
mut nodes: Query<(&mut AccessibilityNode, Ref<Node>, Ref<GlobalTransform>)>,
|
2023-03-01 22:45:04 +00:00
|
|
|
) {
|
|
|
|
if let Ok((camera, camera_transform)) = camera.get_single() {
|
|
|
|
for (mut accessible, node, transform) in &mut nodes {
|
2023-05-08 20:49:55 +00:00
|
|
|
if node.is_changed() || transform.is_changed() {
|
|
|
|
if let Some(translation) =
|
|
|
|
camera.world_to_viewport(camera_transform, transform.translation())
|
|
|
|
{
|
|
|
|
let bounds = Rect::new(
|
|
|
|
translation.x.into(),
|
|
|
|
translation.y.into(),
|
|
|
|
(translation.x + node.calculated_size.x).into(),
|
|
|
|
(translation.y + node.calculated_size.y).into(),
|
|
|
|
);
|
|
|
|
accessible.set_bounds(bounds);
|
|
|
|
}
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn button_changed(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut query: Query<(Entity, &Children, Option<&mut AccessibilityNode>), Changed<Button>>,
|
|
|
|
texts: Query<&Text>,
|
|
|
|
) {
|
|
|
|
for (entity, children, accessible) in &mut query {
|
|
|
|
let name = calc_name(&texts, children);
|
|
|
|
if let Some(mut accessible) = accessible {
|
|
|
|
accessible.set_role(Role::Button);
|
|
|
|
if let Some(name) = name {
|
|
|
|
accessible.set_name(name);
|
|
|
|
} else {
|
|
|
|
accessible.clear_name();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let mut node = NodeBuilder::new(Role::Button);
|
|
|
|
if let Some(name) = name {
|
|
|
|
node.set_name(name);
|
|
|
|
}
|
|
|
|
commands
|
|
|
|
.entity(entity)
|
|
|
|
.insert(AccessibilityNode::from(node));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn image_changed(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut query: Query<
|
|
|
|
(Entity, &Children, Option<&mut AccessibilityNode>),
|
|
|
|
(Changed<UiImage>, Without<Button>),
|
|
|
|
>,
|
|
|
|
texts: Query<&Text>,
|
|
|
|
) {
|
|
|
|
for (entity, children, accessible) in &mut query {
|
|
|
|
let name = calc_name(&texts, children);
|
|
|
|
if let Some(mut accessible) = accessible {
|
|
|
|
accessible.set_role(Role::Image);
|
|
|
|
if let Some(name) = name {
|
|
|
|
accessible.set_name(name);
|
|
|
|
} else {
|
|
|
|
accessible.clear_name();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let mut node = NodeBuilder::new(Role::Image);
|
|
|
|
if let Some(name) = name {
|
|
|
|
node.set_name(name);
|
|
|
|
}
|
|
|
|
commands
|
|
|
|
.entity(entity)
|
|
|
|
.insert(AccessibilityNode::from(node));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn label_changed(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut query: Query<(Entity, &Text, Option<&mut AccessibilityNode>), Changed<Label>>,
|
|
|
|
) {
|
|
|
|
for (entity, text, accessible) in &mut query {
|
|
|
|
let values = text
|
|
|
|
.sections
|
|
|
|
.iter()
|
|
|
|
.map(|v| v.value.to_string())
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
let name = Some(values.join(" ").into_boxed_str());
|
|
|
|
if let Some(mut accessible) = accessible {
|
2023-10-02 21:22:52 +00:00
|
|
|
accessible.set_role(Role::StaticText);
|
2023-03-01 22:45:04 +00:00
|
|
|
if let Some(name) = name {
|
|
|
|
accessible.set_name(name);
|
|
|
|
} else {
|
|
|
|
accessible.clear_name();
|
|
|
|
}
|
|
|
|
} else {
|
2023-10-02 21:22:52 +00:00
|
|
|
let mut node = NodeBuilder::new(Role::StaticText);
|
2023-03-01 22:45:04 +00:00
|
|
|
if let Some(name) = name {
|
|
|
|
node.set_name(name);
|
|
|
|
}
|
|
|
|
commands
|
|
|
|
.entity(entity)
|
|
|
|
.insert(AccessibilityNode::from(node));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `AccessKit` integration for `bevy_ui`.
|
|
|
|
pub(crate) struct AccessibilityPlugin;
|
|
|
|
|
|
|
|
impl Plugin for AccessibilityPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
2023-03-18 01:45:34 +00:00
|
|
|
app.add_systems(
|
2023-05-23 23:50:48 +00:00
|
|
|
PostUpdate,
|
|
|
|
(
|
|
|
|
calc_bounds.after(bevy_transform::TransformSystem::TransformPropagate),
|
|
|
|
button_changed,
|
|
|
|
image_changed,
|
|
|
|
label_changed,
|
|
|
|
),
|
2023-03-18 01:45:34 +00:00
|
|
|
);
|
2023-03-01 22:45:04 +00:00
|
|
|
}
|
|
|
|
}
|