1use accesskit::{
2 Action, ActionHandler, ActionRequest, Node, NodeId, Rect, Role, Tree, TreeId, TreeUpdate,
3};
4use repose_core::runtime::SemNode;
5use repose_core::semantics::Role as CoreRole;
6use rustc_hash::FxHashMap;
7use std::hash::{Hash, Hasher};
8use std::sync::{Arc, Mutex};
9
10pub const WINDOW_ID: NodeId = NodeId(1);
11
12pub struct ReposeActionHandler {
13 pub pending_actions: Arc<Mutex<Vec<ActionRequest>>>,
14}
15
16impl ActionHandler for ReposeActionHandler {
17 fn do_action(&mut self, request: ActionRequest) {
18 let mut q = self.pending_actions.lock().unwrap();
19 q.push(request);
20 }
21}
22
23#[derive(Default)]
24pub struct A11yTree {
25 prev_hash: FxHashMap<u64, u64>,
26 prev_root_hash: u64,
27 prev_focus: Option<u64>,
28 initialized: bool,
29}
30
31impl A11yTree {
32 pub fn initial_tree() -> TreeUpdate {
33 let root = Node::new(Role::Window);
34 TreeUpdate {
35 nodes: vec![(WINDOW_ID, root)],
36 tree: Some(Tree::new(WINDOW_ID)),
37 focus: WINDOW_ID,
38 tree_id: TreeId::ROOT,
39 }
40 }
41
42 pub fn update(
43 &mut self,
44 sems: &[SemNode],
45 scale: f64,
46 focused_id: Option<u64>,
47 ) -> Option<TreeUpdate> {
48 let (root_children, children_map) = build_children(sems);
49
50 let mut changed_nodes: Vec<(NodeId, Node)> = Vec::new();
51
52 let focus = focused_id.map(NodeId).unwrap_or(WINDOW_ID);
53 let focus_changed = self.prev_focus != focused_id;
54 self.prev_focus = focused_id;
55
56 let root_hash = {
58 let mut h = rustc_hash::FxHasher::default();
59 (scale.to_bits()).hash(&mut h);
60 root_children.len().hash(&mut h);
61 for &id in &root_children {
62 id.hash(&mut h);
63 }
64 h.finish()
65 };
66
67 let root_changed = !self.initialized || self.prev_root_hash != root_hash;
68 self.prev_root_hash = root_hash;
69
70 if root_changed {
71 let mut root = Node::new(Role::Window);
72 root.set_children(
73 root_children
74 .iter()
75 .copied()
76 .map(NodeId)
77 .collect::<Vec<_>>(),
78 );
79 changed_nodes.push((WINDOW_ID, root));
80 }
81
82 let mut new_hashes: FxHashMap<u64, u64> = FxHashMap::default();
83
84 for sem in sems {
85 let kids = children_map
86 .get(&sem.id)
87 .map(|v| v.as_slice())
88 .unwrap_or(&[]);
89 let node_hash = hash_sem_node(sem, kids, scale);
90 new_hashes.insert(sem.id, node_hash);
91
92 let prev = self.prev_hash.get(&sem.id).copied();
93 let needs_update = !self.initialized || prev != Some(node_hash);
94
95 if needs_update {
96 let node = build_accesskit_node(sem, kids, scale);
97 changed_nodes.push((NodeId(sem.id), node));
98 }
99 }
100
101 self.prev_hash = new_hashes;
104 self.initialized = true;
105
106 if changed_nodes.is_empty() && !focus_changed {
107 return None;
108 }
109
110 Some(TreeUpdate {
111 nodes: changed_nodes,
112 tree: None,
113 focus,
114 tree_id: TreeId::ROOT,
115 })
116 }
117}
118
119fn build_children(sems: &[SemNode]) -> (Vec<u64>, FxHashMap<u64, Vec<u64>>) {
120 let mut roots: Vec<u64> = Vec::new();
121 let mut map: FxHashMap<u64, Vec<u64>> = FxHashMap::default();
122
123 for s in sems {
124 if let Some(p) = s.parent {
125 map.entry(p).or_default().push(s.id);
126 } else {
127 roots.push(s.id);
128 }
129 }
130
131 (roots, map)
132}
133
134fn build_accesskit_node(sem: &SemNode, children: &[u64], scale: f64) -> Node {
135 let mut node = Node::new(map_role(sem.role));
136
137 let r = sem.rect;
138 node.set_bounds(Rect {
139 x0: r.x as f64 / scale,
140 y0: r.y as f64 / scale,
141 x1: (r.x + r.w) as f64 / scale,
142 y1: (r.y + r.h) as f64 / scale,
143 });
144
145 if let Some(label) = &sem.label {
146 if !label.is_empty() {
147 node.set_label(label.clone());
148 }
149 }
150
151 if !children.is_empty() {
152 node.set_children(children.iter().copied().map(NodeId).collect::<Vec<_>>());
153 }
154
155 if sem.enabled {
156 match sem.role {
157 CoreRole::Button | CoreRole::Checkbox | CoreRole::Switch | CoreRole::RadioButton => {
158 node.add_action(Action::Click);
159 }
160 CoreRole::TextField | CoreRole::Slider => {
161 node.add_action(Action::Focus);
162 }
163 _ => {}
164 }
165 }
166
167 node
168}
169
170fn map_role(role: CoreRole) -> Role {
171 match role {
172 CoreRole::Text => Role::Label,
173 CoreRole::Button => Role::Button,
174 CoreRole::TextField => Role::TextInput,
175 CoreRole::Container => Role::GenericContainer,
176 CoreRole::Checkbox => Role::CheckBox,
177 CoreRole::RadioButton => Role::RadioButton,
178 CoreRole::Switch => Role::Switch,
179 CoreRole::Slider => Role::Slider,
180 CoreRole::ProgressBar => Role::ProgressIndicator,
181 }
182}
183
184fn hash_sem_node(sem: &SemNode, children: &[u64], scale: f64) -> u64 {
185 let mut h = rustc_hash::FxHasher::default();
186
187 (scale.to_bits()).hash(&mut h);
188
189 sem.id.hash(&mut h);
190 sem.parent.hash(&mut h);
191 std::mem::discriminant(&sem.role).hash(&mut h);
192
193 let q = |v: f32| (v * 8.0) as i32;
195 q(sem.rect.x).hash(&mut h);
196 q(sem.rect.y).hash(&mut h);
197 q(sem.rect.w).hash(&mut h);
198 q(sem.rect.h).hash(&mut h);
199
200 sem.focused.hash(&mut h);
201 sem.enabled.hash(&mut h);
202
203 if let Some(lbl) = &sem.label {
204 lbl.hash(&mut h);
205 }
206
207 children.len().hash(&mut h);
208 for &c in children {
209 c.hash(&mut h);
210 }
211
212 h.finish()
213}