p2p_chat/ui/terminal/
render.rs1use anyhow::Result;
3use crossterm::{
4 cursor, queue,
5 style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor},
6 terminal::{Clear, ClearType},
7};
8use std::io::{stdout, Write};
9use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
10
11use crate::ui::UIMode;
12
13use super::TerminalUI;
14
15impl TerminalUI {
16 pub(super) fn render(&mut self) -> Result<()> {
29 let mut stdout = stdout();
30
31 queue!(stdout, Clear(ClearType::All), cursor::MoveTo(0, 0))?;
32
33 let (width, height) = self.state.terminal_size;
34 let message_area_height = (height as f32 * 0.8) as u16; let status_line_row = message_area_height; let input_area_row = status_line_row + 1; let input_area_height = height - input_area_row; match &self.state.mode {
41 UIMode::Chat => {
42 self.chat_mode.render(
43 &mut stdout,
44 &self.state,
45 (0, 0, width, message_area_height),
46 self.node.as_deref(), )?;
48 }
49 UIMode::Logs { .. } => {
50 self.log_mode.render(
51 &mut stdout,
52 &self.state,
53 (0, 0, width, message_area_height),
54 )?;
55 }
56 }
57
58 self.render_status_line(&mut stdout, status_line_row, width)?;
60 self.render_input_area(&mut stdout, input_area_row, width, input_area_height)?;
62
63 stdout.flush()?;
64 Ok(())
65 }
66
67 fn render_status_line(&self, stdout: &mut impl Write, row: u16, width: u16) -> Result<()> {
81 queue!(
82 stdout,
83 cursor::MoveTo(0, row),
84 SetBackgroundColor(Color::DarkGrey),
85 SetForegroundColor(Color::White)
86 )?;
87
88 let status_text = match &self.state.mode {
89 UIMode::Chat => format!(
90 " Status: Chat Mode | Peers: {} | F9: Logs | Ctrl+C: Exit",
91 self.state.connected_peers_count
92 ),
93 UIMode::Logs { filter, level } => {
94 let filter_text = filter
95 .as_ref()
96 .map(|f| format!(" | Filter: {}", f))
97 .unwrap_or_default();
98 format!(
99 " Status: Log Mode | Level: {:?}{} | Entries: {} | F9: Chat",
100 level,
101 filter_text,
102 self.state.logs.len()
103 )
104 }
105 };
106
107 let display_text = if status_text.chars().count() > width as usize {
109 status_text.chars().take(width as usize).collect::<String>()
110 } else {
111 status_text.clone()
112 };
113
114 queue!(stdout, Print(&display_text))?;
115
116 let padding = width as usize - UnicodeWidthStr::width(display_text.as_str());
118 if padding > 0 {
119 queue!(stdout, Print(" ".repeat(padding)))?;
120 }
121
122 queue!(stdout, ResetColor)?;
123 Ok(())
124 }
125
126 fn render_input_area(
142 &self,
143 stdout: &mut impl Write,
144 row: u16,
145 width: u16,
146 height: u16,
147 ) -> Result<()> {
148 queue!(stdout, cursor::MoveTo(0, row))?;
149
150 let prompt = match &self.state.mode {
151 UIMode::Chat => "p2p> ",
152 UIMode::Logs { .. } => "log> ",
153 };
154
155 queue!(
156 stdout,
157 SetForegroundColor(Color::Cyan),
158 Print(prompt),
159 ResetColor
160 )?;
161
162 queue!(stdout, Print(&self.state.input_buffer))?;
164
165 if matches!(self.state.mode, UIMode::Chat) {
167 if let Some(suggestion) = self.chat_mode.get_current_suggestion() {
168 if suggestion.starts_with(&self.state.input_buffer)
170 && suggestion != self.state.input_buffer
171 {
172 let input_char_count = self.state.input_buffer.chars().count();
173 let hint: String = suggestion.chars().skip(input_char_count).collect();
174 queue!(
175 stdout,
176 SetForegroundColor(Color::DarkGrey),
177 Print(hint),
178 ResetColor
179 )?;
180 }
181 }
182 }
183
184 let input_display_width: usize = self
186 .state
187 .input_buffer
188 .chars()
189 .take(self.state.cursor_pos)
190 .map(|c| UnicodeWidthChar::width(c).unwrap_or(0))
191 .sum();
192
193 let prompt_display_width: usize = prompt
194 .chars()
195 .map(|c| UnicodeWidthChar::width(c).unwrap_or(1))
196 .sum();
197
198 let cursor_x = prompt_display_width + input_display_width;
199 if cursor_x < width as usize {
200 queue!(stdout, cursor::MoveTo(cursor_x as u16, row))?;
201 }
202
203 if height > 2 {
205 let help_row = row + height - 1;
206 queue!(stdout, cursor::MoveTo(0, help_row))?;
207
208 let help_text = match &self.state.mode {
209 UIMode::Chat =>
210 " Tab: complete | ↑↓: history | PgUp/Down: scroll | Ctrl+Home/End: H-scroll | F9: logs",
211 UIMode::Logs { .. } =>
212 " Tab: complete | ↑↓: scroll | Ctrl+Home/End: H-scroll | F9: chat",
213 };
214
215 queue!(
216 stdout,
217 SetForegroundColor(Color::DarkGrey),
218 Print(help_text),
219 ResetColor
220 )?;
221 }
222
223 Ok(())
224 }
225}