Add examples and demo

This commit is contained in:
Florian Dehau 2016-11-08 00:35:46 +01:00
parent 4f8a57d500
commit 42fb0803af
14 changed files with 1394 additions and 119 deletions

111
README.md
View File

@ -13,14 +13,123 @@ can either choose from:
- [termion](https://github.com/ticki/termion)
- [rustbox](https://github.com/gchp/rustbox)
However, some features may only be available in one of the two.
The library is based on the principle of immediate rendering with intermediate
buffers. This means that at each new frame you are meant to issue a call for
each widget that is part of the UI. While providing a great flexibility for rich
and interactive UI, this may introduce overhead for highly dynamic content. So, the
implementation try to minimize the number of ansi escapes sequences outputed to
draw the updated UI. In practice, given the speed of rust the overhead rather
comes from the terminal emulator than the library itself.
Moreover, the library does not provide any input handling nor any event system and
you may rely on the previously cited libraries to achieve such features.
## Cargo.toml
```toml
[dependencies]
tui: "0.1"
tui = "0.1"
```
## Get Started
### Create the terminal interface
The first thing to do is to choose from one of the two backends:
For Termion:
```rust
use tui::{Terminal, TermionBackend};
fn main() {
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend);
}
```
For Rustbox:
```rust
use tui::{Terminal, RustboxBackend};
fn main() {
let backend = RustboxBackend::new().unwrap();
let mut terminal = Terminal::new(backend);
}
```
### Layout
The library comes with a basic yet useful layout management object called
`Group`. As you may see below and in the examples, the library makes heavy use
of the builder pattern to provide full customization. And the `Group` object is
no exception:
```rust
use tui::widgets::{Block, border};
use tui::layout::{Group, Rect, Direction};
fn draw(t: &mut Terminal<TermionBackend>) {
let size = t.size().unwrap();
Group::default()
/// You first choose a main direction for the group
.direction(Direction::Vertical)
/// An optional margin
.margin(1)
/// The preferred sizes (heights in this case)
.sizes(&[Size::Fixed(10), Size::Max(20), Size::Min(10)])
/// The computed (or cached) layout is then available as the second argument
/// of the closure
.render(t, &size, |t, chunks| {
/// Continue to describe your UI there.
/// Examples:
Block::default()
.title("Block")
.borders(border::ALL)
.render(t, &chunks[0]);
})
```
This let you describe responsive terminal UI by nesting groups. You should note
that by default the computed layout tries to fill the available space
completely. So if for any reason you might need a blank space somewhere, try to
pass an additional size to the group and don't use the corresponding area inside
the render method.
Once you have finished to describe the UI, you just need to call:
```rust
t.draw().unwrap()
```
to actually draw to the terminal.
### Widgets
The library comes with the following list of widgets:
* [Block](examples/block.rs)
* [Gauge](examples/gauge.rs)
* [Sparkline](examples/sparkline.rs)
* [Chart](examples/chart.rs)
* [BarChart](examples/bar_chart.rs)
* [List](examples/list.rs)
* [Table](examples/table.rs)
* [Paragraph](examples/paragraph.rs)
* [Canvas (with line, point cloud, map)](examples/canvas.rs)
* [Tabs](examples/tabs.rs)
Click on each item to get an example.
### Demo
The [source code](examples/demo.rs) of the source gif.
## License
[MIT](LICENSE)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

159
examples/barchart.rs Normal file
View File

@ -0,0 +1,159 @@
extern crate tui;
extern crate termion;
use std::io;
use std::thread;
use std::time;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border, BarChart};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
struct App<'a> {
data: Vec<(&'a str, u64)>,
}
impl<'a> App<'a> {
fn new() -> App<'a> {
App {
data: 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)],
}
}
fn advance(&mut self) {
let value = self.data.pop().unwrap();
self.data.insert(0, value);
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
let clock_tx = tx.clone();
// Input
thread::spawn(move || {
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') {
break;
}
}
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal, &app);
loop {
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
if input == event::Key::Char('q') {
break;
}
}
Event::Tick => {
app.advance();
}
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
Group::default()
.direction(Direction::Vertical)
.margin(2)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &size, |t, chunks| {
BarChart::default()
.block(Block::default().title("Data1").borders(border::ALL))
.data(&app.data)
.bar_width(9)
.style(Style::default().fg(Color::Yellow))
.value_style(Style::default().fg(Color::Black).bg(Color::Yellow))
.render(t, &chunks[0]);
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[1], |t, chunks| {
BarChart::default()
.block(Block::default().title("Data2").borders(border::ALL))
.data(&app.data)
.bar_width(5)
.bar_gap(3)
.style(Style::default().fg(Color::Green))
.value_style(Style::default().bg(Color::Green).modifier(Modifier::Bold))
.render(t, &chunks[0]);
BarChart::default()
.block(Block::default().title("Data3").borders(border::ALL))
.data(&app.data)
.style(Style::default().fg(Color::Red))
.bar_width(7)
.bar_gap(0)
.value_style(Style::default().bg(Color::Red))
.label_style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic))
.render(t, &chunks[1]);
})
});
t.draw().unwrap();
}

