wg_netmanager/
tui_display.rs

1use std::io;
2use std::sync::mpsc;
3use std::thread;
4
5use log::*;
6
7use crossterm::event::{read, Event, KeyCode};
8//use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
9use crossterm::execute;
10use crossterm::terminal::disable_raw_mode;
11use crossterm::terminal::enable_raw_mode;
12use crossterm::terminal::EnterAlternateScreen;
13use crossterm::terminal::LeaveAlternateScreen;
14use tui::backend::Backend;
15use tui::backend::CrosstermBackend;
16use tui::layout::{Constraint, Direction, Layout, Rect};
17use tui::style::{Color, Modifier, Style};
18use tui::text::{Span, Spans};
19use tui::widgets::{Block, Borders, Tabs};
20use tui::Frame;
21use tui::Terminal;
22use tui_logger::*;
23
24use crate::error::*;
25use crate::event;
26
27pub struct TuiApp {
28    terminal: Option<Terminal<CrosstermBackend<io::Stdout>>>,
29    states: Vec<TuiWidgetState>,
30    tabs: Vec<String>,
31    selected_tab: usize,
32}
33
34#[derive(Debug)]
35pub enum TuiAppEvent {
36    SpaceKey,
37    EscapeKey,
38    PrevPageKey,
39    NextPageKey,
40    UpKey,
41    DownKey,
42    LeftKey,
43    RightKey,
44    PlusKey,
45    MinusKey,
46    HideKey,
47    FocusKey,
48    TabKey,
49    BackTabKey,
50}
51
52impl TuiApp {
53    pub fn off() -> Self {
54        TuiApp {
55            terminal: None,
56            states: vec![],
57            tabs: vec![],
58            selected_tab: 0,
59        }
60    }
61    pub fn init(tx: mpsc::Sender<event::Event>) -> BoxResult<Self> {
62        enable_raw_mode()?;
63        let mut stdout = io::stdout();
64        execute!(
65            stdout,
66            EnterAlternateScreen,
67            //EnableMouseCapture
68        )?;
69        let backend = CrosstermBackend::new(stdout);
70        let mut terminal = Terminal::new(backend)?;
71
72        terminal.clear().unwrap();
73        terminal.hide_cursor().unwrap();
74
75        thread::spawn({
76            move || loop {
77                let evt = read();
78                trace!("Event received {:?}", evt);
79                if let Ok(Event::Key(keyevent)) = evt {
80                    use crate::event::Event::*;
81                    use TuiAppEvent::*;
82                    match keyevent.code {
83                        KeyCode::Char('q') => {
84                            tx.send(event::Event::CtrlC).unwrap();
85                            break;
86                        }
87                        KeyCode::Char(' ') => {
88                            tx.send(TuiApp(SpaceKey)).unwrap();
89                        }
90                        KeyCode::Esc => {
91                            tx.send(TuiApp(EscapeKey)).unwrap();
92                        }
93                        KeyCode::PageUp => {
94                            tx.send(TuiApp(PrevPageKey)).unwrap();
95                        }
96                        KeyCode::PageDown => {
97                            tx.send(TuiApp(NextPageKey)).unwrap();
98                        }
99                        KeyCode::Up => {
100                            tx.send(TuiApp(UpKey)).unwrap();
101                        }
102                        KeyCode::Down => {
103                            tx.send(TuiApp(DownKey)).unwrap();
104                        }
105                        KeyCode::Left => {
106                            tx.send(TuiApp(LeftKey)).unwrap();
107                        }
108                        KeyCode::Right => {
109                            tx.send(TuiApp(RightKey)).unwrap();
110                        }
111                        KeyCode::Char('+') => {
112                            tx.send(TuiApp(PlusKey)).unwrap();
113                        }
114                        KeyCode::Char('-') => {
115                            tx.send(TuiApp(MinusKey)).unwrap();
116                        }
117                        KeyCode::Char('h') => {
118                            tx.send(TuiApp(HideKey)).unwrap();
119                        }
120                        KeyCode::Char('f') => {
121                            tx.send(TuiApp(FocusKey)).unwrap();
122                        }
123                        KeyCode::Tab => {
124                            tx.send(TuiApp(TabKey)).unwrap();
125                        }
126                        KeyCode::BackTab => {
127                            tx.send(TuiApp(BackTabKey)).unwrap();
128                        }
129                        _ => {}
130                    }
131                }
132            }
133        });
134
135        Ok(TuiApp {
136            terminal: Some(terminal),
137            states: vec![],
138            tabs: vec!["1", "2", "3", "4"]
139                .into_iter()
140                .map(|t| t.into())
141                .collect(),
142            selected_tab: 0,
143        })
144    }
145    pub fn deinit(&mut self) -> BoxResult<()> {
146        if let Some(terminal) = self.terminal.as_mut() {
147            // restore terminal
148            disable_raw_mode()?;
149            execute!(
150                terminal.backend_mut(),
151                LeaveAlternateScreen,
152                //DisableMouseCapture
153            )?;
154            terminal.show_cursor()?;
155        }
156        Ok(())
157    }
158    pub fn process_event(&mut self, evt: TuiAppEvent) {
159        use TuiAppEvent::*;
160        let widget_evt: Option<TuiWidgetEvent> = match evt {
161            SpaceKey => Some(TuiWidgetEvent::SpaceKey),
162            EscapeKey => Some(TuiWidgetEvent::EscapeKey),
163            PrevPageKey => Some(TuiWidgetEvent::PrevPageKey),
164            NextPageKey => Some(TuiWidgetEvent::NextPageKey),
165            UpKey => Some(TuiWidgetEvent::UpKey),
166            DownKey => Some(TuiWidgetEvent::DownKey),
167            LeftKey => Some(TuiWidgetEvent::LeftKey),
168            RightKey => Some(TuiWidgetEvent::RightKey),
169            PlusKey => Some(TuiWidgetEvent::PlusKey),
170            MinusKey => Some(TuiWidgetEvent::MinusKey),
171            HideKey => Some(TuiWidgetEvent::HideKey),
172            FocusKey => Some(TuiWidgetEvent::FocusKey),
173            TabKey => {
174                self.selected_tab = (self.selected_tab + 1) % self.tabs.len();
175                None
176            }
177            BackTabKey => {
178                self.selected_tab = (self.selected_tab + self.tabs.len() - 1) % self.tabs.len();
179                None
180            }
181        };
182        if let Some(widget_evt) = widget_evt {
183            self.states[self.selected_tab].transition(&widget_evt);
184        }
185    }
186    pub fn draw(&mut self) -> BoxResult<()> {
187        if let Some(mut terminal) = self.terminal.take() {
188            terminal.draw(|f| {
189                let size = f.size();
190                draw_frame(f, size, self);
191            })?;
192            self.terminal = Some(terminal);
193        }
194        Ok(())
195    }
196}
197fn draw_frame<B: Backend>(t: &mut Frame<B>, size: Rect, app: &mut TuiApp) {
198    let tabs: Vec<Spans> = app
199        .tabs
200        .iter()
201        .map(|t| Spans::from(vec![Span::raw(t)]))
202        .collect();
203    let sel = app.selected_tab;
204
205    while app.states.len() <= sel {
206        app.states
207            .push(TuiWidgetState::new().set_default_display_level(LevelFilter::Info));
208    }
209
210    let constraints = vec![Constraint::Length(3), Constraint::Min(3)];
211    let chunks = Layout::default()
212        .direction(Direction::Vertical)
213        .constraints(constraints)
214        .split(size);
215
216    let tabs = Tabs::new(tabs)
217        .block(Block::default().borders(Borders::ALL))
218        .highlight_style(Style::default().add_modifier(Modifier::REVERSED))
219        .select(sel);
220    t.render_widget(tabs, chunks[0]);
221
222    let tui_sm = TuiLoggerSmartWidget::default()
223        .style_error(Style::default().fg(Color::Red))
224        .style_debug(Style::default().fg(Color::Green))
225        .style_warn(Style::default().fg(Color::Yellow))
226        .style_trace(Style::default().fg(Color::Magenta))
227        .style_info(Style::default().fg(Color::Cyan))
228        .output_separator(':')
229        .output_timestamp(Some("%H:%M:%S".to_string()))
230        .output_level(Some(TuiLoggerLevelOutput::Abbreviated))
231        .output_target(true)
232        .output_file(true)
233        .output_line(true)
234        .state(&app.states[sel]);
235    t.render_widget(tui_sm, chunks[1]);
236}