1use crossterm::{
9 queue,
10 style::{ResetColor, SetBackgroundColor, SetForegroundColor},
11};
12use std::{
13 fmt,
14 io::{self, stdout, BufWriter, Write},
15};
16
17#[derive(Debug)]
19pub struct TerminalSizeError;
20
21impl fmt::Display for TerminalSizeError {
22 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(formatter, "Failed to query terminal size")
24 }
25}
26
27impl std::error::Error for TerminalSizeError {}
28
29pub fn size() -> Result<(usize, usize), TerminalSizeError> {
45 if let Ok((width, height)) = crossterm::terminal::size() {
46 Ok((width as usize, height as usize))
47 } else {
48 Err(TerminalSizeError)
49 }
50}
51
52pub type Color = crossterm::style::Color;
54
55#[derive(Debug, Clone)]
57pub struct Cell {
58 pub upper_block: Option<Option<Color>>,
60 pub lower_block: Option<Option<Color>>,
62 pub char: Option<char>,
66 pub char_color: Option<Color>,
68}
69
70pub struct Buffer {
88 pub cells: Vec<Cell>,
89 pub width: usize,
90 pub height: usize,
91 writer: BufWriter<io::Stdout>,
92}
93
94impl Buffer {
95 pub fn new(width: usize, height: usize, char: char) -> Buffer {
97 Buffer {
98 cells: vec![
99 Cell {
100 upper_block: None,
101 lower_block: None,
102 char: Some(char),
103 char_color: None
104 };
105 width * height
106 ],
107 writer: BufWriter::with_capacity(width * height, stdout()),
108 width,
109 height,
110 }
111 }
112
113 pub fn draw(&mut self) {
119 let writer = &mut self.writer;
120 let mut x = 0;
121 let mut y = 1;
122 for cell in &self.cells {
123 if cell.upper_block.is_some() && cell.lower_block.is_some() {
125 if let Some(Some(upper_color)) = cell.upper_block {
126 if let Some(Some(lower_color)) = cell.lower_block {
127 queue!(writer, SetForegroundColor(upper_color)).unwrap();
128 queue!(writer, SetBackgroundColor(lower_color)).unwrap();
129 writer.write_all("▀".as_bytes()).unwrap();
130 } else {
131 queue!(writer, SetBackgroundColor(upper_color)).unwrap();
132 writer.write_all("▄".as_bytes()).unwrap();
133 }
134 queue!(writer, ResetColor).unwrap();
135 } else if let Some(Some(lower_color)) = cell.lower_block {
136 if let Some(Some(upper_color)) = cell.upper_block {
137 queue!(writer, SetForegroundColor(upper_color)).unwrap();
138 queue!(writer, SetBackgroundColor(lower_color)).unwrap();
139 writer.write_all("▀".as_bytes()).unwrap();
140 } else {
141 queue!(writer, SetBackgroundColor(lower_color)).unwrap();
142 writer.write_all("▀".as_bytes()).unwrap();
143 }
144 queue!(writer, ResetColor).unwrap();
145 } else {
146 writer.write_all("█".as_bytes()).unwrap();
147 }
148 } else if let Some(upper_block) = cell.upper_block {
149 if let Some(color) = upper_block {
150 queue!(writer, SetForegroundColor(color)).unwrap();
151 }
152 writer.write_all("▀".as_bytes()).unwrap();
153 if upper_block.is_some() {
154 queue!(writer, ResetColor).unwrap();
155 }
156 } else if let Some(lower_block) = cell.lower_block {
157 if let Some(color) = lower_block {
158 queue!(writer, SetForegroundColor(color)).unwrap();
159 }
160 writer.write_all("▄".as_bytes()).unwrap();
161 if lower_block.is_some() {
162 queue!(writer, ResetColor).unwrap();
163 }
164 } else if let Some(char) = &cell.char {
165 if let Some(color) = cell.char_color {
166 queue!(writer, SetForegroundColor(color)).unwrap();
167 }
168
169 write!(writer, "{}", char).unwrap();
170 if cell.char_color.is_some() {
171 queue!(writer, ResetColor).unwrap();
172 }
173 } else {
174 unreachable!();
175 }
176
177 x += 1;
178 if y != self.height && x == self.width {
179 writer.write_all(b"\n").unwrap();
180 x = 0;
181 y += 1;
182 }
183 }
184 self.writer.flush().unwrap();
185 }
186
187 pub fn clear(&mut self, char: char) {
189 self.cells.fill(Cell {
190 upper_block: None,
191 lower_block: None,
192 char: Some(char),
193 char_color: None,
194 })
195 }
196
197 pub fn colored_clear(&mut self, char: char, color: Color) {
199 self.cells.fill(Cell {
200 upper_block: None,
201 lower_block: None,
202 char: Some(char),
203 char_color: Some(color),
204 })
205 }
206
207 pub fn set(&mut self, x: usize, y: usize) {
213 let position = x + self.width * (y / 2);
214 let current_cell = &self
215 .cells
216 .get(position)
217 .unwrap_or_else(|| panic!("setting block at ({}, {}) (out of range)", x, y));
218
219 if y % 2 == 0 {
220 self.cells[position] = Cell {
221 upper_block: Some(None),
222 lower_block: current_cell.lower_block,
223 char: None,
224 char_color: None,
225 };
226 } else {
227 self.cells[position] = Cell {
228 upper_block: current_cell.upper_block,
229 lower_block: Some(None),
230 char: None,
231 char_color: None,
232 };
233 }
234 }
235
236 pub fn color(&mut self, x: usize, y: usize, color: Color) {
242 let position = x + self.width * (y / 2);
243 let current_cell = &self
244 .cells
245 .get(position)
246 .unwrap_or_else(|| panic!("coloring block at ({}, {}) (out of range)", x, y));
247
248 if y % 2 == 0 {
249 self.cells[position] = Cell {
250 upper_block: Some(Some(color)),
251 lower_block: current_cell.lower_block,
252 char: None,
253 char_color: None,
254 };
255 } else {
256 self.cells[position] = Cell {
257 upper_block: current_cell.upper_block,
258 lower_block: Some(Some(color)),
259 char: None,
260 char_color: None,
261 };
262 }
263 }
264
265 pub fn print(&mut self, x: usize, y: usize, string: &str) {
271 let position = x + self.width * (y / 2);
272
273 for (index, char) in string.chars().enumerate() {
274 let cell = self
275 .cells
276 .get_mut(index + position)
277 .unwrap_or_else(|| panic!("printing at ({}, {}) (out of range)", x, y));
278
279 *cell = Cell {
280 upper_block: None,
281 lower_block: None,
282 char: Some(char),
283 char_color: None,
284 };
285 }
286 }
287
288 pub fn colored_print(&mut self, x: usize, y: usize, string: &str, color: Color) {
294 let position = x + self.width * (y / 2);
295
296 for (index, char) in string.chars().enumerate() {
297 let cell = self
298 .cells
299 .get_mut(index + position)
300 .unwrap_or_else(|| panic!("printing at ({}, {}) (out of range)", x, y));
301
302 *cell = Cell {
303 upper_block: None,
304 lower_block: None,
305 char: Some(char),
306 char_color: Some(color),
307 };
308 }
309 }
310}