1pub use crossterm;
2use crossterm::{
3 cursor,
4 style::{self, Attribute, Color},
5 terminal, QueueableCommand,
6};
7use std::io::Write;
8
9#[derive(PartialEq, Eq, Clone, Copy, Debug)]
10pub struct Cell {
12 pub ch: char,
14 pub fg: Color,
16 pub bg: Color,
18 pub attr: Attribute,
20}
21
22impl Default for Cell {
23 fn default() -> Self {
24 Self::empty()
25 }
26}
27
28impl Cell {
29 pub const fn empty() -> Self {
31 Self::empty_colored(Color::Black)
32 }
33
34 pub const fn empty_colored(color: Color) -> Self {
36 Self {
37 ch: ' ',
38 fg: Color::White,
39 bg: color,
40 attr: Attribute::Reset,
41 }
42 }
43
44 pub fn render<T: Write>(&self, q: &mut T) -> Result<(), std::io::Error> {
45 q.queue(style::SetAttribute(self.attr))?;
46 q.queue(style::SetForegroundColor(self.fg))?;
47 q.queue(style::SetBackgroundColor(self.bg))?;
48
49 let mut buf = [0u8; 4];
50 let buf = self.ch.encode_utf8(&mut buf).as_bytes();
51 q.write_all(&buf)?;
52 Ok(())
53 }
54}
55
56pub struct TerminalDisplay {
62 pub stdout: std::io::Stdout,
71 prev_chars: Option<Vec<Vec<Cell>>>,
72 chars: Vec<Vec<Cell>>,
73 pub w: u16,
75 pub h: u16,
77}
78
79impl TerminalDisplay {
80 pub fn new() -> Result<Self, std::io::Error> {
82 let (w, h) = terminal::size()?;
83 Ok(Self {
84 stdout: std::io::stdout(),
85 prev_chars: None,
86 chars: Self::init_chars(w, h),
87 w,
88 h,
89 })
90 }
91
92 fn init_chars(w: u16, h: u16) -> Vec<Vec<Cell>> {
93 let mut chars = Vec::with_capacity(h.into());
94 for _ in 0..h {
95 let mut row = Vec::with_capacity(w.into());
96 for _ in 0..w {
97 row.push(Cell::empty());
98 }
99 chars.push(row);
100 }
101 chars
102 }
103
104 pub fn resize(&mut self, w: u16, h: u16) {
107 self.prev_chars = None;
108 self.chars = Self::init_chars(w, h);
109
110 self.w = w;
111 self.h = h;
112 }
113
114 pub fn write(&mut self, x: usize, y: usize, ch: Cell) {
116 self.chars[y][x] = ch;
117 }
118
119 pub fn render(&mut self) -> Result<(), std::io::Error> {
121 for (y, row) in self.chars.iter().enumerate() {
123 if let Some(prev_chars) = &self.prev_chars {
124 for (x, cell) in row.iter().enumerate() {
125 if &prev_chars[y][x] != cell {
126 self.stdout.queue(cursor::MoveTo(x as u16, y as u16))?;
127 cell.render(&mut self.stdout)?;
128 }
129 }
130 } else {
131 self.stdout.queue(cursor::MoveTo(0, y as u16))?;
132 for cell in row {
133 cell.render(&mut self.stdout)?;
134 }
135 }
136 }
137 self.stdout.flush()?;
138
139 self.prev_chars = Some(self.chars.clone());
140 self.chars = Self::init_chars(self.w, self.h);
141
142 Ok(())
143 }
144
145 pub fn clear(&mut self) {
147 for row in self.chars.iter_mut() {
148 row.fill(Cell::empty());
149 }
150 }
151
152 pub fn clear_colored(&mut self, color: Color) {
154 for row in self.chars.iter_mut() {
155 row.fill(Cell::empty_colored(color));
156 }
157 }
158
159 fn queue_clear(&mut self) -> Result<(), std::io::Error> {
160 self.stdout
161 .queue(terminal::Clear(terminal::ClearType::All))?;
162 self.stdout.queue(cursor::MoveTo(0, 0))?;
163 Ok(())
164 }
165}