View File

@ -8,7 +8,7 @@ use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color};
use tui::style::{Style, Color, Modifier};
fn main() {
let mut terminal = Terminal::new(TermionBackend::new().unwrap()).unwrap();
@ -30,41 +30,48 @@ fn draw(t: &mut Terminal<TermionBackend>) {
let size = t.size().unwrap();
// Wrapping block for a group
// Just draw the block and the group on the same area and build the group
// with at least a margin of 1
Block::default()
.borders(border::ALL)
.render(t, &size);
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Fixed(7), Size::Min(0), Size::Fixed(7)])
.margin(4)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &size, |t, chunks| {
Block::default()
.title("Top")
.title_style(Style::default().fg(Color::Magenta))
.border_style(Style::default().fg(Color::Magenta))
.borders(border::BOTTOM)
.render(t, &chunks[0]);
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Fixed(7), Size::Min(0), Size::Fixed(7)])
.render(t, &chunks[1], |t, chunks| {
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[0], |t, chunks| {
Block::default()
.title("Left")
.title("With background")
.title_style(Style::default().fg(Color::Yellow))
.style(Style::default().bg(Color::Green))
.render(t, &chunks[0]);
Block::default()
.title("Middle")
.title_style(Style::default().fg(Color::Cyan))
.title("Styled title")
.title_style(Style::default()
.fg(Color::White)
.bg(Color::Red)
.modifier(Modifier::Bold))
.render(t, &chunks[1]);
});
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[1], |t, chunks| {
Block::default()
.title("With borders")
.borders(border::ALL)
.render(t, &chunks[0]);
Block::default()
.title("With styled borders")
.border_style(Style::default().fg(Color::Cyan))
.borders(border::LEFT | border::RIGHT)
.render(t, &chunks[1]);
Block::default()
.title("Right")
.title_style(Style::default().fg(Color::Green))
.render(t, &chunks[2]);
});
Block::default()
.title("Bottom")
.title_style(Style::default().fg(Color::Green))
.border_style(Style::default().fg(Color::Green))
.borders(border::TOP)
.render(t, &chunks[2]);
});
t.draw().unwrap();

200
examples/canvas.rs Normal file
View File

