use crate::{accessibility::IntoNode, events::ViewHandler, prelude::*};
use accesskit::{Node, NodeId, Orientation as AccessOrientation, Rect, Toggled, Tree, TreeUpdate};
use hashbrown::HashMap;
use vizia_storage::LayoutTreeIterator;
pub fn accessibility_system(cx: &mut Context) {
if !cx.style.reaccess.is_empty() {
let iterator = LayoutTreeIterator::full(&cx.tree);
for entity in iterator {
if !cx.style.reaccess.contains(entity) {
continue;
}
let mut access_context = AccessContext {
current: entity,
tree: &cx.tree,
entity_identifiers: &cx.entity_identifiers,
cache: &cx.cache,
style: &cx.style,
text_context: &mut cx.text_context,
};
if let Some(node) = get_access_node(&mut access_context, &mut cx.views, entity) {
let mut nodes = vec![(node.node_id(), node.node_builder)];
if !node.children.is_empty() {
nodes.extend(
node.children
.into_iter()
.map(|child_node| (child_node.node_id(), child_node.node_builder)),
);
}
cx.tree_updates.push(Some(TreeUpdate {
nodes,
tree: None,
tree_id: accesskit::TreeId::ROOT,
focus: if cx.window_has_focus {
cx.focused.accesskit_id()
} else {
NodeId(0u64)
},
}));
}
}
cx.style.reaccess.clear();
}
}
pub fn initial_accessibility_system(cx: &mut Context) -> TreeUpdate {
let iterator = LayoutTreeIterator::full(&cx.tree);
let mut nodes = vec![];
for entity in iterator {
let mut access_context = AccessContext {
current: entity,
tree: &cx.tree,
entity_identifiers: &cx.entity_identifiers,
cache: &cx.cache,
style: &cx.style,
text_context: &mut cx.text_context,
};
if let Some(node) = get_access_node(&mut access_context, &mut cx.views, entity) {
nodes.push((node.node_id(), node.node_builder));
if !node.children.is_empty() {
nodes.extend(
node.children
.into_iter()
.map(|child_node| (child_node.node_id(), child_node.node_builder)),
);
}
}
}
TreeUpdate {
nodes,
tree: Some(Tree::new(Entity::root().accesskit_id())),
tree_id: accesskit::TreeId::ROOT,
focus: Entity::root().accesskit_id(),
}
}
pub(crate) fn get_access_node(
cx: &mut AccessContext,
views: &mut HashMap<Entity, Box<dyn ViewHandler>>,
entity: Entity,
) -> Option<AccessNode> {
let mut node_builder = Node::default();
let role = cx.style.role.get(entity).copied();
if let Some(role) = role {
node_builder.set_role(role);
}
let bounds = cx.cache.get_bounds(entity);
node_builder.set_bounds(Rect {
x0: bounds.left() as f64,
y0: bounds.top() as f64,
x1: bounds.right() as f64,
y1: bounds.bottom() as f64,
});
if let Some(disabled) = cx.style.disabled.get(entity).copied() {
if disabled {
node_builder.set_disabled();
} else {
node_builder.clear_disabled();
}
}
let focusable = cx
.style
.abilities
.get(entity)
.map(|flags| flags.contains(Abilities::NAVIGABLE))
.unwrap_or(false);
if focusable {
node_builder.add_action(Action::Focus);
} else {
node_builder.remove_action(Action::Focus);
}
if let Some(value) = cx.style.text_value.get(entity) {
node_builder.set_value(value.clone().into_boxed_str());
}
if let Some(name) = cx.style.name.get(entity) {
match cx.style.role.get(entity) {
Some(Role::Label | Role::TextRun) => {
node_builder.set_value(name.clone().into_boxed_str());
}
_ => {
node_builder.set_label(name.clone().into_boxed_str());
}
}
}
if let Some(numeric_value) = cx.style.numeric_value.get(entity) {
node_builder.set_numeric_value(*numeric_value);
}
if let Some(hidden) = cx.style.hidden.get(entity) {
if *hidden {
node_builder.set_hidden();
} else {
node_builder.clear_hidden();
}
}
let has_expanded = cx.style.expanded.get(entity).copied();
if let Some(expanded) = has_expanded {
node_builder.set_expanded(expanded);
}
if let Some(selected) = cx.style.selected.get(entity).copied() {
node_builder.set_selected(selected);
}
if let Some(orientation) = cx.style.orientation.get(entity).copied() {
let orientation = match orientation {
Orientation::Horizontal => AccessOrientation::Horizontal,
Orientation::Vertical => AccessOrientation::Vertical,
};
node_builder.set_orientation(orientation);
}
if let Some(live) = cx.style.live.get(entity) {
node_builder.set_live(*live);
}
if let Some(labelled_by_id) = cx.style.labelled_by.get(entity) {
if let Some(labelled_by) = cx.resolve_entity_identifier(labelled_by_id) {
node_builder.set_labelled_by(vec![labelled_by.accesskit_id()]);
}
}
if let Some(described_by_id) = cx.style.described_by.get(entity) {
if let Some(described_by) = cx.resolve_entity_identifier(described_by_id) {
node_builder.set_described_by(vec![described_by.accesskit_id()]);
}
}
if let Some(controlled_id) = cx.style.controls.get(entity) {
if let Some(controlled) = cx.resolve_entity_identifier(controlled_id) {
node_builder.set_controls(vec![controlled.accesskit_id()]);
}
}
if let Some(active_descendant_id) = cx.style.active_descendant.get(entity) {
if let Some(active_descendant) = cx.resolve_entity_identifier(active_descendant_id) {
node_builder.set_active_descendant(active_descendant.accesskit_id());
}
}
let checkable = cx
.style
.abilities
.get(entity)
.map(|abilities| abilities.contains(Abilities::CHECKABLE))
.unwrap_or_default();
if checkable {
if let Some(checked) = cx
.style
.pseudo_classes
.get(entity)
.map(|pseudoclass| pseudoclass.contains(PseudoClassFlags::CHECKED))
{
if checked {
node_builder.set_toggled(Toggled::True);
} else {
node_builder.set_toggled(Toggled::False);
}
}
}
let mut node =
AccessNode { node_id: entity.accesskit_id(), node_builder, children: Vec::new() };
if let Some(view) = views.remove(&entity) {
view.accessibility(cx, &mut node);
views.insert(entity, view);
}
let children =
entity.child_iter(cx.tree).map(|entity| entity.accesskit_id()).collect::<Vec<_>>();
let mut child_ids =
node.children.iter().map(|child_node| child_node.node_id()).collect::<Vec<_>>();
child_ids.extend(children);
if !child_ids.is_empty() {
node.node_builder.set_children(child_ids);
}
Some(node)
}