Skip to main content

sbom_tools/tui/viewmodel/
filter.rs

1//! Filter state management for TUI views.
2//!
3//! Provides generic filter cycling that can work with any enum-based filter,
4//! eliminating the need to duplicate filter toggle logic across views.
5
6/// Trait for filter types that can cycle through options.
7///
8/// Implement this for your filter enums to enable cycling behavior.
9///
10/// # Example
11///
12/// ```ignore
13/// use crate::tui::viewmodel::CycleFilter;
14///
15/// #[derive(Clone, Copy, Default)]
16/// enum MyFilter {
17///     #[default]
18///     All,
19///     Active,
20///     Completed,
21/// }
22///
23/// impl CycleFilter for MyFilter {
24///     fn next(&self) -> Self {
25///         match self {
26///             Self::All => Self::Active,
27///             Self::Active => Self::Completed,
28///             Self::Completed => Self::All,
29///         }
30///     }
31///
32///     fn prev(&self) -> Self {
33///         match self {
34///             Self::All => Self::Completed,
35///             Self::Active => Self::All,
36///             Self::Completed => Self::Active,
37///         }
38///     }
39///
40///     fn display_name(&self) -> &str {
41///         match self {
42///             Self::All => "All",
43///             Self::Active => "Active",
44///             Self::Completed => "Completed",
45///         }
46///     }
47/// }
48/// ```
49pub trait CycleFilter: Clone + Copy + Default {
50    /// Get the next filter in the cycle.
51    fn next(&self) -> Self;
52
53    /// Get the previous filter in the cycle.
54    fn prev(&self) -> Self;
55
56    /// Get a display name for the filter.
57    fn display_name(&self) -> &str;
58}
59
60/// Generic filter state that works with any CycleFilter enum.
61///
62/// Provides common state management for filter selection including
63/// cycling and display name access.
64#[derive(Debug, Clone)]
65pub struct FilterState<F: CycleFilter> {
66    /// Current filter value
67    pub current: F,
68}
69
70impl<F: CycleFilter> Default for FilterState<F> {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76impl<F: CycleFilter> FilterState<F> {
77    /// Create a new filter state with the default filter.
78    pub fn new() -> Self {
79        Self {
80            current: F::default(),
81        }
82    }
83
84    /// Create a filter state with a specific initial value.
85    pub fn with_filter(filter: F) -> Self {
86        Self { current: filter }
87    }
88
89    /// Cycle to the next filter.
90    pub fn next(&mut self) {
91        self.current = self.current.next();
92    }
93
94    /// Cycle to the previous filter.
95    pub fn prev(&mut self) {
96        self.current = self.current.prev();
97    }
98
99    /// Set a specific filter.
100    pub fn set(&mut self, filter: F) {
101        self.current = filter;
102    }
103
104    /// Reset to the default filter.
105    pub fn reset(&mut self) {
106        self.current = F::default();
107    }
108
109    /// Get the current filter's display name.
110    pub fn display_name(&self) -> &str {
111        self.current.display_name()
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    // Test filter implementation
120    #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
121    enum TestFilter {
122        #[default]
123        All,
124        FilterA,
125        FilterB,
126    }
127
128    impl CycleFilter for TestFilter {
129        fn next(&self) -> Self {
130            match self {
131                Self::All => Self::FilterA,
132                Self::FilterA => Self::FilterB,
133                Self::FilterB => Self::All,
134            }
135        }
136
137        fn prev(&self) -> Self {
138            match self {
139                Self::All => Self::FilterB,
140                Self::FilterA => Self::All,
141                Self::FilterB => Self::FilterA,
142            }
143        }
144
145        fn display_name(&self) -> &str {
146            match self {
147                Self::All => "All Items",
148                Self::FilterA => "Filter A",
149                Self::FilterB => "Filter B",
150            }
151        }
152    }
153
154    #[test]
155    fn test_filter_state_cycling() {
156        let mut state = FilterState::<TestFilter>::new();
157
158        assert_eq!(state.current, TestFilter::All);
159        assert_eq!(state.display_name(), "All Items");
160
161        state.next();
162        assert_eq!(state.current, TestFilter::FilterA);
163        assert_eq!(state.display_name(), "Filter A");
164
165        state.next();
166        assert_eq!(state.current, TestFilter::FilterB);
167
168        state.next();
169        assert_eq!(state.current, TestFilter::All);
170
171        state.prev();
172        assert_eq!(state.current, TestFilter::FilterB);
173    }
174
175    #[test]
176    fn test_filter_state_set_reset() {
177        let mut state = FilterState::<TestFilter>::new();
178
179        state.set(TestFilter::FilterB);
180        assert_eq!(state.current, TestFilter::FilterB);
181
182        state.reset();
183        assert_eq!(state.current, TestFilter::All);
184    }
185
186    #[test]
187    fn test_filter_state_with_initial() {
188        let state = FilterState::with_filter(TestFilter::FilterA);
189        assert_eq!(state.current, TestFilter::FilterA);
190    }
191}