@ -0,0 +1,200 @@
extern crate tui;
extern crate termion;
use std::io;
use std::thread;
use std::time;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border};
use tui::widgets::canvas::{Canvas, Map, MapResolution, Line};
use tui::layout::{Group, Rect, Direction, Size};
use tui::style::Color;
struct App {
size: Rect,
x: f64,
y: f64,
ball: Rect,
playground: Rect,
vx: u16,
vy: u16,
}
impl App {
fn new() -> App {
App {
size: Default::default(),
x: 0.0,
y: 0.0,
ball: Rect::new(20, 20, 10, 10),
playground: Rect::new(10, 10, 100, 100),
vx: 1,
vy: 1,
}
}
fn advance(&mut self) {
if self.ball.left() < self.playground.left() ||
self.ball.right() > self.playground.right() {
self.vx = !self.vx;
} else if self.ball.top() < self.playground.top() ||
self.ball.bottom() > self.playground.bottom() {
self.vy = !self.vy;
}
self.ball.x += self.vx;
self.ball.y += self.vy;
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
let clock_tx = tx.clone();
// Input
thread::spawn(move || {
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') {
break;
}
}
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
let size = terminal.size().unwrap();
app.size = size;
draw(&mut terminal, &app);
loop {
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
match input {
event::Key::Char('q') => {
break;
}
event::Key::Down => {
app.y += 1.0;
}
event::Key::Up => {
app.y -= 1.0;
}
event::Key::Right => {
app.x += 1.0;
}
event::Key::Left => {
app.x -= 1.0;
}
_ => {}
}
}
Event::Tick => {
app.advance();
}
}
let size = terminal.size().unwrap();
if size != app.size {
app.size = size;
terminal.resize(size).unwrap();
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &app.size, |t, chunks| {
Canvas::default()
.block(Block::default()
.borders(border::ALL)
.title("World"))
.paint(|ctx| {
ctx.draw(&Map {
color: Color::White,
resolution: MapResolution::High,
});
ctx.print(app.x, -app.y, "You are here", Color::Yellow);
})
.x_bounds([-180.0, 180.0])
.y_bounds([-90.0, 90.0])
.render(t, &chunks[0]);
Canvas::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.paint(|ctx| {
ctx.draw(&Line {
x1: app.ball.left() as f64,
y1: app.ball.top() as f64,
x2: app.ball.right() as f64,
y2: app.ball.top() as f64,
color: Color::Yellow,
});
ctx.draw(&Line {
x1: app.ball.right() as f64,
y1: app.ball.top() as f64,
x2: app.ball.right() as f64,
y2: app.ball.bottom() as f64,
color: Color::Yellow,
});
ctx.draw(&Line {
x1: app.ball.right() as f64,
y1: app.ball.bottom() as f64,
x2: app.ball.left() as f64,
y2: app.ball.bottom() as f64,
color: Color::Yellow,
});
ctx.draw(&Line {
x1: app.ball.left() as f64,
y1: app.ball.bottom() as f64,
x2: app.ball.left() as f64,
y2: app.ball.top() as f64,
color: Color::Yellow,
});
})
.x_bounds([10.0, 110.0])
.y_bounds([10.0, 110.0])
.render(t, &chunks[1]);
});
t.draw().unwrap();
}

155
examples/chart.rs Normal file
View File

