1pub mod cursor;
4pub mod damage;
5pub mod statusbar;
6pub mod text;
7
8use std::io::{self, Write};
9
10use crossterm::{QueueableCommand, cursor as ct_cursor, style};
11use emux_term::Screen;
12
13use crate::cursor::cursor_style;
14use crate::damage::DamageTracker;
15use crate::text::render_row;
16
17pub struct Renderer {
19 damage: DamageTracker,
20 last_cols: usize,
21 last_rows: usize,
22}
23
24impl Renderer {
25 pub fn new(cols: usize, rows: usize) -> Self {
27 Self {
28 damage: DamageTracker::new(rows),
29 last_cols: cols,
30 last_rows: rows,
31 }
32 }
33
34 pub fn render<W: Write>(&mut self, writer: &mut W, screen: &Screen) -> io::Result<()> {
36 let cols = screen.cols();
37 let rows = screen.rows();
38
39 if cols != self.last_cols || rows != self.last_rows {
41 self.resize(cols, rows);
42 }
43
44 if !self.damage.needs_redraw() {
45 return Ok(());
46 }
47
48 writer.queue(ct_cursor::Hide)?;
50
51 let dirty = self.damage.dirty_rows();
52 for row in dirty {
53 if row >= rows {
54 continue;
55 }
56
57 writer.queue(ct_cursor::MoveTo(0, row as u16))?;
59
60 let grid_row = screen.grid.row(row);
62 let spans = render_row(&grid_row.cells, cols);
63
64 for (content_style, text) in spans {
65 writer.queue(style::ResetColor)?;
66 writer.queue(style::SetStyle(content_style))?;
67 writer.queue(style::Print(&text))?;
68 }
69 }
70
71 writer.queue(style::ResetColor)?;
73 writer.queue(style::SetAttribute(style::Attribute::Reset))?;
74
75 let cursor = &screen.cursor;
77 writer.queue(ct_cursor::MoveTo(cursor.col as u16, cursor.row as u16))?;
78
79 if cursor.visible {
81 writer.queue(ct_cursor::Show)?;
82 writer.queue(cursor_style(cursor.shape))?;
83 } else {
84 writer.queue(ct_cursor::Hide)?;
85 }
86
87 writer.flush()?;
88 self.damage.clear();
89
90 Ok(())
91 }
92
93 pub fn force_redraw(&mut self) {
95 self.damage.mark_all();
96 }
97
98 pub fn resize(&mut self, cols: usize, rows: usize) {
100 self.last_cols = cols;
101 self.last_rows = rows;
102 self.damage.resize(rows);
103 }
104}