Skip to main content

fresh/view/ui/
focus.rs

1//! Focus management utilities
2//!
3//! This module provides a generic focus manager for cycling through
4//! focusable elements, extracted from the settings panel focus logic.
5
6/// Manages focus cycling through a list of elements
7///
8/// This is a generic version of the focus panel cycling pattern used in
9/// settings. It handles wrapping at boundaries and provides methods for
10/// navigating forward, backward, and jumping to specific elements.
11///
12/// # Example
13///
14/// ```
15/// use fresh_editor::view::ui::FocusManager;
16///
17/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18/// enum Panel { Left, Center, Right }
19///
20/// let mut focus = FocusManager::new(vec![Panel::Left, Panel::Center, Panel::Right]);
21///
22/// assert_eq!(focus.current(), Some(Panel::Left));
23/// focus.focus_next();
24/// assert_eq!(focus.current(), Some(Panel::Center));
25/// focus.focus_next();
26/// assert_eq!(focus.current(), Some(Panel::Right));
27/// focus.focus_next(); // wraps around
28/// assert_eq!(focus.current(), Some(Panel::Left));
29/// ```
30#[derive(Debug, Clone)]
31pub struct FocusManager<T> {
32    elements: Vec<T>,
33    current: usize,
34}
35
36impl<T> Default for FocusManager<T> {
37    fn default() -> Self {
38        Self {
39            elements: Vec::new(),
40            current: 0,
41        }
42    }
43}
44
45impl<T: Copy + Eq> FocusManager<T> {
46    /// Create a new focus manager with the given elements
47    ///
48    /// Focus starts at the first element (index 0).
49    pub fn new(elements: Vec<T>) -> Self {
50        Self {
51            elements,
52            current: 0,
53        }
54    }
55
56    /// Get the currently focused element
57    pub fn current(&self) -> Option<T> {
58        self.elements.get(self.current).copied()
59    }
60
61    /// Get the current index
62    pub fn current_index(&self) -> usize {
63        self.current
64    }
65
66    /// Move focus to the next element, wrapping at the end
67    ///
68    /// Returns the newly focused element.
69    pub fn focus_next(&mut self) -> Option<T> {
70        if self.elements.is_empty() {
71            return None;
72        }
73        self.current = (self.current + 1) % self.elements.len();
74        self.current()
75    }
76
77    /// Move focus to the previous element, wrapping at the beginning
78    ///
79    /// Returns the newly focused element.
80    pub fn focus_prev(&mut self) -> Option<T> {
81        if self.elements.is_empty() {
82            return None;
83        }
84        self.current = (self.current + self.elements.len() - 1) % self.elements.len();
85        self.current()
86    }
87
88    /// Set focus to a specific element
89    ///
90    /// Returns true if the element was found and focused, false otherwise.
91    pub fn set(&mut self, element: T) -> bool {
92        if let Some(idx) = self.elements.iter().position(|&e| e == element) {
93            self.current = idx;
94            true
95        } else {
96            false
97        }
98    }
99
100    /// Set focus by index
101    ///
102    /// Returns true if the index was valid, false otherwise.
103    pub fn set_index(&mut self, index: usize) -> bool {
104        if index < self.elements.len() {
105            self.current = index;
106            true
107        } else {
108            false
109        }
110    }
111
112    /// Check if the given element is currently focused
113    pub fn is_current(&self, element: T) -> bool {
114        self.current() == Some(element)
115    }
116
117    /// Get the number of elements
118    pub fn len(&self) -> usize {
119        self.elements.len()
120    }
121
122    /// Check if empty
123    pub fn is_empty(&self) -> bool {
124        self.elements.is_empty()
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
133    enum TestPanel {
134        A,
135        B,
136        C,
137    }
138
139    #[test]
140    fn test_new_starts_at_first() {
141        let focus = FocusManager::new(vec![TestPanel::A, TestPanel::B, TestPanel::C]);
142        assert_eq!(focus.current(), Some(TestPanel::A));
143        assert_eq!(focus.current_index(), 0);
144    }
145
146    #[test]
147    fn test_focus_next_cycles() {
148        let mut focus = FocusManager::new(vec![TestPanel::A, TestPanel::B, TestPanel::C]);
149
150        assert_eq!(focus.focus_next(), Some(TestPanel::B));
151        assert_eq!(focus.focus_next(), Some(TestPanel::C));
152        assert_eq!(focus.focus_next(), Some(TestPanel::A)); // wrap
153    }
154
155    #[test]
156    fn test_focus_prev_cycles() {
157        let mut focus = FocusManager::new(vec![TestPanel::A, TestPanel::B, TestPanel::C]);
158
159        assert_eq!(focus.focus_prev(), Some(TestPanel::C)); // wrap
160        assert_eq!(focus.focus_prev(), Some(TestPanel::B));
161        assert_eq!(focus.focus_prev(), Some(TestPanel::A));
162    }
163
164    #[test]
165    fn test_set_element() {
166        let mut focus = FocusManager::new(vec![TestPanel::A, TestPanel::B, TestPanel::C]);
167
168        assert!(focus.set(TestPanel::C));
169        assert_eq!(focus.current(), Some(TestPanel::C));
170        assert_eq!(focus.current_index(), 2);
171    }
172
173    #[test]
174    fn test_set_index() {
175        let mut focus = FocusManager::new(vec![TestPanel::A, TestPanel::B, TestPanel::C]);
176
177        assert!(focus.set_index(1));
178        assert_eq!(focus.current(), Some(TestPanel::B));
179
180        assert!(!focus.set_index(10)); // out of bounds
181        assert_eq!(focus.current(), Some(TestPanel::B)); // unchanged
182    }
183
184    #[test]
185    fn test_is_current() {
186        let focus = FocusManager::new(vec![TestPanel::A, TestPanel::B, TestPanel::C]);
187
188        assert!(focus.is_current(TestPanel::A));
189        assert!(!focus.is_current(TestPanel::B));
190    }
191
192    #[test]
193    fn test_empty_manager() {
194        let mut focus: FocusManager<TestPanel> = FocusManager::new(vec![]);
195
196        assert_eq!(focus.current(), None);
197        assert_eq!(focus.focus_next(), None);
198        assert_eq!(focus.focus_prev(), None);
199        assert!(focus.is_empty());
200    }
201
202    #[test]
203    fn test_single_element() {
204        let mut focus = FocusManager::new(vec![TestPanel::A]);
205
206        assert_eq!(focus.current(), Some(TestPanel::A));
207        assert_eq!(focus.focus_next(), Some(TestPanel::A)); // stays same
208        assert_eq!(focus.focus_prev(), Some(TestPanel::A)); // stays same
209    }
210}