1use super::flush::flush_diff;
2use super::grid::Grid;
3use super::Theme;
4use crossterm::terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate};
5use crossterm::QueueableCommand;
6use std::io::Write;
7
8pub struct Compositor {
11 current: Grid,
12 previous: Grid,
13 width: u16,
14 height: u16,
15 force_redraw: bool,
16}
17
18impl Compositor {
19 pub fn new(width: u16, height: u16) -> Self {
20 Self {
21 current: Grid::new(width, height),
22 previous: Grid::new(width, height),
23 width,
24 height,
25 force_redraw: true,
26 }
27 }
28
29 pub fn resize(&mut self, width: u16, height: u16) {
30 self.width = width;
31 self.height = height;
32 self.current.resize(width, height);
33 self.previous.resize(width, height);
34 self.force_redraw = true;
35 }
36
37 pub fn render_with<W: Write, F: FnOnce(&mut Grid, &Theme)>(
43 &mut self,
44 theme: &Theme,
45 w: &mut W,
46 paint: F,
47 ) -> std::io::Result<()> {
48 self.current.clear_all();
49 paint(&mut self.current, theme);
50
51 w.queue(BeginSynchronizedUpdate)?;
52
53 if self.force_redraw {
54 flush_full(&self.current, w)?;
55 } else {
56 flush_diff(w, self.current.diff(&self.previous))?;
57 }
58
59 w.queue(EndSynchronizedUpdate)?;
60 w.flush()?;
61
62 self.current.swap_with(&mut self.previous);
63 self.force_redraw = false;
64
65 Ok(())
66 }
67
68 pub fn force_redraw(&mut self) {
69 self.force_redraw = true;
70 }
71
72 pub fn previous(&self) -> &Grid {
74 &self.previous
75 }
76}
77
78fn flush_full<W: Write>(grid: &Grid, w: &mut W) -> std::io::Result<()> {
79 use super::grid::Style;
80 use crossterm::cursor::MoveTo;
81 use crossterm::style::{
82 Attribute, ResetColor, SetAttribute, SetBackgroundColor, SetForegroundColor,
83 };
84
85 use crate::grid::char_width;
86
87 let mut current_style = Style::default();
88 for y in 0..grid.height() {
89 w.queue(MoveTo(0, y))?;
90 let mut terminal_col: u16 = 0;
91 let mut x = 0u16;
92 while x < grid.width() {
93 let cell = grid.cell(x, y);
94 let symbol = if cell.symbol == '\0' {
97 ' '
98 } else {
99 cell.symbol
100 };
101 let cw = char_width(symbol);
102
103 let (sym, emit_w) = if terminal_col + cw > grid.width() {
105 (' ', 1u16)
106 } else {
107 (symbol, cw)
108 };
109
110 if cell.style != current_style {
111 w.queue(SetAttribute(Attribute::Reset))?;
112 w.queue(ResetColor)?;
113 if let Some(fg) = cell.style.fg {
114 w.queue(SetForegroundColor(super::grid::to_crossterm_color(fg)))?;
115 }
116 if let Some(bg) = cell.style.bg {
117 w.queue(SetBackgroundColor(super::grid::to_crossterm_color(bg)))?;
118 }
119 if cell.style.bold {
120 w.queue(SetAttribute(Attribute::Bold))?;
121 }
122 if cell.style.dim {
123 w.queue(SetAttribute(Attribute::Dim))?;
124 }
125 if cell.style.italic {
126 w.queue(SetAttribute(Attribute::Italic))?;
127 }
128 if cell.style.underline {
129 w.queue(SetAttribute(Attribute::Underlined))?;
130 }
131 if cell.style.crossedout {
132 w.queue(SetAttribute(Attribute::CrossedOut))?;
133 }
134 if cell.style.reverse {
135 w.queue(SetAttribute(Attribute::Reverse))?;
136 }
137 current_style = cell.style;
138 }
139 let mut buf = [0u8; 4];
140 let s = sym.encode_utf8(&mut buf);
141 w.write_all(s.as_bytes())?;
142
143 terminal_col += emit_w;
144 x += emit_w;
146 }
147 }
148 w.queue(SetAttribute(Attribute::Reset))?;
149 w.queue(ResetColor)?;
150 Ok(())
151}