vizia_core/systems/
accessibility.rs
1use crate::{accessibility::IntoNode, events::ViewHandler, prelude::*};
2use accesskit::{Node, NodeId, Rect, Toggled, Tree, TreeUpdate};
3use hashbrown::HashMap;
4use vizia_storage::LayoutTreeIterator;
5
6pub fn accessibility_system(cx: &mut Context) {
10 if !cx.style.reaccess.is_empty() {
11 let iterator = LayoutTreeIterator::full(&cx.tree);
12
13 for entity in iterator {
14 if !cx.style.reaccess.contains(entity) {
15 continue;
16 }
17
18 let mut access_context = AccessContext {
19 current: entity,
20 tree: &cx.tree,
21 cache: &cx.cache,
22 style: &cx.style,
23 text_context: &mut cx.text_context,
24 };
25
26 if let Some(node) = get_access_node(&mut access_context, &mut cx.views, entity) {
27 let navigable = cx
28 .style
29 .abilities
30 .get(entity)
31 .copied()
32 .unwrap_or_default()
33 .contains(Abilities::NAVIGABLE);
34
35 if node.node_builder.role() == Role::Unknown && !navigable {
36 continue;
37 }
38
39 let mut nodes = vec![(node.node_id(), node.node_builder)];
40
41 if !node.children.is_empty() {
43 nodes.extend(
44 node.children
45 .into_iter()
46 .map(|child_node| (child_node.node_id(), child_node.node_builder)),
47 );
48 }
49
50 cx.tree_updates.push(Some(TreeUpdate {
51 nodes,
52 tree: None,
53 focus: if cx.window_has_focus {
54 cx.focused.accesskit_id()
55 } else {
56 NodeId(0u64)
57 },
58 }));
59 }
60
61 }
63
64 cx.style.reaccess.clear();
65 }
66}
67
68pub fn initial_accessibility_system(cx: &mut Context) -> TreeUpdate {
69 let iterator = LayoutTreeIterator::full(&cx.tree);
70
71 let mut nodes = vec![];
72
73 for entity in iterator {
74 let mut access_context = AccessContext {
75 current: entity,
76 tree: &cx.tree,
77 cache: &cx.cache,
78 style: &cx.style,
79 text_context: &mut cx.text_context,
80 };
81
82 if let Some(node) = get_access_node(&mut access_context, &mut cx.views, entity) {
83 nodes.push((node.node_id(), node.node_builder));
97
98 if !node.children.is_empty() {
100 nodes.extend(
101 node.children
102 .into_iter()
103 .map(|child_node| (child_node.node_id(), child_node.node_builder)),
104 );
105 }
106 }
107
108 }
110
111 TreeUpdate {
112 nodes,
113 tree: Some(Tree::new(Entity::root().accesskit_id())),
114 focus: Entity::root().accesskit_id(),
115 }
116}
117
118pub(crate) fn get_access_node(
119 cx: &mut AccessContext,
120 views: &mut HashMap<Entity, Box<dyn ViewHandler>>,
121 entity: Entity,
122) -> Option<AccessNode> {
123 let mut node_builder = Node::default();
124
125 if let Some(role) = cx.style.role.get(entity) {
126 node_builder.set_role(*role);
127 }
128
129 let bounds = cx.cache.get_bounds(entity);
130
131 node_builder.set_bounds(Rect {
132 x0: bounds.left() as f64,
133 y0: bounds.top() as f64,
134 x1: bounds.right() as f64,
135 y1: bounds.bottom() as f64,
136 });
137
138 if let Some(disabled) = cx.style.disabled.get(entity).copied() {
139 if disabled {
140 node_builder.set_disabled();
141 } else {
142 node_builder.clear_disabled();
143 }
144 }
145
146 let focusable = cx
147 .style
148 .abilities
149 .get(entity)
150 .map(|flags| flags.contains(Abilities::NAVIGABLE))
151 .unwrap_or(false);
152
153 if focusable {
154 node_builder.add_action(Action::Focus);
155 } else {
156 node_builder.remove_action(Action::Focus);
157 }
158
159 if let Some(value) = cx.style.text_value.get(entity) {
160 node_builder.set_value(value.clone().into_boxed_str());
161 }
162
163 if let Some(numeric_value) = cx.style.numeric_value.get(entity) {
168 node_builder.set_numeric_value(*numeric_value);
169 }
170
171 if let Some(hidden) = cx.style.hidden.get(entity) {
172 if *hidden {
173 node_builder.set_hidden();
174 } else {
175 node_builder.clear_hidden();
176 }
177 }
178
179 if let Some(live) = cx.style.live.get(entity) {
180 node_builder.set_live(*live);
181 }
182
183 if let Some(labelled_by) = cx.style.labelled_by.get(entity) {
188 node_builder.set_labelled_by(vec![labelled_by.accesskit_id()]);
189 }
190
191 let checkable = cx
192 .style
193 .abilities
194 .get(entity)
195 .map(|abilities| abilities.contains(Abilities::CHECKABLE))
196 .unwrap_or_default();
197
198 if checkable {
199 if let Some(checked) = cx
200 .style
201 .pseudo_classes
202 .get(entity)
203 .map(|pseudoclass| pseudoclass.contains(PseudoClassFlags::CHECKED))
204 {
205 if checked {
206 node_builder.set_toggled(Toggled::True);
207 } else {
208 node_builder.set_toggled(Toggled::False);
209 }
210 }
211 }
212
213 let mut node =
214 AccessNode { node_id: entity.accesskit_id(), node_builder, children: Vec::new() };
215
216 if let Some(view) = views.remove(&entity) {
217 view.accessibility(cx, &mut node);
218
219 views.insert(entity, view);
220 }
221
222 let children =
224 entity.child_iter(cx.tree).map(|entity| entity.accesskit_id()).collect::<Vec<_>>();
225
226 let mut child_ids =
228 node.children.iter().map(|child_node| child_node.node_id()).collect::<Vec<_>>();
229
230 child_ids.extend(children);
231
232 if !child_ids.is_empty() {
233 node.node_builder.set_children(child_ids);
234 }
235
236 Some(node)
237}