i_slint_core/
item_focus.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore nesw
5
6/*!
7This module contains the code moving the keyboard focus between items
8*/
9
10use crate::item_tree::ItemTreeNodeArray;
11
12pub fn step_out_of_node(
13    index: u32,
14    item_tree: &crate::item_tree::ItemTreeNodeArray,
15) -> Option<u32> {
16    let mut self_or_ancestor = index;
17    loop {
18        if let Some(sibling) = item_tree.next_sibling(self_or_ancestor) {
19            return Some(sibling);
20        }
21        if let Some(ancestor) = item_tree.parent(self_or_ancestor) {
22            self_or_ancestor = ancestor;
23        } else {
24            return None;
25        }
26    }
27}
28
29pub fn default_next_in_local_focus_chain(
30    index: u32,
31    item_tree: &crate::item_tree::ItemTreeNodeArray,
32) -> Option<u32> {
33    if let Some(child) = item_tree.first_child(index) {
34        return Some(child);
35    }
36
37    step_out_of_node(index, item_tree)
38}
39
40fn step_into_node(item_tree: &ItemTreeNodeArray, index: u32) -> u32 {
41    let mut node = index;
42    loop {
43        if let Some(last_child) = item_tree.last_child(node) {
44            node = last_child;
45        } else {
46            return node;
47        }
48    }
49}
50
51pub fn default_previous_in_local_focus_chain(
52    index: u32,
53    item_tree: &crate::item_tree::ItemTreeNodeArray,
54) -> Option<u32> {
55    if let Some(previous) = item_tree.previous_sibling(index) {
56        Some(step_into_node(item_tree, previous))
57    } else {
58        item_tree.parent(index)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use std::vec;
66
67    use crate::item_tree::ItemTreeNode;
68
69    fn validate_focus_chains(item_tree: ItemTreeNodeArray<'_>) {
70        let forward_chain = {
71            let mut tmp = alloc::vec::Vec::with_capacity(item_tree.node_count());
72            let mut node = 0;
73
74            loop {
75                tmp.push(node);
76                if let Some(next_node) = default_next_in_local_focus_chain(node, &item_tree) {
77                    node = next_node;
78                } else {
79                    break;
80                }
81            }
82            tmp
83        };
84        let reverse_backward_chain = {
85            let mut tmp = alloc::vec::Vec::with_capacity(item_tree.node_count());
86            let mut node = step_into_node(&item_tree, 0);
87
88            loop {
89                tmp.push(node);
90                if let Some(next_node) = default_previous_in_local_focus_chain(node, &item_tree) {
91                    node = next_node;
92                } else {
93                    break;
94                }
95            }
96            tmp.reverse();
97            tmp
98        };
99
100        assert_eq!(forward_chain, reverse_backward_chain);
101        assert_eq!(forward_chain.len(), item_tree.node_count());
102    }
103
104    #[test]
105    fn test_focus_chain_root_only() {
106        let nodes = vec![ItemTreeNode::Item {
107            is_accessible: false,
108            children_count: 0,
109            children_index: 1,
110            parent_index: 0,
111            item_array_index: 0,
112        }];
113
114        let tree: ItemTreeNodeArray = (nodes.as_slice()).into();
115        validate_focus_chains(tree);
116    }
117
118    #[test]
119    fn test_focus_chain_one_child() {
120        let nodes = vec![
121            ItemTreeNode::Item {
122                is_accessible: false,
123                children_count: 1,
124                children_index: 1,
125                parent_index: 0,
126                item_array_index: 0,
127            },
128            ItemTreeNode::Item {
129                is_accessible: false,
130                children_count: 0,
131                children_index: 2,
132                parent_index: 0,
133                item_array_index: 0,
134            },
135        ];
136
137        let tree: ItemTreeNodeArray = (nodes.as_slice()).into();
138        validate_focus_chains(tree);
139    }
140
141    #[test]
142    fn test_focus_chain_three_children() {
143        let nodes = vec![
144            ItemTreeNode::Item {
145                is_accessible: false,
146                children_count: 3,
147                children_index: 1,
148                parent_index: 0,
149                item_array_index: 0,
150            },
151            ItemTreeNode::Item {
152                is_accessible: false,
153                children_count: 0,
154                children_index: 4,
155                parent_index: 0,
156                item_array_index: 0,
157            },
158            ItemTreeNode::Item {
159                is_accessible: false,
160                children_count: 0,
161                children_index: 4,
162                parent_index: 0,
163                item_array_index: 0,
164            },
165            ItemTreeNode::Item {
166                is_accessible: false,
167                children_count: 0,
168                children_index: 4,
169                parent_index: 0,
170                item_array_index: 0,
171            },
172        ];
173
174        let tree: ItemTreeNodeArray = (nodes.as_slice()).into();
175        validate_focus_chains(tree);
176    }
177
178    #[test]
179    fn test_focus_chain_complex_tree() {
180        let nodes = vec![
181            ItemTreeNode::Item {
182                // 0
183                is_accessible: false,
184                children_count: 2,
185                children_index: 1,
186                parent_index: 0,
187                item_array_index: 0,
188            },
189            ItemTreeNode::Item {
190                // 1
191                is_accessible: false,
192                children_count: 2,
193                children_index: 3,
194                parent_index: 0,
195                item_array_index: 0,
196            },
197            ItemTreeNode::Item {
198                // 2
199                is_accessible: false,
200                children_count: 1,
201                children_index: 11,
202                parent_index: 0,
203                item_array_index: 0,
204            },
205            ItemTreeNode::Item {
206                // 3
207                is_accessible: false,
208                children_count: 1,
209                children_index: 5,
210                parent_index: 1,
211                item_array_index: 0,
212            },
213            ItemTreeNode::Item {
214                // 4
215                is_accessible: false,
216                children_count: 2,
217                children_index: 6,
218                parent_index: 1,
219                item_array_index: 0,
220            },
221            ItemTreeNode::Item {
222                // 5
223                is_accessible: false,
224                children_count: 0,
225                children_index: 0,
226                parent_index: 3,
227                item_array_index: 0,
228            },
229            ItemTreeNode::Item {
230                // 6
231                is_accessible: false,
232                children_count: 2,
233                children_index: 8,
234                parent_index: 4,
235                item_array_index: 0,
236            },
237            ItemTreeNode::Item {
238                // 7
239                is_accessible: false,
240                children_count: 1,
241                children_index: 10,
242                parent_index: 4,
243                item_array_index: 0,
244            },
245            ItemTreeNode::Item {
246                // 8
247                is_accessible: false,
248                children_count: 0,
249                children_index: 0,
250                parent_index: 6,
251                item_array_index: 0,
252            },
253            ItemTreeNode::Item {
254                // 9
255                is_accessible: false,
256                children_count: 0,
257                children_index: 0,
258                parent_index: 6,
259                item_array_index: 0,
260            },
261            ItemTreeNode::Item {
262                // 10
263                is_accessible: false,
264                children_count: 0,
265                children_index: 0,
266                parent_index: 7,
267                item_array_index: 0,
268            },
269            ItemTreeNode::Item {
270                // 11
271                is_accessible: false,
272                children_count: 2,
273                children_index: 12,
274                parent_index: 2,
275                item_array_index: 0,
276            },
277            ItemTreeNode::Item {
278                // 12
279                is_accessible: false,
280                children_count: 0,
281                children_index: 0,
282                parent_index: 11,
283                item_array_index: 0,
284            },
285            ItemTreeNode::Item {
286                // 13
287                is_accessible: false,
288                children_count: 0,
289                children_index: 0,
290                parent_index: 11,
291                item_array_index: 0,
292            },
293        ];
294
295        let tree: ItemTreeNodeArray = (nodes.as_slice()).into();
296        validate_focus_chains(tree);
297    }
298}