1use crossterm::event::{KeyCode, KeyEvent};
2
3#[doc = include_str!("docs/focus_ring.md")]
4pub struct FocusRing {
5 focused: usize,
6 len: usize,
7 wrap: bool,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum FocusOutcome {
13 FocusChanged,
15 Unchanged,
17 Ignored,
19}
20
21impl FocusRing {
22 pub fn new(len: usize) -> Self {
26 Self { focused: 0, len, wrap: true }
27 }
28
29 pub fn without_wrap(mut self) -> Self {
32 self.wrap = false;
33 self
34 }
35
36 pub fn focused(&self) -> usize {
38 self.focused
39 }
40
41 pub fn is_focused(&self, index: usize) -> bool {
43 self.focused == index
44 }
45
46 pub fn len(&self) -> usize {
48 self.len
49 }
50
51 pub fn is_empty(&self) -> bool {
53 self.len == 0
54 }
55
56 pub fn set_len(&mut self, len: usize) {
59 self.len = len;
60 if len == 0 {
61 self.focused = 0;
62 } else if self.focused >= len {
63 self.focused = len - 1;
64 }
65 }
66
67 pub fn focus(&mut self, index: usize) -> bool {
70 if index < self.len {
71 self.focused = index;
72 true
73 } else {
74 false
75 }
76 }
77
78 pub fn focus_next(&mut self) -> bool {
80 if self.len == 0 {
81 return false;
82 }
83 if self.focused + 1 < self.len {
84 self.focused += 1;
85 true
86 } else if self.wrap {
87 self.focused = 0;
88 true
89 } else {
90 false
91 }
92 }
93
94 pub fn focus_prev(&mut self) -> bool {
96 if self.len == 0 {
97 return false;
98 }
99 if self.focused > 0 {
100 self.focused -= 1;
101 true
102 } else if self.wrap {
103 self.focused = self.len - 1;
104 true
105 } else {
106 false
107 }
108 }
109
110 pub fn handle_key(&mut self, key_event: KeyEvent) -> FocusOutcome {
116 match key_event.code {
117 KeyCode::Tab => {
118 if self.focus_next() {
119 FocusOutcome::FocusChanged
120 } else {
121 FocusOutcome::Unchanged
122 }
123 }
124 KeyCode::BackTab => {
125 if self.focus_prev() {
126 FocusOutcome::FocusChanged
127 } else {
128 FocusOutcome::Unchanged
129 }
130 }
131 _ => FocusOutcome::Ignored,
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
140
141 fn key(code: KeyCode) -> KeyEvent {
142 KeyEvent { code, modifiers: KeyModifiers::empty(), kind: KeyEventKind::Press, state: KeyEventState::empty() }
143 }
144
145 #[test]
146 fn new_starts_at_zero() {
147 let ring = FocusRing::new(3);
148 assert_eq!(ring.focused(), 0);
149 assert!(ring.is_focused(0));
150 assert!(!ring.is_focused(1));
151 assert_eq!(ring.len(), 3);
152 assert!(!ring.is_empty());
153 }
154
155 #[test]
156 fn cycle_forward_wraps() {
157 let mut ring = FocusRing::new(3);
158 assert!(ring.focus_next());
159 assert_eq!(ring.focused(), 1);
160 assert!(ring.focus_next());
161 assert_eq!(ring.focused(), 2);
162 assert!(ring.focus_next());
163 assert_eq!(ring.focused(), 0); }
165
166 #[test]
167 fn cycle_backward_wraps() {
168 let mut ring = FocusRing::new(3);
169 assert!(ring.focus_prev());
170 assert_eq!(ring.focused(), 2); assert!(ring.focus_prev());
172 assert_eq!(ring.focused(), 1);
173 assert!(ring.focus_prev());
174 assert_eq!(ring.focused(), 0);
175 }
176
177 #[test]
178 fn no_wrap_stops_at_boundaries() {
179 let mut ring = FocusRing::new(3).without_wrap();
180
181 assert!(!ring.focus_prev());
183 assert_eq!(ring.focused(), 0);
184
185 assert!(ring.focus_next());
187 assert!(ring.focus_next());
188 assert_eq!(ring.focused(), 2);
189
190 assert!(!ring.focus_next());
192 assert_eq!(ring.focused(), 2);
193 }
194
195 #[test]
196 fn empty_ring_is_noop() {
197 let mut ring = FocusRing::new(0);
198 assert!(ring.is_empty());
199 assert_eq!(ring.focused(), 0);
200 assert!(!ring.focus_next());
201 assert!(!ring.focus_prev());
202 assert!(!ring.focus(0));
203 }
204
205 #[test]
206 fn programmatic_focus() {
207 let mut ring = FocusRing::new(5);
208 assert!(ring.focus(3));
209 assert_eq!(ring.focused(), 3);
210 assert!(ring.is_focused(3));
211
212 assert!(!ring.focus(5));
214 assert_eq!(ring.focused(), 3); }
216
217 #[test]
218 fn set_len_clamps_focused() {
219 let mut ring = FocusRing::new(5);
220 ring.focus(4);
221 assert_eq!(ring.focused(), 4);
222
223 ring.set_len(3);
224 assert_eq!(ring.len(), 3);
225 assert_eq!(ring.focused(), 2); ring.set_len(0);
228 assert_eq!(ring.focused(), 0);
229 assert!(ring.is_empty());
230 }
231
232 #[test]
233 fn set_len_preserves_focused_when_in_range() {
234 let mut ring = FocusRing::new(5);
235 ring.focus(2);
236 ring.set_len(4);
237 assert_eq!(ring.focused(), 2); }
239
240 #[test]
241 fn handle_key_tab_cycles_forward() {
242 let mut ring = FocusRing::new(3);
243 assert_eq!(ring.handle_key(key(KeyCode::Tab)), FocusOutcome::FocusChanged);
244 assert_eq!(ring.focused(), 1);
245 }
246
247 #[test]
248 fn handle_key_backtab_cycles_backward() {
249 let mut ring = FocusRing::new(3);
250 ring.focus(1);
251 assert_eq!(ring.handle_key(key(KeyCode::BackTab)), FocusOutcome::FocusChanged);
252 assert_eq!(ring.focused(), 0);
253 }
254
255 #[test]
256 fn handle_key_other_keys_ignored() {
257 let mut ring = FocusRing::new(3);
258 assert_eq!(ring.handle_key(key(KeyCode::Enter)), FocusOutcome::Ignored);
259 assert_eq!(ring.handle_key(key(KeyCode::Char('a'))), FocusOutcome::Ignored);
260 assert_eq!(ring.focused(), 0); }
262
263 #[test]
264 fn handle_key_no_wrap_returns_unchanged() {
265 let mut ring = FocusRing::new(2).without_wrap();
266 assert_eq!(ring.handle_key(key(KeyCode::BackTab)), FocusOutcome::Unchanged);
268 assert_eq!(ring.focused(), 0);
269
270 ring.focus(1);
272 assert_eq!(ring.handle_key(key(KeyCode::Tab)), FocusOutcome::Unchanged);
273 assert_eq!(ring.focused(), 1);
274 }
275
276 #[test]
277 fn single_item_wrap_returns_true() {
278 let mut ring = FocusRing::new(1);
281 assert!(ring.focus_next());
282 assert_eq!(ring.focused(), 0);
283 assert!(ring.focus_prev());
284 assert_eq!(ring.focused(), 0);
285 }
286
287 #[test]
288 fn single_item_no_wrap() {
289 let mut ring = FocusRing::new(1).without_wrap();
290 assert!(!ring.focus_next());
291 assert!(!ring.focus_prev());
292 assert_eq!(ring.focused(), 0);
293 }
294}