diff --git a/Cargo.toml b/Cargo.toml index 923a1a2..4950f5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,11 +38,16 @@ failure = "0.1" structopt = "0.2" [[example]] -name = "rustbox" -path = "examples/rustbox.rs" +name = "termion_demo" +path = "examples/termion_demo.rs" +required-features = ["termion"] + +[[example]] +name = "rustbox_demo" +path = "examples/rustbox_demo.rs" required-features = ["rustbox"] [[example]] -name = "crossterm" -path = "examples/crossterm.rs" +name = "crossterm_demo" +path = "examples/crossterm_demo.rs" required-features = ["crossterm"] diff --git a/README.md b/README.md index f95984d..c4cdf09 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,16 @@ you may rely on the previously cited libraries to achieve such features. ### Demo -The [source code](examples/demo.rs) of the demo gif. +The demo shown in the gif can be run with all available backends +(`exmples/*_demo.rs` files). For example to see the `termion` version one could +run: + +``` +cargo run --example termion_demo --release --tick-rate 200 +``` + +The UI code is in [examples/demo/ui.rs](examples/demo/ui.rs) while the +application state is in [examples/demo/app.rs](examples/demo/app.rs). ### Widgets @@ -56,6 +65,8 @@ The library comes with the following list of widgets: Click on each item to see the source of the example. Run the examples with with cargo (e.g. to run the demo `cargo run --example demo`), and quit by pressing `q`. +You can run all examples by running `make run-examples`. + ### Third-party widgets * [tui-logger](https://github.com/gin66/tui-logger) diff --git a/examples/crossterm.rs b/examples/crossterm.rs deleted file mode 100644 index a5173e2..0000000 --- a/examples/crossterm.rs +++ /dev/null @@ -1,38 +0,0 @@ -use tui::backend::CrosstermBackend; -use tui::style::{Color, Modifier, Style}; -use tui::widgets::{Block, Borders, Paragraph, Text, Widget}; -use tui::Terminal; - -fn main() -> Result<(), failure::Error> { - let mut terminal = Terminal::new(CrosstermBackend::new())?; - terminal.clear()?; - terminal.hide_cursor()?; - - loop { - terminal.draw(|mut f| { - let size = f.size(); - let text = [ - Text::raw("It "), - Text::styled("works", Style::default().fg(Color::Yellow)), - ]; - Paragraph::new(text.iter()) - .block( - Block::default() - .title("Crossterm Backend") - .title_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold)) - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Magenta)), - ) - .render(&mut f, size); - })?; - - let input = crossterm::input(); - match input.read_char()? { - 'q' => { - break; - } - _ => {} - }; - } - Ok(()) -} diff --git a/examples/crossterm_demo.rs b/examples/crossterm_demo.rs new file mode 100644 index 0000000..581ccf4 --- /dev/null +++ b/examples/crossterm_demo.rs @@ -0,0 +1,90 @@ +#[allow(dead_code)] +mod demo; +#[allow(dead_code)] +mod util; + +use std::sync::mpsc; +use std::thread; +use std::time::Duration; + +use structopt::StructOpt; +use tui::backend::CrosstermBackend; +use tui::Terminal; + +use crate::demo::{ui, App}; + +enum Event { + Input(I), + Tick, +} + +#[derive(Debug, StructOpt)] +struct Cli { + #[structopt(long = "tick-rate", default_value = "250")] + tick_rate: u64, + #[structopt(long = "log")] + log: bool, +} + +fn main() -> Result<(), failure::Error> { + let cli = Cli::from_args(); + stderrlog::new().quiet(!cli.log).verbosity(4).init()?; + + let backend = CrosstermBackend::new(); + let mut terminal = Terminal::new(backend)?; + terminal.hide_cursor()?; + + // Setup input handling + let (tx, rx) = mpsc::channel(); + { + let tx = tx.clone(); + thread::spawn(move || { + let input = crossterm::input(); + loop { + match input.read_char() { + Ok(key) => { + if let Err(_) = tx.send(Event::Input(key)) { + return; + } + if key == 'q' { + return; + } + } + Err(_) => {} + } + } + }); + } + { + let tx = tx.clone(); + thread::spawn(move || { + let tx = tx.clone(); + loop { + tx.send(Event::Tick).unwrap(); + thread::sleep(Duration::from_millis(cli.tick_rate)); + } + }); + } + + let mut app = App::new("Crossterm Demo"); + + terminal.clear()?; + + loop { + ui::draw(&mut terminal, &app)?; + match rx.recv()? { + Event::Input(key) => { + // TODO: handle key events once they are supported by crossterm + app.on_key(key); + } + Event::Tick => { + app.on_tick(); + } + } + if app.should_quit { + break; + } + } + + Ok(()) +} diff --git a/examples/demo/app.rs b/examples/demo/app.rs new file mode 100644 index 0000000..40f3704 --- /dev/null +++ b/examples/demo/app.rs @@ -0,0 +1,249 @@ +use crate::util::{RandomSignal, SinSignal, TabsState}; + +const TASKS: [&'static str; 24] = [ + "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10", + "Item11", "Item12", "Item13", "Item14", "Item15", "Item16", "Item17", "Item18", "Item19", + "Item20", "Item21", "Item22", "Item23", "Item24", +]; + +const LOGS: [(&'static str, &'static str); 26] = [ + ("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"), +]; + +const EVENTS: [(&'static str, u64); 24] = [ + ("B1", 9), + ("B2", 12), + ("B3", 5), + ("B4", 8), + ("B5", 2), + ("B6", 4), + ("B7", 5), + ("B8", 9), + ("B9", 14), + ("B10", 15), + ("B11", 1), + ("B12", 0), + ("B13", 4), + ("B14", 6), + ("B15", 4), + ("B16", 6), + ("B17", 4), + ("B18", 7), + ("B19", 13), + ("B20", 8), + ("B21", 11), + ("B22", 9), + ("B23", 3), + ("B24", 5), +]; + +pub struct Signal { + source: S, + pub points: Vec, + tick_rate: usize, +} + +impl Signal +where + S: Iterator, +{ + fn on_tick(&mut self) { + for _ in 0..self.tick_rate { + self.points.remove(0); + } + self.points + .extend(self.source.by_ref().take(self.tick_rate)); + } +} + +pub struct Signals { + pub sin1: Signal, + pub sin2: Signal, + pub window: [f64; 2], +} + +impl Signals { + fn on_tick(&mut self) { + self.sin1.on_tick(); + self.sin2.on_tick(); + self.window[0] += 1.0; + self.window[1] += 1.0; + } +} + +pub struct ListState { + pub items: Vec, + pub selected: usize, +} + +impl ListState { + fn new(items: Vec) -> ListState { + ListState { items, selected: 0 } + } + fn select_previous(&mut self) { + if self.selected > 0 { + self.selected -= 1; + } + } + fn select_next(&mut self) { + if self.selected < self.items.len() - 1 { + self.selected += 1 + } + } +} + +pub struct Server<'a> { + pub name: &'a str, + pub location: &'a str, + pub coords: (f64, f64), + pub status: &'a str, +} + +pub struct App<'a> { + pub title: &'a str, + pub should_quit: bool, + pub tabs: TabsState<'a>, + pub show_chart: bool, + pub progress: u16, + pub sparkline: Signal, + pub tasks: ListState<(&'a str)>, + pub logs: ListState<(&'a str, &'a str)>, + pub signals: Signals, + pub barchart: Vec<(&'a str, u64)>, + pub servers: Vec>, +} + +impl<'a> App<'a> { + pub fn new(title: &'a str) -> App<'a> { + let mut rand_signal = RandomSignal::new(0, 100); + let sparkline_points = rand_signal.by_ref().take(300).collect(); + let mut sin_signal = SinSignal::new(0.2, 3.0, 18.0); + let sin1_points = sin_signal.by_ref().take(100).collect(); + let mut sin_signal2 = SinSignal::new(0.1, 2.0, 10.0); + let sin2_points = sin_signal2.by_ref().take(200).collect(); + App { + title, + should_quit: false, + tabs: TabsState::new(vec!["Tab0", "Tab1"]), + show_chart: true, + progress: 0, + sparkline: Signal { + source: rand_signal, + points: sparkline_points, + tick_rate: 1, + }, + tasks: ListState::new(TASKS.to_vec()), + logs: ListState::new(LOGS.to_vec()), + signals: Signals { + sin1: Signal { + source: sin_signal, + points: sin1_points, + tick_rate: 5, + }, + sin2: Signal { + source: sin_signal2, + points: sin2_points, + tick_rate: 10, + }, + window: [0.0, 20.0], + }, + barchart: EVENTS.to_vec(), + servers: vec![ + Server { + name: "NorthAmerica-1", + location: "New York City", + coords: (40.71, -74.00), + status: "Up", + }, + Server { + name: "Europe-1", + location: "Paris", + coords: (48.85, 2.35), + status: "Failure", + }, + Server { + name: "SouthAmerica-1", + location: "São Paulo", + coords: (-23.54, -46.62), + status: "Up", + }, + Server { + name: "Asia-1", + location: "Singapore", + coords: (1.35, 103.86), + status: "Up", + }, + ], + } + } + + pub fn on_up(&mut self) { + self.tasks.select_previous(); + } + + pub fn on_down(&mut self) { + self.tasks.select_next(); + } + + pub fn on_right(&mut self) { + self.tabs.next(); + } + + pub fn on_left(&mut self) { + self.tabs.previous(); + } + + pub fn on_key(&mut self, c: char) { + match c { + 'q' => { + self.should_quit = true; + } + 't' => { + self.show_chart = !self.show_chart; + } + _ => {} + } + } + + pub fn on_tick(&mut self) { + // Update progress + self.progress += 5; + if self.progress > 100 { + self.progress = 0; + } + + self.sparkline.on_tick(); + self.signals.on_tick(); + + let log = self.logs.items.pop().unwrap(); + self.logs.items.insert(0, log); + + let event = self.barchart.pop().unwrap(); + self.barchart.insert(0, event); + } +} diff --git a/examples/demo/mod.rs b/examples/demo/mod.rs new file mode 100644 index 0000000..cbf7f15 --- /dev/null +++ b/examples/demo/mod.rs @@ -0,0 +1,3 @@ +mod app; +pub mod ui; +pub use app::App; diff --git a/examples/demo.rs b/examples/demo/ui.rs similarity index 53% rename from examples/demo.rs rename to examples/demo/ui.rs index 5750b59..e3d010a 100644 --- a/examples/demo.rs +++ b/examples/demo/ui.rs @@ -1,15 +1,6 @@ -#[allow(dead_code)] -mod util; - use std::io; -use std::time::Duration; -use structopt::StructOpt; -use termion::event::Key; -use termion::input::MouseTerminal; -use termion::raw::IntoRawMode; -use termion::screen::AlternateScreen; -use tui::backend::{Backend, TermionBackend}; +use tui::backend::Backend; use tui::layout::{Constraint, Direction, Layout, Rect}; use tui::style::{Color, Modifier, Style}; use tui::widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle}; @@ -19,235 +10,26 @@ use tui::widgets::{ }; use tui::{Frame, Terminal}; -use crate::util::event::{Config, Event, Events}; -use crate::util::{RandomSignal, SinSignal, TabsState}; - -struct Server<'a> { - name: &'a str, - location: &'a str, - coords: (f64, f64), - status: &'a str, -} - -struct App<'a> { - items: Vec<&'a str>, - events: Vec<(&'a str, &'a str)>, - selected: usize, - tabs: TabsState<'a>, - show_chart: bool, - progress: u16, - data: Vec, - data2: Vec<(f64, f64)>, - data3: Vec<(f64, f64)>, - data4: Vec<(&'a str, u64)>, - window: [f64; 2], - colors: [Color; 2], - color_index: usize, - servers: Vec>, -} - -#[derive(Debug, StructOpt)] -struct Cli { - #[structopt(long = "tick-rate", default_value = "250")] - tick_rate: u64, - #[structopt(long = "log")] - log: bool, -} - -fn main() -> Result<(), failure::Error> { - let cli = Cli::from_args(); - - stderrlog::new().quiet(!cli.log).verbosity(4).init()?; - - 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)?; - terminal.hide_cursor()?; - - let events = Events::with_config(Config { - tick_rate: Duration::from_millis(cli.tick_rate), - ..Config::default() - }); - - let mut rand_signal = RandomSignal::new(0, 100); - let mut sin_signal = SinSignal::new(0.2, 3.0, 18.0); - let mut sin_signal2 = SinSignal::new(0.1, 2.0, 10.0); - - let mut app = App { - items: vec![ - "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", - "Item10", "Item11", "Item12", "Item13", "Item14", "Item15", "Item16", "Item17", - "Item18", "Item19", "Item20", "Item21", "Item22", "Item23", "Item24", - ], - 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"), - ], - selected: 0, - tabs: TabsState::new(vec!["Tab0", "Tab1"]), - show_chart: true, - progress: 0, - data: rand_signal.by_ref().take(300).collect(), - data2: sin_signal.by_ref().take(100).collect(), - data3: sin_signal2.by_ref().take(200).collect(), - data4: vec![ - ("B1", 9), - ("B2", 12), - ("B3", 5), - ("B4", 8), - ("B5", 2), - ("B6", 4), - ("B7", 5), - ("B8", 9), - ("B9", 14), - ("B10", 15), - ("B11", 1), - ("B12", 0), - ("B13", 4), - ("B14", 6), - ("B15", 4), - ("B16", 6), - ("B17", 4), - ("B18", 7), - ("B19", 13), - ("B20", 8), - ("B21", 11), - ("B22", 9), - ("B23", 3), - ("B24", 5), - ], - window: [0.0, 20.0], - colors: [Color::Magenta, Color::Red], - color_index: 0, - servers: vec![ - Server { - name: "NorthAmerica-1", - location: "New York City", - coords: (40.71, -74.00), - status: "Up", - }, - Server { - name: "Europe-1", - location: "Paris", - coords: (48.85, 2.35), - status: "Failure", - }, - Server { - name: "SouthAmerica-1", - location: "São Paulo", - coords: (-23.54, -46.62), - status: "Up", - }, - Server { - name: "Asia-1", - location: "Singapore", - coords: (1.35, 103.86), - status: "Up", - }, - ], - }; - - loop { - // Draw UI - terminal.draw(|mut f| { - let chunks = Layout::default() - .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) - .split(f.size()); - Tabs::default() - .block(Block::default().borders(Borders::ALL).title("Tabs")) - .titles(&app.tabs.titles) - .style(Style::default().fg(Color::Green)) - .highlight_style(Style::default().fg(Color::Yellow)) - .select(app.tabs.index) - .render(&mut f, chunks[0]); - match app.tabs.index { - 0 => draw_first_tab(&mut f, &app, chunks[1]), - 1 => draw_second_tab(&mut f, &app, chunks[1]), - _ => {} - }; - })?; +use crate::demo::App; - match events.next()? { - Event::Input(input) => match input { - Key::Char('q') => { - break; - } - Key::Up => { - if app.selected > 0 { - app.selected -= 1 - }; - } - Key::Down => { - if app.selected < app.items.len() - 1 { - app.selected += 1; - } - } - Key::Left => { - app.tabs.previous(); - } - Key::Right => { - app.tabs.next(); - } - Key::Char('t') => { - app.show_chart = !app.show_chart; - } - _ => {} - }, - Event::Tick => { - app.progress += 5; - if app.progress > 100 { - app.progress = 0; - } - app.data.insert(0, rand_signal.next().unwrap()); - app.data.pop(); - for _ in 0..5 { - app.data2.remove(0); - app.data2.push(sin_signal.next().unwrap()); - } - for _ in 0..10 { - app.data3.remove(0); - app.data3.push(sin_signal2.next().unwrap()); - } - let i = app.data4.pop().unwrap(); - app.data4.insert(0, i); - app.window[0] += 1.0; - app.window[1] += 1.0; - let i = app.events.pop().unwrap(); - app.events.insert(0, i); - app.color_index += 1; - if app.color_index >= app.colors.len() { - app.color_index = 0; - } - } - } - } - Ok(()) +pub fn draw(terminal: &mut Terminal, app: &App) -> Result<(), io::Error> { + terminal.draw(|mut f| { + let chunks = Layout::default() + .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) + .split(f.size()); + Tabs::default() + .block(Block::default().borders(Borders::ALL).title(app.title)) + .titles(&app.tabs.titles) + .style(Style::default().fg(Color::Green)) + .highlight_style(Style::default().fg(Color::Yellow)) + .select(app.tabs.index) + .render(&mut f, chunks[0]); + match app.tabs.index { + 0 => draw_first_tab(&mut f, &app, chunks[1]), + 1 => draw_second_tab(&mut f, &app, chunks[1]), + _ => {} + }; + }) } fn draw_first_tab(f: &mut Frame, app: &App, area: Rect) @@ -295,7 +77,7 @@ where Sparkline::default() .block(Block::default().title("Sparkline:")) .style(Style::default().fg(Color::Green)) - .data(&app.data) + .data(&app.sparkline.points) .render(f, chunks[1]); } @@ -323,8 +105,8 @@ where .split(chunks[0]); SelectableList::default() .block(Block::default().borders(Borders::ALL).title("List")) - .items(&app.items) - .select(Some(app.selected)) + .items(&app.tasks.items) + .select(Some(app.tasks.selected)) .highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold)) .highlight_symbol(">") .render(f, chunks[0]); @@ -332,7 +114,7 @@ where let warning_style = Style::default().fg(Color::Yellow); let error_style = Style::default().fg(Color::Magenta); let critical_style = Style::default().fg(Color::Red); - let events = app.events.iter().map(|&(evt, level)| { + let events = app.logs.items.iter().map(|&(evt, level)| { Text::styled( format!("{}: {}", level, evt), match level { @@ -349,7 +131,7 @@ where } BarChart::default() .block(Block::default().borders(Borders::ALL).title("Bar chart")) - .data(&app.data4) + .data(&app.barchart) .bar_width(3) .bar_gap(2) .value_style( @@ -375,11 +157,11 @@ where .title("X Axis") .style(Style::default().fg(Color::Gray)) .labels_style(Style::default().modifier(Modifier::Italic)) - .bounds(app.window) + .bounds(app.signals.window) .labels(&[ - &format!("{}", app.window[0]), - &format!("{}", (app.window[0] + app.window[1]) / 2.0), - &format!("{}", app.window[1]), + &format!("{}", app.signals.window[0]), + &format!("{}", (app.signals.window[0] + app.signals.window[1]) / 2.0), + &format!("{}", app.signals.window[1]), ]), ) .y_axis( @@ -395,12 +177,12 @@ where .name("data2") .marker(Marker::Dot) .style(Style::default().fg(Color::Cyan)) - .data(&app.data2), + .data(&app.signals.sin1.points), Dataset::default() .name("data3") .marker(Marker::Braille) .style(Style::default().fg(Color::Yellow)) - .data(&app.data3), + .data(&app.signals.sin2.points), ]) .render(f, chunks[1]); } diff --git a/examples/rustbox.rs b/examples/rustbox.rs deleted file mode 100644 index 92c5686..0000000 --- a/examples/rustbox.rs +++ /dev/null @@ -1,46 +0,0 @@ -use rustbox::Key; -use std::error::Error; - -use tui::backend::RustboxBackend; -use tui::style::{Color, Modifier, Style}; -use tui::widgets::{Block, Borders, Paragraph, Text, Widget}; -use tui::Terminal; - -fn main() -> Result<(), failure::Error> { - let mut terminal = Terminal::new(RustboxBackend::new().unwrap()).unwrap(); - terminal.clear().unwrap(); - terminal.hide_cursor().unwrap(); - loop { - draw(&mut terminal)?; - match terminal.backend().rustbox().poll_event(false) { - Ok(rustbox::Event::KeyEvent(key)) => { - if key == Key::Char('q') { - break; - } - } - Err(e) => panic!("{}", e.description()), - _ => {} - }; - } - terminal.show_cursor()?; - Ok(()) -} - -fn draw(t: &mut Terminal) -> Result<(), std::io::Error> { - let text = [ - Text::raw("It "), - Text::styled("works", Style::default().fg(Color::Yellow)), - ]; - t.draw(|mut f| { - let size = f.size(); - Paragraph::new(text.iter()) - .block( - Block::default() - .title("Rustbox backend") - .title_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold)) - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Magenta)), - ) - .render(&mut f, size) - }) -} diff --git a/examples/rustbox_demo.rs b/examples/rustbox_demo.rs new file mode 100644 index 0000000..9cf5ffc --- /dev/null +++ b/examples/rustbox_demo.rs @@ -0,0 +1,66 @@ +mod demo; +#[allow(dead_code)] +mod util; + +use std::time::{Duration, Instant}; + +use rustbox::keyboard::Key; +use structopt::StructOpt; +use tui::backend::RustboxBackend; +use tui::Terminal; + +use crate::demo::{ui, App}; + +#[derive(Debug, StructOpt)] +struct Cli { + #[structopt(long = "tick-rate", default_value = "250")] + tick_rate: u64, + #[structopt(long = "log")] + log: bool, +} + +fn main() -> Result<(), failure::Error> { + let cli = Cli::from_args(); + stderrlog::new().quiet(!cli.log).verbosity(4).init()?; + + let backend = RustboxBackend::new()?; + let mut terminal = Terminal::new(backend)?; + terminal.hide_cursor()?; + + let mut app = App::new("Rustbox demo"); + + let mut last_tick = Instant::now(); + let tick_rate = Duration::from_millis(cli.tick_rate); + loop { + ui::draw(&mut terminal, &app)?; + match terminal.backend().rustbox().peek_event(tick_rate, false) { + Ok(rustbox::Event::KeyEvent(key)) => match key { + Key::Char(c) => { + app.on_key(c); + } + Key::Up => { + app.on_up(); + } + Key::Down => { + app.on_down(); + } + Key::Left => { + app.on_left(); + } + Key::Right => { + app.on_right(); + } + _ => {} + }, + _ => {} + } + if last_tick.elapsed() > tick_rate { + app.on_tick(); + last_tick = Instant::now(); + } + if app.should_quit { + break; + } + } + Ok(()) +} diff --git a/examples/termion_demo.rs b/examples/termion_demo.rs new file mode 100644 index 0000000..41660da --- /dev/null +++ b/examples/termion_demo.rs @@ -0,0 +1,75 @@ +mod demo; +#[allow(dead_code)] +mod util; + +use std::io; +use std::time::Duration; + +use structopt::StructOpt; +use termion::event::Key; +use termion::input::MouseTerminal; +use termion::raw::IntoRawMode; +use termion::screen::AlternateScreen; +use tui::backend::TermionBackend; +use tui::Terminal; + +use crate::demo::{ui, App}; +use crate::util::event::{Config, Event, Events}; + +#[derive(Debug, StructOpt)] +struct Cli { + #[structopt(long = "tick-rate", default_value = "250")] + tick_rate: u64, + #[structopt(long = "log")] + log: bool, +} + +fn main() -> Result<(), failure::Error> { + let cli = Cli::from_args(); + stderrlog::new().quiet(!cli.log).verbosity(4).init()?; + + let events = Events::with_config(Config { + tick_rate: Duration::from_millis(cli.tick_rate), + ..Config::default() + }); + + 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)?; + terminal.hide_cursor()?; + + let mut app = App::new("Termion demo"); + loop { + ui::draw(&mut terminal, &app)?; + match events.next()? { + Event::Input(key) => match key { + Key::Char(c) => { + app.on_key(c); + } + Key::Up => { + app.on_up(); + } + Key::Down => { + app.on_down(); + } + Key::Left => { + app.on_left(); + } + Key::Right => { + app.on_right(); + } + _ => {} + }, + Event::Tick => { + app.on_tick(); + } + } + if app.should_quit { + break; + } + } + + Ok(()) +} diff --git a/examples/util/mod.rs b/examples/util/mod.rs index daa65a0..a1c7e5c 100644 --- a/examples/util/mod.rs +++ b/examples/util/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "termion")] pub mod event; use rand::distributions::{Distribution, Uniform};