1pub mod components;
2pub mod rect_ext;
3
4use std::fmt::Display;
5use std::io::{stdout, Stdout};
6use std::time::Duration;
7
8use crossterm::event::{poll, read, Event as TermEvent, KeyEvent, MouseEvent};
9use crossterm::execute;
10use crossterm::terminal::{disable_raw_mode, enable_raw_mode, SetTitle};
11use crossterm::ErrorKind;
12use tui::backend::CrosstermBackend;
13use tui::buffer::Buffer;
14use tui::layout::Rect;
15use tui::text::Spans;
16use tui::widgets::Widget;
17use tui::Terminal;
18
19pub use crossterm;
20pub use tui;
21
22pub struct Wrapper<'a, A: App>(pub &'a mut A);
23
24impl<'a, A: App> Widget for Wrapper<'a, A> {
25 fn render(self, area: Rect, buf: &mut Buffer) {
26 self.0.draw(area, buf)
27 }
28}
29
30pub trait Component {
32 type Response;
33 type DrawResponse;
34
35 fn handle_event(&mut self, event: Event) -> Self::Response;
36
37 fn draw(&mut self, rect: Rect, buffer: &mut Buffer) -> Self::DrawResponse;
38}
39
40pub trait App {
42 fn handle_event(&mut self, event: Event) -> AppResponse;
43
44 fn draw(&mut self, rect: Rect, buffer: &mut Buffer);
45}
46
47pub trait Spannable {
49 fn get_spans<'a, 'b>(&'a self) -> Spans<'b>;
50}
51
52#[derive(Debug, Copy, Clone)]
53pub enum Event {
54 Key(KeyEvent),
55 Mouse(MouseEvent),
56}
57
58pub enum AppResponse {
59 Exit,
60 None,
61}
62
63pub fn run<A: App>(app: &mut A, title: Option<String>) -> Result<(), ErrorKind> {
64 let mut should_refresh = true;
65
66 let mut t = setup_terminal(title)?;
67
68 loop {
69 if should_refresh {
70 t.draw(|f| {
71 let size = f.size();
72 f.render_widget(Wrapper(app), size);
73 })
74 .unwrap();
75 should_refresh = false;
76 }
77
78 if poll(Duration::from_secs_f64(1.0 / 60.0)).unwrap() {
79 should_refresh = true;
80 let event = read().unwrap();
81 let comp_event = match event {
82 TermEvent::Resize(..) => continue,
83 TermEvent::Mouse(m) => Event::Mouse(m),
84 TermEvent::Key(k) => Event::Key(k),
85 };
86 match app.handle_event(comp_event) {
87 AppResponse::Exit => break,
88 AppResponse::None => {}
89 }
90 }
91 }
92
93 close_terminal(&mut t)?;
94 Ok(())
95}
96
97pub fn set_title<S: Display>(title: &S) -> Result<(), ErrorKind> {
98 execute!(stdout(), SetTitle(title))
99}
100
101fn setup_terminal(title: Option<String>) -> Result<Terminal<CrosstermBackend<Stdout>>, ErrorKind> {
102 if let Some(title) = title {
103 set_title(&title)?;
104 }
105
106 enable_raw_mode()?;
107 let mut t = Terminal::new(CrosstermBackend::new(stdout())).unwrap();
108 t.clear().unwrap();
109 Ok(t)
110}
111
112fn close_terminal(_t: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), ErrorKind> {
113 disable_raw_mode()?;
114 Ok(())
115}