use crossterm::cursor::MoveTo;
use crossterm::event::{Event, KeyCode, KeyEventKind};
use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
use crossterm::{
cursor::{self},
event, execute,
terminal::{Clear, ClearType, EnterAlternateScreen, enable_raw_mode},
};
use std::io;
use std::io::{Stdout, Write, stdout};
use std::time::Duration;
#[derive(Eq, PartialEq)]
pub enum Input {
Left,
Right,
Up,
Down,
Esc,
Action1,
Action2,
Start,
}
pub struct Tui {
out: Stdout,
}
impl Drop for Tui {
fn drop(&mut self) {
let _ = execute!(self.out, cursor::Show);
let _ = execute!(self.out, LeaveAlternateScreen);
let _ = disable_raw_mode();
}
}
impl Tui {
pub fn new() -> io::Result<Self> {
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen)?;
execute!(stdout, cursor::Hide)?;
execute!(stdout, Clear(ClearType::All))?;
Ok(Self { out: stdout })
}
pub fn move_to(&self, x: u16, y: u16) {
execute!(&self.out, MoveTo(x, y)).expect("tui: move_to failed");
}
pub fn write(&self, str: &str) {
write!(&self.out, "{str}").expect("tui: write failed");
}
#[must_use]
pub fn poll(&self) -> Option<Input> {
match event::poll(Duration::ZERO) {
Ok(true) => {}
Err(_) | Ok(false) => return None,
}
let Ok(event) = event::read() else {
return None;
};
let Event::Key(key_event) = event else {
return None;
};
if key_event.kind != KeyEventKind::Press {
return None;
}
match key_event.code {
KeyCode::Esc => Some(Input::Esc),
KeyCode::Up => Some(Input::Up),
KeyCode::Down => Some(Input::Down),
KeyCode::Left => Some(Input::Left),
KeyCode::Right => Some(Input::Right),
KeyCode::Enter => Some(Input::Start),
KeyCode::Char(c) => match c {
'w' => Some(Input::Up),
'a' => Some(Input::Left),
's' => Some(Input::Down),
'd' => Some(Input::Right),
'z' | 'q' => Some(Input::Action1),
'x' | 'e' => Some(Input::Action2),
_ => None,
},
_ => None,
}
}
}