@ -0,0 +1,155 @@
extern crate tui;
extern crate termion;
mod util;
use util::*;
use std::io;
use std::thread;
use std::time;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border, Chart, Axis, Marker, Dataset};
use tui::style::{Style, Color, Modifier};
struct App {
signal1: SinSignal,
data1: Vec<(f64, f64)>,
signal2: SinSignal,
data2: Vec<(f64, f64)>,
window: [f64; 2],
}
impl App {
fn new() -> App {
let mut signal1 = SinSignal::new(0.2, 3.0, 18.0);
let mut signal2 = SinSignal::new(0.1, 2.0, 10.0);
let data1 = signal1.by_ref().take(200).collect::<Vec<(f64, f64)>>();
let data2 = signal2.by_ref().take(200).collect::<Vec<(f64, f64)>>();
App {
signal1: signal1,
data1: data1,
signal2: signal2,
data2: data2,
window: [0.0, 20.0],
}
}
fn advance(&mut self) {
for _ in 0..5 {
self.data1.remove(0);
}
self.data1.extend(self.signal1.by_ref().take(5));
for _ in 0..10 {
self.data2.remove(0);
}
self.data2.extend(self.signal2.by_ref().take(10));
self.window[0] += 1.0;
self.window[1] += 1.0;
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
let clock_tx = tx.clone();
// Input
thread::spawn(move || {
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') {
break;
}
}
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal, &app);
loop {
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
if input == event::Key::Char('q') {
break;
}
}
Event::Tick => {
app.advance();
}
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
Chart::default()
.block(Block::default()
.title("Chart")
.title_style(Style::default()
.fg(Color::Cyan)
.modifier(Modifier::Bold))
.borders(border::ALL))
.x_axis(Axis::default()
.title("X Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::Italic))
.bounds(app.window)
.labels(&[&format!("{}", app.window[0]),
&format!("{}", (app.window[0] + app.window[1]) / 2.0),
&format!("{}", app.window[1])]))
.y_axis(Axis::default()
.title("Y Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::Italic))
.bounds([-20.0, 20.0])
.labels(&["-20", "0", "20"]))
.datasets(&[Dataset::default()
.name("data2")
.marker(Marker::Dot)
.style(Style::default().fg(Color::Cyan))
.data(&app.data1),
Dataset::default()
.name("data3")
.marker(Marker::Braille)
.style(Style::default().fg(Color::Yellow))
.data(&app.data2)])
.render(t, &size);
t.draw().unwrap();
}

View File

@ -1,9 +1,11 @@
extern crate tui;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate tui;
#[macro_use]
extern crate termion;
extern crate rand;
mod util;
use std::io;
use std::thread;
@ -11,16 +13,9 @@ use std::env;
use std::time;
use std::sync::mpsc;
use rand::distributions::{IndependentSample, Range};
use termion::event;
use termion::input::TermRead;
use log::LogLevelFilter;
use log4rs::append::file::FileAppender;
use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Root};
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, SelectableList, List, Gauge, Sparkline, Paragraph, border,
Chart, Axis, Dataset, BarChart, Marker, Tabs, Table};
@ -28,55 +23,7 @@ use tui::widgets::canvas::{Canvas, Map, MapResolution, Line};
use tui::layout::{Group, Direction, Size, Rect};
use tui::style::{Style, Color, Modifier};
#[derive(Clone)]
struct RandomSignal {
range: Range<u64>,
rng: rand::ThreadRng,
}
impl RandomSignal {
fn new(r: Range<u64>) -> RandomSignal {
RandomSignal {
range: r,
rng: rand::thread_rng(),
}
}
}
impl Iterator for RandomSignal {
type Item = u64;
fn next(&mut self) -> Option<u64> {
Some(self.range.ind_sample(&mut self.rng))
}
}
#[derive(Clone)]
struct SinSignal {
x: f64,
interval: f64,
period: f64,
scale: f64,
}
impl SinSignal {
fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
SinSignal {
x: 0.0,
interval: interval,
period: period,
scale: scale,
}
}
}
impl Iterator for SinSignal {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
let point = (self.x, (self.x * 1.0 / self.period).sin() * self.scale);
self.x += self.interval;
Some(point)
}
}
use util::*;
struct Server<'a> {
name: &'a str,
@ -85,29 +32,13 @@ struct Server<'a> {
status: &'a str,
}
struct MyTabs {
titles: [&'static str; 2],
selection: usize,
}
impl MyTabs {
fn next(&mut self) {
self.selection = (self.selection + 1) % self.titles.len();
}
fn previous(&mut self) {
if self.selection > 0 {
self.selection -= 1;
}
}
}
struct App<'a> {
size: Rect,
items: Vec<&'a str>,
events: Vec<(&'a str, &'a str)>,
selected: usize,
tabs: MyTabs,
tabs: MyTabs<'a>,
show_chart: bool,
progress: u16,
data: Vec<u64>,
@ -130,23 +61,13 @@ fn main() {
for argument in env::args() {
if argument == "--log" {
let log = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{l} / {d(%H:%M:%S)} / \
{M}:{L}{n}{m}{n}{n}")))
.build("demo.log")
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("log", Box::new(log)))
.build(Root::builder().appender("log").build(LogLevelFilter::Debug))
.unwrap();
log4rs::init_config(config).unwrap();
setup_log("demo.log");
}
}
info!("Start");
let mut rand_signal = RandomSignal::new(Range::new(0, 100));
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);
@ -183,7 +104,7 @@ fn main() {
("Event26", "INFO")],
selected: 0,
tabs: MyTabs {
titles: ["Tab0", "Tab1"],
titles: vec!["Tab0", "Tab1"],
selection: 0,
},
show_chart: true,
@ -272,7 +193,8 @@ fn main() {
}
});
let mut terminal = Terminal::new(TermionBackend::new().unwrap()).unwrap();
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();

