agent_core/tui/layout/
split.rs1use ratatui::layout::{Constraint, Direction, Layout};
4
5use crate::tui::widgets::widget_ids;
6
7use super::types::{LayoutContext, LayoutResult, WidgetSizes};
8
9#[derive(Clone)]
11pub struct SplitOptions {
12 pub direction: Direction,
14 pub first_widget_id: &'static str,
16 pub second_widget_id: &'static str,
18 pub split: SplitRatio,
20 pub input_widget_id: &'static str,
22 pub status_bar_widget_id: Option<&'static str>,
24}
25
26#[derive(Clone)]
28pub enum SplitRatio {
29 Equal,
31 Percentage(u16),
33 FirstFixed(u16),
35 SecondFixed(u16),
37}
38
39impl Default for SplitOptions {
40 fn default() -> Self {
41 Self {
42 direction: Direction::Horizontal,
43 first_widget_id: widget_ids::CHAT_VIEW,
44 second_widget_id: "secondary",
45 split: SplitRatio::Equal,
46 input_widget_id: widget_ids::TEXT_INPUT,
47 status_bar_widget_id: Some(widget_ids::STATUS_BAR),
48 }
49 }
50}
51
52pub fn compute(ctx: &LayoutContext, sizes: &WidgetSizes, opts: &SplitOptions) -> LayoutResult {
54 let mut result = LayoutResult::default();
55 let area = ctx.frame_area;
56
57 let input_height = if ctx.show_throbber {
59 3
60 } else {
61 (ctx.input_visual_lines as u16).max(1) + 2
62 };
63
64 let status_height = if let Some(status_id) = opts.status_bar_widget_id {
65 if sizes.is_active(status_id) {
66 sizes.height(status_id)
67 } else {
68 0
69 }
70 } else {
71 0
72 };
73
74 let v_chunks = Layout::default()
76 .direction(Direction::Vertical)
77 .constraints([
78 Constraint::Min(5),
79 Constraint::Length(input_height),
80 Constraint::Length(status_height),
81 ])
82 .split(area);
83
84 let content_area = v_chunks[0];
85 result.input_area = Some(v_chunks[1]);
86 result.widget_areas.insert(opts.input_widget_id, v_chunks[1]);
87
88 if let Some(status_id) = opts.status_bar_widget_id {
89 if sizes.is_active(status_id) && status_height > 0 {
90 result.widget_areas.insert(status_id, v_chunks[2]);
91 result.render_order.push(status_id);
92 }
93 }
94
95 let split_constraint = match opts.split {
97 SplitRatio::Equal => Constraint::Percentage(50),
98 SplitRatio::Percentage(p) => Constraint::Percentage(p),
99 SplitRatio::FirstFixed(w) => Constraint::Length(w),
100 SplitRatio::SecondFixed(_) => Constraint::Min(1), };
102
103 let second_constraint = match opts.split {
104 SplitRatio::SecondFixed(w) => Constraint::Length(w),
105 _ => Constraint::Min(1),
106 };
107
108 let content_chunks = Layout::default()
109 .direction(opts.direction.clone())
110 .constraints([split_constraint, second_constraint])
111 .split(content_area);
112
113 result.widget_areas.insert(opts.first_widget_id, content_chunks[0]);
114 result.widget_areas.insert(opts.second_widget_id, content_chunks[1]);
115
116 result.render_order = vec![
117 opts.first_widget_id,
118 opts.second_widget_id,
119 opts.input_widget_id,
120 ];
121
122 result
123}