Skip to main content

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