stynx_code_tui/render/
renderer.rs1use ratatui::Frame;
2use ratatui::style::Style;
3use ratatui::widgets::Block;
4
5use crate::layout::MainLayout;
6use crate::state::{AppState, ModalKind};
7use crate::theme;
8use crate::widgets::{DialogSelect, Footer, InfoDialog, InputBox, InputDialog, MessageList, PermissionDialog, Sidebar, SlashPopover, ThinkingPanel, ToastStack};
9
10pub struct Renderer;
11
12impl Renderer {
13 pub fn new() -> Self { Self }
14
15 pub fn draw(frame: &mut Frame, state: &mut AppState) {
16 let full = frame.area();
17 frame.render_widget(
18 Block::default().style(Style::default().bg(theme::BACKGROUND())),
19 full,
20 );
21
22 let thinking_lines = if state.is_streaming && !state.live_thinking.trim().is_empty() {
23 state
24 .live_thinking
25 .lines()
26 .filter(|l| !l.trim().is_empty())
27 .count()
28 } else {
29 0
30 };
31
32 let layout = MainLayout::split(
33 full,
34 state.sidebar.visible,
35 state.input.line_count(),
36 thinking_lines,
37 );
38
39 if let Some(sidebar_area) = layout.sidebar {
40 frame.render_widget(Sidebar::new(&state.sidebar), sidebar_area);
41 }
42
43 frame.render_widget(
44 MessageList::new(&mut state.conversation, state.spinner_frame)
45 .with_tool_details(state.tool_details),
46 layout.messages,
47 );
48 if let Some(thinking_area) = layout.thinking {
49 frame.render_widget(
50 ThinkingPanel::new(&state.live_thinking, state.spinner_frame),
51 thinking_area,
52 );
53 }
54 frame.render_widget(InputBox::new(&state.input, !state.is_streaming), layout.input);
55 if !state.input.slash_matches.is_empty() {
56 frame.render_widget(SlashPopover::new(&state.input, layout.input), full);
57 }
58 frame.render_widget(
59 Footer {
60 cwd: &state.cwd,
61 model: &state.model_name,
62 mode: &state.permission_mode,
63 cost: state.total_cost,
64 git_branch: state.git_branch.as_deref(),
65 is_streaming: state.is_streaming,
66 spinner_frame: state.spinner_frame,
67 },
68 layout.footer,
69 );
70
71 frame.render_widget(ToastStack::new(&state.toasts), full);
72
73 match &state.modal.active {
74 Some(ModalKind::Permission { tool_name, description, choice }) => {
75 frame.render_widget(
76 PermissionDialog::new(tool_name, description, *choice),
77 full,
78 );
79 }
80 Some(ModalKind::Select {
81 title,
82 query,
83 options,
84 selected,
85 current_value,
86 footer_hint,
87 ..
88 }) => {
89 frame.render_widget(
90 DialogSelect::new(title, query, options, *selected)
91 .with_current(current_value.as_deref())
92 .with_footer(footer_hint.as_deref()),
93 full,
94 );
95 }
96 Some(ModalKind::Info { title, rows }) => {
97 frame.render_widget(InfoDialog::new(title, rows), full);
98 }
99 Some(ModalKind::Input { title, prompt, buffer, .. }) => {
100 frame.render_widget(InputDialog::new(title, prompt, buffer), full);
101 }
102 Some(ModalKind::QuitConfirm) => {}
103 None => {}
104 }
105 }
106}
107
108impl Default for Renderer {
109 fn default() -> Self { Self }
110}