1use std::collections::HashSet;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct FocusId(pub u64);
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum FocusDirection {
16 Forward,
18 Backward,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum FocusResult {
25 Moved(FocusId),
27 Wrapped(FocusId),
29 NoFocusableElements,
31 Unchanged,
33}
34
35#[derive(Debug)]
41pub struct FocusManager {
42 current: Option<FocusId>,
44 focus_visible: bool,
46 registered: Vec<(FocusId, i32)>,
48 order: Vec<FocusId>,
50 id_set: HashSet<FocusId>,
52}
53
54impl Default for FocusManager {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl FocusManager {
61 pub fn new() -> Self {
63 Self {
64 current: None,
65 focus_visible: false,
66 registered: Vec::new(),
67 order: Vec::new(),
68 id_set: HashSet::new(),
69 }
70 }
71
72 pub fn current(&self) -> Option<FocusId> {
74 self.current
75 }
76
77 pub fn is_focus_visible(&self) -> bool {
79 self.focus_visible
80 }
81
82 pub fn set_focus_visible(&mut self, visible: bool) {
84 self.focus_visible = visible;
85 }
86
87 pub fn set_focus(&mut self, id: FocusId) {
89 self.current = Some(id);
90 }
91
92 pub fn clear_focus(&mut self) {
94 self.current = None;
95 }
96
97 pub fn register(&mut self, id: FocusId, tabindex: i32) {
104 if self.id_set.contains(&id) {
105 if let Some(entry) = self.registered.iter_mut().find(|(eid, _)| *eid == id) {
107 entry.1 = tabindex;
108 }
109 } else {
110 self.registered.push((id, tabindex));
111 self.id_set.insert(id);
112 }
113 }
114
115 pub fn unregister(&mut self, id: FocusId) {
117 self.registered.retain(|(eid, _)| *eid != id);
118 self.id_set.remove(&id);
119 self.order.retain(|eid| *eid != id);
120 if self.current == Some(id) {
121 self.current = None;
122 }
123 }
124
125 pub fn rebuild_order(&mut self) {
135 let mut navigable: Vec<(usize, FocusId, i32)> = self
137 .registered
138 .iter()
139 .enumerate()
140 .filter(|(_, (_, ti))| *ti >= 0)
141 .map(|(idx, (id, ti))| (idx, *id, *ti))
142 .collect();
143
144 navigable.sort_by(|a, b| {
147 match (a.2, b.2) {
148 (ta, tb) if ta > 0 && tb > 0 => ta.cmp(&tb),
149 (ta, _) if ta > 0 => std::cmp::Ordering::Less,
150 (_, tb) if tb > 0 => std::cmp::Ordering::Greater,
151 _ => a.0.cmp(&b.0),
153 }
154 });
155
156 self.order = navigable.into_iter().map(|(_, id, _)| id).collect();
157 }
158
159 pub fn navigate(&mut self, direction: FocusDirection) -> FocusResult {
163 if self.order.is_empty() {
164 return FocusResult::NoFocusableElements;
165 }
166
167 self.focus_visible = true;
168
169 let len = self.order.len();
170 let current_pos = self
171 .current
172 .and_then(|id| self.order.iter().position(|eid| *eid == id));
173
174 let (new_pos, wrapped) = match (current_pos, direction) {
175 (None, FocusDirection::Forward) => (0, false),
176 (None, FocusDirection::Backward) => (len - 1, false),
177 (Some(pos), FocusDirection::Forward) => {
178 if pos + 1 >= len {
179 (0, true)
180 } else {
181 (pos + 1, false)
182 }
183 }
184 (Some(pos), FocusDirection::Backward) => {
185 if pos == 0 {
186 (len - 1, true)
187 } else {
188 (pos - 1, false)
189 }
190 }
191 };
192
193 let new_id = self.order[new_pos];
194
195 if Some(new_id) == self.current && len == 1 {
197 return FocusResult::Unchanged;
198 }
199
200 self.current = Some(new_id);
201
202 if wrapped {
203 FocusResult::Wrapped(new_id)
204 } else {
205 FocusResult::Moved(new_id)
206 }
207 }
208}
209
210#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn new_manager_has_no_focus() {
220 let fm = FocusManager::new();
221 assert!(fm.current().is_none());
222 assert!(!fm.is_focus_visible());
223 }
224
225 #[test]
226 fn register_and_navigate_forward() {
227 let mut fm = FocusManager::new();
228 fm.register(FocusId(1), 0);
229 fm.register(FocusId(2), 0);
230 fm.register(FocusId(3), 0);
231 fm.rebuild_order();
232
233 let r1 = fm.navigate(FocusDirection::Forward);
234 assert_eq!(r1, FocusResult::Moved(FocusId(1)));
235 assert_eq!(fm.current(), Some(FocusId(1)));
236
237 let r2 = fm.navigate(FocusDirection::Forward);
238 assert_eq!(r2, FocusResult::Moved(FocusId(2)));
239
240 let r3 = fm.navigate(FocusDirection::Forward);
241 assert_eq!(r3, FocusResult::Moved(FocusId(3)));
242 }
243
244 #[test]
245 fn navigate_backward_wraps() {
246 let mut fm = FocusManager::new();
247 fm.register(FocusId(1), 0);
248 fm.register(FocusId(2), 0);
249 fm.rebuild_order();
250
251 fm.set_focus(FocusId(1));
253
254 let r = fm.navigate(FocusDirection::Backward);
255 assert_eq!(r, FocusResult::Wrapped(FocusId(2)));
256 assert_eq!(fm.current(), Some(FocusId(2)));
257 }
258
259 #[test]
260 fn tabindex_negative_skipped() {
261 let mut fm = FocusManager::new();
262 fm.register(FocusId(1), 0);
263 fm.register(FocusId(2), -1); fm.register(FocusId(3), 0);
265 fm.rebuild_order();
266
267 let r1 = fm.navigate(FocusDirection::Forward);
268 assert_eq!(r1, FocusResult::Moved(FocusId(1)));
269
270 let r2 = fm.navigate(FocusDirection::Forward);
271 assert_eq!(r2, FocusResult::Moved(FocusId(3)));
272
273 let r3 = fm.navigate(FocusDirection::Forward);
275 assert_eq!(r3, FocusResult::Wrapped(FocusId(1)));
276 }
277
278 #[test]
279 fn set_and_clear_focus() {
280 let mut fm = FocusManager::new();
281 fm.register(FocusId(7), 0);
282 fm.rebuild_order();
283
284 fm.set_focus(FocusId(7));
285 assert_eq!(fm.current(), Some(FocusId(7)));
286
287 fm.clear_focus();
288 assert!(fm.current().is_none());
289 }
290
291 #[test]
292 fn unregister_removes_from_order() {
293 let mut fm = FocusManager::new();
294 fm.register(FocusId(1), 0);
295 fm.register(FocusId(2), 0);
296 fm.register(FocusId(3), 0);
297 fm.rebuild_order();
298
299 fm.set_focus(FocusId(2));
300 fm.unregister(FocusId(2));
301
302 assert!(fm.current().is_none());
304
305 let r1 = fm.navigate(FocusDirection::Forward);
307 assert_eq!(r1, FocusResult::Moved(FocusId(1)));
308
309 let r2 = fm.navigate(FocusDirection::Forward);
310 assert_eq!(r2, FocusResult::Moved(FocusId(3)));
311 }
312
313 #[test]
314 fn navigate_empty_returns_no_focusable() {
315 let mut fm = FocusManager::new();
316 assert_eq!(
317 fm.navigate(FocusDirection::Forward),
318 FocusResult::NoFocusableElements,
319 );
320 }
321
322 #[test]
323 fn single_element_unchanged() {
324 let mut fm = FocusManager::new();
325 fm.register(FocusId(1), 0);
326 fm.rebuild_order();
327
328 fm.set_focus(FocusId(1));
329 let r = fm.navigate(FocusDirection::Forward);
330 assert_eq!(r, FocusResult::Unchanged);
331 }
332
333 #[test]
334 fn positive_tabindex_ordered_first() {
335 let mut fm = FocusManager::new();
336 fm.register(FocusId(10), 0);
337 fm.register(FocusId(20), 2);
338 fm.register(FocusId(30), 1);
339 fm.rebuild_order();
340
341 let r1 = fm.navigate(FocusDirection::Forward);
343 assert_eq!(r1, FocusResult::Moved(FocusId(30))); let r2 = fm.navigate(FocusDirection::Forward);
346 assert_eq!(r2, FocusResult::Moved(FocusId(20))); let r3 = fm.navigate(FocusDirection::Forward);
349 assert_eq!(r3, FocusResult::Moved(FocusId(10))); }
351}