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