use crossterm::{cursor as c, event as e, execute, queue, style as s, terminal as t, ErrorKind};
use std::io::{self, stdout, Stdout, Write};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum TermError {
#[error("Failure to initialize terminal")]
Init,
#[error("Failure to deinitialize terminal")]
DeInit,
#[error("Failure to run command: {0:?}")]
Command(io::Error),
}
impl From<ErrorKind> for TermError {
fn from(err: ErrorKind) -> TermError {
TermError::Command(err)
}
}
pub struct Rgb {
r: u8,
g: u8,
b: u8,
}
impl From<Rgb> for s::Color {
fn from(rgb: Rgb) -> s::Color {
let Rgb { r, g, b } = rgb;
s::Color::Rgb { r, g, b }
}
}
pub enum IEvent {
Key(e::KeyEvent),
Paste(String),
FocusGained,
FocusLost,
}
#[derive(Clone, Copy)]
pub struct Size {
cols: u16,
rows: u16,
}
pub struct Terminal {
size: Size,
_stdout: Stdout,
}
type Result<T> = std::result::Result<T, TermError>;
impl Terminal {
pub fn new() -> Result<Self> {
let size = t::size()?;
Ok(Self {
size: Size {
cols: size.0,
rows: size.1,
},
_stdout: stdout(),
})
}
pub fn flush(&mut self) -> Result<()> {
Ok(self._stdout.flush()?)
}
pub fn read_event(&mut self) -> Result<IEvent> {
loop {
match e::read()? {
e::Event::Key(key) => return Ok(IEvent::Key(key)),
e::Event::Mouse(mouse) => self.cursor_position(mouse.column, mouse.row)?,
e::Event::Resize(x, y) => self.size = Size { cols: x, rows: y },
e::Event::Paste(s) => return Ok(IEvent::Paste(s)),
e::Event::FocusGained => return Ok(IEvent::FocusGained),
e::Event::FocusLost => return Ok(IEvent::FocusLost),
}
}
}
pub fn cursor_hide(&mut self) -> Result<()> {
Ok(queue!(self._stdout, c::Hide)?)
}
pub fn cursor_show(&mut self) -> Result<()> {
Ok(queue!(self._stdout, c::Show)?)
}
pub fn cursor_position(&mut self, col: u16, row: u16) -> Result<()> {
Ok(queue!(self._stdout, c::MoveTo(col, row))?)
}
pub fn clear_screen(&mut self) -> Result<()> {
Ok(queue!(self._stdout, t::Clear(t::ClearType::All))?)
}
pub fn clear_line(&mut self) -> Result<()> {
Ok(queue!(self._stdout, t::Clear(t::ClearType::CurrentLine))?)
}
pub fn fg_set(&mut self, rgb: Rgb) -> Result<()> {
Ok(queue!(self._stdout, s::SetForegroundColor(rgb.into()))?)
}
pub fn bg_set(&mut self, rgb: Rgb) -> Result<()> {
Ok(queue!(self._stdout, s::SetBackgroundColor(rgb.into()))?)
}
pub fn fg_reset(&mut self) -> Result<()> {
Ok(queue!(
self._stdout,
s::SetForegroundColor(s::Color::Reset)
)?)
}
pub fn bg_reset(&mut self) -> Result<()> {
Ok(queue!(
self._stdout,
s::SetBackgroundColor(s::Color::Reset)
)?)
}
pub fn size_get(&self) -> &Size {
&self.size
}
pub fn size_set(&mut self, size: Size) -> Result<()> {
self.size = size;
Ok(queue!(
self._stdout,
t::SetSize(self.size.cols, self.size.rows)
)?)
}
pub fn init(&mut self) -> Result<()> {
execute!(
self._stdout,
t::EnterAlternateScreen,
s::ResetColor,
t::Clear(t::ClearType::All),
c::MoveTo(0, 0),
)
.map_err(|_| TermError::Init)?;
t::enable_raw_mode().map_err(|_| TermError::Init)
}
pub fn deinit(&mut self) -> Result<()> {
execute!(
self._stdout,
s::ResetColor,
c::Show,
t::LeaveAlternateScreen
)
.map_err(|_| TermError::DeInit)
}
}