1mod buffer;
4
5use crate::{
6 color::{self, Color, Color2},
7 coord,
8 coord::{Coord, Vec2},
9 error::Error,
10 screen::buffer::ScreenBuffer,
11 stdio,
12 stdio::{restore_screen, save_screen, LockedStdout, Stdout},
13 string::{TermGrapheme, TermString},
14 style::Style,
15 terminal::Shared,
16 tile::{self, Tile},
17};
18use std::{
19 fmt::Write,
20 sync::atomic::{AtomicBool, Ordering::*},
21 time::Duration,
22};
23use tokio::{
24 io,
25 sync::{Mutex, MutexGuard, Notify},
26 task,
27 time,
28};
29
30#[derive(Debug)]
32pub(crate) struct ScreenData {
33 min_size: Vec2,
35 frame_time: Duration,
37 cleanedup: AtomicBool,
40 stdout: Stdout,
42 buffer: Mutex<ScreenBuffer>,
44 notifier: Notify,
46}
47
48impl ScreenData {
49 pub fn new(size: Vec2, min_size: Vec2, frame_time: Duration) -> Self {
53 let corrected_size = if size.x >= min_size.x && size.y >= min_size.y {
54 size
55 } else {
56 min_size
57 };
58 Self {
59 min_size,
60 frame_time,
61 cleanedup: AtomicBool::new(false),
62 stdout: Stdout::new(),
63 buffer: Mutex::new(ScreenBuffer::blank(corrected_size)),
64 notifier: Notify::new(),
65 }
66 }
67
68 pub fn notify(&self) {
70 self.notifier.notify_waiters()
71 }
72
73 async fn subscribe(&self) {
75 self.notifier.notified().await
76 }
77
78 pub async fn lock<'this>(&'this self) -> Screen<'this> {
80 Screen::new(self).await
81 }
82
83 pub async fn setup(&self) -> Result<(), Error> {
85 let mut buf = String::new();
86 save_screen(&mut buf)?;
87 write!(
88 buf,
89 "{}{}{}{}",
90 crossterm::style::SetBackgroundColor(
91 crossterm::style::Color::Black
92 ),
93 crossterm::style::SetForegroundColor(
94 crossterm::style::Color::White
95 ),
96 crossterm::cursor::Hide,
97 crossterm::terminal::Clear(crossterm::terminal::ClearType::All),
98 )?;
99 self.stdout.write_and_flush(buf.as_bytes()).await?;
100 Ok(())
101 }
102
103 pub async fn cleanup(&self) -> Result<(), Error> {
105 task::block_in_place(|| crossterm::terminal::disable_raw_mode())
106 .map_err(Error::from_crossterm)?;
107 let mut buf = String::new();
108 write!(buf, "{}", crossterm::cursor::Show)?;
109 restore_screen(&mut buf)?;
110 self.stdout.write_and_flush(buf.as_bytes()).await?;
111 self.cleanedup.store(true, Release);
112 Ok(())
113 }
114}
115
116impl Drop for ScreenData {
117 fn drop(&mut self) {
118 if !self.cleanedup.load(Relaxed) {
119 let _ = crossterm::terminal::disable_raw_mode();
120 let mut buf = String::new();
121 write!(buf, "{}", crossterm::cursor::Show)
122 .ok()
123 .and_then(|_| stdio::restore_screen(&mut buf).ok())
124 .map(|_| println!("{}", buf));
125 }
126 }
127}
128
129#[cold]
132#[inline(never)]
133fn out_of_bounds(point: Vec2, size: Vec2) -> ! {
134 panic!(
135 "Point x: {}, y: {} out of screen size x: {}, y: {}",
136 point.x, point.y, size.x, size.y
137 )
138}
139
140#[derive(Debug)]
144pub struct Screen<'terminal> {
145 data: &'terminal ScreenData,
147 buffer: MutexGuard<'terminal, ScreenBuffer>,
149}
150
151impl<'terminal> Screen<'terminal> {
152 pub(crate) async fn new<'param>(
155 data: &'param ScreenData,
156 ) -> Screen<'terminal>
157 where
158 'param: 'terminal,
159 {
160 Self { data, buffer: data.buffer.lock().await }
161 }
162
163 pub fn size(&self) -> Vec2 {
165 self.buffer.size()
166 }
167
168 pub fn valid_size(&self) -> bool {
171 self.buffer.valid
172 }
173
174 pub fn min_size(&self) -> Vec2 {
176 self.data.min_size
177 }
178
179 pub fn set<T>(&mut self, point: Vec2, updater: T)
185 where
186 T: tile::Updater,
187 {
188 let index = self
189 .buffer
190 .make_index(point)
191 .unwrap_or_else(|| out_of_bounds(point, self.buffer.size()));
192 updater.update(&mut self.buffer.curr[index]);
193 if self.buffer.old[index] != self.buffer.curr[index] {
194 self.buffer.changed.insert(point);
195 } else {
196 self.buffer.changed.remove(&point);
197 }
198 }
199
200 pub fn get(&self, point: Vec2) -> &Tile {
203 let index = self
204 .buffer
205 .make_index(point)
206 .unwrap_or_else(|| out_of_bounds(point, self.buffer.size()));
207 &self.buffer.curr[index]
208 }
209
210 pub fn clear(&mut self, background: Color) {
212 let size = self.buffer.size();
213 let tile = Tile {
214 colors: Color2 { background, ..Color2::default() },
215 grapheme: TermGrapheme::space(),
216 };
217
218 for y in 0 .. size.y {
219 for x in 0 .. size.x {
220 self.set(Vec2 { x, y }, tile.clone());
221 }
222 }
223 }
224
225 pub fn styled_text<C>(
229 &mut self,
230 tstring: &TermString,
231 style: Style<C>,
232 ) -> Coord
233 where
234 C: color::Updater,
235 {
236 let mut len = tstring.count_graphemes();
237 let mut slice = tstring.index(..);
238 let screen_size = self.buffer.size();
239 let size = style.make_size(screen_size);
240
241 let mut cursor = Vec2 { x: 0, y: style.top_margin };
242 let mut is_inside = cursor.y - style.top_margin < size.y;
243
244 while len > 0 && is_inside {
245 is_inside = cursor.y - style.top_margin + 1 < size.y;
246 let width = coord::to_index(size.x);
247 let pos = self.find_break_pos(width, len, size, &slice, is_inside);
248
249 cursor.x = size.x - coord::from_index(pos);
250 cursor.x = cursor.x + style.left_margin - style.right_margin;
251 cursor.x = cursor.x * style.align_numer / style.align_denom;
252
253 slice = slice.index(.. pos);
254
255 self.write_styled_slice(&slice, &style, &mut cursor);
256
257 if pos != len && !is_inside {
258 self.set(cursor, |tile: &mut Tile| {
259 let grapheme = TermGrapheme::new_lossy("…");
260 let colors = style.colors.update(tile.colors);
261 *tile = Tile { grapheme, colors };
262 });
263 }
264
265 cursor.y += 1;
266 len -= pos;
267 }
268 cursor.y
269 }
270
271 fn find_break_pos(
273 &self,
274 width: usize,
275 total_graphemes: usize,
276 term_size: Vec2,
277 slice: &TermString,
278 is_inside: bool,
279 ) -> usize {
280 if width <= slice.len() {
281 let mut pos = slice
282 .index(.. coord::to_index(term_size.x))
283 .iter()
284 .rev()
285 .position(|grapheme| grapheme == TermGrapheme::space())
286 .map_or(width, |rev| total_graphemes - rev);
287 if !is_inside {
288 pos -= 1;
289 }
290 pos
291 } else {
292 total_graphemes
293 }
294 }
295
296 fn write_styled_slice<C>(
298 &mut self,
299 slice: &TermString,
300 style: &Style<C>,
301 cursor: &mut Vec2,
302 ) where
303 C: color::Updater,
304 {
305 for grapheme in slice {
306 self.set(*cursor, |tile: &mut Tile| {
307 tile.grapheme = grapheme;
308 tile.colors = style.colors.update(tile.colors);
309 });
310 cursor.x += 1;
311 }
312 }
313
314 pub(crate) async fn check_resize(
318 &mut self,
319 new_size: Vec2,
320 guard: &mut Option<LockedStdout<'terminal>>,
321 ) -> io::Result<()> {
322 let min_size = self.data.min_size;
323 if new_size.x < min_size.x || new_size.y < min_size.y {
324 if guard.is_none() {
325 self.buffer.valid = false;
326 let mut stdout = self.data.stdout.lock().await;
327 self.ask_resize(&mut stdout, min_size).await?;
328 *guard = Some(stdout);
329 }
330 } else {
331 let mut stdout = match guard.take() {
332 Some(stdout) => stdout,
333 None => self.data.stdout.lock().await,
334 };
335 self.buffer.valid = true;
336 self.resize(new_size, &mut stdout).await?;
337 }
338
339 Ok(())
340 }
341
342 async fn ask_resize(
344 &mut self,
345 stdout: &mut LockedStdout<'terminal>,
346 min_size: Vec2,
347 ) -> io::Result<()> {
348 let buf = format!(
349 "{}{}RESIZE {}x{}",
350 crossterm::terminal::Clear(crossterm::terminal::ClearType::All),
351 crossterm::cursor::MoveTo(0, 0),
352 min_size.x,
353 min_size.y,
354 );
355
356 stdout.write_and_flush(buf.as_bytes()).await?;
357
358 Ok(())
359 }
360
361 async fn resize(
363 &mut self,
364 new_size: Vec2,
365 stdout: &mut LockedStdout<'terminal>,
366 ) -> io::Result<()> {
367 let buf = format!(
368 "{}{}{}",
369 crossterm::style::SetForegroundColor(
370 crossterm::style::Color::White
371 ),
372 crossterm::style::SetBackgroundColor(
373 crossterm::style::Color::Black
374 ),
375 crossterm::terminal::Clear(crossterm::terminal::ClearType::All)
376 );
377 stdout.write_and_flush(buf.as_bytes()).await?;
378 self.buffer.resize(new_size);
379
380 Ok(())
381 }
382
383 pub(crate) async fn render(
385 &mut self,
386 buf: &mut String,
387 ) -> Result<(), Error> {
388 let screen_size = self.buffer.size();
389 buf.clear();
390
391 let mut colors = Color2::default();
392 let mut cursor = Vec2 { x: 0, y: 0 };
393 self.render_init_term(buf, colors, cursor)?;
394
395 for &coord in self.buffer.changed.iter() {
396 self.render_tile(
397 buf,
398 &mut colors,
399 &mut cursor,
400 screen_size,
401 coord,
402 )?;
403 }
404
405 if let Some(mut stdout) = self.data.stdout.try_lock() {
406 stdout.write_and_flush(buf.as_bytes()).await?;
407 }
408
409 self.buffer.next_tick();
410
411 Ok(())
412 }
413
414 fn render_init_term(
416 &self,
417 buf: &mut String,
418 colors: Color2,
419 cursor: Vec2,
420 ) -> Result<(), Error> {
421 write!(
422 buf,
423 "{}{}{}",
424 crossterm::style::SetForegroundColor(
425 colors.foreground.to_crossterm()
426 ),
427 crossterm::style::SetBackgroundColor(
428 colors.background.to_crossterm()
429 ),
430 crossterm::cursor::MoveTo(
431 coord::to_crossterm(cursor.x),
432 coord::to_crossterm(cursor.y)
433 ),
434 )?;
435
436 Ok(())
437 }
438
439 fn render_tile(
441 &self,
442 buf: &mut String,
443 colors: &mut Color2,
444 cursor: &mut Vec2,
445 screen_size: Vec2,
446 coord: Vec2,
447 ) -> Result<(), Error> {
448 if *cursor != coord {
449 write!(
450 buf,
451 "{}",
452 crossterm::cursor::MoveTo(
453 coord::to_crossterm(coord.x),
454 coord::to_crossterm(coord.y)
455 )
456 )?;
457 }
458 *cursor = coord;
459
460 let tile = self.get(*cursor);
461 if colors.background != tile.colors.background {
462 let color = tile.colors.background.to_crossterm();
463 write!(buf, "{}", crossterm::style::SetBackgroundColor(color))?;
464 }
465 if colors.foreground != tile.colors.foreground {
466 let color = tile.colors.foreground.to_crossterm();
467 write!(buf, "{}", crossterm::style::SetForegroundColor(color))?;
468 }
469 *colors = tile.colors;
470
471 write!(buf, "{}", tile.grapheme)?;
472
473 if cursor.x <= screen_size.x {
474 cursor.x += 1;
475 }
476
477 Ok(())
478 }
479}
480
481pub(crate) async fn renderer(shared: &Shared) -> Result<(), Error> {
484 let mut interval = time::interval(shared.screen().frame_time);
485 let mut buf = String::new();
486
487 loop {
488 {
489 let _guard = shared.service_guard().await?;
490 let mut screen = shared.screen().lock().await;
491 screen.render(&mut buf).await?;
492 }
493
494 tokio::select! {
495 _ = interval.tick() => (),
496 _ = shared.screen().subscribe() => break,
497 };
498 }
499
500 Ok(())
501}