Skip to main content

ratatui_interact/state/
focus.rs

1//! Focus Manager - Handles Tab navigation between focusable elements
2//!
3//! The `FocusManager` tracks which element has focus and handles
4//! Tab/Shift+Tab navigation between elements.
5//!
6//! # Example
7//!
8//! ```rust
9//! use ratatui_interact::state::FocusManager;
10//!
11//! #[derive(Clone, PartialEq, Eq, Hash, Debug)]
12//! enum DialogElement {
13//!     NameInput,
14//!     EmailInput,
15//!     SubmitButton,
16//!     CancelButton,
17//! }
18//!
19//! let mut focus = FocusManager::new();
20//!
21//! // Register elements in Tab order
22//! focus.register(DialogElement::NameInput);
23//! focus.register(DialogElement::EmailInput);
24//! focus.register(DialogElement::SubmitButton);
25//! focus.register(DialogElement::CancelButton);
26//!
27//! // First element is auto-focused
28//! assert_eq!(focus.current(), Some(&DialogElement::NameInput));
29//!
30//! // Tab to next
31//! focus.next();
32//! assert_eq!(focus.current(), Some(&DialogElement::EmailInput));
33//!
34//! // Shift+Tab to previous
35//! focus.prev();
36//! assert_eq!(focus.current(), Some(&DialogElement::NameInput));
37//!
38//! // Tab wraps around
39//! focus.last();
40//! focus.next();
41//! assert_eq!(focus.current(), Some(&DialogElement::NameInput));
42//! ```
43
44use std::hash::Hash;
45
46/// Focus manager for Tab navigation.
47///
48/// Manages a list of focusable elements and tracks which one currently has focus.
49/// Elements are navigated in registration order.
50///
51/// # Type Parameters
52///
53/// * `T` - The type used to identify focusable elements. Must implement
54///   `Clone`, `Eq`, and `Hash`. Commonly an enum or integer type.
55#[derive(Debug, Clone)]
56pub struct FocusManager<T: Clone + Eq + Hash = usize> {
57    /// Ordered list of focusable elements (by registration order).
58    elements: Vec<T>,
59    /// Current focus index.
60    current_index: Option<usize>,
61}
62
63impl<T: Clone + Eq + Hash> Default for FocusManager<T> {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl<T: Clone + Eq + Hash> FocusManager<T> {
70    /// Create a new empty focus manager.
71    pub fn new() -> Self {
72        Self {
73            elements: Vec::new(),
74            current_index: None,
75        }
76    }
77
78    /// Create a new focus manager with pre-allocated capacity.
79    pub fn with_capacity(capacity: usize) -> Self {
80        Self {
81            elements: Vec::with_capacity(capacity),
82            current_index: None,
83        }
84    }
85
86    /// Register a focusable element.
87    ///
88    /// Elements are added to the end of the navigation order.
89    /// Duplicate elements are ignored.
90    ///
91    /// The first registered element is automatically focused.
92    pub fn register(&mut self, element: T) {
93        if !self.elements.contains(&element) {
94            self.elements.push(element);
95            // Auto-focus first element
96            if self.current_index.is_none() {
97                self.current_index = Some(0);
98            }
99        }
100    }
101
102    /// Register multiple elements at once.
103    pub fn register_all(&mut self, elements: impl IntoIterator<Item = T>) {
104        for element in elements {
105            self.register(element);
106        }
107    }
108
109    /// Clear all registered elements and reset focus.
110    pub fn clear(&mut self) {
111        self.elements.clear();
112        self.current_index = None;
113    }
114
115    /// Get the currently focused element.
116    ///
117    /// Returns `None` if no elements are registered.
118    pub fn current(&self) -> Option<&T> {
119        self.current_index.and_then(|i| self.elements.get(i))
120    }
121
122    /// Get the current focus index.
123    pub fn current_index(&self) -> Option<usize> {
124        self.current_index
125    }
126
127    /// Check if an element is currently focused.
128    pub fn is_focused(&self, element: &T) -> bool {
129        self.current() == Some(element)
130    }
131
132    /// Move focus to the next element.
133    ///
134    /// Wraps around to the first element after the last.
135    pub fn next(&mut self) {
136        if self.elements.is_empty() {
137            return;
138        }
139
140        self.current_index = Some(
141            self.current_index
142                .map(|i| (i + 1) % self.elements.len())
143                .unwrap_or(0),
144        );
145    }
146
147    /// Move focus to the previous element.
148    ///
149    /// Wraps around to the last element before the first.
150    pub fn prev(&mut self) {
151        if self.elements.is_empty() {
152            return;
153        }
154
155        self.current_index = Some(
156            self.current_index
157                .map(|i| {
158                    if i == 0 {
159                        self.elements.len() - 1
160                    } else {
161                        i - 1
162                    }
163                })
164                .unwrap_or(0),
165        );
166    }
167
168    /// Set focus to a specific element.
169    ///
170    /// If the element is not registered, focus is unchanged.
171    pub fn set(&mut self, element: T) {
172        if let Some(idx) = self.elements.iter().position(|e| *e == element) {
173            self.current_index = Some(idx);
174        }
175    }
176
177    /// Set focus by index.
178    ///
179    /// If the index is out of bounds, focus is unchanged.
180    pub fn set_index(&mut self, index: usize) {
181        if index < self.elements.len() {
182            self.current_index = Some(index);
183        }
184    }
185
186    /// Focus the first element.
187    pub fn first(&mut self) {
188        if !self.elements.is_empty() {
189            self.current_index = Some(0);
190        }
191    }
192
193    /// Focus the last element.
194    pub fn last(&mut self) {
195        if !self.elements.is_empty() {
196            self.current_index = Some(self.elements.len() - 1);
197        }
198    }
199
200    /// Remove focus (no element focused).
201    pub fn unfocus(&mut self) {
202        self.current_index = None;
203    }
204
205    /// Check if any element has focus.
206    pub fn has_focus(&self) -> bool {
207        self.current_index.is_some()
208    }
209
210    /// Get the number of registered elements.
211    pub fn len(&self) -> usize {
212        self.elements.len()
213    }
214
215    /// Check if no elements are registered.
216    pub fn is_empty(&self) -> bool {
217        self.elements.is_empty()
218    }
219
220    /// Get all registered elements.
221    pub fn elements(&self) -> &[T] {
222        &self.elements
223    }
224
225    /// Remove an element from the focus manager.
226    ///
227    /// If the removed element was focused, focus moves to the next element
228    /// (or previous if it was the last).
229    pub fn remove(&mut self, element: &T) -> bool {
230        if let Some(idx) = self.elements.iter().position(|e| e == element) {
231            self.elements.remove(idx);
232
233            // Adjust current index
234            if self.elements.is_empty() {
235                self.current_index = None;
236            } else if let Some(current) = self.current_index {
237                if current == idx {
238                    // Was focused - stay at same index (now next element)
239                    // or move back if we removed the last
240                    if current >= self.elements.len() {
241                        self.current_index = Some(self.elements.len() - 1);
242                    }
243                } else if current > idx {
244                    // Adjust index for removed element
245                    self.current_index = Some(current - 1);
246                }
247            }
248
249            true
250        } else {
251            false
252        }
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[derive(Clone, PartialEq, Eq, Hash, Debug)]
261    enum TestElement {
262        First,
263        Second,
264        Third,
265    }
266
267    #[test]
268    fn test_new_manager() {
269        let manager: FocusManager<usize> = FocusManager::new();
270        assert!(manager.is_empty());
271        assert_eq!(manager.len(), 0);
272        assert_eq!(manager.current(), None);
273        assert!(!manager.has_focus());
274    }
275
276    #[test]
277    fn test_register_auto_focus() {
278        let mut manager = FocusManager::new();
279        manager.register(TestElement::First);
280
281        assert_eq!(manager.len(), 1);
282        assert!(manager.has_focus());
283        assert_eq!(manager.current(), Some(&TestElement::First));
284    }
285
286    #[test]
287    fn test_register_duplicates_ignored() {
288        let mut manager = FocusManager::new();
289        manager.register(TestElement::First);
290        manager.register(TestElement::First); // Duplicate
291
292        assert_eq!(manager.len(), 1);
293    }
294
295    #[test]
296    fn test_register_all() {
297        let mut manager = FocusManager::new();
298        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
299
300        assert_eq!(manager.len(), 3);
301        assert_eq!(manager.current(), Some(&TestElement::First));
302    }
303
304    #[test]
305    fn test_next_navigation() {
306        let mut manager = FocusManager::new();
307        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
308
309        assert_eq!(manager.current(), Some(&TestElement::First));
310
311        manager.next();
312        assert_eq!(manager.current(), Some(&TestElement::Second));
313
314        manager.next();
315        assert_eq!(manager.current(), Some(&TestElement::Third));
316
317        // Wrap around
318        manager.next();
319        assert_eq!(manager.current(), Some(&TestElement::First));
320    }
321
322    #[test]
323    fn test_prev_navigation() {
324        let mut manager = FocusManager::new();
325        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
326
327        // Wrap around from first to last
328        manager.prev();
329        assert_eq!(manager.current(), Some(&TestElement::Third));
330
331        manager.prev();
332        assert_eq!(manager.current(), Some(&TestElement::Second));
333
334        manager.prev();
335        assert_eq!(manager.current(), Some(&TestElement::First));
336    }
337
338    #[test]
339    fn test_set_focus() {
340        let mut manager = FocusManager::new();
341        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
342
343        manager.set(TestElement::Third);
344        assert_eq!(manager.current(), Some(&TestElement::Third));
345
346        manager.set(TestElement::First);
347        assert_eq!(manager.current(), Some(&TestElement::First));
348    }
349
350    #[test]
351    fn test_set_index() {
352        let mut manager = FocusManager::new();
353        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
354
355        manager.set_index(2);
356        assert_eq!(manager.current(), Some(&TestElement::Third));
357        assert_eq!(manager.current_index(), Some(2));
358
359        // Out of bounds - no change
360        manager.set_index(10);
361        assert_eq!(manager.current(), Some(&TestElement::Third));
362    }
363
364    #[test]
365    fn test_first_last() {
366        let mut manager = FocusManager::new();
367        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
368
369        manager.last();
370        assert_eq!(manager.current(), Some(&TestElement::Third));
371
372        manager.first();
373        assert_eq!(manager.current(), Some(&TestElement::First));
374    }
375
376    #[test]
377    fn test_unfocus() {
378        let mut manager = FocusManager::new();
379        manager.register(TestElement::First);
380
381        assert!(manager.has_focus());
382
383        manager.unfocus();
384        assert!(!manager.has_focus());
385        assert_eq!(manager.current(), None);
386    }
387
388    #[test]
389    fn test_is_focused() {
390        let mut manager = FocusManager::new();
391        manager.register_all([TestElement::First, TestElement::Second]);
392
393        assert!(manager.is_focused(&TestElement::First));
394        assert!(!manager.is_focused(&TestElement::Second));
395
396        manager.next();
397        assert!(!manager.is_focused(&TestElement::First));
398        assert!(manager.is_focused(&TestElement::Second));
399    }
400
401    #[test]
402    fn test_clear() {
403        let mut manager = FocusManager::new();
404        manager.register_all([TestElement::First, TestElement::Second]);
405
406        manager.clear();
407        assert!(manager.is_empty());
408        assert!(!manager.has_focus());
409    }
410
411    #[test]
412    fn test_remove_unfocused() {
413        let mut manager = FocusManager::new();
414        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
415
416        // Remove unfocused element
417        let removed = manager.remove(&TestElement::Third);
418        assert!(removed);
419        assert_eq!(manager.len(), 2);
420        assert_eq!(manager.current(), Some(&TestElement::First)); // Focus unchanged
421    }
422
423    #[test]
424    fn test_remove_focused() {
425        let mut manager = FocusManager::new();
426        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
427
428        // Remove focused element
429        let removed = manager.remove(&TestElement::First);
430        assert!(removed);
431        assert_eq!(manager.len(), 2);
432        assert_eq!(manager.current(), Some(&TestElement::Second)); // Focus moves to next
433    }
434
435    #[test]
436    fn test_remove_last_focused() {
437        let mut manager = FocusManager::new();
438        manager.register_all([TestElement::First, TestElement::Second, TestElement::Third]);
439        manager.last();
440
441        // Remove last focused element
442        let removed = manager.remove(&TestElement::Third);
443        assert!(removed);
444        assert_eq!(manager.current(), Some(&TestElement::Second)); // Focus moves back
445    }
446
447    #[test]
448    fn test_empty_navigation() {
449        let mut manager: FocusManager<usize> = FocusManager::new();
450
451        // Should not panic on empty manager
452        manager.next();
453        manager.prev();
454        manager.first();
455        manager.last();
456
457        assert!(!manager.has_focus());
458    }
459
460    #[test]
461    fn test_integer_focus_manager() {
462        let mut manager: FocusManager<usize> = FocusManager::new();
463        manager.register_all([0, 1, 2, 3, 4]);
464
465        assert_eq!(manager.current(), Some(&0));
466        manager.next();
467        assert_eq!(manager.current(), Some(&1));
468    }
469}