agent_core/tui/layout/
minimal.rs

1//! Minimal layout template
2
3use ratatui::layout::{Constraint, Direction, Layout};
4
5use crate::tui::widgets::widget_ids;
6
7use super::types::{LayoutContext, LayoutResult, WidgetSizes};
8
9/// Options for minimal layout (no status bar, no panels)
10#[derive(Clone)]
11pub struct MinimalOptions {
12    /// Widget ID for the main content area
13    pub main_widget_id: &'static str,
14    /// Widget ID for the input area
15    pub input_widget_id: &'static str,
16    /// Fixed input height (None = auto-size)
17    pub fixed_input_height: Option<u16>,
18}
19
20impl Default for MinimalOptions {
21    fn default() -> Self {
22        Self {
23            main_widget_id: widget_ids::CHAT_VIEW,
24            input_widget_id: widget_ids::TEXT_INPUT,
25            fixed_input_height: None,
26        }
27    }
28}
29
30/// Compute the minimal layout
31pub fn compute(ctx: &LayoutContext, _sizes: &WidgetSizes, opts: &MinimalOptions) -> LayoutResult {
32    let mut result = LayoutResult::default();
33    let area = ctx.frame_area;
34
35    let input_height = opts.fixed_input_height.unwrap_or_else(|| {
36        if ctx.show_throbber {
37            3
38        } else {
39            (ctx.input_visual_lines as u16).max(1) + 2
40        }
41    });
42
43    let chunks = Layout::default()
44        .direction(Direction::Vertical)
45        .constraints([Constraint::Min(1), Constraint::Length(input_height)])
46        .split(area);
47
48    result.widget_areas.insert(opts.main_widget_id, chunks[0]);
49    result.widget_areas.insert(opts.input_widget_id, chunks[1]);
50    result.input_area = Some(chunks[1]);
51
52    result.render_order = vec![opts.main_widget_id, opts.input_widget_id];
53
54    result
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use crate::tui::themes::Theme;
61    use crate::tui::widgets::widget_ids;
62    use ratatui::layout::Rect;
63    use std::collections::{HashMap, HashSet};
64
65    fn test_context(area: Rect) -> LayoutContext<'static> {
66        static THEME: std::sync::LazyLock<Theme> = std::sync::LazyLock::new(Theme::default);
67        LayoutContext {
68            frame_area: area,
69            show_throbber: false,
70            input_visual_lines: 1,
71            theme: &THEME,
72            active_widgets: HashSet::new(),
73        }
74    }
75
76    fn test_sizes() -> WidgetSizes {
77        WidgetSizes {
78            heights: HashMap::new(),
79            is_active: HashMap::new(),
80        }
81    }
82
83    #[test]
84    fn test_minimal_layout() {
85        let area = Rect::new(0, 0, 80, 24);
86        let ctx = test_context(area);
87        let sizes = test_sizes();
88
89        let result = compute(&ctx, &sizes, &MinimalOptions::default());
90
91        assert!(result.widget_areas.contains_key(widget_ids::CHAT_VIEW));
92        assert!(result.widget_areas.contains_key(widget_ids::TEXT_INPUT));
93        // Minimal layout doesn't include status bar as a widget
94        assert!(!result.widget_areas.contains_key(widget_ids::STATUS_BAR));
95    }
96}