crossterm_display/
lib.rs

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)]
10/// A single character on the screen
11pub struct Cell {
12    /// The character itself
13    pub ch: char,
14    /// The foreground color
15    pub fg: Color,
16    /// The background color
17    pub bg: Color,
18    /// The attribute (bold, italics, etc.)
19    pub attr: Attribute,
20}
21
22impl Default for Cell {
23    fn default() -> Self {
24        Self::empty()
25    }
26}
27
28impl Cell {
29    /// Create an empty cell with a black background
30    pub const fn empty() -> Self {
31        Self::empty_colored(Color::Black)
32    }
33
34    /// Create an empty cell of a certain color
35    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
56/// Your main handle into crossterm-display.
57/// 
58/// To create one use TerminalDisplay::new().
59///
60/// The recommended way to use this is to pass it around to functions that need it
61pub struct TerminalDisplay {
62    /// The TerminalDisplay's handle to stdout. Use it when you need to directly send
63    /// a command to the terminal without going through crossterm-display
64    /// ```rust
65    /// use crossterm_display::*;
66    /// use crossterm::QueueableCommand;
67    /// let td = TerminalDisplay::new().unwrap();
68    /// td.stdout.queue(crossterm::cursor::Hide).unwrap();
69    /// ```
70    pub stdout: std::io::Stdout,
71    prev_chars: Option<Vec<Vec<Cell>>>,
72    chars: Vec<Vec<Cell>>,
73    /// The width of the display
74    pub w: u16,
75    /// The height of the display
76    pub h: u16,
77}
78
79impl TerminalDisplay {
80    /// Create a new TerminalDisplay
81    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    /// Safely resize the TerminalDisplay. Should always be called whenever the underlying
105    /// terminal window resizes
106    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    /// Write a cell into the TerminalDisplay
115    pub fn write(&mut self, x: usize, y: usize, ch: Cell) {
116        self.chars[y][x] = ch;
117    }
118
119    /// Render the TerminalDisplay
120    pub fn render(&mut self) -> Result<(), std::io::Error> {
121        //self.stdout.queue(cursor::MoveTo(0, 0))?;
122        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    /// Clear the TerminalDisplay
146    pub fn clear(&mut self) {
147        for row in self.chars.iter_mut() {
148            row.fill(Cell::empty());
149        }
150    }
151
152    /// Clear the TerminalDisplay with a certain color
153    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}