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: u32 = index;
17 loop {
18 if let Some(sibling: u32) = item_tree.next_sibling(index:self_or_ancestor) {
19 return Some(sibling);
20 }
21 if let Some(ancestor: u32) = item_tree.parent(index: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: u32) = 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: u32 = index;
42 loop {
43 if let Some(last_child: u32) = item_tree.last_child(index: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: u32) = item_tree.previous_sibling(index) {
56 Some(step_into_node(item_tree, index: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}
299