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    #[must_use]
45    pub const fn new() -> Self {
46        Self { current: None }
47    }
48
49    /// Check if any overlay is currently visible.
50    #[must_use]
51    pub const fn has_overlay(&self) -> bool {
52        self.current.is_some()
53    }
54
55    /// Check if a specific overlay is visible.
56    #[must_use]
57    pub fn is_showing(&self, kind: OverlayKind) -> bool {
58        self.current == Some(kind)
59    }
60
61    /// Get the currently visible overlay.
62    #[must_use]
63    pub const fn current(&self) -> Option<OverlayKind> {
64        self.current
65    }
66
67    /// Show a specific overlay, closing any other.
68    pub const fn show(&mut self, kind: OverlayKind) {
69        self.current = Some(kind);
70    }
71
72    /// Close the current overlay.
73    pub const fn close(&mut self) {
74        self.current = None;
75    }
76
77    /// Close all overlays.
78    pub const fn close_all(&mut self) {
79        self.current = None;
80    }
81
82    /// Toggle a specific overlay.
83    ///
84    /// If the overlay is showing, close it. Otherwise, show it.
85    pub fn toggle(&mut self, kind: OverlayKind) {
86        if self.current == Some(kind) {
87            self.current = None;
88        } else {
89            self.current = Some(kind);
90        }
91    }
92
93    /// Toggle help overlay.
94    pub fn toggle_help(&mut self) {
95        self.toggle(OverlayKind::Help);
96    }
97
98    /// Toggle export overlay.
99    pub fn toggle_export(&mut self) {
100        self.toggle(OverlayKind::Export);
101    }
102
103    /// Toggle legend overlay.
104    pub fn toggle_legend(&mut self) {
105        self.toggle(OverlayKind::Legend);
106    }
107
108    // Convenience accessors for backwards compatibility
109
110    /// Check if help overlay is visible.
111    #[must_use]
112    pub fn show_help(&self) -> bool {
113        self.is_showing(OverlayKind::Help)
114    }
115
116    /// Check if export overlay is visible.
117    #[must_use]
118    pub fn show_export(&self) -> bool {
119        self.is_showing(OverlayKind::Export)
120    }
121
122    /// Check if legend overlay is visible.
123    #[must_use]
124    pub fn show_legend(&self) -> bool {
125        self.is_showing(OverlayKind::Legend)
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_overlay_state_new() {
135        let state = OverlayState::new();
136        assert!(!state.has_overlay());
137        assert!(state.current().is_none());
138    }
139
140    #[test]
141    fn test_overlay_state_show() {
142        let mut state = OverlayState::new();
143
144        state.show(OverlayKind::Help);
145        assert!(state.has_overlay());
146        assert!(state.is_showing(OverlayKind::Help));
147        assert!(state.show_help());
148        assert!(!state.show_export());
149        assert!(!state.show_legend());
150    }
151
152    #[test]
153    fn test_overlay_state_mutual_exclusion() {
154        let mut state = OverlayState::new();
155
156        state.show(OverlayKind::Help);
157        assert!(state.is_showing(OverlayKind::Help));
158
159        state.show(OverlayKind::Export);
160        assert!(state.is_showing(OverlayKind::Export));
161        assert!(!state.is_showing(OverlayKind::Help));
162
163        state.show(OverlayKind::Legend);
164        assert!(state.is_showing(OverlayKind::Legend));
165        assert!(!state.is_showing(OverlayKind::Export));
166    }
167
168    #[test]
169    fn test_overlay_state_toggle() {
170        let mut state = OverlayState::new();
171
172        // Toggle on
173        state.toggle(OverlayKind::Help);
174        assert!(state.is_showing(OverlayKind::Help));
175
176        // Toggle off
177        state.toggle(OverlayKind::Help);
178        assert!(!state.has_overlay());
179
180        // Toggle different overlay
181        state.toggle(OverlayKind::Help);
182        state.toggle(OverlayKind::Export);
183        assert!(state.is_showing(OverlayKind::Export));
184        assert!(!state.is_showing(OverlayKind::Help));
185    }
186
187    #[test]
188    fn test_overlay_state_close() {
189        let mut state = OverlayState::new();
190
191        state.show(OverlayKind::Help);
192        assert!(state.has_overlay());
193
194        state.close();
195        assert!(!state.has_overlay());
196    }
197
198    #[test]
199    fn test_overlay_state_close_all() {
200        let mut state = OverlayState::new();
201
202        state.show(OverlayKind::Legend);
203        state.close_all();
204        assert!(!state.has_overlay());
205    }
206
207    #[test]
208    fn test_overlay_state_convenience_toggles() {
209        let mut state = OverlayState::new();
210
211        state.toggle_help();
212        assert!(state.show_help());
213
214        state.toggle_export();
215        assert!(state.show_export());
216        assert!(!state.show_help());
217
218        state.toggle_legend();
219        assert!(state.show_legend());
220        assert!(!state.show_export());
221    }
222}