use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::prelude::*;
use unicode_width::UnicodeWidthChar;
use crate::tui::state::UiState;
use crate::tui::widgets::input::PROMPT_WIDTH;
use crate::tui::widgets::sidebar::{SIDEBAR_MIN_WIDTH, SIDEBAR_WIDTH};
use crate::tui::widgets::{input, output, popup, sidebar, status};
pub fn render(frame: &mut Frame, state: &UiState, working_dir: &str) {
let size = frame.area();
let show_sidebar = !state.fast_mode && size.width >= SIDEBAR_MIN_WIDTH;
let (main_area, sidebar_area_opt) = if show_sidebar {
let main_w = size.width.saturating_sub(SIDEBAR_WIDTH);
let cols = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Length(main_w),
Constraint::Length(SIDEBAR_WIDTH),
])
.split(size);
(cols[0], Some(cols[1]))
} else {
(size, None)
};
let text_w = main_area.width.saturating_sub(PROMPT_WIDTH) as usize;
let input_line_height: u16 = {
let mut rows = 1u16;
let mut col = 0usize;
for ch in state.input.chars() {
if ch == '\n' {
rows += 1;
col = 0;
} else {
let w = ch.width().unwrap_or(1);
if text_w > 0 && col + w > text_w {
rows += 1;
col = 0;
}
col += w;
}
}
rows
};
let input_height = (1 + input_line_height + 1).clamp(3, 10);
let main_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Min(10), Constraint::Length(input_height), Constraint::Length(3), ])
.split(main_area);
output::render(state, main_layout[0], frame.buffer_mut());
if let Some(sa) = sidebar_area_opt {
sidebar::render(state, sa, frame.buffer_mut(), working_dir);
}
input::render(state, main_layout[1], frame.buffer_mut());
status::render(state, main_layout[2], frame.buffer_mut());
popup::render(state, size, frame.buffer_mut());
if let Some((msg, shown_at)) = &state.soul_toast {
let elapsed = shown_at.elapsed().as_millis();
let color = if elapsed < 2000 {
state.theme.text_dim
} else if elapsed < 3000 {
state.theme.text_muted
} else if elapsed < 4000 {
state.theme.border
} else {
return;
};
let toast_text = format!(" ✦ {msg} ");
let x = main_area.x + main_area.width.saturating_sub(toast_text.len() as u16 + 2);
let y = main_layout[0].y + main_layout[0].height.saturating_sub(2);
let buf = frame.buffer_mut();
for (i, ch) in toast_text.chars().enumerate() {
let cell = buf.cell_mut((x + i as u16, y));
if let Some(c) = cell {
c.set_char(ch);
c.set_fg(color);
}
}
}
if !state.agent_busy {
let input_area = main_layout[1];
let tw = input_area.width.saturating_sub(PROMPT_WIDTH) as usize;
let text_before = &state.input[..state.cursor.min(state.input.len())];
let mut col = 0usize;
let mut row = 0u16;
for ch in text_before.chars() {
if ch == '\n' {
row += 1;
col = 0;
} else {
let w = ch.width().unwrap_or(1);
if tw > 0 && col + w > tw {
row += 1;
col = 0;
}
col += w;
}
}
let cursor_x = (input_area.x + PROMPT_WIDTH + col as u16)
.min(input_area.x + input_area.width.saturating_sub(1));
let cursor_y = input_area.y + 1 + row; if cursor_y < input_area.y + input_area.height {
frame.set_cursor_position((cursor_x, cursor_y));
}
}
}