agent_air_tui/layout/
sidebar.rs1use ratatui::layout::{Constraint, Direction, Layout};
4
5use super::standard::{self, StandardOptions};
6use super::types::{LayoutContext, LayoutResult, WidgetSizes};
7
8#[derive(Clone)]
10pub struct SidebarOptions {
11 pub main_options: StandardOptions,
13 pub sidebar_widget_id: &'static str,
15 pub sidebar_width: SidebarWidth,
17 pub sidebar_position: SidebarPosition,
19}
20
21#[derive(Clone)]
23pub enum SidebarWidth {
24 Fixed(u16),
26 Percentage(u16),
28 Min(u16),
30}
31
32impl From<u16> for SidebarWidth {
33 fn from(width: u16) -> Self {
34 Self::Fixed(width)
35 }
36}
37
38#[derive(Clone, Copy, Default)]
40pub enum SidebarPosition {
41 Left,
42 #[default]
43 Right,
44}
45
46impl Default for SidebarOptions {
47 fn default() -> Self {
48 Self {
49 main_options: StandardOptions::default(),
50 sidebar_widget_id: "sidebar",
51 sidebar_width: SidebarWidth::Fixed(30),
52 sidebar_position: SidebarPosition::Right,
53 }
54 }
55}
56
57pub fn compute(ctx: &LayoutContext, sizes: &WidgetSizes, opts: &SidebarOptions) -> LayoutResult {
59 let area = ctx.frame_area;
60
61 let sidebar_constraint = match opts.sidebar_width {
63 SidebarWidth::Fixed(w) => Constraint::Length(w),
64 SidebarWidth::Percentage(p) => Constraint::Percentage(p),
65 SidebarWidth::Min(w) => Constraint::Min(w),
66 };
67
68 let h_constraints = match opts.sidebar_position {
70 SidebarPosition::Left => vec![sidebar_constraint, Constraint::Min(1)],
71 SidebarPosition::Right => vec![Constraint::Min(1), sidebar_constraint],
72 };
73
74 let h_chunks = Layout::default()
75 .direction(Direction::Horizontal)
76 .constraints(h_constraints)
77 .split(area);
78
79 let (main_area, sidebar_area) = match opts.sidebar_position {
80 SidebarPosition::Left => (h_chunks[1], h_chunks[0]),
81 SidebarPosition::Right => (h_chunks[0], h_chunks[1]),
82 };
83
84 let main_ctx = LayoutContext {
86 frame_area: main_area,
87 show_throbber: ctx.show_throbber,
88 input_visual_lines: ctx.input_visual_lines,
89 theme: ctx.theme,
90 active_widgets: ctx.active_widgets.clone(),
91 };
92 let mut result = standard::compute(&main_ctx, sizes, &opts.main_options);
93
94 result
96 .widget_areas
97 .insert(opts.sidebar_widget_id, sidebar_area);
98 result.render_order.insert(0, opts.sidebar_widget_id);
100
101 result
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::themes::Theme;
108 use crate::widgets::widget_ids;
109 use ratatui::layout::Rect;
110 use std::collections::{HashMap, HashSet};
111
112 fn test_context(area: Rect) -> LayoutContext<'static> {
113 static THEME: std::sync::LazyLock<Theme> = std::sync::LazyLock::new(Theme::default);
114 LayoutContext {
115 frame_area: area,
116 show_throbber: false,
117 input_visual_lines: 1,
118 theme: &THEME,
119 active_widgets: HashSet::new(),
120 }
121 }
122
123 fn test_sizes() -> WidgetSizes {
124 WidgetSizes {
125 heights: HashMap::new(),
126 is_active: HashMap::new(),
127 }
128 }
129
130 #[test]
131 fn test_sidebar_layout() {
132 let area = Rect::new(0, 0, 100, 24);
133 let ctx = test_context(area);
134 let sizes = test_sizes();
135
136 let opts = SidebarOptions {
137 sidebar_widget_id: "file_browser",
138 sidebar_width: SidebarWidth::Fixed(30),
139 ..Default::default()
140 };
141
142 let result = compute(&ctx, &sizes, &opts);
143
144 assert!(result.widget_areas.contains_key(widget_ids::CHAT_VIEW));
145 assert!(result.widget_areas.contains_key("file_browser"));
146
147 let sidebar_area = result.widget_areas.get("file_browser").unwrap();
148 assert_eq!(sidebar_area.width, 30);
149 }
150}