Skip to main content

vtcode_tui/core_tui/widgets/
layout_mode.rs

1use ratatui::layout::Rect;
2
3/// Responsive layout mode based on terminal dimensions
4///
5/// This enum provides a single source of truth for layout decisions
6/// across the UI, enabling consistent responsive behavior.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum LayoutMode {
9    /// Minimal chrome for tiny terminals (< 80 cols or < 20 rows)
10    Compact,
11    /// Default layout for standard terminals
12    Standard,
13    /// Enhanced layout with sidebar for wide terminals (>= 120 cols, >= 24 rows)
14    Wide,
15}
16
17impl LayoutMode {
18    /// Determine layout mode from viewport dimensions
19    pub fn from_area(area: Rect) -> Self {
20        if area.width < 80 || area.height < 20 {
21            LayoutMode::Compact
22        } else if area.width >= 120 && area.height >= 24 {
23            LayoutMode::Wide
24        } else {
25            LayoutMode::Standard
26        }
27    }
28
29    /// Check if borders should be shown
30    pub fn show_borders(self) -> bool {
31        !matches!(self, LayoutMode::Compact)
32    }
33
34    /// Check if panel titles should be shown
35    pub fn show_titles(self) -> bool {
36        !matches!(self, LayoutMode::Compact)
37    }
38
39    /// Check if sidebar can be shown
40    pub fn allow_sidebar(self) -> bool {
41        matches!(self, LayoutMode::Wide)
42    }
43
44    /// Check if logs panel should be visible
45    pub fn show_logs_panel(self) -> bool {
46        !matches!(self, LayoutMode::Compact)
47    }
48
49    /// Get the footer height for this mode
50    /// Footer is disabled in all modes to avoid duplicating header info
51    pub fn footer_height(self) -> u16 {
52        0
53    }
54
55    /// Check if footer should be shown
56    /// Footer is disabled to avoid duplicating the header status bar
57    pub fn show_footer(self) -> bool {
58        false
59    }
60
61    /// Get the maximum header height as percentage of viewport
62    pub fn max_header_percent(self) -> f32 {
63        match self {
64            LayoutMode::Compact => 0.15,
65            LayoutMode::Standard => 0.25,
66            LayoutMode::Wide => 0.30,
67        }
68    }
69
70    /// Get the sidebar width percentage (only meaningful in Wide mode)
71    pub fn sidebar_width_percent(self) -> u16 {
72        match self {
73            LayoutMode::Wide => 28,
74            _ => 0,
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_compact_mode() {
85        let area = Rect::new(0, 0, 60, 15);
86        assert_eq!(LayoutMode::from_area(area), LayoutMode::Compact);
87    }
88
89    #[test]
90    fn test_standard_mode() {
91        let area = Rect::new(0, 0, 100, 30);
92        assert_eq!(LayoutMode::from_area(area), LayoutMode::Standard);
93    }
94
95    #[test]
96    fn test_wide_mode() {
97        let area = Rect::new(0, 0, 150, 40);
98        assert_eq!(LayoutMode::from_area(area), LayoutMode::Wide);
99    }
100
101    #[test]
102    fn test_mode_properties() {
103        // Compact: no borders, no footer, no sidebar
104        assert!(!LayoutMode::Compact.show_borders());
105        assert!(!LayoutMode::Compact.show_footer());
106        assert!(!LayoutMode::Compact.allow_sidebar());
107        assert_eq!(LayoutMode::Compact.footer_height(), 0);
108
109        // Standard: borders but no footer
110        assert!(LayoutMode::Standard.show_borders());
111        assert!(!LayoutMode::Standard.show_footer());
112        assert!(!LayoutMode::Standard.allow_sidebar());
113        assert_eq!(LayoutMode::Standard.footer_height(), 0);
114
115        // Wide: borders + sidebar, but no footer (header already shows status)
116        assert!(LayoutMode::Wide.show_borders());
117        assert!(!LayoutMode::Wide.show_footer());
118        assert!(LayoutMode::Wide.allow_sidebar());
119        assert_eq!(LayoutMode::Wide.footer_height(), 0);
120    }
121
122    #[test]
123    fn test_boundary_conditions() {
124        // Exactly at 80 cols should be Standard
125        let area_80 = Rect::new(0, 0, 80, 24);
126        assert_eq!(LayoutMode::from_area(area_80), LayoutMode::Standard);
127
128        // Exactly at 120 cols should be Wide
129        let area_120 = Rect::new(0, 0, 120, 24);
130        assert_eq!(LayoutMode::from_area(area_120), LayoutMode::Wide);
131
132        // 79 cols should be Compact
133        let area_79 = Rect::new(0, 0, 79, 24);
134        assert_eq!(LayoutMode::from_area(area_79), LayoutMode::Compact);
135
136        // Wide width but short height should be Standard (not Wide)
137        let area_wide_short = Rect::new(0, 0, 150, 20);
138        assert_eq!(LayoutMode::from_area(area_wide_short), LayoutMode::Standard);
139    }
140}