#[allow(dead_code)] mod util; use crate::util::{ event::{Event, Events}, SelectableList, }; use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, layout::{Constraint, Corner, Direction, Layout}, style::{Color, Modifier, Style}, text::{Span, Spans}, widgets::{Block, Borders, List, ListItem}, Terminal, }; /// This struct holds the current state of the app. In particular, it has the `items` field which is a wrapper /// around `ListState`. Keeping track of the items state let us render the associated widget with its state /// and have access to features such as natural scrolling. /// /// Check the event handling at the bottom to see how to change the state on incoming events. /// Check the drawing logic for items on how to specify the highlighting style for selected items. struct App<'a> { items: SelectableList<(&'a str, usize)>, events: Vec<(&'a str, &'a str)>, } impl<'a> App<'a> { fn new() -> App<'a> { App { items: SelectableList::with_items(vec![ ("Item0", 1), ("Item1", 2), ("Item2", 1), ("Item3", 3), ("Item4", 1), ("Item5", 4), ("Item6", 1), ("Item7", 3), ("Item8", 1), ("Item9", 6), ("Item10", 1), ("Item11", 3), ("Item12", 1), ("Item13", 2), ("Item14", 1), ("Item15", 1), ("Item16", 4), ("Item17", 1), ("Item18", 5), ("Item19", 4), ("Item20", 1), ("Item21", 2), ("Item22", 1), ("Item23", 3), ("Item24", 1), ]), events: vec![ ("Event1", "INFO"), ("Event2", "INFO"), ("Event3", "CRITICAL"), ("Event4", "ERROR"), ("Event5", "INFO"), ("Event6", "INFO"), ("Event7", "WARNING"), ("Event8", "INFO"), ("Event9", "INFO"), ("Event10", "INFO"), ("Event11", "CRITICAL"), ("Event12", "INFO"), ("Event13", "INFO"), ("Event14", "INFO"), ("Event15", "INFO"), ("Event16", "INFO"), ("Event17", "ERROR"), ("Event18", "ERROR"), ("Event19", "INFO"), ("Event20", "INFO"), ("Event21", "WARNING"), ("Event22", "INFO"), ("Event23", "INFO"), ("Event24", "WARNING"), ("Event25", "INFO"), ("Event26", "INFO"), ], } } /// Rotate through the event list. /// This only exists to simulate some kind of "progress" fn advance(&mut self) { let event = self.events.remove(0); self.events.push(event); } } fn main() -> Result<(), Box> { // Terminal initialization let stdout = io::stdout().into_raw_mode()?; let stdout = MouseTerminal::from(stdout); let stdout = AlternateScreen::from(stdout); let backend = TermionBackend::new(stdout); let mut terminal = Terminal::new(backend)?; let events = Events::new(); // Create a new app with some exapmle state let mut app = App::new(); loop { terminal.draw(|f| { // Create two chunks with equal horizontal screen space let chunks = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(f.size()); // Iterate through all elements in the `items` app and append some debug text to it. let items: Vec = app .items .items .iter() .map(|i| { let mut lines = vec![Spans::from(i.0)]; for _ in 0..i.1 { lines.push(Spans::from(Span::styled( "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", Style::default().add_modifier(Modifier::ITALIC), ))); } ListItem::new(lines).style(Style::default().fg(Color::Black).bg(Color::White)) }) .collect(); // Create a List from all list items and highlight the currently selected one let items = List::new(items) .block(Block::default().borders(Borders::ALL).title("List")) .select(app.items.selected) .highlight_style( Style::default() .bg(Color::LightGreen) .add_modifier(Modifier::BOLD), ) .highlight_symbol(">> "); // We can now render the item list f.render_widget(items, chunks[0]); // Let's do the same for the events. // The event list doesn't have any state and only displays the current state of the list. let events: Vec = app .events .iter() .rev() .map(|&(event, level)| { // Colorcode the level depending on its type let s = match level { "CRITICAL" => Style::default().fg(Color::Red), "ERROR" => Style::default().fg(Color::Magenta), "WARNING" => Style::default().fg(Color::Yellow), "INFO" => Style::default().fg(Color::Blue), _ => Style::default(), }; // Add a example datetime and apply proper spacing between them let header = Spans::from(vec![ Span::styled(format!("{:<9}", level), s), Span::raw(" "), Span::styled( "2020-01-01 10:00:00", Style::default().add_modifier(Modifier::ITALIC), ), ]); // The event gets it's own line let log = Spans::from(vec![Span::raw(event)]); // Here several things happen: // 1. Add a `---` spacing line above the final list entry // 2. Add the Level + datetime // 3. Add a spacer line // 4. Add the actual event ListItem::new(vec![ Spans::from("-".repeat(chunks[1].width as usize)), header, Spans::from(""), log, ]) }) .collect(); let events_list = List::new(events) .block(Block::default().borders(Borders::ALL).title("List")) .start_corner(Corner::BottomLeft); f.render_widget(events_list, chunks[1]); })?; // This is a simple example on how to handle events // 1. This breaks the loop and exits the program on `q` button press. // 2. The `up`/`down` keys change the currently selected item in the App's `items` list. // 3. `left` unselects the current item. match events.next()? { Event::Input(input) => match input { Key::Char('q') => { break; } Key::Left => { app.items.unselect(); } Key::Down => { app.items.next(); } Key::Up => { app.items.previous(); } _ => {} }, Event::Tick => { app.advance(); } } } Ok(()) }