Skip to main content

fret_ui/tree/ui_tree_mutation/
focus.rs

1use super::super::*;
2
3impl<H: UiHost> UiTree<H> {
4    pub fn children(&self, parent: NodeId) -> Vec<NodeId> {
5        self.nodes
6            .get(parent)
7            .map(|n| n.children.clone())
8            .unwrap_or_default()
9    }
10
11    pub(crate) fn children_ref(&self, parent: NodeId) -> &[NodeId] {
12        self.nodes
13            .get(parent)
14            .map(|n| n.children.as_slice())
15            .unwrap_or(&[])
16    }
17
18    /// Best-effort repair pass for parent pointers based on child edges from layer roots.
19    ///
20    /// Parent pointers are used for cache-root discovery (`nearest_view_cache_root`) and for
21    /// determining whether nodes are attached to any layer (`node_layer`). If a bug or GC edge
22    /// case leaves a reachable node with a missing/incorrect `parent`, this can cascade into
23    /// incorrect invalidation truncation and overly-aggressive subtree sweeping.
24    ///
25    /// This intentionally only walks nodes reachable from the installed layer roots; it does not
26    /// attempt to "rescue" detached islands.
27    pub(crate) fn repair_parent_pointers_from_layer_roots(&mut self) -> u32 {
28        let roots = self.all_layer_roots();
29        if roots.is_empty() {
30            return 0;
31        }
32
33        let mut repaired: u32 = 0;
34        let mut visited: HashSet<NodeId> = HashSet::new();
35        let mut stack: Vec<(Option<NodeId>, NodeId)> = Vec::with_capacity(roots.len());
36        for root in roots {
37            stack.push((None, root));
38        }
39
40        while let Some((expected_parent, node)) = stack.pop() {
41            if !visited.insert(node) {
42                continue;
43            }
44
45            let (current_parent, children) = match self.nodes.get(node) {
46                Some(n) => (n.parent, n.children.clone()),
47                None => continue,
48            };
49
50            if current_parent != expected_parent
51                && let Some(n) = self.nodes.get_mut(node)
52            {
53                n.parent = expected_parent;
54                repaired = repaired.saturating_add(1);
55            }
56
57            for child in children {
58                stack.push((Some(node), child));
59            }
60        }
61
62        repaired
63    }
64
65    pub fn node_parent(&self, node: NodeId) -> Option<NodeId> {
66        self.nodes.get(node).and_then(|n| n.parent)
67    }
68
69    pub fn debug_node_measured_size(&self, node: NodeId) -> Option<Size> {
70        self.nodes.get(node).map(|n| n.measured_size)
71    }
72
73    /// Debug helper for mapping a `NodeId` back to the declarative `ElementInstance` kind (when
74    /// the node is driven by the declarative renderer).
75    pub fn debug_declarative_instance_kind(
76        &self,
77        app: &mut H,
78        window: AppWindowId,
79        node: NodeId,
80    ) -> Option<&'static str> {
81        crate::declarative::element_record_for_node(app, window, node)
82            .map(|record| record.instance.kind_name())
83    }
84
85    pub fn first_focusable_ancestor_including_declarative(
86        &self,
87        app: &mut H,
88        window: AppWindowId,
89        start: NodeId,
90    ) -> Option<NodeId> {
91        let mut node = Some(start);
92        while let Some(id) = node {
93            let focusable = if let Some(record) =
94                crate::declarative::element_record_for_node(app, window, id)
95            {
96                match &record.instance {
97                    crate::declarative::ElementInstance::TextInput(_) => true,
98                    crate::declarative::ElementInstance::TextArea(_) => true,
99                    crate::declarative::ElementInstance::TextInputRegion(_) => true,
100                    crate::declarative::ElementInstance::Pressable(p) => p.enabled && p.focusable,
101                    _ => false,
102                }
103            } else {
104                self.nodes
105                    .get(id)
106                    .and_then(|n| n.widget.as_ref())
107                    .is_some_and(|w| w.is_focusable())
108            };
109
110            if focusable {
111                return Some(id);
112            }
113
114            node = self.nodes.get(id).and_then(|n| n.parent);
115        }
116        None
117    }
118
119    pub fn first_focusable_descendant(&self, root: NodeId) -> Option<NodeId> {
120        let mut stack = vec![root];
121        while let Some(id) = stack.pop() {
122            let focusable = self
123                .nodes
124                .get(id)
125                .and_then(|n| n.widget.as_ref())
126                .is_some_and(|w| w.is_focusable());
127            if focusable {
128                return Some(id);
129            }
130
131            if let Some(node) = self.nodes.get(id) {
132                let traverse_children = node
133                    .widget
134                    .as_ref()
135                    .map(|w| w.focus_traversal_children())
136                    .unwrap_or(true);
137                if traverse_children {
138                    for &child in node.children.iter().rev() {
139                        stack.push(child);
140                    }
141                }
142            }
143        }
144        None
145    }
146
147    /// Like `first_focusable_descendant`, but also considers declarative element instances that
148    /// haven't run layout yet.
149    ///
150    /// This is needed because declarative nodes derive focusability from their element instance
151    /// (`PressableProps.focusable`, `TextInput`, ...), and the `ElementHostWidget` only caches that
152    /// information during layout. Overlay policies commonly want to set initial focus immediately
153    /// after installing an overlay root, before layout runs.
154    pub fn first_focusable_descendant_including_declarative(
155        &self,
156        app: &mut H,
157        window: AppWindowId,
158        root: NodeId,
159    ) -> Option<NodeId> {
160        let mut stack = vec![root];
161        while let Some(id) = stack.pop() {
162            let (focusable, traverse_children) = if let Some(record) =
163                crate::declarative::element_record_for_node(app, window, id)
164            {
165                let focusable = match &record.instance {
166                    crate::declarative::ElementInstance::TextInput(_) => true,
167                    crate::declarative::ElementInstance::TextArea(_) => true,
168                    crate::declarative::ElementInstance::TextInputRegion(_) => true,
169                    crate::declarative::ElementInstance::Pressable(p) => p.enabled && p.focusable,
170                    crate::declarative::ElementInstance::Semantics(p) => {
171                        p.focusable && !p.disabled && !p.hidden
172                    }
173                    _ => false,
174                };
175                let traverse_children = match &record.instance {
176                    crate::declarative::ElementInstance::Pressable(p) => p.enabled,
177                    crate::declarative::ElementInstance::InteractivityGate(p) => {
178                        p.present && p.interactive
179                    }
180                    crate::declarative::ElementInstance::Spinner(_) => false,
181                    _ => true,
182                };
183                (focusable, traverse_children)
184            } else {
185                let traverse_children = self
186                    .nodes
187                    .get(id)
188                    .and_then(|n| n.widget.as_ref())
189                    .map(|w| w.focus_traversal_children())
190                    .unwrap_or(true);
191                let focusable = self
192                    .nodes
193                    .get(id)
194                    .and_then(|n| n.widget.as_ref())
195                    .is_some_and(|w| w.is_focusable());
196                (focusable, traverse_children)
197            };
198
199            if focusable {
200                return Some(id);
201            }
202
203            if traverse_children && let Some(node) = self.nodes.get(id) {
204                for &child in node.children.iter().rev() {
205                    stack.push(child);
206                }
207            }
208        }
209        None
210    }
211
212    /// Like `first_focusable_descendant_including_declarative`, but treats `InteractivityGate`
213    /// as a *pointer/activation* gate, not a traversal boundary for initial focus.
214    ///
215    /// This is useful for overlay autofocus policies where content may be temporarily
216    /// non-interactive (e.g. during motion) but still present and should be eligible for focus.
217    pub fn first_focusable_descendant_including_declarative_present_only(
218        &self,
219        app: &mut H,
220        window: AppWindowId,
221        root: NodeId,
222    ) -> Option<NodeId> {
223        let mut stack = vec![root];
224        while let Some(id) = stack.pop() {
225            let (focusable, traverse_children) = if let Some(record) =
226                crate::declarative::element_record_for_node(app, window, id)
227            {
228                let focusable = match &record.instance {
229                    crate::declarative::ElementInstance::TextInput(_) => true,
230                    crate::declarative::ElementInstance::TextArea(_) => true,
231                    crate::declarative::ElementInstance::TextInputRegion(_) => true,
232                    crate::declarative::ElementInstance::Pressable(p) => p.enabled && p.focusable,
233                    crate::declarative::ElementInstance::Semantics(p) => {
234                        p.focusable && !p.disabled && !p.hidden
235                    }
236                    _ => false,
237                };
238                let traverse_children = match &record.instance {
239                    crate::declarative::ElementInstance::Pressable(p) => p.enabled,
240                    crate::declarative::ElementInstance::InteractivityGate(p) => p.present,
241                    crate::declarative::ElementInstance::Spinner(_) => false,
242                    _ => true,
243                };
244                (focusable, traverse_children)
245            } else {
246                let traverse_children = self
247                    .nodes
248                    .get(id)
249                    .and_then(|n| n.widget.as_ref())
250                    .map(|w| w.focus_traversal_children())
251                    .unwrap_or(true);
252                let focusable = self
253                    .nodes
254                    .get(id)
255                    .and_then(|n| n.widget.as_ref())
256                    .is_some_and(|w| w.is_focusable());
257                (focusable, traverse_children)
258            };
259
260            if focusable {
261                return Some(id);
262            }
263
264            if traverse_children && let Some(node) = self.nodes.get(id) {
265                for &child in node.children.iter().rev() {
266                    stack.push(child);
267                }
268            }
269        }
270        None
271    }
272}