1#![forbid(unsafe_code)]
16
17use std::collections::{HashMap, HashSet};
18use std::hash::{DefaultHasher, Hash, Hasher};
19
20use smudge_core::app::App;
21use smudge_core::geometry::Rect;
22use smudge_core::semantics::Role;
23use smudge_core::semantics::SemanticsNode;
24use smudge_core::tree::WidgetId;
25
26use accesskit::{Node, NodeId, Rect as AccessKitRect};
27use accesskit::{Role as AccessKitRole, TreeId, TreeInfo, TreeUpdate};
28
29const WINDOW_ROOT: NodeId = NodeId(u64::MAX);
35
36pub struct Projection {
41 title: String,
43 surface: (f64, f64),
45 sent: HashMap<NodeId, Sent>,
51 roots: Vec<NodeId>,
53}
54
55struct Sent {
57 digest: u64,
59 children: Vec<NodeId>,
61}
62
63impl Projection {
64 pub fn new(title: impl Into<String>) -> Projection {
66 Projection {
67 title: title.into(),
68 surface: (0.0, 0.0),
69 sent: HashMap::new(),
70 roots: Vec::new(),
71 }
72 }
73
74 pub fn update(
88 &mut self,
89 app: &mut App,
90 surface_width: f64,
91 surface_height: f64,
92 scale: f32,
93 full: bool,
94 ) -> TreeUpdate {
95 let changes = app.take_a11y_changes();
96 let surface = (surface_width, surface_height);
97 let mut update = if full || changes.reset {
98 self.build_full(app, surface, scale)
99 } else {
100 let mut nodes: Vec<(NodeId, Node)> = Vec::new();
101 for node in changes
102 .changed
103 .iter()
104 .filter_map(|id| app.semantics_node(*id))
105 {
106 let id = NodeId(node.id.as_u64());
107 let digest = digest(&node, scale);
108 if self.sent.get(&id).is_some_and(|sent| sent.digest == digest) {
109 continue;
110 }
111 let children = node_ids(&node.children);
112 let previous = self.sent.insert(
113 id,
114 Sent {
115 digest,
116 children: children.clone(),
117 },
118 );
119 if let Some(previous) = previous {
120 self.forget_dropped(&previous.children, &children);
121 }
122 nodes.push((id, build_node(&node, scale)));
123 }
124 if changes.roots_changed || surface != self.surface {
125 let roots = app.semantics_roots();
126 let roots = node_ids(&roots);
127 let previous = std::mem::replace(&mut self.roots, roots.clone());
128 self.forget_dropped(&previous, &roots);
129 nodes.push((WINDOW_ROOT, self.window_node(roots, surface)));
130 }
131 TreeUpdate {
132 nodes,
133 tree: None,
134 tree_id: TreeId::ROOT,
135 focus: WINDOW_ROOT,
136 }
137 };
138 self.surface = surface;
139 update.focus = current_focus(app);
140 update
141 }
142
143 fn build_full(&mut self, app: &App, surface: (f64, f64), scale: f32) -> TreeUpdate {
146 let projected = app.semantics_nodes();
147 let roots: Vec<WidgetId> = projected
148 .iter()
149 .filter(|node| node.parent.is_none())
150 .map(|node| node.id)
151 .collect();
152 let mut nodes = Vec::with_capacity(projected.len() + 1);
153 let roots = node_ids(&roots);
154 self.roots = roots.clone();
155 nodes.push((WINDOW_ROOT, self.window_node(roots, surface)));
156 self.sent.clear();
157 for node in &projected {
158 let id = NodeId(node.id.as_u64());
159 let sent = Sent {
160 digest: digest(node, scale),
161 children: node_ids(&node.children),
162 };
163 self.sent.insert(id, sent);
164 nodes.push((id, build_node(node, scale)));
165 }
166 let mut tree = TreeInfo::new(WINDOW_ROOT);
167 tree.toolkit_name = Some("smudge".to_owned());
168 TreeUpdate {
169 nodes,
170 tree: Some(tree),
171 tree_id: TreeId::ROOT,
172 focus: WINDOW_ROOT,
173 }
174 }
175
176 fn forget_dropped(&mut self, previous: &[NodeId], current: &[NodeId]) {
180 if previous == current {
181 return;
182 }
183 let current: HashSet<NodeId> = current.iter().copied().collect();
184 let mut stack: Vec<NodeId> = previous
185 .iter()
186 .filter(|id| !current.contains(id))
187 .copied()
188 .collect();
189 while let Some(id) = stack.pop() {
190 if let Some(sent) = self.sent.remove(&id) {
191 stack.extend(sent.children);
192 }
193 }
194 }
195
196 fn window_node(&self, roots: Vec<NodeId>, (width, height): (f64, f64)) -> Node {
198 let mut window = Node::new(AccessKitRole::Window);
199 window.set_label(self.title.clone());
200 window.set_bounds(AccessKitRect {
201 x0: 0.0,
202 y0: 0.0,
203 x1: width,
204 y1: height,
205 });
206 window.set_children(roots);
207 window
208 }
209}
210
211fn digest(node: &SemanticsNode, scale: f32) -> u64 {
214 let mut hasher = DefaultHasher::new();
215 node.id.as_u64().hash(&mut hasher);
216 node.parent.map(WidgetId::as_u64).hash(&mut hasher);
217 for child in &node.children {
218 child.as_u64().hash(&mut hasher);
219 }
220 map_role(node.role).hash(&mut hasher);
221 node.label.hash(&mut hasher);
222 node.value.hash(&mut hasher);
223 let bounds = node.bounds;
224 for coordinate in [bounds.x0, bounds.y0, bounds.x1, bounds.y1, scale] {
225 coordinate.to_bits().hash(&mut hasher);
226 }
227 (node.focusable, node.disabled, node.focused, node.pressable).hash(&mut hasher);
228 hasher.finish()
229}
230
231fn build_node(node: &SemanticsNode, scale: f32) -> Node {
238 let mut out = Node::new(map_role(node.role));
239 if let Some(label) = &node.label {
240 out.set_label(label.clone());
241 }
242 if let Some(value) = &node.value {
243 out.set_value(value.clone());
244 }
245 out.set_bounds(scaled_bounds(node.bounds, scale));
246 if !node.children.is_empty() {
247 out.set_children(node_ids(&node.children));
248 }
249 if node.disabled {
250 out.set_disabled();
251 }
252 if node.pressable {
253 out.add_action(accesskit::Action::Click);
254 }
255 if node.focusable {
256 out.add_action(accesskit::Action::Focus);
257 }
258 out
259}
260
261fn node_ids(ids: &[WidgetId]) -> Vec<NodeId> {
263 ids.iter().map(|id| NodeId(id.as_u64())).collect()
264}
265
266fn current_focus(app: &App) -> NodeId {
272 match app.focused().filter(|id| app.semantics_node(*id).is_some()) {
273 Some(id) => NodeId(id.as_u64()),
274 None => WINDOW_ROOT,
275 }
276}
277
278fn map_role(role: Role) -> AccessKitRole {
280 match role {
281 Role::Window => AccessKitRole::Window,
282 Role::Group => AccessKitRole::Group,
283 Role::Button => AccessKitRole::Button,
284 Role::Label => AccessKitRole::Label,
285 Role::TextInput => AccessKitRole::TextInput,
286 Role::MultilineTextInput => AccessKitRole::MultilineTextInput,
287 Role::ScrollView => AccessKitRole::ScrollView,
288 Role::List => AccessKitRole::List,
289 Role::ListItem => AccessKitRole::ListItem,
290 _ => AccessKitRole::Group,
292 }
293}
294
295fn scaled_bounds(bounds: Rect, scale: f32) -> AccessKitRect {
297 AccessKitRect {
298 x0: f64::from((bounds.x0 * scale).floor()),
299 y0: f64::from((bounds.y0 * scale).floor()),
300 x1: f64::from((bounds.x1 * scale).ceil()),
301 y1: f64::from((bounds.y1 * scale).ceil()),
302 }
303}