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
pub mod components;
pub mod rect_ext;
use std::io::{stdout, Stdout};
use std::time::Duration;
use crossterm::event::{poll, read, Event as TermEvent, KeyEvent, MouseEvent};
use crossterm::execute;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, SetTitle};
use crossterm::ErrorKind;
use tui::backend::CrosstermBackend;
use tui::buffer::Buffer;
use tui::layout::Rect;
use tui::text::Spans;
use tui::widgets::Widget;
use tui::Terminal;
pub use crossterm;
pub use tui;
pub struct Wrapper<'a, A: App>(pub &'a mut A);
impl<'a, A: App> Widget for Wrapper<'a, A> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.0.draw(area, buf)
}
}
pub trait Component {
type Response;
type DrawResponse;
fn handle_event(&mut self, event: Event) -> Self::Response;
fn draw(&mut self, rect: Rect, buffer: &mut Buffer) -> Self::DrawResponse;
}
pub trait App {
fn handle_event(&mut self, event: Event) -> AppResponse;
fn draw(&mut self, rect: Rect, buffer: &mut Buffer);
}
pub trait Spannable {
fn get_spans<'a, 'b>(&'a self) -> Spans<'b>;
}
#[derive(Debug, Copy, Clone)]
pub enum Event {
Key(KeyEvent),
Mouse(MouseEvent),
}
pub enum AppResponse {
Exit,
None,
}
pub fn run<A: App>(app: &mut A, title: Option<String>) -> Result<(), ErrorKind> {
let mut should_refresh = true;
let mut t = setup_terminal(title)?;
loop {
if should_refresh {
t.draw(|f| {
let size = f.size();
f.render_widget(Wrapper(app), size);
})
.unwrap();
should_refresh = false;
}
if poll(Duration::from_secs_f64(1.0 / 60.0)).unwrap() {
should_refresh = true;
let event = read().unwrap();
let comp_event = match event {
TermEvent::Resize(..) => continue,
TermEvent::Mouse(m) => Event::Mouse(m),
TermEvent::Key(k) => Event::Key(k),
};
match app.handle_event(comp_event) {
AppResponse::Exit => break,
AppResponse::None => {}
}
}
}
close_terminal(&mut t)?;
Ok(())
}
fn setup_terminal(title: Option<String>) -> Result<Terminal<CrosstermBackend<Stdout>>, ErrorKind> {
if let Some(title) = title {
execute!(stdout(), SetTitle(&title))?;
}
enable_raw_mode()?;
let mut t = Terminal::new(CrosstermBackend::new(stdout())).unwrap();
t.clear().unwrap();
Ok(t)
}
fn close_terminal(_t: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), ErrorKind> {
disable_raw_mode()?;
Ok(())
}