149
examples/gauge.rs Normal file
View File

@ -0,0 +1,149 @@
extern crate tui;
extern crate termion;
use std::io;
use std::thread;
use std::time;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border, Gauge};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
struct App {
progress1: u16,
progress2: u16,
progress3: u16,
progress4: u16,
}
impl App {
fn new() -> App {
App {
progress1: 0,
progress2: 0,
progress3: 0,
progress4: 0,
}
}
fn advance(&mut self) {
self.progress1 += 5;
if self.progress1 > 100 {
self.progress1 = 0;
}
self.progress2 += 10;
if self.progress2 > 100 {
self.progress2 = 0;
}
self.progress3 += 1;
if self.progress3 > 100 {
self.progress3 = 0;
}
self.progress4 += 3;
if self.progress4 > 100 {
self.progress4 = 0;
}
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
let clock_tx = tx.clone();
// Input
thread::spawn(move || {
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') {
break;
}
}
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal, &app);
loop {
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
if input == event::Key::Char('q') {
break;
}
}
Event::Tick => {
app.advance();
}
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
Group::default()
.direction(Direction::Vertical)
.margin(2)
.sizes(&[Size::Percent(25), Size::Percent(25), Size::Percent(25), Size::Percent(25)])
.render(t, &size, |t, chunks| {
Gauge::default()
.block(Block::default().title("Gauge1").borders(border::ALL))
.style(Style::default().fg(Color::Yellow))
.percent(app.progress1)
.render(t, &chunks[0]);
Gauge::default()
.block(Block::default().title("Gauge2").borders(border::ALL))
.style(Style::default().fg(Color::Magenta).bg(Color::Green))
.percent(app.progress2)
.label(&format!("{}/100", app.progress2))
.render(t, &chunks[1]);
Gauge::default()
.block(Block::default().title("Gauge2").borders(border::ALL))
.style(Style::default().fg(Color::Yellow))
.percent(app.progress3)
.render(t, &chunks[2]);
Gauge::default()
.block(Block::default().title("Gauge3").borders(border::ALL))
.style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic))
.percent(app.progress4)
.label(&format!("{}/100", app.progress2))
.render(t, &chunks[3]);
});
t.draw().unwrap();
}

188
examples/list.rs Normal file
View File

@ -0,0 +1,188 @@
extern crate tui;
extern crate termion;
use std::io;
use std::thread;
use std::time;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border, SelectableList, List};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
struct App<'a> {
items: Vec<&'a str>,
selected: usize,
events: Vec<(&'a str, &'a str)>,
info_style: Style,
warning_style: Style,
error_style: Style,
critical_style: Style,
}
impl<'a> App<'a> {
fn new() -> App<'a> {
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"],
selected: 0,
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")],
info_style: Style::default().fg(Color::White),
warning_style: Style::default().fg(Color::Yellow),
error_style: Style::default().fg(Color::Magenta),
critical_style: Style::default().fg(Color::Red),
}
}
fn advance(&mut self) {
let event = self.events.pop().unwrap();
self.events.insert(0, event);
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
let clock_tx = tx.clone();
// Input
thread::spawn(move || {
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') {
break;
}
}
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal, &app);
loop {
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
match input {
event::Key::Char('q') => {
break;
}
event::Key::Down => {
app.selected += 1;
if app.selected > app.items.len() - 1 {
app.selected = 0;
}
}
event::Key::Up => {
if app.selected > 0 {
app.selected -= 1;
} else {
app.selected = app.items.len() - 1;
}
}
_ => {}
}
}
Event::Tick => {
app.advance();
}
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &size, |t, chunks| {
SelectableList::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.items(&app.items)
.select(app.selected)
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(t, &chunks[0]);
List::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.items(&app.events
.iter()
.map(|&(evt, level)| {
(format!("{}: {}", level, evt),
match level {
"ERROR" => &app.error_style,
"CRITICAL" => &app.critical_style,
"WARNING" => &app.warning_style,
_ => &app.info_style,
})
})
.collect::<Vec<(String, &Style)>>())
.render(t, &chunks[1]);
});
t.draw().unwrap();
}

