1use crossterm::event::{KeyCode, KeyEvent};
2
3pub struct FocusRing {
27 focused: usize,
28 len: usize,
29 wrap: bool,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum FocusOutcome {
35 FocusChanged,
37 Unchanged,
39 Ignored,
41}
42
43impl FocusRing {
44 pub fn new(len: usize) -> Self {
48 Self {
49 focused: 0,
50 len,
51 wrap: true,
52 }
53 }
54
55 pub fn without_wrap(mut self) -> Self {
58 self.wrap = false;
59 self
60 }
61
62 pub fn focused(&self) -> usize {
64 self.focused
65 }
66
67 pub fn is_focused(&self, index: usize) -> bool {
69 self.focused == index
70 }
71
72 pub fn len(&self) -> usize {
74 self.len
75 }
76
77 pub fn is_empty(&self) -> bool {
79 self.len == 0
80 }
81
82 pub fn set_len(&mut self, len: usize) {
85 self.len = len;
86 if len == 0 {
87 self.focused = 0;
88 } else if self.focused >= len {
89 self.focused = len - 1;
90 }
91 }
92
93 pub fn focus(&mut self, index: usize) -> bool {
96 if index < self.len {
97 self.focused = index;
98 true
99 } else {
100 false
101 }
102 }
103
104 pub fn focus_next(&mut self) -> bool {
106 if self.len == 0 {
107 return false;
108 }
109 if self.focused + 1 < self.len {
110 self.focused += 1;
111 true
112 } else if self.wrap {
113 self.focused = 0;
114 true
115 } else {
116 false
117 }
118 }
119
120 pub fn focus_prev(&mut self) -> bool {
122 if self.len == 0 {
123 return false;
124 }
125 if self.focused > 0 {
126 self.focused -= 1;
127 true
128 } else if self.wrap {
129 self.focused = self.len - 1;
130 true
131 } else {
132 false
133 }
134 }
135
136 pub fn handle_key(&mut self, key_event: KeyEvent) -> FocusOutcome {
142 match key_event.code {
143 KeyCode::Tab => {
144 if self.focus_next() {
145 FocusOutcome::FocusChanged
146 } else {
147 FocusOutcome::Unchanged
148 }
149 }
150 KeyCode::BackTab => {
151 if self.focus_prev() {
152 FocusOutcome::FocusChanged
153 } else {
154 FocusOutcome::Unchanged
155 }
156 }
157 _ => FocusOutcome::Ignored,
158 }
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
166
167 fn key(code: KeyCode) -> KeyEvent {
168 KeyEvent {
169 code,
170 modifiers: KeyModifiers::empty(),
171 kind: KeyEventKind::Press,
172 state: KeyEventState::empty(),
173 }
174 }
175
176 #[test]
177 fn new_starts_at_zero() {
178 let ring = FocusRing::new(3);
179 assert_eq!(ring.focused(), 0);
180 assert!(ring.is_focused(0));
181 assert!(!ring.is_focused(1));
182 assert_eq!(ring.len(), 3);
183 assert!(!ring.is_empty());
184 }
185
186 #[test]
187 fn cycle_forward_wraps() {
188 let mut ring = FocusRing::new(3);
189 assert!(ring.focus_next());
190 assert_eq!(ring.focused(), 1);
191 assert!(ring.focus_next());
192 assert_eq!(ring.focused(), 2);
193 assert!(ring.focus_next());
194 assert_eq!(ring.focused(), 0); }
196
197 #[test]
198 fn cycle_backward_wraps() {
199 let mut ring = FocusRing::new(3);
200 assert!(ring.focus_prev());
201 assert_eq!(ring.focused(), 2); assert!(ring.focus_prev());
203 assert_eq!(ring.focused(), 1);
204 assert!(ring.focus_prev());
205 assert_eq!(ring.focused(), 0);
206 }
207
208 #[test]
209 fn no_wrap_stops_at_boundaries() {
210 let mut ring = FocusRing::new(3).without_wrap();
211
212 assert!(!ring.focus_prev());
214 assert_eq!(ring.focused(), 0);
215
216 assert!(ring.focus_next());
218 assert!(ring.focus_next());
219 assert_eq!(ring.focused(), 2);
220
221 assert!(!ring.focus_next());
223 assert_eq!(ring.focused(), 2);
224 }
225
226 #[test]
227 fn empty_ring_is_noop() {
228 let mut ring = FocusRing::new(0);
229 assert!(ring.is_empty());
230 assert_eq!(ring.focused(), 0);
231 assert!(!ring.focus_next());
232 assert!(!ring.focus_prev());
233 assert!(!ring.focus(0));
234 }
235
236 #[test]
237 fn programmatic_focus() {
238 let mut ring = FocusRing::new(5);
239 assert!(ring.focus(3));
240 assert_eq!(ring.focused(), 3);
241 assert!(ring.is_focused(3));
242
243 assert!(!ring.focus(5));
245 assert_eq!(ring.focused(), 3); }
247
248 #[test]
249 fn set_len_clamps_focused() {
250 let mut ring = FocusRing::new(5);
251 ring.focus(4);
252 assert_eq!(ring.focused(), 4);
253
254 ring.set_len(3);
255 assert_eq!(ring.len(), 3);
256 assert_eq!(ring.focused(), 2); ring.set_len(0);
259 assert_eq!(ring.focused(), 0);
260 assert!(ring.is_empty());
261 }
262
263 #[test]
264 fn set_len_preserves_focused_when_in_range() {
265 let mut ring = FocusRing::new(5);
266 ring.focus(2);
267 ring.set_len(4);
268 assert_eq!(ring.focused(), 2); }
270
271 #[test]
272 fn handle_key_tab_cycles_forward() {
273 let mut ring = FocusRing::new(3);
274 assert_eq!(
275 ring.handle_key(key(KeyCode::Tab)),
276 FocusOutcome::FocusChanged
277 );
278 assert_eq!(ring.focused(), 1);
279 }
280
281 #[test]
282 fn handle_key_backtab_cycles_backward() {
283 let mut ring = FocusRing::new(3);
284 ring.focus(1);
285 assert_eq!(
286 ring.handle_key(key(KeyCode::BackTab)),
287 FocusOutcome::FocusChanged
288 );
289 assert_eq!(ring.focused(), 0);
290 }
291
292 #[test]
293 fn handle_key_other_keys_ignored() {
294 let mut ring = FocusRing::new(3);
295 assert_eq!(ring.handle_key(key(KeyCode::Enter)), FocusOutcome::Ignored);
296 assert_eq!(
297 ring.handle_key(key(KeyCode::Char('a'))),
298 FocusOutcome::Ignored
299 );
300 assert_eq!(ring.focused(), 0); }
302
303 #[test]
304 fn handle_key_no_wrap_returns_unchanged() {
305 let mut ring = FocusRing::new(2).without_wrap();
306 assert_eq!(
308 ring.handle_key(key(KeyCode::BackTab)),
309 FocusOutcome::Unchanged
310 );
311 assert_eq!(ring.focused(), 0);
312
313 ring.focus(1);
315 assert_eq!(ring.handle_key(key(KeyCode::Tab)), FocusOutcome::Unchanged);
316 assert_eq!(ring.focused(), 1);
317 }
318
319 #[test]
320 fn single_item_wrap_returns_true() {
321 let mut ring = FocusRing::new(1);
324 assert!(ring.focus_next());
325 assert_eq!(ring.focused(), 0);
326 assert!(ring.focus_prev());
327 assert_eq!(ring.focused(), 0);
328 }
329
330 #[test]
331 fn single_item_no_wrap() {
332 let mut ring = FocusRing::new(1).without_wrap();
333 assert!(!ring.focus_next());
334 assert!(!ring.focus_prev());
335 assert_eq!(ring.focused(), 0);
336 }
337}