use std::io::{self, Write};
#[cfg(not(target_os = "wasi"))]
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputAction {
None,
Pause,
Quit,
}
pub struct Terminal {
width: u16,
height: u16,
}
#[cfg(not(target_os = "wasi"))]
mod sys {
use super::*;
use crossterm::event::KeyCode;
use crossterm::{
cursor::{Hide, Show},
event::{Event, poll, read},
execute,
terminal::{
Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode,
enable_raw_mode,
},
};
pub fn init() -> io::Result<(u16, u16)> {
enable_raw_mode()?;
let (w, h) = crossterm::terminal::size()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, Hide, Clear(ClearType::All))?;
Ok((w, h))
}
pub fn cleanup() -> io::Result<()> {
let mut stdout = io::stdout();
execute!(stdout, Show, LeaveAlternateScreen)?;
disable_raw_mode()?;
Ok(())
}
pub fn check_input() -> io::Result<InputAction> {
let mut action = InputAction::None;
while poll(Duration::from_millis(0))? {
if let Event::Key(key_event) = read()? {
if key_event.kind == crossterm::event::KeyEventKind::Press {
match key_event.code {
KeyCode::Char(' ') | KeyCode::Char('p') | KeyCode::Char('P') => {
action = InputAction::Pause;
}
KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
action = InputAction::Quit;
}
KeyCode::Char('c')
if key_event
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL) =>
{
action = InputAction::Quit;
}
_ => {}
}
}
}
}
Ok(action)
}
}
#[cfg(target_os = "wasi")]
mod sys {
use super::*;
pub fn init() -> io::Result<(u16, u16)> {
let w = std::env::var("COLUMNS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(80);
let h = std::env::var("LINES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(24);
let mut stdout = io::stdout();
write!(stdout, "\x1B[?1049h\x1B[?25l\x1B[2J")?;
stdout.flush()?;
Ok((w, h))
}
pub fn cleanup() -> io::Result<()> {
let mut stdout = io::stdout();
write!(stdout, "\x1B[?25h\x1B[?1049l")?;
stdout.flush()?;
Ok(())
}
pub fn check_input() -> io::Result<InputAction> {
Ok(InputAction::None)
}
}
impl Terminal {
pub fn new() -> io::Result<Self> {
let (width, height) = sys::init()?;
Ok(Terminal { width, height })
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn render_frame(&self, frame: String) -> io::Result<()> {
let mut stdout = io::stdout();
write!(stdout, "{}", frame)?;
stdout.flush()
}
pub fn cleanup(&self) -> io::Result<()> {
sys::cleanup()
}
pub fn check_input(&self) -> io::Result<InputAction> {
sys::check_input()
}
}
impl Drop for Terminal {
fn drop(&mut self) {
let _ = self.cleanup();
}
}