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
6/// Updates node properties from view properties
7/// Should be run after layout so that things like bounding box are correct.
8/// This system doesn't change the structure of the accessibility tree as this is done when views are built/removed.
9pub 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 child nodes were generated then append them to the nodes list
42                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            // }
62        }
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            // let navigable = cx
84            //     .style
85            //     .abilities
86            //     .get(entity)
87            //     .copied()
88            //     .unwrap_or_default()
89            //     .contains(Abilities::NAVIGABLE);
90
91            // if node.node_builder.role() == Role::Unknown && !navigable {
92            //     continue;
93            // }
94
95            //let mut nodes = vec![(node.node_id(), node.node_builder)];
96            nodes.push((node.node_id(), node.node_builder));
97
98            // If child nodes were generated then append them to the nodes list
99            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        // }
109    }
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(name) = cx.style.name.get(entity) {
164    //     node_builder.set_name(name.clone().into_boxed_str());
165    // }
166
167    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(default_action_verb) = cx.style.default_action_verb.get(entity) {
184    //     node_builder.set_default_action_verb(*default_action_verb);
185    // }
186
187    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    // Layout children
223    let children =
224        entity.child_iter(cx.tree).map(|entity| entity.accesskit_id()).collect::<Vec<_>>();
225
226    // Children added by `accessibility` function
227    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}