zui_core/term/
mod.rs

1//! # Abstraction over the terminal
2//! How the terminal should be handled. Controls stuff like:
3//! - Clearing the Screen
4//! - Moving the Cursor
5//! - Getting the size of screen, and detecting a change
6//! - Entering raw mode
7//! - Getting keys
8//! - Switching screen
9//!
10//! In short, for most applications, the majority of the work will be done here.
11//!
12//! ## Example
13//! Go to <https://git.dumrich.com/zui/tree/examples>
14//!
15// Author: Abhinav Chavali
16// Date: October 6th, 2021
17// Updated: October 6th, 2021
18
19// Declarations
20pub mod cursor;
21mod sys;
22
23// Imports
24use crate::key::KeyIterator;
25use crate::term::cursor::Cursor;
26use std::fmt::Debug;
27use std::io::{self, Error, Read, Stdin, Write};
28use std::sync::mpsc;
29use std::thread;
30use sys::{get_attr, set_attr, set_raw, Termios};
31
32/// Two possible modes for terminal: Cannonical and Raw
33pub enum TermMode {
34    Cannonical,
35    Raw,
36}
37
38/// Terminal control struct
39pub struct Terminal<'a, T: Write> {
40    pub rel_size: (u16, u16),
41    pub pix_size: (u16, u16),
42    pub stdout: &'a mut T, //TODO: Change to & and RefCell
43    pub x_pos: u16,
44    pub y_pos: u16,
45    pub cursor_mode: Cursor,
46    pub screen_num: u8,
47    pub mode: TermMode,
48    prev_ios: Termios,
49}
50
51impl<T: Write> Debug for Terminal<'_, T> {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("Terminal")
54            .field("Relative Size", &(self.rel_size))
55            .field("Pixels Size", &(self.pix_size))
56            .field("x_pos, y_pos", &(self.x_pos, self.y_pos))
57            .field("Cursor Mode", &self.cursor_mode)
58            .finish()
59    }
60}
61
62impl<T: Write> Drop for Terminal<'_, T> {
63    fn drop(&mut self) {
64        set_attr(&mut self.prev_ios);
65        self.mode = TermMode::Cannonical;
66        self.screen_num = 0;
67        write!(self.stdout, "\u{001b}[?1049l").unwrap();
68    }
69}
70
71impl<'a, T: Write> Terminal<'a, T> {
72    pub fn new(stdout: &'a mut T) -> Result<Terminal<T>, Error> {
73        let (rel_size, pix_size) = sys::term_size();
74
75        Ok(Terminal {
76            rel_size,
77            pix_size,
78            stdout,
79            x_pos: 0,
80            y_pos: 0,
81            cursor_mode: Cursor::Default,
82            screen_num: 0,
83            mode: TermMode::Cannonical,
84            prev_ios: get_attr(),
85        })
86    }
87
88    pub fn print(&mut self, x: &str) -> io::Result<()> {
89        write!(self.stdout, "{}", x)
90    }
91
92    pub fn size_did_change(&mut self) -> bool {
93        let (rel_size, pix_size) = sys::term_size();
94
95        if rel_size == (self.rel_size) {
96            false
97        } else {
98            self.rel_size = rel_size;
99            self.pix_size = pix_size;
100            true
101        }
102    }
103
104    pub fn enter_raw_mode(&mut self) -> io::Result<()> {
105        let mut ios = get_attr();
106
107        set_raw(&mut ios);
108
109        set_attr(&mut ios);
110        self.mode = TermMode::Raw;
111
112        Ok(())
113    }
114
115    // TODO: Move this shit to a trait
116    pub fn keys(&self, stdin: Stdin) -> KeyIterator {
117        let rx = async_stdin(stdin);
118
119        KeyIterator::from(rx)
120    }
121
122    pub fn switch_screen(&mut self) -> io::Result<()> {
123        self.screen_num = 1;
124        write!(self.stdout, "\u{001b}[?1049h")
125    }
126
127    pub fn switch_main(&mut self) -> io::Result<()> {
128        self.screen_num = 0;
129        write!(self.stdout, "\u{001b}[?1049l")
130    }
131
132    pub fn get_size(&self) -> (u16, u16) {
133        self.rel_size
134    }
135
136    pub fn get_position(&self) -> (u16, u16) {
137        (self.x_pos, self.y_pos)
138    }
139}
140
141fn async_stdin(d: Stdin) -> mpsc::Receiver<Result<u8, Error>> {
142    let (tx, rx) = mpsc::channel();
143
144    thread::spawn(move || {
145        for b in d.bytes() {
146            tx.send(b).unwrap();
147        }
148    });
149
150    rx
151}
152
153// Cursor Methods
154impl<T: Write> Terminal<'_, T> {
155    pub fn set_cursor_to(&mut self, x_pos: u16, y_pos: u16) -> io::Result<()> {
156        if x_pos <= self.rel_size.0 && y_pos <= self.rel_size.1 {
157            let result = write!(self.stdout, "\u{001b}[{};{}f", y_pos, x_pos)?;
158            self.stdout.flush().unwrap();
159            self.x_pos = x_pos;
160            self.y_pos = y_pos;
161            Ok(result)
162        } else {
163            panic!("Cursor set to out of bounds");
164        }
165    }
166
167    pub fn get_cursor(&self) -> io::Result<(u16, u16)> {
168        Ok((self.x_pos, self.y_pos))
169    }
170
171    pub fn show_cursor(&mut self) -> io::Result<()> {
172        self.cursor_mode = Cursor::Default;
173        let result = write!(self.stdout, "\u{001b}[?25h")?;
174        self.stdout.flush().unwrap();
175        Ok(result)
176    }
177
178    pub fn hide_cursor(&mut self) -> io::Result<()> {
179        self.cursor_mode = Cursor::Hidden;
180        let result = write!(self.stdout, "\u{001b}[?25l")?;
181        self.stdout.flush().unwrap();
182        Ok(result)
183    }
184
185    pub fn blinking_block(&mut self) -> io::Result<()> {
186        self.cursor_mode = Cursor::BlinkingBlock;
187        let result = write!(self.stdout, "\u{001b}[1 q")?;
188        self.stdout.flush().unwrap();
189        Ok(result)
190    }
191
192    pub fn steady_block(&mut self) -> io::Result<()> {
193        self.cursor_mode = Cursor::Block;
194        let result = write!(self.stdout, "\u{001b}[2 q")?;
195        self.stdout.flush().unwrap();
196        Ok(result)
197    }
198
199    pub fn blinking_underline(&mut self) -> io::Result<()> {
200        self.cursor_mode = Cursor::BlinkingUnderline;
201        let result = write!(self.stdout, "\u{001b}[3 q")?;
202        self.stdout.flush().unwrap();
203        Ok(result)
204    }
205
206    pub fn steady_underline(&mut self) -> io::Result<()> {
207        self.cursor_mode = Cursor::Underline;
208        let result = write!(self.stdout, "\u{001b}[4 q")?;
209        self.stdout.flush().unwrap();
210        Ok(result)
211    }
212
213    pub fn blinking_bar(&mut self) -> io::Result<()> {
214        self.cursor_mode = Cursor::BlinkingBar;
215        let result = write!(self.stdout, "\u{001b}[5 q")?;
216        self.stdout.flush().unwrap();
217        Ok(result)
218    }
219
220    pub fn steady_bar(&mut self) -> io::Result<()> {
221        self.cursor_mode = Cursor::Bar;
222        let result = write!(self.stdout, "\u{001b}[6 q")?;
223        self.stdout.flush().unwrap();
224        Ok(result)
225    }
226
227    pub fn reset_cursor(&mut self) -> io::Result<()> {
228        self.cursor_mode = Cursor::Default;
229        let result = write!(self.stdout, "\u{001b}[0 q")?;
230        self.stdout.flush().unwrap();
231        Ok(result)
232    }
233}
234
235impl<T: Write> Terminal<'_, T> {
236    pub fn clear_screen(&mut self) -> io::Result<()> {
237        let result = write!(self.stdout, "\u{001b}[2J")?;
238        self.stdout.flush().unwrap();
239        Ok(result)
240    }
241
242    pub fn clear_below_cursor(&mut self) -> io::Result<()> {
243        let result = write!(self.stdout, "\u{001b}[0J")?;
244        self.stdout.flush().unwrap();
245        Ok(result)
246    }
247
248    pub fn clear_above_cursor(&mut self) -> io::Result<()> {
249        let result = write!(self.stdout, "\u{001b}[1J")?;
250        self.stdout.flush().unwrap();
251        Ok(result)
252    }
253
254    pub fn clear_line(&mut self) -> io::Result<()> {
255        let result = write!(self.stdout, "\u{001b}[K")?;
256        self.stdout.flush().unwrap();
257        Ok(result)
258    }
259}