1use super::*;
2
3impl<H: UiHost> UiTree<H> {
4 pub fn request_semantics_snapshot(&mut self) {
5 self.semantics_requested = true;
6 }
7
8 pub fn semantics_snapshot(&self) -> Option<&SemanticsSnapshot> {
9 self.semantics.as_deref()
10 }
11
12 pub fn semantics_snapshot_arc(&self) -> Option<Arc<SemanticsSnapshot>> {
13 self.semantics.clone()
14 }
15
16 pub(in crate::tree) fn refresh_semantics_snapshot(&mut self, app: &mut H) {
17 let Some(window) = self.window else {
18 self.semantics = None;
19 return;
20 };
21
22 let profile_semantics = crate::runtime_config::ui_runtime_config().semantics_profile;
23 let profile_started = profile_semantics.then(Instant::now);
24 let mut t_element_id_map: Option<Duration> = None;
25 let mut t_window_frame_children: Option<Duration> = None;
26 let mut t_traversal: Option<Duration> = None;
27 let mut t_relations: Option<Duration> = None;
28
29 let base_root = self
30 .base_layer
31 .and_then(|id| self.layers.get(id).map(|l| l.root));
32
33 let visible_layers: Vec<UiLayerId> = self.visible_layers_in_paint_order().collect();
34 if visible_layers.is_empty() {
35 self.semantics = Some(Arc::new(SemanticsSnapshot {
36 window,
37 ..SemanticsSnapshot::default()
38 }));
39 return;
40 }
41
42 let element_id_map = {
43 let started = profile_semantics.then(Instant::now);
44 let out = crate::declarative::frame::element_id_map_for_window(app, window);
45 if let Some(started) = started {
46 t_element_id_map = Some(started.elapsed());
47 }
48 out
49 };
50
51 let window_frame_children: slotmap::SecondaryMap<NodeId, Arc<[NodeId]>> = {
56 let started = profile_semantics.then(Instant::now);
57 let out = if self.view_cache_active() {
58 crate::declarative::with_window_frame(app, window, |window_frame| {
59 window_frame.map(|w| w.children.clone()).unwrap_or_default()
60 })
61 } else {
62 slotmap::SecondaryMap::new()
63 };
64 if let Some(started) = started {
65 t_window_frame_children = Some(started.elapsed());
66 }
67 out
68 };
69
70 let mut barrier_index: Option<usize> = None;
71 for (idx, layer) in visible_layers.iter().enumerate() {
72 if self.layers[*layer].blocks_underlay_input {
73 barrier_index = Some(idx);
74 }
75 }
76 let barrier_root = barrier_index.map(|idx| self.layers[visible_layers[idx]].root);
77
78 let mut focus_barrier_index: Option<usize> = None;
79 for (idx, layer) in visible_layers.iter().enumerate() {
80 if self.layers[*layer].blocks_underlay_focus {
81 focus_barrier_index = Some(idx);
82 }
83 }
84 let focus_barrier_root =
85 focus_barrier_index.map(|idx| self.layers[visible_layers[idx]].root);
86
87 let mut roots: Vec<SemanticsRoot> = Vec::with_capacity(visible_layers.len());
88 for (z, layer_id) in visible_layers.iter().enumerate() {
89 let layer = &self.layers[*layer_id];
90 roots.push(SemanticsRoot {
91 root: layer.root,
92 visible: layer.visible,
93 blocks_underlay_input: layer.blocks_underlay_input,
94 hit_testable: layer.hit_testable,
95 z_index: z as u32,
96 });
97 }
98
99 let focus = self.focus;
100 let captured = self.captured_for(PointerId(0));
101
102 let mut nodes: Vec<SemanticsNode> = Vec::with_capacity(self.nodes.len());
103
104 let traversal_started = profile_semantics.then(Instant::now);
105 for root in roots.iter().map(|r| r.root) {
106 let mut visited = self.take_scratch_semantics_visited();
107 visited.clear();
108 let mut stack = self.take_scratch_semantics_stack();
111 stack.clear();
112 stack.push((root, Transform2D::IDENTITY));
113 while let Some((id, before)) = stack.pop() {
114 if !visited.insert(id) {
115 if crate::strict_runtime::strict_runtime_enabled() {
116 panic!("cycle detected while building semantics snapshot: node={id:?}");
117 }
118 tracing::error!(?id, "cycle detected while building semantics snapshot");
119 continue;
120 }
121 let (
122 parent,
123 bounds,
124 children,
125 is_text_input,
126 is_focusable,
127 traverse_children,
128 before_child,
129 ) = {
130 let Some(node) = self.nodes.get(id) else {
131 continue;
132 };
133
134 if node.element.is_some()
142 && crate::declarative::frame::element_record_for_node(app, window, id)
143 .is_some_and(|record| {
144 matches!(
145 record.instance,
146 crate::declarative::frame::ElementInstance::InteractivityGate(p)
147 if !p.present
148 )
149 })
150 {
151 continue;
152 }
153 let widget = node.widget.as_ref();
154 if widget.is_some_and(|w| !w.semantics_present()) {
155 continue;
156 }
157
158 let prepaint = (!self.inspection_active && !node.invalidation.hit_test)
168 .then_some(node.prepaint_hit_test)
169 .flatten();
170
171 let node_transform = prepaint
172 .as_ref()
173 .and_then(|p| p.render_transform_inv)
174 .and_then(|inv| inv.inverse())
175 .or_else(|| {
176 widget
177 .and_then(|w| w.render_transform(node.bounds))
178 .filter(|t| t.inverse().is_some())
179 })
180 .unwrap_or(Transform2D::IDENTITY);
181 let at_node = before.compose(node_transform);
182 let bounds = rect_aabb_transformed(node.bounds, at_node);
183 let ui_children = node.children.clone();
184 let children = match window_frame_children.get(id) {
185 None => ui_children,
186 Some(frame_children) if ui_children.is_empty() => {
187 frame_children.as_ref().to_vec()
188 }
189 Some(frame_children) => {
190 let mut out = ui_children;
191 for &child in frame_children.iter() {
192 if !out.contains(&child) {
193 out.push(child);
194 }
195 }
196 out
197 }
198 };
199 let is_text_input = widget.is_some_and(|w| w.is_text_input());
200 let is_focusable = widget.is_some_and(|w| w.is_focusable());
201 let traverse_children = widget.map(|w| w.semantics_children()).unwrap_or(true);
202 let child_transform = prepaint
203 .as_ref()
204 .and_then(|p| p.children_render_transform_inv)
205 .and_then(|inv| inv.inverse())
206 .or_else(|| {
207 widget
208 .and_then(|w| w.children_render_transform(node.bounds))
209 .filter(|t| t.inverse().is_some())
210 })
211 .unwrap_or(Transform2D::IDENTITY);
212 let before_child = at_node.compose(child_transform);
213
214 (
215 node.parent,
216 bounds,
217 children,
218 is_text_input,
219 is_focusable,
220 traverse_children,
221 before_child,
222 )
223 };
224
225 let mut role = if Some(id) == base_root {
226 SemanticsRole::Window
227 } else {
228 SemanticsRole::Generic
229 };
230 if is_text_input {
233 role = SemanticsRole::TextField;
234 }
235
236 let mut flags = fret_core::SemanticsFlags {
237 focused: focus == Some(id),
238 captured: captured == Some(id),
239 ..fret_core::SemanticsFlags::default()
240 };
241
242 let mut active_descendant: Option<NodeId> = None;
243 let mut pos_in_set: Option<u32> = None;
244 let mut set_size: Option<u32> = None;
245 let mut label: Option<String> = None;
246 let mut value: Option<String> = None;
247 let mut extra = fret_core::SemanticsNodeExtra::default();
248 let mut test_id: Option<String> = None;
249 let mut text_selection: Option<(u32, u32)> = None;
250 let mut text_composition: Option<(u32, u32)> = None;
251 let mut labelled_by: Vec<NodeId> = Vec::new();
252 let mut described_by: Vec<NodeId> = Vec::new();
253 let mut controls: Vec<NodeId> = Vec::new();
254 let mut inline_spans: Vec<fret_core::SemanticsInlineSpan> = Vec::new();
255 let mut actions = fret_core::SemanticsActions {
256 focus: is_focusable || is_text_input,
257 invoke: false,
258 set_value: is_text_input,
259 decrement: false,
260 increment: false,
261 scroll_by: false,
262 set_text_selection: is_text_input,
263 };
264
265 if let Some(widget) = self.nodes.get_mut(id).and_then(|node| node.widget.as_mut()) {
267 let mut cx = SemanticsCx {
268 app,
269 node: id,
270 window: Some(window),
271 element_id_map: Some(element_id_map.as_ref()),
272 bounds,
273 children: children.as_slice(),
274 focus,
275 captured,
276 role: &mut role,
277 flags: &mut flags,
278 label: &mut label,
279 value: &mut value,
280 test_id: &mut test_id,
281 extra: &mut extra,
282 text_selection: &mut text_selection,
283 text_composition: &mut text_composition,
284 actions: &mut actions,
285 active_descendant: &mut active_descendant,
286 pos_in_set: &mut pos_in_set,
287 set_size: &mut set_size,
288 labelled_by: &mut labelled_by,
289 described_by: &mut described_by,
290 controls: &mut controls,
291 inline_spans: &mut inline_spans,
292 };
293 widget.semantics(&mut cx);
294 }
295
296 if (role == SemanticsRole::Slider
302 || role == SemanticsRole::SpinButton
303 || role == SemanticsRole::Splitter)
304 && (actions.increment || actions.decrement)
305 {
306 let numeric = extra.numeric;
307 let has_range = numeric.min.is_some() && numeric.max.is_some();
308 let has_value = numeric.value.is_some();
309 let has_step = numeric.step.is_some_and(|v| v.is_finite() && v > 0.0);
310 actions.set_value = has_range && has_value && has_step;
311 } else if role == SemanticsRole::Slider
312 || role == SemanticsRole::SpinButton
313 || role == SemanticsRole::Splitter
314 {
315 actions.set_value = false;
316 }
317
318 if pos_in_set.is_some_and(|p| p == 0) {
319 pos_in_set = None;
320 }
321 if set_size.is_some_and(|s| s == 0) {
322 set_size = None;
323 }
324 if let (Some(pos), Some(size)) = (pos_in_set, set_size)
325 && pos > size
326 {
327 pos_in_set = None;
328 set_size = None;
329 }
330
331 nodes.push(SemanticsNode {
332 id,
333 parent,
334 role,
335 bounds,
336 flags,
337 test_id,
338 active_descendant,
339 pos_in_set,
340 set_size,
341 label,
342 value,
343 extra,
344 text_selection,
345 text_composition,
346 actions,
347 labelled_by,
348 described_by,
349 controls,
350 inline_spans,
351 });
352
353 if traverse_children {
354 for &child in children.iter().rev() {
356 stack.push((child, before_child));
357 }
358 }
359 }
360
361 visited.clear();
362 stack.clear();
363 self.restore_scratch_semantics_visited(visited);
364 self.restore_scratch_semantics_stack(stack);
365 }
366 if let Some(started) = traversal_started {
367 t_traversal = Some(started.elapsed());
368 }
369
370 let relations_started = profile_semantics.then(Instant::now);
375 let mut index_by_id: HashMap<NodeId, usize> = HashMap::with_capacity(nodes.len());
376 for (idx, node) in nodes.iter().enumerate() {
377 index_by_id.insert(node.id, idx);
378 }
379 for idx in 0..nodes.len() {
380 let controlled = nodes[idx].id;
381 let controlled_role = nodes[idx].role;
382 let controllers = nodes[idx].labelled_by.clone();
383 for controller in controllers {
384 if let Some(&controller_idx) = index_by_id.get(&controller) {
385 let controller_role = nodes[controller_idx].role;
386 let derive = matches!(
387 controlled_role,
388 SemanticsRole::TabPanel | SemanticsRole::ListBox
389 ) && matches!(
390 controller_role,
391 SemanticsRole::Tab
392 | SemanticsRole::TextField
393 | SemanticsRole::ComboBox
394 | SemanticsRole::Button
395 );
396 if !derive {
397 continue;
398 }
399 if !nodes[controller_idx].controls.contains(&controlled) {
400 nodes[controller_idx].controls.push(controlled);
401 }
402 }
403 }
404 }
405 if let Some(started) = relations_started {
406 t_relations = Some(started.elapsed());
407 }
408
409 let nodes_len = nodes.len();
410 self.semantics = Some(Arc::new(SemanticsSnapshot {
411 window,
412 roots,
413 barrier_root,
414 focus_barrier_root,
415 focus,
416 captured,
417 nodes,
418 }));
419
420 if let Some(snapshot) = self.semantics.as_deref() {
421 semantics::validate_semantics_if_enabled(snapshot);
422 }
423
424 if let Some(started) = profile_started {
425 let total = started.elapsed();
426 tracing::info!(
427 window = ?window,
428 view_cache_active = self.view_cache_active(),
429 nodes = nodes_len,
430 total_ms = total.as_millis(),
431 element_id_map_ms = t_element_id_map.map(|d| d.as_millis()),
432 window_frame_children_ms = t_window_frame_children.map(|d| d.as_millis()),
433 traversal_ms = t_traversal.map(|d| d.as_millis()),
434 relations_ms = t_relations.map(|d| d.as_millis()),
435 "semantics snapshot built"
436 );
437 }
438 }
439
440 pub(in crate::tree) fn node_root(&self, mut node: NodeId) -> Option<NodeId> {
441 while let Some(parent) = self.nodes.get(node).and_then(|n| n.parent) {
442 node = parent;
443 }
444 self.nodes.contains_key(node).then_some(node)
445 }
446
447 pub fn is_descendant(&self, root: NodeId, mut node: NodeId) -> bool {
448 if root == node {
449 return true;
450 }
451 while let Some(parent) = self.nodes.get(node).and_then(|n| n.parent) {
452 if parent == root {
453 return true;
454 }
455 node = parent;
456 }
457 false
458 }
459}