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 | /*! |
7 | This module contains the code moving the keyboard focus between items |
8 | */ |
9 | |
10 | use crate::item_tree::ItemTreeNodeArray; |
11 | |
12 | pub 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 | |
29 | pub 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 | |
40 | fn 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 | |
51 | pub 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)] |
63 | mod 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 | |