Skip to main content

oxicode_vtui/design/layout/
mod.rs

1//! Layout system — responsive mode + agent view geometry.
2//!
3//! ## Modules
4//!
5//! | Module | Purpose |
6//! |---|---|
7//! | [`agent`] | `AgentViewLayout`, `ActivePane`, `PaneAreas` — pure geometry |
8//! | [`config`] | `LayoutConfig`, `ScrollbarConfig` — appearance settings |
9//! | [`status_bar`] | `StatusBar` + `StatusBarBuilder` — 1-row top bar |
10//! | [`shortcuts_bar`] | `ShortcutsBar` + `HintItem` — bottom keyboard hints |
11//! | [`welcome`] | `WelcomeLayout` — welcome screen geometry |
12//!
13//! The existing [`LayoutMode`] (ported from `vtcode-ui`) provides responsive
14//! breakpoints.  The new `AgentViewLayout` provides the grok-build-style
15//! pure-compute layout engine for the agent conversation view.
16
17pub mod agent;
18pub mod config;
19pub mod shortcuts_bar;
20pub mod status_bar;
21pub mod welcome;
22
23// ───────────────────────────────────────────────────────────────────────────
24// Re-exports
25// ───────────────────────────────────────────────────────────────────────────
26
27pub use agent::{
28    AUTO_COMPACT_MAX_ROWS, ActivePane, AgentViewLayout, LayoutInput, PaneAreas,
29    SHORT_TERMINAL_ROWS, effective_compact,
30};
31pub use config::{LayoutConfig, ScrollbarConfig};
32pub use shortcuts_bar::{
33    CompactConfig, HintItem, PendingHint, ShortcutBarStyling, ShortcutsBar, compute_effective_hints,
34};
35pub use status_bar::{StatusBar, StatusBarBuilder, StatusBarStyling};
36pub use welcome::{HERO_BOX_MIN_WIDTH, PROMPT_HEIGHT, WelcomeLayout, WelcomePromptFocus};
37
38// ───────────────────────────────────────────────────────────────────────────
39// LayoutMode (preserved from the original vtcode-ui port)
40// ───────────────────────────────────────────────────────────────────────────
41
42use ratatui::layout::Rect;
43
44use crate::design::constants::{COMPACT_MAX_COLS, COMPACT_MAX_ROWS, WIDE_MIN_COLS, WIDE_MIN_ROWS};
45
46/// Responsive layout mode based on terminal dimensions.
47///
48/// This enum provides a single source of truth for layout decisions
49/// across the UI, enabling consistent responsive behavior.
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum LayoutMode {
52    /// Minimal chrome for tiny terminals (< 80 cols or < 20 rows)
53    Compact,
54    /// Default layout for standard terminals
55    Standard,
56    /// Enhanced layout with sidebar for wide terminals (>= 120 cols, >= 24 rows)
57    Wide,
58}
59
60impl LayoutMode {
61    /// Determine layout mode from viewport dimensions.
62    pub(crate) fn from_area(area: Rect) -> Self {
63        if area.width <= COMPACT_MAX_COLS || area.height <= COMPACT_MAX_ROWS {
64            LayoutMode::Compact
65        } else if area.width >= WIDE_MIN_COLS && area.height >= WIDE_MIN_ROWS {
66            LayoutMode::Wide
67        } else {
68            LayoutMode::Standard
69        }
70    }
71
72    /// Check if borders should be shown.
73    pub(crate) fn show_borders(self) -> bool {
74        !matches!(self, LayoutMode::Compact)
75    }
76
77    /// Check if panel titles should be shown.
78    pub(crate) fn show_titles(self) -> bool {
79        !matches!(self, LayoutMode::Compact)
80    }
81
82    /// Check if sidebar can be shown.
83    pub(crate) fn allow_sidebar(self) -> bool {
84        matches!(self, LayoutMode::Wide)
85    }
86
87    /// Check if logs panel should be visible.
88    pub(crate) fn show_logs_panel(self) -> bool {
89        !matches!(self, LayoutMode::Compact)
90    }
91
92    /// Get the footer height for this mode.
93    pub(crate) fn footer_height(self) -> u16 {
94        0
95    }
96
97    /// Check if footer should be shown.
98    pub(crate) fn show_footer(self) -> bool {
99        false
100    }
101
102    /// Get the maximum header height as percentage of viewport.
103    pub(crate) fn max_header_percent(self) -> f32 {
104        match self {
105            LayoutMode::Compact => 0.2,
106            _ => 0.3,
107        }
108    }
109
110    /// Get the sidebar width percentage (only meaningful in Wide mode).
111    pub(crate) fn sidebar_width_percent(self) -> u16 {
112        match self {
113            LayoutMode::Wide => 25,
114            _ => 0,
115        }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn compact_mode_for_small_terminals() {
125        assert_eq!(
126            LayoutMode::from_area(Rect::new(0, 0, 60, 20)),
127            LayoutMode::Compact
128        );
129        assert_eq!(
130            LayoutMode::from_area(Rect::new(0, 0, 80, 15)),
131            LayoutMode::Compact
132        );
133    }
134
135    #[test]
136    fn standard_mode_for_normal_terminals() {
137        assert_eq!(
138            LayoutMode::from_area(Rect::new(0, 0, 100, 22)),
139            LayoutMode::Standard
140        );
141    }
142
143    #[test]
144    fn wide_mode_for_large_terminals() {
145        assert_eq!(
146            LayoutMode::from_area(Rect::new(0, 0, 140, 30)),
147            LayoutMode::Wide
148        );
149    }
150}