Skip to main content

sbom_tools/tui/viewmodel/
overlay.rs

1//! Overlay state management for TUI views.
2//!
3//! Provides a unified way to manage overlay visibility (help, export, legend)
4//! across both diff and view TUI modes.
5
6/// Available overlay types.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum OverlayKind {
9    /// Help/shortcuts overlay
10    Help,
11    /// Export dialog
12    Export,
13    /// Color legend
14    Legend,
15}
16
17/// Manages overlay visibility with mutual exclusion.
18///
19/// Only one overlay can be visible at a time - showing a new overlay
20/// automatically closes others.
21///
22/// # Example
23///
24/// ```ignore
25/// use crate::tui::viewmodel::OverlayState;
26///
27/// let mut overlay = OverlayState::new();
28///
29/// overlay.toggle(OverlayKind::Help);
30/// assert!(overlay.is_showing(OverlayKind::Help));
31///
32/// overlay.toggle(OverlayKind::Export);
33/// assert!(overlay.is_showing(OverlayKind::Export));
34/// assert!(!overlay.is_showing(OverlayKind::Help)); // Auto-closed
35/// ```
36#[derive(Debug, Clone, Default)]
37pub struct OverlayState {
38    /// Currently visible overlay (if any)
39    current: Option<OverlayKind>,
40}
41
42impl OverlayState {
43    /// Create a new overlay state with no overlay visible.
44    pub fn new() -> Self {
45        Self { current: None }
46    }
47
48    /// Check if any overlay is currently visible.
49    pub fn has_overlay(&self) -> bool {
50        self.current.is_some()
51    }
52
53    /// Check if a specific overlay is visible.
54    pub fn is_showing(&self, kind: OverlayKind) -> bool {
55        self.current == Some(kind)
56    }
57
58    /// Get the currently visible overlay.
59    pub fn current(&self) -> Option<OverlayKind> {
60        self.current
61    }
62
63    /// Show a specific overlay, closing any other.
64    pub fn show(&mut self, kind: OverlayKind) {
65        self.current = Some(kind);
66    }
67
68    /// Close the current overlay.
69    pub fn close(&mut self) {
70        self.current = None;
71    }
72
73    /// Close all overlays.
74    pub fn close_all(&mut self) {
75        self.current = None;
76    }
77
78    /// Toggle a specific overlay.
79    ///
80    /// If the overlay is showing, close it. Otherwise, show it.
81    pub fn toggle(&mut self, kind: OverlayKind) {
82        if self.current == Some(kind) {
83            self.current = None;
84        } else {
85            self.current = Some(kind);
86        }
87    }
88
89    /// Toggle help overlay.
90    pub fn toggle_help(&mut self) {
91        self.toggle(OverlayKind::Help);
92    }
93
94    /// Toggle export overlay.
95    pub fn toggle_export(&mut self) {
96        self.toggle(OverlayKind::Export);
97    }
98
99    /// Toggle legend overlay.
100    pub fn toggle_legend(&mut self) {
101        self.toggle(OverlayKind::Legend);
102    }
103
104    // Convenience accessors for backwards compatibility
105
106    /// Check if help overlay is visible.
107    pub fn show_help(&self) -> bool {
108        self.is_showing(OverlayKind::Help)
109    }
110
111    /// Check if export overlay is visible.
112    pub fn show_export(&self) -> bool {
113        self.is_showing(OverlayKind::Export)
114    }
115
116    /// Check if legend overlay is visible.
117    pub fn show_legend(&self) -> bool {
118        self.is_showing(OverlayKind::Legend)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_overlay_state_new() {
128        let state = OverlayState::new();
129        assert!(!state.has_overlay());
130        assert!(state.current().is_none());
131    }
132
133    #[test]
134    fn test_overlay_state_show() {
135        let mut state = OverlayState::new();
136
137        state.show(OverlayKind::Help);
138        assert!(state.has_overlay());
139        assert!(state.is_showing(OverlayKind::Help));
140        assert!(state.show_help());
141        assert!(!state.show_export());
142        assert!(!state.show_legend());
143    }
144
145    #[test]
146    fn test_overlay_state_mutual_exclusion() {
147        let mut state = OverlayState::new();
148
149        state.show(OverlayKind::Help);
150        assert!(state.is_showing(OverlayKind::Help));
151
152        state.show(OverlayKind::Export);
153        assert!(state.is_showing(OverlayKind::Export));
154        assert!(!state.is_showing(OverlayKind::Help));
155
156        state.show(OverlayKind::Legend);
157        assert!(state.is_showing(OverlayKind::Legend));
158        assert!(!state.is_showing(OverlayKind::Export));
159    }
160
161    #[test]
162    fn test_overlay_state_toggle() {
163        let mut state = OverlayState::new();
164
165        // Toggle on
166        state.toggle(OverlayKind::Help);
167        assert!(state.is_showing(OverlayKind::Help));
168
169        // Toggle off
170        state.toggle(OverlayKind::Help);
171        assert!(!state.has_overlay());
172
173        // Toggle different overlay
174        state.toggle(OverlayKind::Help);
175        state.toggle(OverlayKind::Export);
176        assert!(state.is_showing(OverlayKind::Export));
177        assert!(!state.is_showing(OverlayKind::Help));
178    }
179
180    #[test]
181    fn test_overlay_state_close() {
182        let mut state = OverlayState::new();
183
184        state.show(OverlayKind::Help);
185        assert!(state.has_overlay());
186
187        state.close();
188        assert!(!state.has_overlay());
189    }
190
191    #[test]
192    fn test_overlay_state_close_all() {
193        let mut state = OverlayState::new();
194
195        state.show(OverlayKind::Legend);
196        state.close_all();
197        assert!(!state.has_overlay());
198    }
199
200    #[test]
201    fn test_overlay_state_convenience_toggles() {
202        let mut state = OverlayState::new();
203
204        state.toggle_help();
205        assert!(state.show_help());
206
207        state.toggle_export();
208        assert!(state.show_export());
209        assert!(!state.show_help());
210
211        state.toggle_legend();
212        assert!(state.show_legend());
213        assert!(!state.show_export());
214    }
215}