57
examples/paragraph.rs Normal file
View File

@ -0,0 +1,57 @@
extern crate tui;
extern crate termion;
use std::io;
use termion::event;
use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, Paragraph};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color};
fn main() {
let mut terminal = Terminal::new(TermionBackend::new().unwrap()).unwrap();
let stdin = io::stdin();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal);
for c in stdin.keys() {
draw(&mut terminal);
let evt = c.unwrap();
if evt == event::Key::Char('q') {
break;
}
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>) {
let size = t.size().unwrap();
Block::default()
.style(Style::default().bg(Color::White))
.render(t, &size);
Group::default()
.direction(Direction::Vertical)
.margin(5)
.sizes(&[Size::Percent(100)])
.render(t, &size, |t, chunks| {
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(100)])
.render(t, &chunks[0], |t, chunks| {
Paragraph::default()
.text("This is a line\n{fg=red This is a line}\n{bg=red This is a \
line}\n{mod=italic This is a line}\n{mod=bold This is a \
line}\n{mod=crossed_out This is a line}\n{mod=invert This is a \
line}\n{mod=underline This is a \
line}\n{bg=green;fg=yellow;mod=italic This is a line}\n")
.render(t, &chunks[0]);
});
});
t.draw().unwrap();
}

View File

@ -17,12 +17,9 @@ fn main() {
loop {
match terminal.backend().rustbox().poll_event(false) {
Ok(rustbox::Event::KeyEvent(key)) => {
match key {
Key::Char('q') => {
if key == Key::Char('q') {
break;
}
_ => {}
}
}
Err(e) => panic!("{}", e.description()),
_ => {}

143
examples/sparkline.rs Normal file
View File

@ -0,0 +1,143 @@
extern crate tui;
extern crate termion;
mod util;
use util::*;
use std::io;
use std::thread;
use std::time;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border, Sparkline};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color};
struct App {
signal: RandomSignal,
data1: Vec<u64>,
data2: Vec<u64>,
data3: Vec<u64>,
}
impl App {
fn new() -> App {
let mut signal = RandomSignal::new(0, 100);
let data1 = signal.by_ref().take(200).collect::<Vec<u64>>();
let data2 = signal.by_ref().take(200).collect::<Vec<u64>>();
let data3 = signal.by_ref().take(200).collect::<Vec<u64>>();
App {
signal: signal,
data1: data1,
data2: data2,
data3: data3,
}
}
fn advance(&mut self) {
let value = self.signal.next().unwrap();
self.data1.pop();
self.data1.insert(0, value);
let value = self.signal.next().unwrap();
self.data2.pop();
self.data2.insert(0, value);
let value = self.signal.next().unwrap();
self.data3.pop();
self.data3.insert(0, value);
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
let clock_tx = tx.clone();
// Input
thread::spawn(move || {
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') {
break;
}
}
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal, &app);
loop {
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
if input == event::Key::Char('q') {
break;
}
}
Event::Tick => {
app.advance();
}
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
Group::default()
.direction(Direction::Vertical)
.margin(2)
.sizes(&[Size::Fixed(3), Size::Fixed(3), Size::Fixed(7), Size::Min(0)])
.render(t, &size, |t, chunks| {
Sparkline::default()
.block(Block::default().title("Data1").borders(border::LEFT | border::RIGHT))
.data(&app.data1)
.style(Style::default().fg(Color::Yellow))
.render(t, &chunks[0]);
Sparkline::default()
.block(Block::default().title("Data2").borders(border::LEFT | border::RIGHT))
.data(&app.data2)
.style(Style::default().bg(Color::Green))
.render(t, &chunks[1]);
// Multiline
Sparkline::default()
.block(Block::default().title("Data3").borders(border::LEFT | border::RIGHT))
.data(&app.data3)
.style(Style::default().fg(Color::Red))
.render(t, &chunks[2]);
});
t.draw().unwrap();
}

94
examples/tabs.rs Normal file
View File

@ -0,0 +1,94 @@
extern crate tui;
extern crate termion;
mod util;
use util::*;
use std::io;
use termion::event;
use termion::input::TermRead;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border, Tabs};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color};
struct App<'a> {
tabs: MyTabs<'a>,
}
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// App
let mut app = App {
tabs: MyTabs {
titles: vec!["Tab0", "Tab1", "Tab2", "Tab3"],
selection: 0,
},
};
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal, &mut app);
// Main loop
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
match evt {
event::Key::Char('q') => {
break;
}
event::Key::Right => app.tabs.next(),
event::Key::Left => app.tabs.previous(),
_ => {}
}
draw(&mut terminal, &mut app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &mut App) {
let size = t.size().unwrap();
Block::default()
.style(Style::default().bg(Color::White))
.render(t, &size);
Group::default()
.direction(Direction::Vertical)
.margin(5)
.sizes(&[Size::Fixed(3), Size::Min(0)])
.render(t, &size, |t, chunks| {
Tabs::default()
.block(Block::default().borders(border::ALL).title("Tabs"))
.titles(&app.tabs.titles)
.select(app.tabs.selection)
.style(Style::default().fg(Color::Cyan))
.highlight_style(Style::default().fg(Color::Yellow))
.render(t, &chunks[0]);
match app.tabs.selection {
0 => {
Block::default().title("Inner 0").borders(border::ALL).render(t, &chunks[1]);
}
1 => {
Block::default().title("Inner 1").borders(border::ALL).render(t, &chunks[1]);
}
2 => {
Block::default().title("Inner 2").borders(border::ALL).render(t, &chunks[1]);
}
3 => {
Block::default().title("Inner 3").borders(border::ALL).render(t, &chunks[1]);
}
_ => {}
}
});
t.draw().unwrap();
}

