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