1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
use crate::app::launcher::App;
use crate::app::renderer;
use crate::term::event::EventHandler;
use anyhow::{Context, Result};
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
use std::io;
use std::panic;
use std::sync::atomic::Ordering;
use tui::backend::Backend;
use tui::Terminal;
/// Text-based user interface.
///
/// `Tui` is responsible for setting up the terminal
/// and initializing the interface. Terminal event
/// handler can be accessed via [`events`] field.
///
/// [`events`]: Tui::events
#[derive(Debug)]
pub struct Tui<B: Backend> {
/// Interface to the Terminal.
terminal: Terminal<B>,
/// Terminal event handler.
pub events: EventHandler,
/// Is the interface paused?
pub paused: bool,
}
impl<B: Backend> Tui<B> {
/// Constructs a new instance of `Tui`.
pub fn new(terminal: Terminal<B>, events: EventHandler) -> Self {
Self {
terminal,
events,
paused: false,
}
}
/// Initializes the terminal interface.
///
/// It enables the raw mode and sets terminal properties.
pub fn init(&mut self) -> Result<()> {
terminal::enable_raw_mode()?;
crossterm::execute!(
io::stderr(),
EnterAlternateScreen,
EnableMouseCapture
)?;
let panic_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic| {
Self::reset().expect("failed to reset the terminal");
panic_hook(panic);
}));
self.terminal.hide_cursor()?;
self.terminal.clear()?;
Ok(())
}
/// Toggles the [`paused`] state of interface.
///
/// It disables the key input and exits the
/// terminal interface on pause (and vice-versa).
///
/// [`paused`]: Tui::paused
pub fn toggle_pause(&mut self) -> Result<()> {
self.paused = !self.paused;
if self.paused {
self.exit()?;
} else {
self.init()?;
}
self.events
.key_input_disabled
.store(self.paused, Ordering::Relaxed);
Ok(())
}
/// Enables the mouse capture.
pub fn enable_mouse_capture(&mut self) -> Result<()> {
Ok(crossterm::execute!(io::stderr(), EnableMouseCapture)?)
}
/// Disables the mouse capture.
pub fn disable_mouse_capture(&mut self) -> Result<()> {
Ok(crossterm::execute!(io::stderr(), DisableMouseCapture)?)
}
/// [`Draw`] the terminal interface by [`rendering`] the widgets.
///
/// [`Draw`]: tui::Terminal::draw
/// [`rendering`]: crate::app::renderer::render
pub fn draw(&mut self, app: &mut App) -> Result<()> {
self.terminal
.draw(|frame| renderer::render(app, frame))
.context("failed to draw TUI")?;
Ok(())
}
/// Exits the terminal interface.
///
/// It disables the raw mode and reverts back the terminal properties.
pub fn exit(&mut self) -> Result<()> {
Self::reset()?;
self.terminal.show_cursor()?;
Ok(())
}
/// Resets the terminal interface.
fn reset() -> Result<()> {
terminal::disable_raw_mode()?;
crossterm::execute!(
io::stderr(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
Ok(())
}
}
#[cfg(feature = "tui-tests")]
#[cfg(test)]
mod tests {
use super::*;
use tui::backend::TestBackend;
#[test]
fn test_term_tui() -> Result<()> {
let backend = TestBackend::new(10, 10);
let terminal = Terminal::new(backend)?;
let mut tui = Tui::new(terminal, EventHandler::new(10));
tui.init()?;
tui.exit()?;
Ok(())
}
}
|