umbra/
terminal.rs

1use crossterm::{cursor as c, event as e, execute, queue, style as s, terminal as t, ErrorKind};
2use std::io::{self, stdout, Stdout, Write};
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum TermError {
7    #[error("Failure to initialize terminal")]
8    Init,
9    #[error("Failure to deinitialize terminal")]
10    DeInit,
11    #[error("Failure to run command: {0:?}")]
12    Command(io::Error),
13}
14impl From<ErrorKind> for TermError {
15    fn from(err: ErrorKind) -> TermError {
16        TermError::Command(err)
17    }
18}
19
20pub struct Rgb {
21    r: u8,
22    g: u8,
23    b: u8,
24}
25impl From<Rgb> for s::Color {
26    fn from(rgb: Rgb) -> s::Color {
27        let Rgb { r, g, b } = rgb;
28        s::Color::Rgb { r, g, b }
29    }
30}
31
32/// Event is a wrapper around crossterm's Event enum
33/// NOTE: Resize taken out because it should be handled immediately
34pub enum IEvent {
35    Key(e::KeyEvent),
36    Paste(String),
37    FocusGained,
38    FocusLost,
39    // Mouse(e::MouseEvent),
40    // Resize(crossterm::event::Resize),
41    // None,
42}
43
44#[derive(Clone, Copy)]
45pub struct Size {
46    cols: u16,
47    rows: u16,
48}
49
50pub struct Terminal {
51    size: Size,
52    _stdout: Stdout,
53}
54
55type Result<T> = std::result::Result<T, TermError>;
56
57impl Terminal {
58    pub fn new() -> Result<Self> {
59        let size = t::size()?;
60        Ok(Self {
61            size: Size {
62                cols: size.0,
63                rows: size.1,
64            },
65            _stdout: stdout(),
66        })
67    }
68
69    pub fn flush(&mut self) -> Result<()> {
70        Ok(self._stdout.flush()?)
71    }
72
73    /// Reads and returns an Event from the terminal
74    /// Handles resize events automatically
75    /// NOTE: Handles Mouse and Resize events from here
76    pub fn read_event(&mut self) -> Result<IEvent> {
77        loop {
78            match e::read()? {
79                e::Event::Key(key) => return Ok(IEvent::Key(key)),
80                e::Event::Mouse(mouse) => self.cursor_position(mouse.column, mouse.row)?,
81                e::Event::Resize(x, y) => self.size = Size { cols: x, rows: y },
82                e::Event::Paste(s) => return Ok(IEvent::Paste(s)),
83                e::Event::FocusGained => return Ok(IEvent::FocusGained),
84                e::Event::FocusLost => return Ok(IEvent::FocusLost),
85            }
86        }
87    }
88
89    /// Queues a cursor hide command
90    pub fn cursor_hide(&mut self) -> Result<()> {
91        Ok(queue!(self._stdout, c::Hide)?)
92    }
93    /// Queues a cursor show command
94    pub fn cursor_show(&mut self) -> Result<()> {
95        Ok(queue!(self._stdout, c::Show)?)
96    }
97
98    /// Queue cursor position to be updated
99    ///
100    /// * `col`: column position
101    /// * `row`: row position
102    pub fn cursor_position(&mut self, col: u16, row: u16) -> Result<()> {
103        Ok(queue!(self._stdout, c::MoveTo(col, row))?)
104    }
105
106    /// Queues the screen to be cleared
107    pub fn clear_screen(&mut self) -> Result<()> {
108        Ok(queue!(self._stdout, t::Clear(t::ClearType::All))?)
109    }
110    /// Queues the current line to be cleared
111    pub fn clear_line(&mut self) -> Result<()> {
112        Ok(queue!(self._stdout, t::Clear(t::ClearType::CurrentLine))?)
113    }
114
115    pub fn fg_set(&mut self, rgb: Rgb) -> Result<()> {
116        Ok(queue!(self._stdout, s::SetForegroundColor(rgb.into()))?)
117    }
118    pub fn bg_set(&mut self, rgb: Rgb) -> Result<()> {
119        Ok(queue!(self._stdout, s::SetBackgroundColor(rgb.into()))?)
120    }
121    pub fn fg_reset(&mut self) -> Result<()> {
122        Ok(queue!(
123            self._stdout,
124            s::SetForegroundColor(s::Color::Reset)
125        )?)
126    }
127    pub fn bg_reset(&mut self) -> Result<()> {
128        Ok(queue!(
129            self._stdout,
130            s::SetBackgroundColor(s::Color::Reset)
131        )?)
132    }
133
134    pub fn size_get(&self) -> &Size {
135        &self.size
136    }
137    pub fn size_set(&mut self, size: Size) -> Result<()> {
138        self.size = size;
139        Ok(queue!(
140            self._stdout,
141            t::SetSize(self.size.cols, self.size.rows)
142        )?)
143    }
144
145    pub fn init(&mut self) -> Result<()> {
146        execute!(
147            self._stdout,
148            t::EnterAlternateScreen,
149            s::ResetColor,
150            t::Clear(t::ClearType::All),
151            c::MoveTo(0, 0),
152        )
153        .map_err(|_| TermError::Init)?;
154
155        t::enable_raw_mode().map_err(|_| TermError::Init)
156    }
157    pub fn deinit(&mut self) -> Result<()> {
158        execute!(
159            self._stdout,
160            s::ResetColor,
161            c::Show,
162            t::LeaveAlternateScreen
163        )
164        .map_err(|_| TermError::DeInit)
165    }
166}