agent_core/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.widget_areas.insert(opts.sidebar_widget_id, sidebar_area);
96 result.render_order.insert(0, opts.sidebar_widget_id);
98
99 result
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::tui::themes::Theme;
106 use crate::tui::widgets::widget_ids;
107 use ratatui::layout::Rect;
108 use std::collections::{HashMap, HashSet};
109
110 fn test_context(area: Rect) -> LayoutContext<'static> {
111 static THEME: std::sync::LazyLock<Theme> = std::sync::LazyLock::new(Theme::default);
112 LayoutContext {
113 frame_area: area,
114 show_throbber: false,
115 input_visual_lines: 1,
116 theme: &THEME,
117 active_widgets: HashSet::new(),
118 }
119 }
120
121 fn test_sizes() -> WidgetSizes {
122 WidgetSizes {
123 heights: HashMap::new(),
124 is_active: HashMap::new(),
125 }
126 }
127
128 #[test]
129 fn test_sidebar_layout() {
130 let area = Rect::new(0, 0, 100, 24);
131 let ctx = test_context(area);
132 let sizes = test_sizes();
133
134 let opts = SidebarOptions {
135 sidebar_widget_id: "file_browser",
136 sidebar_width: SidebarWidth::Fixed(30),
137 ..Default::default()
138 };
139
140 let result = compute(&ctx, &sizes, &opts);
141
142 assert!(result.widget_areas.contains_key(widget_ids::CHAT_VIEW));
143 assert!(result.widget_areas.contains_key("file_browser"));
144
145 let sidebar_area = result.widget_areas.get("file_browser").unwrap();
146 assert_eq!(sidebar_area.width, 30);
147 }
148}