95
examples/util/mod.rs Normal file
View File

@ -0,0 +1,95 @@
#![allow(dead_code)]
extern crate rand;
extern crate log4rs;
extern crate log;
use self::rand::distributions::{IndependentSample, Range};
use self::log::LogLevelFilter;
use self::log4rs::append::file::FileAppender;
use self::log4rs::encode::pattern::PatternEncoder;
use self::log4rs::config::{Appender, Config, Root};
pub fn setup_log(file_name: &str) {
let log = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{l} / {d(%H:%M:%S)} / \
{M}:{L}{n}{m}{n}{n}")))
.build(file_name)
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("log", Box::new(log)))
.build(Root::builder().appender("log").build(LogLevelFilter::Debug))
.unwrap();
log4rs::init_config(config).unwrap();
}
#[derive(Clone)]
pub struct RandomSignal {
range: Range<u64>,
rng: rand::ThreadRng,
}
impl RandomSignal {
pub fn new(lower: u64, upper: u64) -> RandomSignal {
RandomSignal {
range: Range::new(lower, upper),
rng: rand::thread_rng(),
}
}
}
impl Iterator for RandomSignal {
type Item = u64;
fn next(&mut self) -> Option<u64> {
Some(self.range.ind_sample(&mut self.rng))
}
}
#[derive(Clone)]
pub struct SinSignal {
x: f64,
interval: f64,
period: f64,
scale: f64,
}
impl SinSignal {
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
SinSignal {
x: 0.0,
interval: interval,
period: period,
scale: scale,
}
}
}
impl Iterator for SinSignal {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
let point = (self.x, (self.x * 1.0 / self.period).sin() * self.scale);
self.x += self.interval;
Some(point)
}
}
pub struct MyTabs<'a> {
pub titles: Vec<&'a str>,
pub selection: usize,
}
impl<'a> MyTabs<'a> {
pub fn next(&mut self) {
self.selection = (self.selection + 1) % self.titles.len();
}
pub fn previous(&mut self) {
if self.selection > 0 {
self.selection -= 1;
} else {
self.selection = self.titles.len() - 1;
}
}
}