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 #[must_use]
113 pub const fn new() -> Self {
114 Self {
115 view_mode: QualityViewMode::Summary,
116 selected_recommendation: 0,
117 total_recommendations: 0,
118 scroll_offset: 0,
119 }
120 }
121
122 #[must_use]
123 pub const fn with_recommendations(total: usize) -> Self {
124 Self {
125 view_mode: QualityViewMode::Summary,
126 selected_recommendation: 0,
127 total_recommendations: total,
128 scroll_offset: 0,
129 }
130 }
131
132 pub const fn next_mode(&mut self) {
134 self.view_mode = match self.view_mode {
135 QualityViewMode::Summary => QualityViewMode::Recommendations,
136 QualityViewMode::Recommendations => QualityViewMode::Metrics,
137 QualityViewMode::Metrics => QualityViewMode::Summary,
138 };
139 }
140
141 pub const fn select_next(&mut self) {
143 if self.selected_recommendation < self.total_recommendations.saturating_sub(1) {
144 self.selected_recommendation += 1;
145 }
146 }
147
148 pub const fn select_prev(&mut self) {
150 if self.selected_recommendation > 0 {
151 self.selected_recommendation -= 1;
152 }
153 }
154
155 pub const fn go_first(&mut self) {
157 self.selected_recommendation = 0;
158 self.scroll_offset = 0;
159 }
160
161 pub const fn go_last(&mut self) {
163 self.selected_recommendation = self.total_recommendations.saturating_sub(1);
164 }
165
166 pub const fn set_total(&mut self, total: usize) {
168 self.total_recommendations = total;
169 if self.selected_recommendation >= total {
170 self.selected_recommendation = total.saturating_sub(1);
171 }
172 }
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
177pub enum QualityViewMode {
178 #[default]
179 Summary,
180 Recommendations,
181 Metrics,
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_quality_view_state_navigation() {
190 let mut state = QualityViewState::with_recommendations(10);
191
192 assert_eq!(state.selected_recommendation, 0);
193
194 state.select_next();
195 assert_eq!(state.selected_recommendation, 1);
196
197 state.select_prev();
198 assert_eq!(state.selected_recommendation, 0);
199
200 state.select_prev();
202 assert_eq!(state.selected_recommendation, 0);
203
204 state.go_last();
206 assert_eq!(state.selected_recommendation, 9);
207
208 state.select_next();
210 assert_eq!(state.selected_recommendation, 9);
211
212 state.go_first();
214 assert_eq!(state.selected_recommendation, 0);
215 }
216
217 #[test]
218 fn test_quality_view_state_mode_cycling() {
219 let mut state = QualityViewState::new();
220
221 assert_eq!(state.view_mode, QualityViewMode::Summary);
222
223 state.next_mode();
224 assert_eq!(state.view_mode, QualityViewMode::Recommendations);
225
226 state.next_mode();
227 assert_eq!(state.view_mode, QualityViewMode::Metrics);
228
229 state.next_mode();
230 assert_eq!(state.view_mode, QualityViewMode::Summary);
231 }
232
233 #[test]
234 fn test_quality_view_state_set_total() {
235 let mut state = QualityViewState::with_recommendations(10);
236 state.selected_recommendation = 8;
237
238 state.set_total(5);
240 assert_eq!(state.total_recommendations, 5);
241 assert_eq!(state.selected_recommendation, 4);
242
243 state.set_total(20);
245 assert_eq!(state.total_recommendations, 20);
246 assert_eq!(state.selected_recommendation, 4);
247 }
248}