| 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 | |