sericom_core/screen_buffer/
render.rs1use crossterm::style::Color;
2use std::io::BufWriter;
3use tracing::instrument;
4
5use super::{Cursor, EscapeState, Line, ScreenBuffer, UIAction};
6use crate::configs::get_config;
7
8const MIN_RENDER_INTERVAL: tokio::time::Duration = tokio::time::Duration::from_millis(33);
9
10impl ScreenBuffer {
11 #[instrument(name = "Add Data", skip(self, data))]
15 pub fn add_data(&mut self, data: &[u8]) {
16 let text = String::from_utf8_lossy(data);
17 let mut chars = text.chars().peekable();
18
19 while let Some(ch) = chars.next() {
20 match self.escape_state {
21 EscapeState::Normal => {
22 match ch {
23 '\r' => {
24 self.cursor_pos.x = 0;
25 if chars.peek() == Some(&'\n') {
26 chars.next();
27 self.new_line();
28 }
29 }
30 '\n' => {
31 self.new_line();
32 }
33 '\x07' => {}
34 '\x0E' => {}
35 '\x0F' => {}
36 '\x08' => {
37 let mut temp_chars = chars.clone();
38 if let (Some(' '), Some('\x08')) =
40 (temp_chars.next(), temp_chars.next())
41 {
42 chars.next();
44 chars.next();
45 self.move_cursor_left(1);
46 self.set_char_at_cursor(' ');
47 } else {
48 self.move_cursor_left(1);
51 }
52 }
53 '\x1B' => self.escape_state = EscapeState::Esc,
54 c => {
55 let mut batch = vec![c];
56 while let Some(&next_ch) = chars.peek() {
57 if next_ch.is_control()
58 || next_ch == '\x1B'
59 || self.cursor_pos.x + batch.len() as u16 >= self.width
60 {
61 break;
62 }
63 batch.push(chars.next().unwrap());
64 }
65 self.add_char_batch(&batch);
66 }
67 }
68 }
69 EscapeState::Esc => match ch {
70 '[' => self.escape_state = EscapeState::Csi,
71 _ => self.escape_state = EscapeState::Normal,
72 },
73 EscapeState::Csi => match ch {
74 ';' => self.escape_sequence.insert_separator(),
75 c if ch.is_ascii_digit() => self.escape_sequence.push_num(c),
76 c if c.is_ascii_alphabetic() => {
77 self.escape_sequence.push_action(c);
79 self.parse_sequence();
80 self.escape_sequence.reset();
81 self.escape_state = EscapeState::Normal;
82 }
83 _ => self.escape_state = EscapeState::Normal,
85 },
86 }
87 }
88 self.scroll_to_bottom();
90 }
91
92 fn add_char_batch(&mut self, chars: &[char]) {
93 tracing::debug!("CharBatch: '{:?}'", chars);
94 while self.cursor_pos.y >= self.lines.len() {
95 self.lines.push_back(Line::new(self.width as usize));
96 }
97
98 if let Some(line) = self.lines.get_mut(self.cursor_pos.y) {
99 for &ch in chars {
100 line.set_char(self.cursor_pos.x as usize, ch);
101 self.cursor_pos.x += 1;
102 if self.cursor_pos.x >= self.width {
103 self.new_line();
104 break;
105 }
106 }
107 }
108 }
109
110 pub fn should_render_now(&self) -> bool {
112 use tokio::time::Instant;
113
114 if !self.needs_render {
115 return false;
116 }
117
118 let now = Instant::now();
119 match self.last_render {
120 Some(last) => now.duration_since(last) >= MIN_RENDER_INTERVAL,
121 None => true,
122 }
123 }
124
125 pub fn render(&mut self) -> std::io::Result<()> {
135 use crossterm::{cursor, queue, style};
136 use std::io::{self, Write};
137 use tokio::time::Instant;
138
139 if !self.needs_render {
140 return Ok(());
141 }
142
143 let mut writer = BufWriter::new(io::stdout());
144 queue!(writer, cursor::Hide)?;
145 let config = get_config();
146
147 for screen_y in 0..self.height {
148 let line_idx = self.view_start + screen_y as usize;
149 queue!(writer, cursor::MoveTo(0, screen_y))?;
150
151 if let Some(line) = self.lines.get_mut(line_idx) {
152 let mut current_fg = Color::from(&config.appearance.fg);
153 let mut current_bg = Color::from(&config.appearance.bg);
154 queue!(
155 writer,
156 style::SetForegroundColor(current_fg),
157 style::SetBackgroundColor(current_bg)
158 )?;
159
160 for cell in line {
161 let global_reverse = self.display_attributes.has(style::Attribute::Reverse);
162
163 let fg = if (cell.is_selected && !global_reverse)
164 || (!cell.is_selected && global_reverse)
165 {
166 cell.bg_color
167 } else {
168 cell.fg_color
169 };
170
171 let bg = if (cell.is_selected && !global_reverse)
172 || (!cell.is_selected && global_reverse)
173 {
174 cell.fg_color
175 } else {
176 cell.bg_color
177 };
178
179 if fg != current_fg {
180 queue!(writer, style::SetForegroundColor(fg))?;
181 current_fg = fg;
182 }
183 if bg != current_bg {
184 queue!(writer, style::SetBackgroundColor(bg))?;
185 current_bg = bg;
186 }
187
188 if self.display_attributes.has(style::Attribute::Bold) {
189 queue!(
190 writer,
191 style::SetAttribute(style::Attribute::Bold),
192 style::Print(cell.character)
193 )?;
194 } else {
195 queue!(writer, style::Print(cell.character))?;
196 }
197 }
198 } else {
199 queue!(
200 writer,
201 style::ResetColor,
202 style::Print(" ".repeat(self.width as usize))
203 )?;
204 }
205 }
206
207 let screen_cursor_y = if self.cursor_pos.y >= self.view_start
210 && self.cursor_pos.y < self.view_start + self.height as usize
211 {
212 (self.cursor_pos.y - self.view_start) as u16
213 } else {
214 self.height - 1
215 };
216
217 queue!(
218 writer,
219 cursor::MoveTo(self.cursor_pos.x, screen_cursor_y),
220 cursor::Show
221 )?;
222 writer.flush()?;
223
224 self.last_render = Some(Instant::now());
225 self.needs_render = false;
226 Ok(())
227 }
228}