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