sbom_tools/tui/viewmodel/
mod.rs1mod filter;
76mod overlay;
77mod search;
78pub mod security_filter;
79mod status;
80
81pub use filter::{CycleFilter, FilterState};
82pub use overlay::{OverlayKind as ViewModelOverlayKind, OverlayState};
83pub use search::{SearchState, SearchStateCore};
84pub use security_filter::{
85 LicenseCategory, QuickFilter, RiskLevel, SecurityFilterCriteria, SecurityFilterState,
86};
87pub use status::StatusMessage;
88
89#[derive(Debug, Clone)]
94pub struct QualityViewState {
95 pub view_mode: QualityViewMode,
97 pub selected_recommendation: usize,
99 pub total_recommendations: usize,
101 pub scroll_offset: usize,
103}
104
105impl Default for QualityViewState {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111impl QualityViewState {
112 pub fn new() -> Self {
113 Self {
114 view_mode: QualityViewMode::Summary,
115 selected_recommendation: 0,
116 total_recommendations: 0,
117 scroll_offset: 0,
118 }
119 }
120
121 pub fn with_recommendations(total: usize) -> Self {
122 Self {
123 view_mode: QualityViewMode::Summary,
124 selected_recommendation: 0,
125 total_recommendations: total,
126 scroll_offset: 0,
127 }
128 }
129
130 pub fn next_mode(&mut self) {
132 self.view_mode = match self.view_mode {
133 QualityViewMode::Summary => QualityViewMode::Recommendations,
134 QualityViewMode::Recommendations => QualityViewMode::Metrics,
135 QualityViewMode::Metrics => QualityViewMode::Summary,
136 };
137 }
138
139 pub fn select_next(&mut self) {
141 if self.selected_recommendation < self.total_recommendations.saturating_sub(1) {
142 self.selected_recommendation += 1;
143 }
144 }
145
146 pub fn select_prev(&mut self) {
148 if self.selected_recommendation > 0 {
149 self.selected_recommendation -= 1;
150 }
151 }
152
153 pub fn go_first(&mut self) {
155 self.selected_recommendation = 0;
156 self.scroll_offset = 0;
157 }
158
159 pub fn go_last(&mut self) {
161 self.selected_recommendation = self.total_recommendations.saturating_sub(1);
162 }
163
164 pub fn set_total(&mut self, total: usize) {
166 self.total_recommendations = total;
167 if self.selected_recommendation >= total {
168 self.selected_recommendation = total.saturating_sub(1);
169 }
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
175pub enum QualityViewMode {
176 #[default]
177 Summary,
178 Recommendations,
179 Metrics,
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_quality_view_state_navigation() {
188 let mut state = QualityViewState::with_recommendations(10);
189
190 assert_eq!(state.selected_recommendation, 0);
191
192 state.select_next();
193 assert_eq!(state.selected_recommendation, 1);
194
195 state.select_prev();
196 assert_eq!(state.selected_recommendation, 0);
197
198 state.select_prev();
200 assert_eq!(state.selected_recommendation, 0);
201
202 state.go_last();
204 assert_eq!(state.selected_recommendation, 9);
205
206 state.select_next();
208 assert_eq!(state.selected_recommendation, 9);
209
210 state.go_first();
212 assert_eq!(state.selected_recommendation, 0);
213 }
214
215 #[test]
216 fn test_quality_view_state_mode_cycling() {
217 let mut state = QualityViewState::new();
218
219 assert_eq!(state.view_mode, QualityViewMode::Summary);
220
221 state.next_mode();
222 assert_eq!(state.view_mode, QualityViewMode::Recommendations);
223
224 state.next_mode();
225 assert_eq!(state.view_mode, QualityViewMode::Metrics);
226
227 state.next_mode();
228 assert_eq!(state.view_mode, QualityViewMode::Summary);
229 }
230
231 #[test]
232 fn test_quality_view_state_set_total() {
233 let mut state = QualityViewState::with_recommendations(10);
234 state.selected_recommendation = 8;
235
236 state.set_total(5);
238 assert_eq!(state.total_recommendations, 5);
239 assert_eq!(state.selected_recommendation, 4);
240
241 state.set_total(20);
243 assert_eq!(state.total_recommendations, 20);
244 assert_eq!(state.selected_recommendation, 4);
245 }
246}