1use crossterm::cursor::MoveTo;
6use crossterm::event::{Event, KeyCode, KeyEventKind};
7use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
8use crossterm::{
9 cursor::{self},
10 event, execute,
11 terminal::{Clear, ClearType, EnterAlternateScreen, enable_raw_mode},
12};
13use std::io;
14use std::io::{Stdout, Write, stdout};
15use std::time::Duration;
16
17#[derive(Eq, PartialEq)]
18pub enum Input {
19 Left,
20 Right,
21 Up,
22 Down,
23 Esc,
24 Action1,
25 Action2,
26 Start,
27}
28
29pub struct Tui {
34 out: Stdout,
35}
36
37impl Drop for Tui {
38 fn drop(&mut self) {
39 let _ = execute!(self.out, cursor::Show);
45 let _ = execute!(self.out, LeaveAlternateScreen);
46 let _ = disable_raw_mode();
47 }
48}
49
50impl Tui {
51 pub fn new() -> io::Result<Self> {
54 enable_raw_mode()?;
57
58 let mut stdout = stdout();
59
60 execute!(stdout, EnterAlternateScreen)?;
62
63 execute!(stdout, cursor::Hide)?;
66
67 execute!(stdout, Clear(ClearType::All))?;
68
69 Ok(Self { out: stdout })
70 }
71
72 pub fn move_to(&self, x: u16, y: u16) {
73 execute!(&self.out, MoveTo(x, y)).expect("tui: move_to failed");
74 }
75
76 pub fn write(&self, str: &str) {
77 write!(&self.out, "{str}").expect("tui: write failed");
78 }
79
80 #[must_use]
81 pub fn poll(&self) -> Option<Input> {
82 match event::poll(Duration::ZERO) {
83 Ok(true) => {}
84 Err(_) | Ok(false) => return None,
85 }
86
87 let Ok(event) = event::read() else {
88 return None;
89 };
90
91 let Event::Key(key_event) = event else {
92 return None;
93 };
94
95 if key_event.kind != KeyEventKind::Press {
96 return None;
97 }
98
99 match key_event.code {
100 KeyCode::Esc => Some(Input::Esc),
101 KeyCode::Up => Some(Input::Up),
102 KeyCode::Down => Some(Input::Down),
103 KeyCode::Left => Some(Input::Left),
104 KeyCode::Right => Some(Input::Right),
105 KeyCode::Enter => Some(Input::Start),
106 KeyCode::Char(c) => match c {
107 'w' => Some(Input::Up),
108 'a' => Some(Input::Left),
109 's' => Some(Input::Down),
110 'd' => Some(Input::Right),
111 'z' | 'q' => Some(Input::Action1),
112 'x' | 'e' => Some(Input::Action2),
113 _ => None,
114 },
115 _ => None,
116 }
117 }
118}