1use std::io::{self, Write};
2
3#[cfg(not(target_os = "wasi"))]
4use std::time::Duration;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum InputAction {
8 None,
9 Pause,
10 Quit,
11}
12
13pub struct Terminal {
14 width: u16,
15 height: u16,
16}
17
18#[cfg(not(target_os = "wasi"))]
19mod sys {
20 use super::*;
21 use crossterm::event::KeyCode;
22 use crossterm::{
23 cursor::{Hide, Show},
24 event::{Event, poll, read},
25 execute,
26 terminal::{
27 Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode,
28 enable_raw_mode,
29 },
30 };
31
32 pub fn init() -> io::Result<(u16, u16)> {
33 enable_raw_mode()?;
34 let (w, h) = crossterm::terminal::size()?;
35 let mut stdout = io::stdout();
36 execute!(stdout, EnterAlternateScreen, Hide, Clear(ClearType::All))?;
37 Ok((w, h))
38 }
39
40 pub fn cleanup() -> io::Result<()> {
41 let mut stdout = io::stdout();
42 execute!(stdout, Show, LeaveAlternateScreen)?;
43 disable_raw_mode()?;
44 Ok(())
45 }
46
47 pub fn check_input() -> io::Result<InputAction> {
48 let mut action = InputAction::None;
49 while poll(Duration::from_millis(0))? {
50 if let Event::Key(key_event) = read()? {
51 if key_event.kind == crossterm::event::KeyEventKind::Press {
52 match key_event.code {
53 KeyCode::Char(' ') | KeyCode::Char('p') | KeyCode::Char('P') => {
54 action = InputAction::Pause;
55 }
56 KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
57 action = InputAction::Quit;
58 }
59 KeyCode::Char('c')
60 if key_event
61 .modifiers
62 .contains(crossterm::event::KeyModifiers::CONTROL) =>
63 {
64 action = InputAction::Quit;
65 }
66 _ => {}
67 }
68 }
69 }
70 }
71 Ok(action)
72 }
73}
74
75#[cfg(target_os = "wasi")]
76mod sys {
77 use super::*;
78
79 pub fn init() -> io::Result<(u16, u16)> {
80 let w = std::env::var("COLUMNS")
81 .ok()
82 .and_then(|v| v.parse().ok())
83 .unwrap_or(80);
84 let h = std::env::var("LINES")
85 .ok()
86 .and_then(|v| v.parse().ok())
87 .unwrap_or(24);
88
89 let mut stdout = io::stdout();
90 write!(stdout, "\x1B[?1049h\x1B[?25l\x1B[2J")?;
91 stdout.flush()?;
92 Ok((w, h))
93 }
94
95 pub fn cleanup() -> io::Result<()> {
96 let mut stdout = io::stdout();
97 write!(stdout, "\x1B[?25h\x1B[?1049l")?;
98 stdout.flush()?;
99 Ok(())
100 }
101
102 pub fn check_input() -> io::Result<InputAction> {
103 Ok(InputAction::None)
104 }
105}
106
107impl Terminal {
108 pub fn new() -> io::Result<Self> {
109 let (width, height) = sys::init()?;
110 Ok(Terminal { width, height })
111 }
112
113 pub fn width(&self) -> u16 {
114 self.width
115 }
116
117 pub fn height(&self) -> u16 {
118 self.height
119 }
120
121 pub fn render_frame(&self, frame: String) -> io::Result<()> {
123 let mut stdout = io::stdout();
124 write!(stdout, "{}", frame)?;
125 stdout.flush()
126 }
127
128 pub fn cleanup(&self) -> io::Result<()> {
129 sys::cleanup()
130 }
131
132 pub fn check_input(&self) -> io::Result<InputAction> {
133 sys::check_input()
134 }
135}
136
137impl Drop for Terminal {
138 fn drop(&mut self) {
139 let _ = self.cleanup();
140 }
141}