1pub type FocusId = u32;
5
6pub struct FocusManager {
8 focusable: Vec<FocusId>,
9 current: Option<usize>,
10}
11
12impl FocusManager {
13 pub fn new() -> Self {
14 Self {
15 focusable: Vec::new(),
16 current: None,
17 }
18 }
19
20 pub fn register(&mut self, id: FocusId) {
21 if !self.focusable.contains(&id) {
22 self.focusable.push(id);
23 if self.current.is_none() {
24 self.current = Some(0);
25 }
26 }
27 }
28
29 pub fn unregister(&mut self, id: FocusId) {
30 if let Some(pos) = self.focusable.iter().position(|&x| x == id) {
31 let current = self.normalized_current();
32 self.focusable.remove(pos);
33 if self.focusable.is_empty() {
34 self.current = None;
35 } else if let Some(cur) = current {
36 if pos < cur {
37 self.current = Some(cur - 1);
38 } else if cur >= self.focusable.len() {
39 self.current = Some(self.focusable.len() - 1);
40 }
41 }
42 }
43 }
44
45 pub fn focus_next(&mut self) {
46 if self.focusable.is_empty() {
47 return;
48 }
49 self.current = Some(match self.current {
50 Some(_) => {
51 let current = self.normalized_current().unwrap_or(0);
52 if current + 1 == self.focusable.len() {
53 0
54 } else {
55 current + 1
56 }
57 }
58 None => 0,
59 });
60 }
61
62 pub fn focus_prev(&mut self) {
63 if self.focusable.is_empty() {
64 return;
65 }
66 self.current = Some(match self.current {
67 Some(_) => self
68 .normalized_current()
69 .and_then(|idx| idx.checked_sub(1))
70 .unwrap_or_else(|| self.focusable.len() - 1),
71 None => 0,
72 });
73 }
74
75 pub fn focus(&mut self, id: FocusId) {
76 if let Some(pos) = self.focusable.iter().position(|&x| x == id) {
77 self.current = Some(pos);
78 }
79 }
80
81 pub fn is_focused(&self, id: FocusId) -> bool {
82 match self.normalized_current() {
83 Some(idx) => self.focusable.get(idx) == Some(&id),
84 None => false,
85 }
86 }
87
88 pub fn current(&self) -> Option<FocusId> {
89 self.normalized_current()
90 .and_then(|idx| self.focusable.get(idx).copied())
91 }
92
93 pub fn clear(&mut self) {
94 self.focusable.clear();
95 self.current = None;
96 }
97
98 fn normalized_current(&self) -> Option<usize> {
99 self.current
100 .map(|idx| idx.min(self.focusable.len().saturating_sub(1)))
101 }
102}
103
104impl Default for FocusManager {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn register_sets_initial_focus() {
116 let mut fm = FocusManager::new();
117 fm.register(1);
118 assert_eq!(fm.current(), Some(1));
119 assert!(fm.is_focused(1));
120 }
121
122 #[test]
123 fn focus_next_cycles() {
124 let mut fm = FocusManager::new();
125 fm.register(1);
126 fm.register(2);
127 fm.register(3);
128 assert_eq!(fm.current(), Some(1));
129 fm.focus_next();
130 assert_eq!(fm.current(), Some(2));
131 fm.focus_next();
132 assert_eq!(fm.current(), Some(3));
133 fm.focus_next();
134 assert_eq!(fm.current(), Some(1));
135 }
136
137 #[test]
138 fn focus_prev_cycles() {
139 let mut fm = FocusManager::new();
140 fm.register(1);
141 fm.register(2);
142 fm.register(3);
143 fm.focus_prev();
144 assert_eq!(fm.current(), Some(3));
145 fm.focus_prev();
146 assert_eq!(fm.current(), Some(2));
147 }
148
149 #[test]
150 fn unregister_adjusts_focus() {
151 let mut fm = FocusManager::new();
152 fm.register(1);
153 fm.register(2);
154 fm.register(3);
155 fm.focus_next();
156 fm.focus_next();
157 assert_eq!(fm.current(), Some(3));
158 fm.unregister(3);
159 assert_eq!(fm.current(), Some(2));
160 }
161
162 #[test]
163 fn unregister_before_current_preserves_focused_id() {
164 let mut fm = FocusManager::new();
165 fm.register(1);
166 fm.register(2);
167 fm.register(3);
168 fm.focus(2);
169
170 fm.unregister(1);
171
172 assert_eq!(fm.current(), Some(2));
173 assert!(fm.is_focused(2));
174 }
175
176 #[test]
177 fn unregister_all_clears_focus() {
178 let mut fm = FocusManager::new();
179 fm.register(1);
180 fm.unregister(1);
181 assert_eq!(fm.current(), None);
182 }
183
184 #[test]
185 fn focus_specific_id() {
186 let mut fm = FocusManager::new();
187 fm.register(10);
188 fm.register(20);
189 fm.register(30);
190 fm.focus(20);
191 assert!(fm.is_focused(20));
192 assert!(!fm.is_focused(10));
193 }
194
195 #[test]
196 fn clear_removes_all() {
197 let mut fm = FocusManager::new();
198 fm.register(1);
199 fm.register(2);
200 fm.clear();
201 assert_eq!(fm.current(), None);
202 }
203
204 #[test]
205 fn focus_next_on_empty_is_noop() {
206 let mut fm = FocusManager::new();
207 fm.focus_next();
208 assert_eq!(fm.current(), None);
209 }
210
211 #[test]
212 fn stale_current_is_normalized_for_queries() {
213 let mut fm = FocusManager::new();
214 fm.register(1);
215 fm.register(2);
216 fm.register(3);
217 fm.current = Some(usize::MAX);
218
219 assert_eq!(fm.current(), Some(3));
220 assert!(fm.is_focused(3));
221 }
222
223 #[test]
224 fn focus_next_normalizes_stale_current() {
225 let mut fm = FocusManager::new();
226 fm.register(1);
227 fm.register(2);
228 fm.register(3);
229 fm.current = Some(usize::MAX);
230
231 fm.focus_next();
232
233 assert_eq!(fm.current(), Some(1));
234 }
235
236 #[test]
237 fn focus_prev_normalizes_stale_current() {
238 let mut fm = FocusManager::new();
239 fm.register(1);
240 fm.register(2);
241 fm.register(3);
242 fm.current = Some(usize::MAX);
243
244 fm.focus_prev();
245
246 assert_eq!(fm.current(), Some(2));
247 }
248
249 #[test]
250 fn unregister_normalizes_stale_current() {
251 let mut fm = FocusManager::new();
252 fm.register(1);
253 fm.register(2);
254 fm.register(3);
255 fm.current = Some(usize::MAX);
256
257 fm.unregister(1);
258
259 assert_eq!(fm.current(), Some(3));
260 }
261}