use super::input_panel::render_input_box;
use super::layout::{centered_overlay_rect, home_layout, main_layout};
use crate::app::agent::agent::loop_::cli::logo_text_lines;
use crate::app::agent::agent::loop_::cli::render_execution_indicator;
use crate::app::agent::agent::loop_::cli::theme::{TEXT_MUTED, TEXT_SUBTLE, WARNING};
use crate::app::agent::agent::loop_::cli::transcript::wrap_trim_disabled;
use crate::app::agent::agent::loop_::cli::tui_utils::cursor_position;
use ratatui::layout::{Alignment, Layout};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
fn adaptive_logo_scale(area: ratatui::layout::Rect, input_height: u16) -> usize {
let max_by_height = area.height.saturating_sub(input_height.saturating_add(6));
let max_by_width = area.width.saturating_sub(3) / 16;
usize::from(max_by_height.min(max_by_width).clamp(1, 8))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn draw_home_screen(
f: &mut ratatui::Frame<'_>,
area: ratatui::layout::Rect,
input: &str,
cursor_idx: usize,
busy: bool,
model_name: &str,
_workspace: &str,
input_height: u16,
neon: Color,
spinner_idx: usize,
show_menu: bool,
) {
let logo_lines = logo_text_lines(adaptive_logo_scale(area, input_height));
let logo_height = u16::try_from(logo_lines.len()).unwrap_or(u16::MAX);
let (chunks, center_chunks) = home_layout(area, logo_height, input_height);
f.render_widget(
Paragraph::new(Text::from(logo_lines)).alignment(Alignment::Center),
center_chunks[0],
);
let input_row = Layout::default()
.direction(ratatui::layout::Direction::Horizontal)
.constraints([
ratatui::layout::Constraint::Percentage(12),
ratatui::layout::Constraint::Percentage(76),
ratatui::layout::Constraint::Percentage(12),
])
.split(center_chunks[2]);
let input_inner_area = input_row[1];
let input_text_area = render_input_box(f, input_inner_area, input);
render_execution_indicator(f, input_row[0], busy, spinner_idx);
let esc_line = if busy {
Line::from(vec![
Span::styled("esc", Style::default().fg(WARNING).add_modifier(Modifier::BOLD)),
Span::styled(" 取消", Style::default().fg(TEXT_MUTED)),
])
} else {
Line::from(Span::raw(""))
};
f.render_widget(Paragraph::new(esc_line).alignment(Alignment::Right), input_row[2]);
let meta_row = Layout::default()
.direction(ratatui::layout::Direction::Horizontal)
.constraints([
ratatui::layout::Constraint::Percentage(12),
ratatui::layout::Constraint::Percentage(76),
ratatui::layout::Constraint::Percentage(12),
])
.split(center_chunks[3]);
let meta_inner = Layout::default()
.direction(ratatui::layout::Direction::Horizontal)
.constraints([
ratatui::layout::Constraint::Percentage(45),
ratatui::layout::Constraint::Percentage(55),
])
.split(meta_row[1]);
let active_mode_style = Style::default().fg(neon).add_modifier(Modifier::BOLD);
let build_line = Line::from(vec![Span::styled(model_name, Style::default().fg(TEXT_MUTED))]);
f.render_widget(Paragraph::new(build_line), meta_inner[0]);
let hint_line = Line::from(vec![
Span::styled("ctrl+p", active_mode_style),
Span::styled(" 命令 ", Style::default().fg(TEXT_SUBTLE)),
Span::styled("ctrl+t", active_mode_style),
Span::styled(" 工具详情 ", Style::default().fg(TEXT_SUBTLE)),
Span::styled("ctrl+y", active_mode_style),
Span::styled(" 思考详情", Style::default().fg(TEXT_SUBTLE)),
]);
f.render_widget(Paragraph::new(hint_line).alignment(Alignment::Right), meta_inner[1]);
let tip_row = Layout::default()
.direction(ratatui::layout::Direction::Horizontal)
.constraints([
ratatui::layout::Constraint::Percentage(12),
ratatui::layout::Constraint::Percentage(76),
ratatui::layout::Constraint::Percentage(12),
])
.split(center_chunks[4]);
let tip_line = Line::from(vec![
Span::styled("● ", Style::default().fg(WARNING)),
Span::styled(
"提示:Shift+Enter 或 Ctrl+J 换行,Enter 发送,Ctrl+Z 挂起",
Style::default().fg(TEXT_MUTED),
),
]);
f.render_widget(Paragraph::new(tip_line).alignment(Alignment::Center), tip_row[1]);
f.render_widget(Paragraph::new(Line::from(Span::raw(""))), chunks[3]);
let safe_cursor = cursor_idx.min(input.chars().count());
let (row, col) = cursor_position(input, safe_cursor);
let max_row = input_text_area.height.saturating_sub(1);
let max_col = input_text_area.width.saturating_sub(1);
let safe_row: u16 = row.min(max_row);
let safe_col: u16 = col.min(max_col);
let cursor_x = input_text_area.x.saturating_add(safe_col);
let cursor_y = input_text_area.y.saturating_add(safe_row);
f.set_cursor_position((cursor_x, cursor_y));
if show_menu {
let overlay = centered_overlay_rect(area, 2, 5, 10);
let lines = vec![Line::from(Span::styled("暂无命令", Style::default().fg(TEXT_SUBTLE)))];
let popup = Paragraph::new(Text::from(lines))
.block(Block::default().borders(Borders::ALL).title("Commands"));
f.render_widget(Clear, overlay);
f.render_widget(popup, overlay);
}
}
pub(crate) fn draw_main_screen(
f: &mut ratatui::Frame<'_>,
right_chunks: Vec<ratatui::layout::Rect>,
session_title: &str,
modified_files: &[String],
files_collapsed: bool,
neon_border: Style,
_workspace: &str,
awaiting_clear_confirm: bool,
show_menu: bool,
) {
let session_widget = Paragraph::new(session_title)
.block(Block::default().borders(Borders::ALL).title("Session").border_style(neon_border));
f.render_widget(session_widget, right_chunks[0]);
let mut files_lines: Vec<Line> = if files_collapsed {
vec![Line::from(Span::styled("已折叠", Style::default().fg(TEXT_SUBTLE)))]
} else if modified_files.is_empty() {
vec![Line::from(Span::styled("暂无修改文件", Style::default().fg(TEXT_SUBTLE)))]
} else {
modified_files.iter().map(|path| Line::from(Span::raw(format!("• {}", path)))).collect()
};
let files_visible_height = right_chunks[1].height.saturating_sub(2) as usize;
if files_visible_height == 0 {
files_lines.clear();
} else if !files_collapsed && files_lines.len() > files_visible_height {
let hidden = files_lines.len().saturating_sub(files_visible_height).saturating_add(1);
files_lines.truncate(files_visible_height);
if !files_lines.is_empty() {
files_lines.pop();
}
files_lines.push(Line::from(Span::styled(
format!("… +{hidden}"),
Style::default().fg(TEXT_SUBTLE),
)));
}
let files_widget = Paragraph::new(Text::from(files_lines))
.block(Block::default().borders(Borders::ALL).title("修改文件").border_style(neon_border))
.wrap(wrap_trim_disabled());
f.render_widget(files_widget, right_chunks[1]);
let mut footer = Vec::new();
if awaiting_clear_confirm {
footer.push(Span::styled(
"confirm clear: type y or yes",
Style::default().fg(WARNING).add_modifier(Modifier::BOLD),
));
}
let layout = main_layout(f.area());
let footer_area = layout.footer_area();
f.render_widget(Paragraph::new(Line::from(footer)), footer_area);
if show_menu {
let overlay = centered_overlay_rect(f.area(), 2, 5, 10);
let lines = vec![Line::from(Span::styled("暂无命令", Style::default().fg(TEXT_SUBTLE)))];
let popup = Paragraph::new(Text::from(lines))
.block(Block::default().borders(Borders::ALL).title("Commands"));
f.render_widget(Clear, overlay);
f.render_widget(popup, overlay);
}
}
#[cfg(test)]
#[path = "build_screen_tests.rs"]
mod build_screen_tests;