diff --git a/bin/map/src/app.rs b/bin/map/src/app.rs new file mode 100644 index 000000000..798c8be99 --- /dev/null +++ b/bin/map/src/app.rs @@ -0,0 +1,39 @@ +use crate::list::StatefulList; + +pub struct App<'a> { + pub items: StatefulList<(&'a str, usize)>, +} + +impl<'a> App<'a> { + pub fn new() -> App<'a> { + App { + items: StatefulList::with_items(vec![ + ("node0", 1), + ("node1", 2), + ("node2", 1), + ("node3", 3), + ("node4", 1), + ("node5", 4), + ("node6", 1), + ("node7", 3), + ("node8", 1), + ("node9", 6), + ("node10", 1), + ("node11", 3), + ("node12", 1), + ("node13", 2), + ("node14", 1), + ("node15", 1), + ("node16", 4), + ("node17", 1), + ("node18", 5), + ("node19", 4), + ("node20", 1), + ("node21", 2), + ("node22", 1), + ("node23", 3), + ("node24", 1), + ]), + } + } +} diff --git a/bin/map/src/list.rs b/bin/map/src/list.rs new file mode 100644 index 000000000..d6cb76590 --- /dev/null +++ b/bin/map/src/list.rs @@ -0,0 +1,44 @@ +use tui::widgets::ListState; + +pub struct StatefulList { + pub state: ListState, + pub items: Vec, +} + +impl StatefulList { + pub fn with_items(items: Vec) -> StatefulList { + StatefulList { state: ListState::default(), items } + } + + pub fn next(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i >= self.items.len() - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.state.select(Some(i)); + } + + pub fn previous(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i == 0 { + self.items.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + self.state.select(Some(i)); + } + + pub fn unselect(&mut self) { + self.state.select(None); + } +} diff --git a/bin/map/src/main.rs b/bin/map/src/main.rs index af4b3e7b7..52e0a8178 100644 --- a/bin/map/src/main.rs +++ b/bin/map/src/main.rs @@ -1,87 +1,138 @@ // make async task that updates info // this display that //use drk::Result; -use std::{io, io::Read}; +use std::{ + error::Error, + io, + io::Read, + time::{Duration, Instant}, +}; use termion::{async_stdin, event::Key, input::TermRead, raw::IntoRawMode}; use tui::{ - backend::TermionBackend, + backend::{Backend, TermionBackend}, layout::{Constraint, Direction, Layout}, style::{Color, Modifier, Style}, text::{Span, Spans, Text}, - widgets::{ListState, Block, Borders, List, ListItem, Paragraph, Wrap}, + widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Wrap}, Terminal, }; +pub mod app; +pub mod list; +pub mod ui; + +use crate::app::App; + fn main() -> Result<(), io::Error> { // Set up terminal output let stdout = io::stdout().into_raw_mode()?; let backend = TermionBackend::new(stdout); let mut terminal = Terminal::new(backend)?; + // create app and run it + let tick_rate = Duration::from_millis(250); + let app = App::new(); + let res = run_app(&mut terminal, app, tick_rate); + + terminal.clear()?; + + if let Err(err) = res { + println!("{:?}", err) + } + + Ok(()) // Create a separate thread to poll stdin. // This provides non-blocking input support. + //let mut asi = async_stdin(); + + //// Clear the terminal before first draw. + //terminal.clear()?; + //loop { + // // Lock the terminal and start a drawing session. + // terminal.draw(|frame| { + // // Create a layout into which to place our blocks. + // let chunks = Layout::default() + // .direction(Direction::Vertical) + // .constraints([Constraint::Percentage(6), Constraint::Percentage(94)].as_ref()) + // .split(frame.size()); + + // //let size = frame.size(); + + // // The text lines for our text box. + // let txt = vec![Spans::from("\n Press q to quit.\n")]; + // // Create a paragraph with the above text... + // let graph = Paragraph::new(txt) + // // In a block with borders and the given title... + // .block(Block::default().title("").borders(Borders::ALL)) + // // With white foreground and black background... + // .style(Style::default().fg(Color::White).bg(Color::Black)); + + // // Render into the layout. + // frame.render_widget(graph, chunks[0]); + + // // create a list + // //let mut items: Vec = Vec::new(); + // //for num in 1..100 { + // // let new_item = ListItem::new(format!("Node {}", num)); + // // items.push(new_item); + // //} + + // //let list = List::new(items) + // // .block(Block::default().title("Nodes").borders(Borders::ALL)) + // // .style(Style::default().fg(Color::White)) + // // .highlight_style(Style::default().add_modifier(Modifier::ITALIC)) + // // .highlight_symbol(">>"); + + // //// draw a list + // //frame.render_widget(list, chunks[1]); + + // // make a paragraph + // let mut text1 = String::new(); + // for num in 1..10000 { + // let text2 = format!("\n Node {}\n", num); + // text1.push_str(&text2); + // } + + // let text = Spans::from(vec![Span::raw(String::from(text1))]); + // let graph = Paragraph::new(text) + // .block(Block::default().title("").borders(Borders::ALL)) + // .style(Style::default().fg(Color::White).bg(Color::Black)) + // .scroll((0, 10000)) + // .wrap(Wrap { trim: true }); + + // frame.render_widget(graph, chunks[1]); + // })?; + + // // Iterate over all the keys that have been pressed since the + // // last time we checked. + // for k in asi.by_ref().keys() { + // match k.unwrap() { + // // If any of them is q, quit + // Key::Char('q') => { + // // Clear the terminal before exit so as not to leave + // // a mess. + // terminal.clear()?; + // return Ok(()) + // } + // Key::Char('j') => {} + // // Otherwise, throw them away. + // _ => (), + // } + // } + //} +} + +fn run_app( + terminal: &mut Terminal, + mut app: App, + tick_rate: Duration, +) -> io::Result<()> { let mut asi = async_stdin(); - // Clear the terminal before first draw. - terminal.clear()?; + let mut last_tick = Instant::now(); loop { - // Lock the terminal and start a drawing session. - terminal.draw(|frame| { - // Create a layout into which to place our blocks. - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Percentage(6), Constraint::Percentage(94)].as_ref()) - .split(frame.size()); + terminal.draw(|f| ui::ui(f, &mut app))?; - //let size = frame.size(); - - // The text lines for our text box. - let txt = vec![Spans::from("\n Press q to quit.\n")]; - // Create a paragraph with the above text... - let graph = Paragraph::new(txt) - // In a block with borders and the given title... - .block(Block::default().title("").borders(Borders::ALL)) - // With white foreground and black background... - .style(Style::default().fg(Color::White).bg(Color::Black)); - - // Render into the layout. - frame.render_widget(graph, chunks[0]); - - // create a list - //let mut items: Vec = Vec::new(); - //for num in 1..100 { - // let new_item = ListItem::new(format!("Node {}", num)); - // items.push(new_item); - //} - - //let list = List::new(items) - // .block(Block::default().title("Nodes").borders(Borders::ALL)) - // .style(Style::default().fg(Color::White)) - // .highlight_style(Style::default().add_modifier(Modifier::ITALIC)) - // .highlight_symbol(">>"); - - //// draw a list - //frame.render_widget(list, chunks[1]); - - // make a paragraph - let mut text1 = String::new(); - for num in 1..10000 { - let text2 = format!("\n Node {}\n", num); - text1.push_str(&text2); - } - - let text = Spans::from(vec![Span::raw(String::from(text1))]); - let graph = Paragraph::new(text) - .block(Block::default().title("").borders(Borders::ALL)) - .style(Style::default().fg(Color::White).bg(Color::Black)) - .scroll((0, 10000)) - .wrap(Wrap { trim: true }); - - frame.render_widget(graph, chunks[1]); - })?; - - // Iterate over all the keys that have been pressed since the - // last time we checked. for k in asi.by_ref().keys() { match k.unwrap() { // If any of them is q, quit @@ -91,11 +142,15 @@ fn main() -> Result<(), io::Error> { terminal.clear()?; return Ok(()) } - Key::Char('j') => { - } + Key::Char('j') => app.items.next(), + Key::Char('k') => app.items.previous(), // Otherwise, throw them away. _ => (), } } + + if last_tick.elapsed() >= tick_rate { + last_tick = Instant::now(); + } } } diff --git a/bin/map/src/mod.rs b/bin/map/src/mod.rs new file mode 100644 index 000000000..defe68cba --- /dev/null +++ b/bin/map/src/mod.rs @@ -0,0 +1,7 @@ +pub mod list; +pub mod app; +pub mod ui; + +pub use list::StatefulList; +pub use app::App; +pub use ui::ui; diff --git a/bin/map/src/ui.rs b/bin/map/src/ui.rs new file mode 100644 index 000000000..6e9169481 --- /dev/null +++ b/bin/map/src/ui.rs @@ -0,0 +1,42 @@ +use crate::app::App; +use tui::{ + backend::Backend, + layout::{Constraint, Direction, Layout}, + style::{Color, Modifier, Style}, + text::{Span, Spans}, + widgets::{Block, Borders, List, ListItem}, + Frame, +}; + +pub fn ui(f: &mut Frame, app: &mut App) { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(6), Constraint::Percentage(94)].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( + "Just some random garbage", + Style::default().add_modifier(Modifier::ITALIC), + ))); + } + ListItem::new(lines).style(Style::default()) + }) + .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")) + .highlight_style(Style::default().bg(Color::LightGreen).add_modifier(Modifier::BOLD)) + .highlight_symbol(">> "); + + // Render the item list + f.render_stateful_widget(items, chunks[0], &mut app.items.state); +}