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    #[must_use]
52    fn next(&self) -> Self;
53
54    /// Get the previous filter in the cycle.
55    #[must_use]
56    fn prev(&self) -> Self;
57
58    /// Get a display name for the filter.
59    fn display_name(&self) -> &str;
60}
61
62/// Generic filter state that works with any `CycleFilter` enum.
63///
64/// Provides common state management for filter selection including
65/// cycling and display name access.
66#[derive(Debug, Clone)]
67pub struct FilterState<F: CycleFilter> {
68    /// Current filter value
69    pub current: F,
70}
71
72impl<F: CycleFilter> Default for FilterState<F> {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl<F: CycleFilter> FilterState<F> {
79    /// Create a new filter state with the default filter.
80    #[must_use]
81    pub fn new() -> Self {
82        Self {
83            current: F::default(),
84        }
85    }
86
87    /// Create a filter state with a specific initial value.
88    pub const fn with_filter(filter: F) -> Self {
89        Self { current: filter }
90    }
91
92    /// Cycle to the next filter.
93    pub fn next(&mut self) {
94        self.current = self.current.next();
95    }
96
97    /// Cycle to the previous filter.
98    pub fn prev(&mut self) {
99        self.current = self.current.prev();
100    }
101
102    /// Set a specific filter.
103    pub const fn set(&mut self, filter: F) {
104        self.current = filter;
105    }
106
107    /// Reset to the default filter.
108    pub fn reset(&mut self) {
109        self.current = F::default();
110    }
111
112    /// Get the current filter's display name.
113    pub fn display_name(&self) -> &str {
114        self.current.display_name()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    // Test filter implementation
123    #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
124    enum TestFilter {
125        #[default]
126        All,
127        FilterA,
128        FilterB,
129    }
130
131    impl CycleFilter for TestFilter {
132        fn next(&self) -> Self {
133            match self {
134                Self::All => Self::FilterA,
135                Self::FilterA => Self::FilterB,
136                Self::FilterB => Self::All,
137            }
138        }
139
140        fn prev(&self) -> Self {
141            match self {
142                Self::All => Self::FilterB,
143                Self::FilterA => Self::All,
144                Self::FilterB => Self::FilterA,
145            }
146        }
147
148        fn display_name(&self) -> &str {
149            match self {
150                Self::All => "All Items",
151                Self::FilterA => "Filter A",
152                Self::FilterB => "Filter B",
153            }
154        }
155    }
156
157    #[test]
158    fn test_filter_state_cycling() {
159        let mut state = FilterState::<TestFilter>::new();
160
161        assert_eq!(state.current, TestFilter::All);
162        assert_eq!(state.display_name(), "All Items");
163
164        state.next();
165        assert_eq!(state.current, TestFilter::FilterA);
166        assert_eq!(state.display_name(), "Filter A");
167
168        state.next();
169        assert_eq!(state.current, TestFilter::FilterB);
170
171        state.next();
172        assert_eq!(state.current, TestFilter::All);
173
174        state.prev();
175        assert_eq!(state.current, TestFilter::FilterB);
176    }
177
178    #[test]
179    fn test_filter_state_set_reset() {
180        let mut state = FilterState::<TestFilter>::new();
181
182        state.set(TestFilter::FilterB);
183        assert_eq!(state.current, TestFilter::FilterB);
184
185        state.reset();
186        assert_eq!(state.current, TestFilter::All);
187    }
188
189    #[test]
190    fn test_filter_state_with_initial() {
191        let state = FilterState::with_filter(TestFilter::FilterA);
192        assert_eq!(state.current, TestFilter::FilterA);
193    }
194}