wg_netmanager/
tui_display.rs1use std::io;
2use std::sync::mpsc;
3use std::thread;
4
5use log::*;
6
7use crossterm::event::{read, Event, KeyCode};
8use 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 )?;
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 disable_raw_mode()?;
149 execute!(
150 terminal.backend_mut(),
151 LeaveAlternateScreen,
152 )?;
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}