Compare commits

..

18 Commits

Author SHA1 Message Date
Florian Dehau
56fc43400a Release v0.3.0-beta.3 2018-09-24 08:09:00 +02:00
Florian Dehau
7b4d35d224 feat: restore the cursor state on terminal drop 2018-09-24 08:03:52 +02:00
Florian Dehau
a99fc115f8 Release v0.3.0-beta.2 2018-09-23 21:16:32 +02:00
Florian Dehau
d8e5f57d53 style: fmt 2018-09-23 21:00:36 +02:00
Florian Dehau
aa85e597d9 fix(crossterm): fix goto coordinates 2018-09-23 21:00:18 +02:00
Florian Dehau
08ab92da80 refactor: clean examples
* Introduce a common event handler in order to focus on the drawing part
* Remove deprecated custom termion backends
2018-09-23 20:59:51 +02:00
Florian Dehau
5d52fd2486 refactor: remove custom termion backends 2018-09-23 20:55:50 +02:00
Florian Dehau
4ae9850e13 fix: replace links to assets 2018-09-09 08:55:51 +02:00
Florian Dehau
e14190ae4b fix: update crossterm example 2018-09-09 08:54:12 +02:00
Florian Dehau
ce445a8096 chore: remove scripts 2018-09-09 08:53:37 +02:00
Florian Dehau
dd71d6471c Release v0.3.0-beta.1 2018-09-08 09:23:22 +02:00
Antoine Büsch
f795173886 Unify Item and Text 2018-09-08 08:41:57 +02:00
Antoine Büsch
e42ab1fed8 Move Text to widgets/mod.rs 2018-09-08 08:41:57 +02:00
Antoine Büsch
0544c023f5 Rename Text::{Data -> Raw, StyledData -> Styled} 2018-09-08 08:41:57 +02:00
Antoine Büsch
ff47f9480b Introduce builder methods for Text to make it more ergonomic 2018-09-08 08:41:57 +02:00
Antoine Büsch
70561b7c54 Fix examples and doctests 2018-09-08 08:41:57 +02:00
Antoine Büsch
559c9c75f3 Make Text accept both borrowed and owned strings 2018-09-08 08:41:57 +02:00
Florian Dehau
6c69160d6b feat: remove unecessary borrows of Style 2018-09-07 22:24:52 +02:00
41 changed files with 1374 additions and 1417 deletions

View File

@@ -2,6 +2,46 @@
## To be released
## v0.3.0-beta.3 - 2018-09-24
### Changed
* `show_cursor` is called when `Terminal` is dropped if the cursor is hidden.
## v0.3.0-beta.2 - 2018-09-23
### Changed
* Remove custom `termion` backends. This is motivated by the fact that
`termion` structs are meant to be combined/wrapped to provide additional
functionalities to the terminal (e.g AlternateScreen, Mouse support, ...).
Thus providing exclusive types do not make a lot of sense and give a false
hint that additional features cannot be used together. The recommended
approach is now to create your own version of `stdout`:
```rust
let stdout = io::stdout().into_raw_mode()?;
let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
```
and then to create the corresponding `termion` backend:
```rust
let backend = TermionBackend::new(stdout);
```
The resulting code is more verbose but it works with all combinations of
additional `termion` features.
## v0.3.0-beta.1 - 2018-09-08
### Changed
* Replace `Item` by a generic and flexible `Text` that can be used in both
`Paragraph` and `List` widgets.
* Remove unecessary borrows on `Style`.
## v0.3.0-beta.0 - 2018-09-04
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "tui"
version = "0.3.0-beta.0"
version = "0.3.0-beta.3"
authors = ["Florian Dehau <work@fdehau.com>"]
description = """
A library to build rich terminal user interfaces or dashboards
@@ -8,7 +8,7 @@ A library to build rich terminal user interfaces or dashboards
keywords = ["tui", "terminal", "dashboard"]
repository = "https://github.com/fdehau/tui-rs"
license = "MIT"
exclude = ["docs/*", ".travis.yml"]
exclude = ["assets/*", ".travis.yml"]
autoexamples = true
[badges]
@@ -32,6 +32,7 @@ crossterm = { version = "0.4", optional = true }
[dev-dependencies]
stderrlog = "0.4"
rand = "0.4"
failure = "0.1"
[[example]]
name = "rustbox"

View File

@@ -21,7 +21,7 @@ endif
# ================================ Help =======================================
.PHONY: help
help: ## Print all the available commands
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
@@ -29,25 +29,29 @@ help: ## Print all the available commands
# =============================== Build =======================================
.PHONY: check
check: ## Validate the project code
$(CARGO) check
.PHONY: build
build: ## Build the project in debug mode
$(CARGO) build $(CARGO_FLAGS)
.PHONY: release
release: CARGO_FLAGS += --release
release: build ## Build the project in release mode
# ================================ Lint =======================================
RUSTFMT_WRITEMODE ?= 'diff'
.PHONY: lint
lint: fmt clippy ## Lint project files
.PHONY: fmt
fmt: ## Check the format of the source code
cargo fmt --all -- --check
.PHONY: clippy
clippy: RUST_CHANNEL = nightly
clippy: ## Check the style of the source code and catch common errors
$(CARGO) clippy --features="termion rustbox"
@@ -55,13 +59,27 @@ clippy: ## Check the style of the source code and catch common errors
# ================================ Test =======================================
.PHONY: test
test: ## Run the tests
$(CARGO) test
$(CARGO) test --features=termion,crossterm
# =============================== Examples ====================================
.PHONY: build-examples
build-examples: ## Build all examples
@$(CARGO) build --examples --features=termion,crossterm
.PHONY: run-examples
run-examples: ## Run all examples
@for file in examples/*.rs; do \
name=$$(basename $${file//.rs/}); \
$(CARGO) run --features=termion,crossterm --example $$name; \
done;
# ================================ Doc ========================================
.PHONY: doc
doc: ## Build the documentation (available at ./target/doc)
$(CARGO) doc
@@ -70,22 +88,28 @@ doc: ## Build the documentation (available at ./target/doc)
# Requires watchman and watchman-make (https://facebook.github.io/watchman/docs/install.html)
.PHONY: watch
watch: ## Watch file changes and build the project if any
watchman-make -p 'src/**/*.rs' -t check build
.PHONY: watch-test
watch-test: ## Watch files changes and run the tests if any
watchman-make -p 'src/**/*.rs' 'tests/**/*.rs' 'examples/**/*.rs' -t test
.PHONY: watch-doc
watch-doc: ## Watch file changes and rebuild the documentation if any
watchman-make -p 'src/**/*.rs' -t doc
# ================================= Pipelines =================================
.PHONY: stable
stable: RUST_CHANNEL = stable
stable: build test ## Run build and tests for stable
.PHONY: beta
beta: RUST_CHANNEL = beta
beta: build test ## Run build and tests for beta
.PHONY: nightly
nightly: RUST_CHANNEL = nightly
nightly: build lint test ## Run build, lint and tests for nightly

View File

@@ -4,7 +4,7 @@
[![Crate Status](https://img.shields.io/crates/v/tui.svg)](https://crates.io/crates/tui)
[![Docs Status](https://docs.rs/tui/badge.svg)](https://docs.rs/crate/tui/)
<img src="./docs/demo.gif" alt="Demo cast under Linux Termite with Inconsolata font 12pt">
<img src="./assets/demo.gif" alt="Demo cast under Linux Termite with Inconsolata font 12pt">
`tui-rs` is a [Rust](https://www.rust-lang.org) library to build rich terminal
user interfaces and dashboards. It is heavily inspired by the `Javascript`

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -1,20 +1,24 @@
extern crate failure;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::widgets::{BarChart, Block, Borders, Widget};
use tui::Terminal;
use util::event::{Event, Events};
struct App<'a> {
size: Rect,
data: Vec<(&'a str, u64)>,
@@ -53,112 +57,80 @@ impl<'a> App<'a> {
}
}
fn advance(&mut self) {
fn update(&mut self) {
let value = self.data.pop().unwrap();
self.data.insert(0, value);
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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()?;
// 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));
});
// Setup event handlers
let events = Events::new();
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
loop {
let size = terminal.size().unwrap();
let size = terminal.size()?;
if app.size != size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(app.size);
BarChart::default()
.block(Block::default().title("Data1").borders(Borders::ALL))
.data(&app.data)
.bar_width(9)
.style(Style::default().fg(Color::Yellow))
.value_style(Style::default().fg(Color::Black).bg(Color::Yellow))
.render(&mut f, chunks[0]);
{
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[1]);
BarChart::default()
.block(Block::default().title("Data2").borders(Borders::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(&mut f, chunks[0]);
BarChart::default()
.block(Block::default().title("Data3").borders(Borders::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(&mut f, chunks[1]);
}
})?;
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => if input == event::Key::Char('q') {
match events.next()? {
Event::Input(input) => if input == Key::Char('q') {
break;
},
Event::Tick => {
app.advance();
app.update();
}
}
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(app.size);
BarChart::default()
.block(Block::default().title("Data1").borders(Borders::ALL))
.data(&app.data)
.bar_width(9)
.style(Style::default().fg(Color::Yellow))
.value_style(Style::default().fg(Color::Black).bg(Color::Yellow))
.render(&mut f, chunks[0]);
{
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[1]);
BarChart::default()
.block(Block::default().title("Data2").borders(Borders::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(&mut f, chunks[0]);
BarChart::default()
.block(Block::default().title("Data3").borders(Borders::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(&mut f, chunks[1]);
}
})
Ok(())
}

View File

@@ -1,84 +1,109 @@
extern crate failure;
extern crate termion;
extern crate tui;
use std::io;
use termion::event;
use termion::input::TermRead;
#[allow(dead_code)]
mod util;
use tui::backend::MouseBackend;
use std::io;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Widget};
use tui::Terminal;
fn main() {
let mut terminal = Terminal::new(MouseBackend::new().unwrap()).unwrap();
let stdin = io::stdin();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
use util::event::{Event, Events};
let mut term_size = terminal.size().unwrap();
draw(&mut terminal, term_size).unwrap();
for c in stdin.keys() {
let size = terminal.size().unwrap();
if term_size != size {
terminal.resize(size).unwrap();
term_size = size;
}
draw(&mut terminal, term_size).unwrap();
let evt = c.unwrap();
if evt == event::Key::Char('q') {
break;
struct App {
size: Rect,
}
impl Default for App {
fn default() -> App {
App {
size: Rect::default(),
}
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, size: Rect) -> Result<(), io::Error> {
t.draw(|mut f| {
// 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(Borders::ALL).render(&mut f, size);
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(4)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(size);
{
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[0]);
Block::default()
.title("With background")
.title_style(Style::default().fg(Color::Yellow))
.style(Style::default().bg(Color::Green))
.render(&mut f, chunks[0]);
Block::default()
.title("Styled title")
.title_style(
Style::default()
.fg(Color::White)
.bg(Color::Red)
.modifier(Modifier::Bold),
)
.render(&mut f, chunks[1]);
fn main() -> Result<(), failure::Error> {
// 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)?;
terminal.hide_cursor()?;
// Create default app state
let mut app = App::default();
// Setup event handlers
let events = Events::new();
loop {
let size = terminal.size()?;
if app.size != size {
terminal.resize(size)?;
app.size = size;
}
{
terminal.draw(|mut f| {
// 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(Borders::ALL).render(&mut f, size);
let chunks = Layout::default()
.direction(Direction::Horizontal)
.direction(Direction::Vertical)
.margin(4)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[1]);
Block::default()
.title("With borders")
.borders(Borders::ALL)
.render(&mut f, chunks[0]);
Block::default()
.title("With styled borders")
.border_style(Style::default().fg(Color::Cyan))
.borders(Borders::LEFT | Borders::RIGHT)
.render(&mut f, chunks[1]);
.split(app.size);
{
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[0]);
Block::default()
.title("With background")
.title_style(Style::default().fg(Color::Yellow))
.style(Style::default().bg(Color::Green))
.render(&mut f, chunks[0]);
Block::default()
.title("Styled title")
.title_style(
Style::default()
.fg(Color::White)
.bg(Color::Red)
.modifier(Modifier::Bold),
).render(&mut f, chunks[1]);
}
{
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[1]);
Block::default()
.title("With borders")
.borders(Borders::ALL)
.render(&mut f, chunks[0]);
Block::default()
.title("With styled borders")
.border_style(Style::default().fg(Color::Cyan))
.borders(Borders::LEFT | Borders::RIGHT)
.render(&mut f, chunks[1]);
}
})?;
match events.next()? {
Event::Input(key) => if key == Key::Char('q') {
break;
},
_ => {}
}
})
}
Ok(())
}

View File

@@ -1,21 +1,26 @@
extern crate failure;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time;
use std::time::Duration;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::Color;
use tui::widgets::canvas::{Canvas, Line, Map, MapResolution};
use tui::widgets::{Block, Borders, Widget};
use tui::Terminal;
use util::event::{Config, Event, Events};
struct App {
size: Rect,
x: f64,
@@ -43,7 +48,7 @@ impl App {
}
}
fn advance(&mut self) {
fn update(&mut self) {
if self.ball.left() < self.playground.left() || self.ball.right() > self.playground.right()
{
self.dir_x = !self.dir_x;
@@ -67,138 +72,109 @@ impl App {
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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()?;
// 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));
});
// Setup event handlers
let config = Config {
tick_rate: Duration::from_millis(100),
..Default::default()
};
let events = Events::with_config(config);
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
loop {
let size = terminal.size().unwrap();
let size = terminal.size()?;
if size != app.size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(app.size);
Canvas::default()
.block(Block::default().borders(Borders::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(&mut f, chunks[0]);
Canvas::default()
.block(Block::default().borders(Borders::ALL).title("Pong"))
.paint(|ctx| {
ctx.draw(&Line {
x1: f64::from(app.ball.left()),
y1: f64::from(app.ball.top()),
x2: f64::from(app.ball.right()),
y2: f64::from(app.ball.top()),
color: Color::Yellow,
});
ctx.draw(&Line {
x1: f64::from(app.ball.right()),
y1: f64::from(app.ball.top()),
x2: f64::from(app.ball.right()),
y2: f64::from(app.ball.bottom()),
color: Color::Yellow,
});
ctx.draw(&Line {
x1: f64::from(app.ball.right()),
y1: f64::from(app.ball.bottom()),
x2: f64::from(app.ball.left()),
y2: f64::from(app.ball.bottom()),
color: Color::Yellow,
});
ctx.draw(&Line {
x1: f64::from(app.ball.left()),
y1: f64::from(app.ball.bottom()),
x2: f64::from(app.ball.left()),
y2: f64::from(app.ball.top()),
color: Color::Yellow,
});
}).x_bounds([10.0, 110.0])
.y_bounds([10.0, 110.0])
.render(&mut f, chunks[1]);
})?;
match events.next()? {
Event::Input(input) => match input {
event::Key::Char('q') => {
Key::Char('q') => {
break;
}
event::Key::Down => {
Key::Down => {
app.y += 1.0;
}
event::Key::Up => {
Key::Up => {
app.y -= 1.0;
}
event::Key::Right => {
Key::Right => {
app.x += 1.0;
}
event::Key::Left => {
Key::Left => {
app.x -= 1.0;
}
_ => {}
},
Event::Tick => {
app.advance();
app.update();
}
}
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(app.size);
Canvas::default()
.block(Block::default().borders(Borders::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(&mut f, chunks[0]);
Canvas::default()
.block(Block::default().borders(Borders::ALL).title("List"))
.paint(|ctx| {
ctx.draw(&Line {
x1: f64::from(app.ball.left()),
y1: f64::from(app.ball.top()),
x2: f64::from(app.ball.right()),
y2: f64::from(app.ball.top()),
color: Color::Yellow,
});
ctx.draw(&Line {
x1: f64::from(app.ball.right()),
y1: f64::from(app.ball.top()),
x2: f64::from(app.ball.right()),
y2: f64::from(app.ball.bottom()),
color: Color::Yellow,
});
ctx.draw(&Line {
x1: f64::from(app.ball.right()),
y1: f64::from(app.ball.bottom()),
x2: f64::from(app.ball.left()),
y2: f64::from(app.ball.bottom()),
color: Color::Yellow,
});
ctx.draw(&Line {
x1: f64::from(app.ball.left()),
y1: f64::from(app.ball.bottom()),
x2: f64::from(app.ball.left()),
y2: f64::from(app.ball.top()),
color: Color::Yellow,
});
})
.x_bounds([10.0, 110.0])
.y_bounds([10.0, 110.0])
.render(&mut f, chunks[1]);
})
Ok(())
}

View File

@@ -1,23 +1,25 @@
extern crate failure;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use util::*;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::Rect;
use tui::style::{Color, Modifier, Style};
use tui::widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Widget};
use tui::Terminal;
use util::event::{Event, Events};
use util::SinSignal;
struct App {
size: Rect,
signal1: SinSignal,
@@ -43,7 +45,7 @@ impl App {
}
}
fn advance(&mut self) {
fn update(&mut self) {
for _ in 0..5 {
self.data1.remove(0);
}
@@ -57,111 +59,75 @@ impl App {
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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()?;
// 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));
});
let events = Events::new();
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
loop {
let size = terminal.size().unwrap();
let size = terminal.size()?;
if app.size != size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => if input == event::Key::Char('q') {
terminal.draw(|mut f| {
Chart::default()
.block(
Block::default()
.title("Chart")
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold))
.borders(Borders::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(&mut f, app.size);
})?;
match events.next()? {
Event::Input(input) => if input == Key::Char('q') {
break;
},
Event::Tick => {
app.advance();
app.update();
}
}
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
Chart::default()
.block(
Block::default()
.title("Chart")
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold))
.borders(Borders::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(&mut f, app.size);
})
Ok(())
}

View File

@@ -1,50 +1,59 @@
extern crate crossterm;
extern crate failure;
extern crate tui;
use std::error::Error;
use std::io;
use tui::backend::CrosstermBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::layout::Rect;
use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Paragraph, Text, Widget};
use tui::Terminal;
fn main() {
let mut terminal = Terminal::new(CrosstermBackend::new()).unwrap();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal).unwrap();
loop {
{
let input = crossterm::input(terminal.backend().screen());
match input.read_char() {
Ok(c) => if c == 'q' {
break;
},
Err(e) => panic!("{}", e.description()),
};
}
draw(&mut terminal).unwrap();
}
terminal.show_cursor().unwrap();
struct App {
size: Rect,
}
fn draw(t: &mut Terminal<CrosstermBackend>) -> io::Result<()> {
let size = t.size()?;
t.draw(|mut f| {
let text = [
Text::Data("It "),
Text::StyledData("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);
})
impl Default for App {
fn default() -> App {
App {
size: Rect::default(),
}
}
}
fn main() -> Result<(), failure::Error> {
let mut terminal = Terminal::new(CrosstermBackend::new())?;
terminal.clear()?;
terminal.hide_cursor()?;
let mut app = App::default();
loop {
let size = terminal.size()?;
if app.size != size {
terminal.resize(size)?;
app.size = size;
}
terminal.draw(|mut f| {
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(terminal.backend().screen());
match input.read_char()? {
'q' => {
break;
}
_ => {}
};
}
Ok(())
}

View File

@@ -1,12 +1,37 @@
extern crate failure;
extern crate termion;
extern crate tui;
use tui::backend::MouseBackend;
#[allow(dead_code)]
mod util;
use std::io;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::buffer::Buffer;
use tui::layout::Rect;
use tui::style::Style;
use tui::widgets::Widget;
use tui::Terminal;
use util::event::{Event, Events};
struct App {
size: Rect,
}
impl Default for App {
fn default() -> App {
App {
size: Rect::default(),
}
}
}
struct Label<'a> {
text: &'a str,
}
@@ -19,7 +44,7 @@ impl<'a> Default for Label<'a> {
impl<'a> Widget for Label<'a> {
fn draw(&mut self, area: Rect, buf: &mut Buffer) {
buf.set_string(area.left(), area.top(), self.text, &Style::default());
buf.set_string(area.left(), area.top(), self.text, Style::default());
}
}
@@ -30,13 +55,36 @@ impl<'a> Label<'a> {
}
}
fn main() {
let mut terminal = Terminal::new(MouseBackend::new().unwrap()).unwrap();
let size = terminal.size().unwrap();
terminal.clear().unwrap();
terminal
.draw(|mut f| {
Label::default().text("Test").render(&mut f, size);
})
.unwrap();
fn main() -> Result<(), failure::Error> {
// 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();
let mut app = App::default();
loop {
let size = terminal.size()?;
if app.size != size {
terminal.resize(size)?;
app.size = size;
}
terminal.draw(|mut f| {
Label::default().text("Test").render(&mut f, app.size);
})?;
match events.next()? {
Event::Input(key) => if key == Key::Char('q') {
break;
},
_ => {}
}
}
Ok(())
}

View File

@@ -1,30 +1,32 @@
#[macro_use]
extern crate failure;
extern crate log;
extern crate stderrlog;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::{IntoRawMode, RawTerminal};
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::widgets::canvas::{Canvas, Line, Map, MapResolution};
use tui::widgets::{
Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, Item, List, Marker, Paragraph, Row,
Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, List, Marker, Paragraph, Row,
SelectableList, Sparkline, Table, Tabs, Text, Widget,
};
use tui::{Frame, Terminal};
use util::*;
use util::event::{Event, Events};
use util::{RandomSignal, SinSignal, TabsState};
type Backend = TermionBackend<AlternateScreen<MouseTerminal<RawTerminal<io::Stdout>>>>;
struct Server<'a> {
name: &'a str,
@@ -38,7 +40,7 @@ struct App<'a> {
items: Vec<&'a str>,
events: Vec<(&'a str, &'a str)>,
selected: usize,
tabs: MyTabs<'a>,
tabs: TabsState<'a>,
show_chart: bool,
progress: u16,
data: Vec<u64>,
@@ -51,18 +53,20 @@ struct App<'a> {
servers: Vec<Server<'a>>,
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
fn main() -> Result<(), failure::Error> {
stderrlog::new()
.module(module_path!())
.verbosity(4)
.init()
.unwrap();
info!("Start");
.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::new();
let mut rand_signal = RandomSignal::new(0, 100);
let mut sin_signal = SinSignal::new(0.2, 3.0, 18.0);
@@ -104,15 +108,12 @@ fn main() {
("Event26", "INFO"),
],
selected: 0,
tabs: MyTabs {
titles: vec!["Tab0", "Tab1"],
selection: 0,
},
tabs: TabsState::new(vec!["Tab0", "Tab1"]),
show_chart: true,
progress: 0,
data: rand_signal.clone().take(300).collect(),
data2: sin_signal.clone().take(100).collect(),
data3: sin_signal2.clone().take(200).collect(),
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),
@@ -169,68 +170,53 @@ fn main() {
},
],
};
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
for _ in 0..100 {
sin_signal.next();
}
for _ in 0..200 {
sin_signal2.next();
}
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;
}
}
});
thread::spawn(move || {
let tx = tx.clone();
loop {
tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(250));
}
});
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
loop {
let size = terminal.size().unwrap();
let size = terminal.size()?;
if size != app.size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
draw(&mut terminal, &app).unwrap();
let evt = rx.recv().unwrap();
match evt {
// Draw UI
terminal.draw(|mut f| {
let chunks = Layout::default()
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(app.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]),
_ => {}
};
})?;
match events.next()? {
Event::Input(input) => match input {
event::Key::Char('q') => {
Key::Char('q') => {
break;
}
event::Key::Up => {
Key::Up => {
if app.selected > 0 {
app.selected -= 1
};
}
event::Key::Down => if app.selected < app.items.len() - 1 {
Key::Down => if app.selected < app.items.len() - 1 {
app.selected += 1;
},
event::Key::Left => {
Key::Left => {
app.tabs.previous();
}
event::Key::Right => {
Key::Right => {
app.tabs.next();
}
event::Key::Char('t') => {
Key::Char('t') => {
app.show_chart = !app.show_chart;
}
_ => {}
@@ -263,50 +249,25 @@ fn main() {
}
}
}
terminal.show_cursor().unwrap();
terminal.clear().unwrap();
Ok(())
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(app.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.selection)
.render(&mut f, chunks[0]);
match app.tabs.selection {
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<MouseBackend>, app: &App, area: Rect) {
fn draw_first_tab(f: &mut Frame<Backend>, app: &App, area: Rect) {
let chunks = Layout::default()
.constraints(
[
Constraint::Length(7),
Constraint::Min(7),
Constraint::Length(7),
].as_ref(),
)
.split(area);
]
.as_ref(),
).split(area);
draw_gauges(f, app, chunks[0]);
draw_charts(f, app, chunks[1]);
draw_text(f, chunks[2]);
}
fn draw_gauges(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
fn draw_gauges(f: &mut Frame<Backend>, app: &App, area: Rect) {
let chunks = Layout::default()
.constraints([Constraint::Length(2), Constraint::Length(3)].as_ref())
.margin(1)
@@ -322,8 +283,7 @@ fn draw_gauges(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
.fg(Color::Magenta)
.bg(Color::Black)
.modifier(Modifier::Italic),
)
.label(&format!("{} / 100", app.progress))
).label(&format!("{} / 100", app.progress))
.percent(app.progress)
.render(f, chunks[0]);
Sparkline::default()
@@ -333,7 +293,7 @@ fn draw_gauges(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
.render(f, chunks[1]);
}
fn draw_charts(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
fn draw_charts(f: &mut Frame<Backend>, app: &App, area: Rect) {
let constraints = if app.show_chart {
vec![Constraint::Percentage(50), Constraint::Percentage(50)]
} else {
@@ -364,13 +324,13 @@ fn draw_charts(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
let error_style = Style::default().fg(Color::Magenta);
let critical_style = Style::default().fg(Color::Red);
let events = app.events.iter().map(|&(evt, level)| {
Item::StyledData(
Text::styled(
format!("{}: {}", level, evt),
match level {
"ERROR" => &error_style,
"CRITICAL" => &critical_style,
"WARNING" => &warning_style,
_ => &info_style,
"ERROR" => error_style,
"CRITICAL" => critical_style,
"WARNING" => warning_style,
_ => info_style,
},
)
});
@@ -388,8 +348,7 @@ fn draw_charts(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
.fg(Color::Black)
.bg(Color::Green)
.modifier(Modifier::Italic),
)
.label_style(Style::default().fg(Color::Yellow))
).label_style(Style::default().fg(Color::Yellow))
.style(Style::default().fg(Color::Green))
.render(f, chunks[1]);
}
@@ -400,8 +359,7 @@ fn draw_charts(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
.title("Chart")
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold))
.borders(Borders::ALL),
)
.x_axis(
).x_axis(
Axis::default()
.title("X Axis")
.style(Style::default().fg(Color::Gray))
@@ -412,16 +370,14 @@ fn draw_charts(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
&format!("{}", (app.window[0] + app.window[1]) / 2.0),
&format!("{}", app.window[1]),
]),
)
.y_axis(
).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(&[
).datasets(&[
Dataset::default()
.name("data2")
.marker(Marker::Dot)
@@ -432,28 +388,27 @@ fn draw_charts(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
.marker(Marker::Braille)
.style(Style::default().fg(Color::Yellow))
.data(&app.data3),
])
.render(f, chunks[1]);
]).render(f, chunks[1]);
}
}
fn draw_text(f: &mut Frame<MouseBackend>, area: Rect) {
fn draw_text(f: &mut Frame<Backend>, area: Rect) {
let text = [
Text::Data("This is a paragraph with several lines. You can change style your text the way you want.\n\nFox example: "),
Text::StyledData("under", Style::default().fg(Color::Red)),
Text::Data(" "),
Text::StyledData("the", Style::default().fg(Color::Green)),
Text::Data(" "),
Text::StyledData("rainbow", Style::default().fg(Color::Blue)),
Text::Data(".\nOh and if you didn't "),
Text::StyledData("notice", Style::default().modifier(Modifier::Italic)),
Text::Data(" you can "),
Text::StyledData("automatically", Style::default().modifier(Modifier::Bold)),
Text::Data(" "),
Text::StyledData("wrap", Style::default().modifier(Modifier::Invert)),
Text::Data(" your "),
Text::StyledData("text", Style::default().modifier(Modifier::Underline)),
Text::Data(".\nOne more thing is that it should display unicode characters: 10€")
Text::raw("This is a paragraph with several lines. You can change style your text the way you want.\n\nFox example: "),
Text::styled("under", Style::default().fg(Color::Red)),
Text::raw(" "),
Text::styled("the", Style::default().fg(Color::Green)),
Text::raw(" "),
Text::styled("rainbow", Style::default().fg(Color::Blue)),
Text::raw(".\nOh and if you didn't "),
Text::styled("notice", Style::default().modifier(Modifier::Italic)),
Text::raw(" you can "),
Text::styled("automatically", Style::default().modifier(Modifier::Bold)),
Text::raw(" "),
Text::styled("wrap", Style::default().modifier(Modifier::Invert)),
Text::raw(" your "),
Text::styled("text", Style::default().modifier(Modifier::Underline)),
Text::raw(".\nOne more thing is that it should display unicode characters: 10€")
];
Paragraph::new(text.iter())
.block(
@@ -461,29 +416,28 @@ fn draw_text(f: &mut Frame<MouseBackend>, area: Rect) {
.borders(Borders::ALL)
.title("Footer")
.title_style(Style::default().fg(Color::Magenta).modifier(Modifier::Bold)),
)
.wrap(true)
).wrap(true)
.render(f, area);
}
fn draw_second_tab(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
fn draw_second_tab(f: &mut Frame<Backend>, app: &App, area: Rect) {
let chunks = Layout::default()
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref())
.direction(Direction::Horizontal)
.split(area);
let up_style = Style::default().fg(Color::Green);
let failure_style = Style::default().fg(Color::Red);
Table::new(
["Server", "Location", "Status"].into_iter(),
app.servers.iter().map(|s| {
let style = if s.status == "Up" {
&up_style
} else {
&failure_style
};
Row::StyledData(vec![s.name, s.location, s.status].into_iter(), style)
}),
).block(Block::default().title("Servers").borders(Borders::ALL))
let header = ["Server", "Location", "Status"];
let rows = app.servers.iter().map(|s| {
let style = if s.status == "Up" {
up_style
} else {
failure_style
};
Row::StyledData(vec![s.name, s.location, s.status].into_iter(), style)
});
Table::new(header.into_iter(), rows)
.block(Block::default().title("Servers").borders(Borders::ALL))
.header_style(Style::default().fg(Color::Yellow))
.widths(&[15, 15, 10])
.render(f, chunks[0]);
@@ -515,8 +469,7 @@ fn draw_second_tab(f: &mut Frame<MouseBackend>, app: &App, area: Rect) {
};
ctx.print(server.coords.1, server.coords.0, "X", color);
}
})
.x_bounds([-180.0, 180.0])
}).x_bounds([-180.0, 180.0])
.y_bounds([-90.0, 90.0])
.render(f, chunks[1]);
}

View File

@@ -1,20 +1,24 @@
extern crate failure;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Gauge, Widget};
use tui::Terminal;
use util::event::{Event, Events};
struct App {
size: Rect,
progress1: u16,
@@ -34,7 +38,7 @@ impl App {
}
}
fn advance(&mut self) {
fn update(&mut self) {
self.progress1 += 5;
if self.progress1 > 100 {
self.progress1 = 0;
@@ -54,105 +58,72 @@ impl App {
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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()?;
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
let clock_tx = tx.clone();
let events = Events::new();
// 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();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
loop {
let size = terminal.size().unwrap();
let size = terminal.size()?;
if size != app.size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => if input == event::Key::Char('q') {
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
]
.as_ref(),
).split(app.size);
Gauge::default()
.block(Block::default().title("Gauge1").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow))
.percent(app.progress1)
.render(&mut f, chunks[0]);
Gauge::default()
.block(Block::default().title("Gauge2").borders(Borders::ALL))
.style(Style::default().fg(Color::Magenta).bg(Color::Green))
.percent(app.progress2)
.label(&format!("{}/100", app.progress2))
.render(&mut f, chunks[1]);
Gauge::default()
.block(Block::default().title("Gauge2").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow))
.percent(app.progress3)
.render(&mut f, chunks[2]);
Gauge::default()
.block(Block::default().title("Gauge3").borders(Borders::ALL))
.style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic))
.percent(app.progress4)
.label(&format!("{}/100", app.progress2))
.render(&mut f, chunks[3]);
})?;
match events.next()? {
Event::Input(input) => if input == Key::Char('q') {
break;
},
Event::Tick => {
app.advance();
app.update();
}
}
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
].as_ref(),
)
.split(app.size);
Gauge::default()
.block(Block::default().title("Gauge1").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow))
.percent(app.progress1)
.render(&mut f, chunks[0]);
Gauge::default()
.block(Block::default().title("Gauge2").borders(Borders::ALL))
.style(Style::default().fg(Color::Magenta).bg(Color::Green))
.percent(app.progress2)
.label(&format!("{}/100", app.progress2))
.render(&mut f, chunks[1]);
Gauge::default()
.block(Block::default().title("Gauge2").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow))
.percent(app.progress3)
.render(&mut f, chunks[2]);
Gauge::default()
.block(Block::default().title("Gauge3").borders(Borders::ALL))
.style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic))
.percent(app.progress4)
.label(&format!("{}/100", app.progress2))
.render(&mut f, chunks[3]);
})
Ok(())
}

View File

@@ -1,20 +1,25 @@
extern crate failure;
extern crate log;
extern crate stderrlog;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use std::io;
use std::sync::mpsc;
use std::thread;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::widgets::{Block, Borders, Widget};
use tui::Terminal;
use util::event::{Event, Events};
struct App {
size: Rect,
}
@@ -27,81 +32,58 @@ impl App {
}
}
enum Event {
Input(event::Key),
}
fn main() {
stderrlog::new().verbosity(4).init().unwrap();
fn main() -> Result<(), failure::Error> {
stderrlog::new().verbosity(4).init()?;
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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()?;
// Channels
let (tx, rx) = mpsc::channel();
let input_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;
}
}
});
let events = Events::new();
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
loop {
let size = terminal.size().unwrap();
let size = terminal.size()?;
if size != app.size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => if let event::Key::Char('q') = input {
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage(10),
Constraint::Percentage(80),
Constraint::Percentage(10),
]
.as_ref(),
).split(app.size);
Block::default()
.title("Block")
.borders(Borders::ALL)
.render(&mut f, chunks[0]);
Block::default()
.title("Block 2")
.borders(Borders::ALL)
.render(&mut f, chunks[2]);
})?;
match events.next()? {
Event::Input(input) => if let Key::Char('q') = input {
break;
},
_ => {}
}
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage(10),
Constraint::Percentage(80),
Constraint::Percentage(10),
].as_ref(),
)
.split(app.size);
Block::default()
.title("Block")
.borders(Borders::ALL)
.render(&mut f, chunks[0]);
Block::default()
.title("Block 2")
.borders(Borders::ALL)
.render(&mut f, chunks[2]);
})
Ok(())
}

View File

@@ -1,20 +1,24 @@
extern crate failure;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Corner, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Item, List, SelectableList, Widget};
use tui::widgets::{Block, Borders, List, SelectableList, Text, Widget};
use tui::Terminal;
use util::event::{Event, Events};
struct App<'a> {
size: Rect,
items: Vec<&'a str>,
@@ -77,65 +81,70 @@ impl<'a> App<'a> {
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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()?;
// 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));
});
let events = Events::new();
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
loop {
let size = terminal.size().unwrap();
let size = terminal.size()?;
if size != app.size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(app.size);
let style = Style::default().fg(Color::Black).bg(Color::White);
SelectableList::default()
.block(Block::default().borders(Borders::ALL).title("List"))
.items(&app.items)
.select(app.selected)
.style(style)
.highlight_style(style.fg(Color::LightGreen).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(&mut f, chunks[0]);
{
let events = app.events.iter().map(|&(evt, level)| {
Text::styled(
format!("{}: {}", level, evt),
match level {
"ERROR" => app.error_style,
"CRITICAL" => app.critical_style,
"WARNING" => app.warning_style,
_ => app.info_style,
},
)
});
List::new(events)
.block(Block::default().borders(Borders::ALL).title("List"))
.start_corner(Corner::BottomLeft)
.render(&mut f, chunks[1]);
}
})?;
match events.next()? {
Event::Input(input) => match input {
event::Key::Char('q') => {
Key::Char('q') => {
break;
}
event::Key::Left => {
Key::Left => {
app.selected = None;
}
event::Key::Down => {
Key::Down => {
app.selected = if let Some(selected) = app.selected {
if selected >= app.items.len() - 1 {
Some(0)
@@ -146,7 +155,7 @@ fn main() {
Some(0)
}
}
event::Key::Up => {
Key::Up => {
app.selected = if let Some(selected) = app.selected {
if selected > 0 {
Some(selected - 1)
@@ -163,45 +172,7 @@ fn main() {
app.advance();
}
}
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
terminal.clear().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(app.size);
let style = Style::default().fg(Color::Black).bg(Color::White);
SelectableList::default()
.block(Block::default().borders(Borders::ALL).title("List"))
.items(&app.items)
.select(app.selected)
.style(style)
.highlight_style(style.fg(Color::LightGreen).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(&mut f, chunks[0]);
{
let events = app.events.iter().map(|&(evt, level)| {
Item::StyledData(
format!("{}: {}", level, evt),
match level {
"ERROR" => &app.error_style,
"CRITICAL" => &app.critical_style,
"WARNING" => &app.warning_style,
_ => &app.info_style,
},
)
});
List::new(events)
.block(Block::default().borders(Borders::ALL).title("List"))
.start_corner(Corner::BottomLeft)
.render(&mut f, chunks[1]);
}
})
Ok(())
}

View File

@@ -1,83 +1,106 @@
extern crate failure;
extern crate termion;
extern crate tui;
use std::io;
use termion::event;
use termion::input::TermRead;
#[allow(dead_code)]
mod util;
use tui::backend::MouseBackend;
use std::io;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Paragraph, Text, Widget};
use tui::Terminal;
fn main() {
let mut terminal = Terminal::new(MouseBackend::new().unwrap()).unwrap();
let stdin = io::stdin();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
use util::event::{Event, Events};
let mut term_size = terminal.size().unwrap();
draw(&mut terminal, term_size).unwrap();
struct App {
size: Rect,
}
for c in stdin.keys() {
let size = terminal.size().unwrap();
if size != term_size {
terminal.resize(size).unwrap();
term_size = size;
}
draw(&mut terminal, term_size).unwrap();
let evt = c.unwrap();
if evt == event::Key::Char('q') {
break;
impl Default for App {
fn default() -> App {
App {
size: Rect::default(),
}
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, size: Rect) -> Result<(), io::Error> {
t.draw(|mut f| {
Block::default()
.style(Style::default().bg(Color::White))
.render(&mut f, size);
fn main() -> Result<(), failure::Error> {
// 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)?;
terminal.hide_cursor()?;
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(5)
.constraints(
[
Constraint::Percentage(30),
Constraint::Percentage(30),
Constraint::Percentage(30),
].as_ref(),
)
.split(size);
let events = Events::new();
let text = [
Text::Data("This a line\n"),
Text::StyledData("This a line\n", Style::default().fg(Color::Red)),
Text::StyledData("This a line\n", Style::default().bg(Color::Blue)),
Text::StyledData(
"This a longer line\n",
Style::default().modifier(Modifier::CrossedOut),
),
Text::StyledData(
"This a line\n",
Style::default().fg(Color::Green).modifier(Modifier::Italic),
),
];
let mut app = App::default();
Paragraph::new(text.iter())
.alignment(Alignment::Left)
.render(&mut f, chunks[0]);
Paragraph::new(text.iter())
.alignment(Alignment::Center)
.wrap(true)
.render(&mut f, chunks[1]);
Paragraph::new(text.iter())
.alignment(Alignment::Right)
.wrap(true)
.render(&mut f, chunks[2]);
})
loop {
let size = terminal.size()?;
if size != app.size {
terminal.resize(size)?;
app.size = size;
}
terminal.draw(|mut f| {
Block::default()
.style(Style::default().bg(Color::White))
.render(&mut f, size);
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(5)
.constraints(
[
Constraint::Percentage(30),
Constraint::Percentage(30),
Constraint::Percentage(30),
]
.as_ref(),
).split(size);
let text = [
Text::raw("This a line\n"),
Text::styled("This a line\n", Style::default().fg(Color::Red)),
Text::styled("This a line\n", Style::default().bg(Color::Blue)),
Text::styled(
"This a longer line\n",
Style::default().modifier(Modifier::CrossedOut),
),
Text::styled(
"This a line\n",
Style::default().fg(Color::Green).modifier(Modifier::Italic),
),
];
Paragraph::new(text.iter())
.alignment(Alignment::Left)
.render(&mut f, chunks[0]);
Paragraph::new(text.iter())
.alignment(Alignment::Center)
.wrap(true)
.render(&mut f, chunks[1]);
Paragraph::new(text.iter())
.alignment(Alignment::Right)
.wrap(true)
.render(&mut f, chunks[2]);
})?;
match events.next()? {
Event::Input(key) => if key == Key::Char('q') {
break;
},
_ => {}
}
}
Ok(())
}

View File

@@ -39,8 +39,7 @@ fn draw(t: &mut Terminal<RustboxBackend>) {
.title_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Magenta)),
)
.text("It {yellow works}!")
).text("It {yellow works}!")
.render(&mut f, &size);
}

View File

@@ -1,23 +1,25 @@
extern crate failure;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use util::*;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, Sparkline, Widget};
use tui::Terminal;
use util::event::{Event, Events};
use util::RandomSignal;
struct App {
size: Rect,
signal: RandomSignal,
@@ -41,7 +43,7 @@ impl App {
}
}
fn advance(&mut self) {
fn update(&mut self) {
let value = self.signal.next().unwrap();
self.data1.pop();
self.data1.insert(0, value);
@@ -54,111 +56,77 @@ impl App {
}
}
enum Event {
Input(event::Key),
Tick,
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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()?;
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
let clock_tx = tx.clone();
// Setup event handlers
let events = Events::new();
// 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
// Create default app state
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
loop {
let size = terminal.size().unwrap();
let size = terminal.size()?;
if size != app.size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => if input == event::Key::Char('q') {
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(7),
Constraint::Min(0),
]
.as_ref(),
).split(app.size);
Sparkline::default()
.block(
Block::default()
.title("Data1")
.borders(Borders::LEFT | Borders::RIGHT),
).data(&app.data1)
.style(Style::default().fg(Color::Yellow))
.render(&mut f, chunks[0]);
Sparkline::default()
.block(
Block::default()
.title("Data2")
.borders(Borders::LEFT | Borders::RIGHT),
).data(&app.data2)
.style(Style::default().bg(Color::Green))
.render(&mut f, chunks[1]);
// Multiline
Sparkline::default()
.block(
Block::default()
.title("Data3")
.borders(Borders::LEFT | Borders::RIGHT),
).data(&app.data3)
.style(Style::default().fg(Color::Red))
.render(&mut f, chunks[2]);
})?;
match events.next()? {
Event::Input(input) => if input == Key::Char('q') {
break;
},
Event::Tick => {
app.advance();
app.update();
}
}
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(7),
Constraint::Min(0),
].as_ref(),
)
.split(app.size);
Sparkline::default()
.block(
Block::default()
.title("Data1")
.borders(Borders::LEFT | Borders::RIGHT),
)
.data(&app.data1)
.style(Style::default().fg(Color::Yellow))
.render(&mut f, chunks[0]);
Sparkline::default()
.block(
Block::default()
.title("Data2")
.borders(Borders::LEFT | Borders::RIGHT),
)
.data(&app.data2)
.style(Style::default().bg(Color::Green))
.render(&mut f, chunks[1]);
// Multiline
Sparkline::default()
.block(
Block::default()
.title("Data3")
.borders(Borders::LEFT | Borders::RIGHT),
)
.data(&app.data3)
.style(Style::default().fg(Color::Red))
.render(&mut f, chunks[2]);
})
Ok(())
}

View File

@@ -1,17 +1,24 @@
extern crate failure;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use std::io;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Row, Table, Widget};
use tui::Terminal;
use util::event::{Event, Events};
struct App<'a> {
size: Rect,
items: Vec<Vec<&'a str>>,
@@ -35,74 +42,71 @@ impl<'a> App<'a> {
}
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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::new();
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
// Input
let stdin = io::stdin();
for c in stdin.keys() {
let size = terminal.size().unwrap();
loop {
let size = terminal.size()?;
if size != app.size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
let evt = c.unwrap();
match evt {
event::Key::Char('q') => {
break;
}
event::Key::Down => {
app.selected += 1;
if app.selected > app.items.len() - 1 {
app.selected = 0;
terminal.draw(|mut f| {
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold);
let normal_style = Style::default().fg(Color::White);
let header = ["Header1", "Header2", "Header3"];
let rows = app.items.iter().enumerate().map(|(i, item)| {
if i == app.selected {
Row::StyledData(item.into_iter(), selected_style)
} else {
Row::StyledData(item.into_iter(), normal_style)
}
}
event::Key::Up => if app.selected > 0 {
app.selected -= 1;
} else {
app.selected = app.items.len() - 1;
});
let rects = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.margin(5)
.split(app.size);
Table::new(header.into_iter(), rows)
.block(Block::default().borders(Borders::ALL).title("Table"))
.widths(&[10, 10, 10])
.render(&mut f, rects[0]);
})?;
match events.next()? {
Event::Input(key) => match key {
Key::Char('q') => {
break;
}
Key::Down => {
app.selected += 1;
if app.selected > app.items.len() - 1 {
app.selected = 0;
}
}
Key::Up => if app.selected > 0 {
app.selected -= 1;
} else {
app.selected = app.items.len() - 1;
},
_ => {}
},
_ => {}
};
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
terminal.clear().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold);
let normal_style = Style::default().fg(Color::White);
let header = ["Header1", "Header2", "Header3"];
let rows = app.items.iter().enumerate().map(|(i, item)| {
if i == app.selected {
Row::StyledData(item.into_iter(), &selected_style)
} else {
Row::StyledData(item.into_iter(), &normal_style)
}
});
let rects = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.margin(5)
.split(app.size);
Table::new(header.into_iter(), rows)
.block(Block::default().borders(Borders::ALL).title("Table"))
.widths(&[10, 10, 10])
.render(&mut f, rects[0]);
})
Ok(())
}

View File

@@ -1,112 +1,104 @@
extern crate failure;
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use util::*;
use std::io;
use termion::event;
use termion::input::TermRead;
use tui::backend::MouseBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, Tabs, Widget};
use tui::Terminal;
use util::event::{Event, Events};
use util::TabsState;
struct App<'a> {
size: Rect,
tabs: MyTabs<'a>,
tabs: TabsState<'a>,
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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::new();
// App
let mut app = App {
size: Rect::default(),
tabs: MyTabs {
titles: vec!["Tab0", "Tab1", "Tab2", "Tab3"],
selection: 0,
},
tabs: TabsState::new(vec!["Tab0", "Tab1", "Tab2", "Tab3"]),
};
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &mut app).unwrap();
// Main loop
let stdin = io::stdin();
for c in stdin.keys() {
let size = terminal.size().unwrap();
loop {
let size = terminal.size()?;
if size != app.size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
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).unwrap();
}
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(5)
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(app.size);
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(5)
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(app.size);
Block::default()
.style(Style::default().bg(Color::White))
.render(&mut f, app.size);
Tabs::default()
.block(Block::default().borders(Borders::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(&mut f, chunks[0]);
match app.tabs.selection {
0 => {
Block::default()
Block::default()
.style(Style::default().bg(Color::White))
.render(&mut f, app.size);
Tabs::default()
.block(Block::default().borders(Borders::ALL).title("Tabs"))
.titles(&app.tabs.titles)
.select(app.tabs.index)
.style(Style::default().fg(Color::Cyan))
.highlight_style(Style::default().fg(Color::Yellow))
.render(&mut f, chunks[0]);
match app.tabs.index {
0 => Block::default()
.title("Inner 0")
.borders(Borders::ALL)
.render(&mut f, chunks[1]);
}
1 => {
Block::default()
.render(&mut f, chunks[1]),
1 => Block::default()
.title("Inner 1")
.borders(Borders::ALL)
.render(&mut f, chunks[1]);
}
2 => {
Block::default()
.render(&mut f, chunks[1]),
2 => Block::default()
.title("Inner 2")
.borders(Borders::ALL)
.render(&mut f, chunks[1]);
}
3 => {
Block::default()
.render(&mut f, chunks[1]),
3 => Block::default()
.title("Inner 3")
.borders(Borders::ALL)
.render(&mut f, chunks[1]);
.render(&mut f, chunks[1]),
_ => {}
}
})?;
match events.next()? {
Event::Input(input) => match input {
Key::Char('q') => {
break;
}
Key::Right => app.tabs.next(),
Key::Left => app.tabs.previous(),
_ => {}
},
_ => {}
}
})
}
Ok(())
}

View File

@@ -1,3 +1,4 @@
extern crate failure;
/// A simple example demonstrating how to handle user input. This is
/// a bit out of the scope of the library as it does not provide any
/// input handling out of the box. However, it may helps some to get
@@ -12,27 +13,35 @@
extern crate termion;
extern crate tui;
#[allow(dead_code)]
mod util;
use std::io;
use std::sync::mpsc;
use std::thread;
use termion::event;
use termion::input::TermRead;
use tui::backend::AlternateScreenBackend;
use termion::event::Key;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, Item, List, Paragraph, Text, Widget};
use tui::widgets::{Block, Borders, List, Paragraph, Text, Widget};
use tui::Terminal;
use util::event::{Event, Events};
/// App holds the state of the application
struct App {
/// Current size of the terminal
size: Rect,
/// Current value of the input box
input: String,
/// History of recorded messages
messages: Vec<String>,
}
impl App {
fn new() -> App {
impl Default for App {
fn default() -> App {
App {
size: Rect::default(),
input: String::new(),
@@ -41,89 +50,69 @@ impl App {
}
}
enum Event {
Input(event::Key),
}
fn main() {
fn main() -> Result<(), failure::Error> {
// Terminal initialization
let backend = AlternateScreenBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
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()?;
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
// Setup event handlers
let events = Events::new();
// 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;
}
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app).unwrap();
// Create default app state
let mut app = App::default();
loop {
let size = terminal.size().unwrap();
// Handle resize
let size = terminal.size()?;
if app.size != size {
terminal.resize(size).unwrap();
terminal.resize(size)?;
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
// Draw UI
terminal.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints([Constraint::Length(3), Constraint::Min(1)].as_ref())
.split(app.size);
Paragraph::new([Text::raw(&app.input)].iter())
.style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("Input"))
.render(&mut f, chunks[0]);
let messages = app
.messages
.iter()
.enumerate()
.map(|(i, m)| Text::raw(format!("{}: {}", i, m)));
List::new(messages)
.block(Block::default().borders(Borders::ALL).title("Messages"))
.render(&mut f, chunks[1]);
})?;
// Handle input
match events.next()? {
Event::Input(input) => match input {
event::Key::Char('q') => {
Key::Char('q') => {
break;
}
event::Key::Char('\n') => {
Key::Char('\n') => {
app.messages.push(app.input.drain(..).collect());
}
event::Key::Char(c) => {
Key::Char(c) => {
app.input.push(c);
}
event::Key::Backspace => {
Key::Backspace => {
app.input.pop();
}
_ => {}
},
_ => {}
}
draw(&mut terminal, &app).unwrap();
}
terminal.show_cursor().unwrap();
terminal.clear().unwrap();
}
fn draw(t: &mut Terminal<AlternateScreenBackend>, app: &App) -> Result<(), io::Error> {
t.draw(|mut f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints([Constraint::Length(3), Constraint::Min(1)].as_ref())
.split(app.size);
Paragraph::new([Text::Data(&app.input)].iter())
.style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("Input"))
.render(&mut f, chunks[0]);
List::new(
app.messages
.iter()
.enumerate()
.map(|(i, m)| Item::Data(format!("{}: {}", i, m))),
).block(Block::default().borders(Borders::ALL).title("Messages"))
.render(&mut f, chunks[1]);
})
Ok(())
}

83
examples/util/event.rs Normal file
View File

@@ -0,0 +1,83 @@
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use termion::event::Key;
use termion::input::TermRead;
pub enum Event<I> {
Input(I),
Tick,
}
/// An small event handler that wrap termion input and tick events. Each event
/// type is handled in its own thread and returned to a common `Receiver`
pub struct Events {
rx: mpsc::Receiver<Event<Key>>,
input_handle: thread::JoinHandle<()>,
tick_handle: thread::JoinHandle<()>,
}
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub exit_key: Key,
pub tick_rate: Duration,
}
impl Default for Config {
fn default() -> Config {
Config {
exit_key: Key::Char('q'),
tick_rate: Duration::from_millis(250),
}
}
}
impl Events {
pub fn new() -> Events {
Events::with_config(Config::default())
}
pub fn with_config(config: Config) -> Events {
let (tx, rx) = mpsc::channel();
let input_handle = {
let tx = tx.clone();
thread::spawn(move || {
let stdin = io::stdin();
for evt in stdin.keys() {
match evt {
Ok(key) => {
if let Err(_) = tx.send(Event::Input(key)) {
return;
}
if key == config.exit_key {
return;
}
}
Err(_) => {}
}
}
})
};
let tick_handle = {
let tx = tx.clone();
thread::spawn(move || {
let tx = tx.clone();
loop {
tx.send(Event::Tick).unwrap();
thread::sleep(config.tick_rate);
}
})
};
Events {
rx,
input_handle,
tick_handle,
}
}
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
self.rx.recv()
}
}

View File

@@ -1,7 +1,7 @@
#![allow(dead_code)]
extern crate rand;
pub mod event;
use self::rand::distributions::{IndependentSample, Range};
#[derive(Clone)]
@@ -54,21 +54,24 @@ impl Iterator for SinSignal {
}
}
pub struct MyTabs<'a> {
pub struct TabsState<'a> {
pub titles: Vec<&'a str>,
pub selection: usize,
pub index: usize,
}
impl<'a> MyTabs<'a> {
impl<'a> TabsState<'a> {
pub fn new(titles: Vec<&'a str>) -> TabsState {
TabsState { titles, index: 0 }
}
pub fn next(&mut self) {
self.selection = (self.selection + 1) % self.titles.len();
self.index = (self.index + 1) % self.titles.len();
}
pub fn previous(&mut self) {
if self.selection > 0 {
self.selection -= 1;
if self.index > 0 {
self.index -= 1;
} else {
self.selection = self.titles.len() - 1;
self.index = self.titles.len() - 1;
}
}
}

View File

@@ -1,15 +0,0 @@
#!/bin/bash
# Build all examples in examples directory
set -u -o pipefail
for file in examples/*.rs; do
name=$(basename ${file//.rs/})
echo "[EXAMPLE] $name"
if [[ "$name" == "rustbox" ]]; then
cargo build --features rustbox --example "$name"
else
cargo build --example "$name"
fi
done

View File

@@ -1,14 +0,0 @@
#!/bin/bash
# Run all examples in examples directory
set -u -o pipefail
for file in examples/*.rs; do
name=$(basename ${file//.rs/})
if [[ "$name" == "rustbox" ]]; then
cargo run --features rustbox --example "$name"
else
cargo run --example "$name"
fi
done

View File

@@ -66,7 +66,7 @@ impl Backend for CrosstermBackend {
let mut last_x = 0;
for (x, y, cell) in content {
if y != last_y || x != last_x + 1 {
cursor.goto(x + 1, y + 1);
cursor.goto(x, y);
}
last_x = x;
last_y = y;

View File

@@ -11,7 +11,7 @@ pub use self::rustbox::RustboxBackend;
#[cfg(feature = "termion")]
mod termion;
#[cfg(feature = "termion")]
pub use self::termion::{AlternateScreenBackend, MouseBackend, RawBackend, TermionBackend};
pub use self::termion::TermionBackend;
#[cfg(feature = "crossterm")]
mod crossterm;

View File

@@ -3,8 +3,6 @@ extern crate termion;
use std::io;
use std::io::Write;
use self::termion::raw::IntoRawMode;
use super::Backend;
use buffer::Cell;
use layout::Rect;
@@ -17,40 +15,11 @@ where
stdout: W,
}
pub type RawBackend = TermionBackend<termion::raw::RawTerminal<io::Stdout>>;
pub type MouseBackend =
TermionBackend<termion::input::MouseTerminal<termion::raw::RawTerminal<io::Stdout>>>;
pub type AlternateScreenBackend =
TermionBackend<termion::screen::AlternateScreen<termion::raw::RawTerminal<io::Stdout>>>;
impl RawBackend {
pub fn new() -> Result<RawBackend, io::Error> {
let raw = io::stdout().into_raw_mode()?;
Ok(TermionBackend::with_stdout(raw))
}
}
impl MouseBackend {
pub fn new() -> Result<MouseBackend, io::Error> {
let raw = io::stdout().into_raw_mode()?;
let mouse = termion::input::MouseTerminal::from(raw);
Ok(TermionBackend::with_stdout(mouse))
}
}
impl AlternateScreenBackend {
pub fn new() -> Result<AlternateScreenBackend, io::Error> {
let raw = io::stdout().into_raw_mode()?;
let screen = termion::screen::AlternateScreen::from(raw);
Ok(TermionBackend::with_stdout(screen))
}
}
impl<W> TermionBackend<W>
where
W: Write,
{
pub fn with_stdout(stdout: W) -> TermionBackend<W> {
pub fn new(stdout: W) -> TermionBackend<W> {
TermionBackend { stdout }
}
}

View File

@@ -81,7 +81,7 @@ impl Default for Cell {
/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
/// buf.get_mut(0, 2).set_symbol("x");
/// assert_eq!(buf.get(0, 2).symbol, "x");
/// buf.set_string(3, 0, "string", &Style::default().fg(Color::Red).bg(Color::White));
/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
/// assert_eq!(buf.get(5, 0), &Cell{
/// symbol: String::from("r"),
/// style: Style {
@@ -233,20 +233,26 @@ impl Buffer {
}
/// Print a string, starting at the position (x, y)
pub fn set_string(&mut self, x: u16, y: u16, string: &str, style: &Style) {
pub fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style)
where
S: AsRef<str>,
{
self.set_stringn(x, y, string, usize::MAX, style);
}
/// Print at most the first n characters of a string if enough space is available
/// until the end of the line
pub fn set_stringn(&mut self, x: u16, y: u16, string: &str, limit: usize, style: &Style) {
pub fn set_stringn<S>(&mut self, x: u16, y: u16, string: S, limit: usize, style: Style)
where
S: AsRef<str>,
{
let mut index = self.index_of(x, y);
let graphemes = UnicodeSegmentation::graphemes(string, true).collect::<Vec<&str>>();
let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
let max_index = min((self.area.right() - x) as usize, limit);
for s in graphemes.into_iter().take(max_index) {
for s in graphemes.take(max_index) {
self.content[index].symbol.clear();
self.content[index].symbol.push_str(s);
self.content[index].style = *style;
self.content[index].style = style;
index += 1;
}
}

View File

@@ -1,7 +1,7 @@
//! [tui](https://github.com/fdehau/tui-rs) is a library used to build rich
//! terminal users interfaces and dashboards.
//!
//! ![](https://raw.githubusercontent.com/fdehau/tui-rs/master/docs/demo.gif)
//! ![](https://raw.githubusercontent.com/fdehau/tui-rs/master/assets/demo.gif)
//!
//! # Get started
//!
@@ -14,13 +14,18 @@
//!
//! ```rust,no_run
//! extern crate tui;
//! extern crate termion;
//!
//! use std::io;
//! use tui::Terminal;
//! use tui::backend::RawBackend;
//! use tui::backend::TermionBackend;
//! use termion::raw::IntoRawMode;
//!
//! fn main() {
//! let backend = RawBackend::new().unwrap();
//! let mut terminal = Terminal::new(backend).unwrap();
//! fn main() -> Result<(), io::Error> {
//! let stdout = io::stdout().into_raw_mode()?;
//! let backend = TermionBackend::new(stdout);
//! let mut terminal = Terminal::new(backend)?;
//! Ok(())
//! }
//! ```
//!
@@ -42,9 +47,10 @@
//! use tui::Terminal;
//! use tui::backend::RustboxBackend;
//!
//! fn main() {
//! let backend = RustboxBackend::new().unwrap();
//! let mut terminal = Terminal::new(backend).unwrap();
//! fn main() -> Result<(), io::Error> {
//! let backend = RustboxBackend::new()?;
//! let mut terminal = Terminal::new(backend);
//! Ok(())
//! }
//! ```
//!
@@ -63,28 +69,21 @@
//!
//! ```rust,no_run
//! extern crate tui;
//! extern crate termion;
//!
//! use std::io;
//!
//! use termion::raw::IntoRawMode;
//! use tui::Terminal;
//! use tui::backend::RawBackend;
//! use tui::backend::TermionBackend;
//! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Layout, Constraint, Direction};
//!
//! fn main() {
//! let mut terminal = init().expect("Failed initialization");
//! draw(&mut terminal).expect("Failed to draw");
//! }
//!
//! fn init() -> Result<Terminal<RawBackend>, io::Error> {
//! let backend = RawBackend::new()?;
//! Terminal::new(backend)
//! }
//!
//! fn draw(t: &mut Terminal<RawBackend>) -> Result<(), io::Error> {
//!
//! let size = t.size()?;
//! t.draw(|mut f| {
//! fn main() -> Result<(), io::Error> {
//! let stdout = io::stdout().into_raw_mode()?;
//! let backend = TermionBackend::new(stdout);
//! let mut terminal = Terminal::new(backend)?;
//! let size = terminal.size()?;
//! terminal.draw(|mut f| {
//! Block::default()
//! .title("Block")
//! .borders(Borders::ALL)
@@ -101,28 +100,21 @@
//!
//! ```rust,no_run
//! extern crate tui;
//! extern crate termion;
//!
//! use std::io;
//!
//! use termion::raw::IntoRawMode;
//! use tui::Terminal;
//! use tui::backend::RawBackend;
//! use tui::backend::TermionBackend;
//! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Layout, Constraint, Direction};
//!
//! fn main() {
//! let mut terminal = init().expect("Failed initialization");
//! draw(&mut terminal).expect("Failed to draw");
//! }
//!
//! fn init() -> Result<Terminal<RawBackend>, io::Error> {
//! let backend = RawBackend::new()?;
//! Terminal::new(backend)
//! }
//!
//! fn draw(t: &mut Terminal<RawBackend>) -> Result<(), io::Error> {
//!
//! let size = t.size()?;
//! t.draw(|mut f| {
//! fn main() -> Result<(), io::Error> {
//! let stdout = io::stdout().into_raw_mode()?;
//! let backend = TermionBackend::new(stdout);
//! let mut terminal = Terminal::new(backend)?;
//! let size = terminal.size()?;
//! terminal.draw(|mut f| {
//! let chunks = Layout::default()
//! .direction(Direction::Vertical)
//! .margin(1)

View File

@@ -17,6 +17,8 @@ where
buffers: [Buffer; 2],
/// Index of the current buffer in the previous array
current: usize,
/// Whether the cursor is currently hidden
hidden_cursor: bool,
}
pub struct Frame<'a, B: 'a>
@@ -39,18 +41,33 @@ where
}
}
impl<B> Drop for Terminal<B>
where
B: Backend,
{
fn drop(&mut self) {
// Attempt to restore the cursor state
if self.hidden_cursor {
if let Err(_) = self.show_cursor() {
error!("Failed to show the cursor");
}
}
}
}
impl<B> Terminal<B>
where
B: Backend,
{
/// Wrapper around Termion initialization. Each buffer is initialized with a blank string and
/// default colors for the foreground and the background
pub fn new(backend: B) -> Result<Terminal<B>, io::Error> {
pub fn new(backend: B) -> io::Result<Terminal<B>> {
let size = backend.size()?;
Ok(Terminal {
backend,
buffers: [Buffer::empty(size), Buffer::empty(size)],
current: 0,
hidden_cursor: false,
})
}
@@ -72,7 +89,7 @@ where
/// Builds a string representing the minimal escape sequences and characters set necessary to
/// update the UI and writes it to stdout.
pub fn flush(&mut self) -> Result<(), io::Error> {
pub fn flush(&mut self) -> io::Result<()> {
let width = self.buffers[self.current].area.width;
let content = self.buffers[self.current]
.content
@@ -94,7 +111,7 @@ where
/// Updates the interface so that internal buffers matches the current size of the terminal.
/// This leads to a full redraw of the screen.
pub fn resize(&mut self, area: Rect) -> Result<(), io::Error> {
pub fn resize(&mut self, area: Rect) -> io::Result<()> {
self.buffers[self.current].resize(area);
self.buffers[1 - self.current].reset();
self.buffers[1 - self.current].resize(area);
@@ -102,7 +119,7 @@ where
}
/// Flushes the current internal state and prepares the interface for the next draw call
pub fn draw<F>(&mut self, f: F) -> Result<(), io::Error>
pub fn draw<F>(&mut self, f: F) -> io::Result<()>
where
F: FnOnce(Frame<B>),
{
@@ -120,16 +137,20 @@ where
Ok(())
}
pub fn hide_cursor(&mut self) -> Result<(), io::Error> {
self.backend.hide_cursor()
pub fn hide_cursor(&mut self) -> io::Result<()> {
self.backend.hide_cursor()?;
self.hidden_cursor = true;
Ok(())
}
pub fn show_cursor(&mut self) -> Result<(), io::Error> {
self.backend.show_cursor()
pub fn show_cursor(&mut self) -> io::Result<()> {
self.backend.show_cursor()?;
self.hidden_cursor = false;
Ok(())
}
pub fn clear(&mut self) -> Result<(), io::Error> {
pub fn clear(&mut self) -> io::Result<()> {
self.backend.clear()
}
pub fn size(&self) -> Result<Rect, io::Error> {
pub fn size(&self) -> io::Result<Rect> {
self.backend.size()
}
}

View File

@@ -155,7 +155,7 @@ impl<'a> Widget for BarChart<'a> {
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x,
chart_area.top() + j,
).set_symbol(symbol)
.set_style(self.style);
.set_style(self.style);
}
if d.1 > 8 {
@@ -177,7 +177,7 @@ impl<'a> Widget for BarChart<'a> {
+ (self.bar_width - width) / 2,
chart_area.bottom() - 2,
value_label,
&self.value_style,
self.value_style,
);
}
}
@@ -186,7 +186,7 @@ impl<'a> Widget for BarChart<'a> {
chart_area.bottom() - 1,
label,
self.bar_width as usize,
&self.label_style,
self.label_style,
);
}
}

View File

@@ -178,7 +178,7 @@ impl<'a> Widget for Block<'a> {
area.top(),
title,
width as usize,
&self.title_style,
self.title_style,
);
}
}

View File

@@ -285,7 +285,7 @@ where
dx + canvas_area.left(),
dy + canvas_area.top(),
label.text,
&style.fg(label.color),
style.fg(label.color),
);
}
}

View File

@@ -361,12 +361,12 @@ where
if let Some((x, y)) = layout.title_x {
let title = self.x_axis.title.unwrap();
buf.set_string(x, y, title, &self.x_axis.style);
buf.set_string(x, y, title, self.x_axis.style);
}
if let Some((x, y)) = layout.title_y {
let title = self.y_axis.title.unwrap();
buf.set_string(x, y, title, &self.y_axis.style);
buf.set_string(x, y, title, self.y_axis.style);
}
if let Some(y) = layout.label_x {
@@ -380,7 +380,7 @@ where
- label.as_ref().width() as u16,
y,
label.as_ref(),
&self.x_axis.labels_style,
self.x_axis.labels_style,
);
}
}
@@ -396,7 +396,7 @@ where
x,
graph_area.bottom() - 1 - dy,
label.as_ref(),
&self.y_axis.labels_style,
self.y_axis.labels_style,
);
}
}
@@ -456,8 +456,7 @@ where
coords: dataset.data,
color: dataset.style.fg,
});
})
.draw(graph_area, buf);
}).draw(graph_area, buf);
}
}
}
@@ -471,7 +470,7 @@ where
legend_area.x + 1,
legend_area.y + 1 + i as u16,
dataset.name,
&dataset.style,
dataset.style,
);
}
}

View File

@@ -92,7 +92,7 @@ impl<'a> Widget for Gauge<'a> {
let label = self.label.unwrap_or(&precent_label);
let label_width = label.width() as u16;
let middle = (gauge_area.width - label_width) / 2 + gauge_area.left();
buf.set_string(middle, y, label, &self.style);
buf.set_string(middle, y, label, self.style);
}
// Fix colors

View File

@@ -1,4 +1,3 @@
use std::fmt::Display;
use std::iter;
use std::iter::Iterator;
@@ -7,16 +6,11 @@ use unicode_width::UnicodeWidthStr;
use buffer::Buffer;
use layout::{Corner, Rect};
use style::Style;
use widgets::{Block, Widget};
use widgets::{Block, Text, Widget};
pub enum Item<'i, D: 'i> {
Data(D),
StyledData(D, &'i Style),
}
pub struct List<'b, 'i, L, D: 'i>
pub struct List<'b, L>
where
L: Iterator<Item = Item<'i, D>>,
L: Iterator<Item = Text<'b>>,
{
block: Option<Block<'b>>,
items: L,
@@ -24,11 +18,11 @@ where
start_corner: Corner,
}
impl<'b, 'i, L, D> Default for List<'b, 'i, L, D>
impl<'b, L> Default for List<'b, L>
where
L: Iterator<Item = Item<'i, D>> + Default,
L: Iterator<Item = Text<'b>> + Default,
{
fn default() -> List<'b, 'i, L, D> {
fn default() -> List<'b, L> {
List {
block: None,
items: L::default(),
@@ -38,11 +32,11 @@ where
}
}
impl<'b, 'i, L, D> List<'b, 'i, L, D>
impl<'b, L> List<'b, L>
where
L: Iterator<Item = Item<'i, D>>,
L: Iterator<Item = Text<'b>>,
{
pub fn new(items: L) -> List<'b, 'i, L, D> {
pub fn new(items: L) -> List<'b, L> {
List {
block: None,
items,
@@ -51,34 +45,33 @@ where
}
}
pub fn block(mut self, block: Block<'b>) -> List<'b, 'i, L, D> {
pub fn block(mut self, block: Block<'b>) -> List<'b, L> {
self.block = Some(block);
self
}
pub fn items<I>(mut self, items: I) -> List<'b, 'i, L, D>
pub fn items<I>(mut self, items: I) -> List<'b, L>
where
I: IntoIterator<Item = Item<'i, D>, IntoIter = L>,
I: IntoIterator<Item = Text<'b>, IntoIter = L>,
{
self.items = items.into_iter();
self
}
pub fn style(mut self, style: Style) -> List<'b, 'i, L, D> {
pub fn style(mut self, style: Style) -> List<'b, L> {
self.style = style;
self
}
pub fn start_corner(mut self, corner: Corner) -> List<'b, 'i, L, D> {
pub fn start_corner(mut self, corner: Corner) -> List<'b, L> {
self.start_corner = corner;
self
}
}
impl<'b, 'i, L, D> Widget for List<'b, 'i, L, D>
impl<'b, L> Widget for List<'b, L>
where
L: Iterator<Item = Item<'i, D>>,
D: Display,
L: Iterator<Item = Text<'b>>,
{
fn draw(&mut self, area: Rect, buf: &mut Buffer) {
let list_area = match self.block {
@@ -108,17 +101,11 @@ where
_ => (list_area.left(), list_area.top() + i as u16),
};
match item {
Item::Data(ref v) => {
buf.set_stringn(
x,
y,
&format!("{}", v),
list_area.width as usize,
&Style::default(),
);
Text::Raw(ref v) => {
buf.set_stringn(x, y, v, list_area.width as usize, Style::default());
}
Item::StyledData(ref v, s) => {
buf.set_stringn(x, y, &format!("{}", v), list_area.width as usize, s);
Text::Styled(ref v, s) => {
buf.set_stringn(x, y, v, list_area.width as usize, s);
}
};
}
@@ -216,8 +203,8 @@ impl<'b> Widget for SelectableList<'b> {
// Use highlight_style only if something is selected
let (selected, highlight_style) = match self.selected {
Some(i) => (Some(i), &self.highlight_style),
None => (None, &self.style),
Some(i) => (Some(i), self.highlight_style),
None => (None, self.style),
};
let highlight_symbol = self.highlight_symbol.unwrap_or("");
let blank_symbol = iter::repeat(" ")
@@ -239,18 +226,17 @@ impl<'b> Widget for SelectableList<'b> {
.items
.iter()
.enumerate()
.map(|(i, item)| {
.map(|(i, &item)| {
if let Some(s) = selected {
if i == s {
Item::StyledData(format!("{} {}", highlight_symbol, item), highlight_style)
Text::styled(format!("{} {}", highlight_symbol, item), highlight_style)
} else {
Item::StyledData(format!("{} {}", blank_symbol, item), &self.style)
Text::styled(format!("{} {}", blank_symbol, item), self.style)
}
} else {
Item::StyledData(item.to_string(), &self.style)
Text::styled(item, self.style)
}
})
.skip(offset as usize);
}).skip(offset as usize);
List::new(items)
.block(self.block.unwrap_or_default())
.style(self.style)

View File

@@ -1,3 +1,5 @@
use std::borrow::Cow;
mod barchart;
mod block;
pub mod canvas;
@@ -13,8 +15,8 @@ pub use self::barchart::BarChart;
pub use self::block::Block;
pub use self::chart::{Axis, Chart, Dataset, Marker};
pub use self::gauge::Gauge;
pub use self::list::{Item, List, SelectableList};
pub use self::paragraph::{Paragraph, Text};
pub use self::list::{List, SelectableList};
pub use self::paragraph::Paragraph;
pub use self::sparkline::Sparkline;
pub use self::table::{Row, Table};
pub use self::tabs::Tabs;
@@ -22,7 +24,7 @@ pub use self::tabs::Tabs;
use backend::Backend;
use buffer::Buffer;
use layout::Rect;
use style::Color;
use style::{Color, Style};
use terminal::Frame;
/// Bitflags that can be composed to set the visible borders essentially on the block widget.
@@ -43,6 +45,21 @@ bitflags! {
}
}
pub enum Text<'b> {
Raw(Cow<'b, str>),
Styled(Cow<'b, str>, Style),
}
impl<'b> Text<'b> {
pub fn raw<D: Into<Cow<'b, str>>>(data: D) -> Text<'b> {
Text::Raw(data.into())
}
pub fn styled<D: Into<Cow<'b, str>>>(data: D, style: Style) -> Text<'b> {
Text::Styled(data.into(), style)
}
}
/// Base requirements for a Widget
pub trait Widget {
/// Draws the current state of the widget in the given buffer. That the only method required to

View File

@@ -6,7 +6,7 @@ use unicode_width::UnicodeWidthStr;
use buffer::Buffer;
use layout::{Alignment, Rect};
use style::Style;
use widgets::{Block, Widget};
use widgets::{Block, Text, Widget};
/// A widget to display some text.
///
@@ -19,8 +19,8 @@ use widgets::{Block, Widget};
/// # use tui::layout::{Alignment};
/// # fn main() {
/// let text = [
/// Text::Data("First line\n"),
/// Text::StyledData("Second line\n", Style::default().fg(Color::Red))
/// Text::raw("First line\n"),
/// Text::styled("Second line\n", Style::default().fg(Color::Red))
/// ];
/// Paragraph::new(text.iter())
/// .block(Block::default().title("Paragraph").borders(Borders::ALL))
@@ -49,11 +49,6 @@ where
alignment: Alignment,
}
pub enum Text<'b> {
Data(&'b str),
StyledData(&'b str, Style),
}
impl<'a, 't, T> Paragraph<'a, 't, T>
where
T: Iterator<Item = &'t Text<'t>>,
@@ -122,11 +117,13 @@ where
let style = self.style;
let styled = self.text.by_ref().flat_map(|t| match *t {
Text::Data(d) => {
Either::Left(UnicodeSegmentation::graphemes(d, true).map(|g| (g, style)))
Text::Raw(ref d) => {
let data: &'t str = d; // coerce to &str
Either::Left(UnicodeSegmentation::graphemes(data, true).map(|g| (g, style)))
}
Text::StyledData(d, s) => {
Either::Right(UnicodeSegmentation::graphemes(d, true).map(move |g| (g, s)))
Text::Styled(ref d, s) => {
let data: &'t str = d; // coerce to &str
Either::Right(UnicodeSegmentation::graphemes(data, true).map(move |g| (g, s)))
}
});
let mut styled = multipeek(styled);

View File

@@ -7,13 +7,13 @@ use style::Style;
use widgets::{Block, Widget};
/// Holds data to be displayed in a Table widget
pub enum Row<'i, D, I>
pub enum Row<D, I>
where
D: Iterator<Item = I>,
I: Display,
{
Data(D),
StyledData(D, &'i Style),
StyledData(D, Style),
}
/// A widget to display data in formatted columns
@@ -28,9 +28,9 @@ where
/// Table::new(
/// ["Col1", "Col2", "Col3"].into_iter(),
/// vec![
/// Row::StyledData(["Row11", "Row12", "Row13"].into_iter(), &row_style),
/// Row::StyledData(["Row21", "Row22", "Row23"].into_iter(), &row_style),
/// Row::StyledData(["Row31", "Row32", "Row33"].into_iter(), &row_style),
/// Row::StyledData(["Row11", "Row12", "Row13"].into_iter(), row_style),
/// Row::StyledData(["Row21", "Row22", "Row23"].into_iter(), row_style),
/// Row::StyledData(["Row31", "Row32", "Row33"].into_iter(), row_style),
/// Row::Data(["Row41", "Row42", "Row43"].into_iter())
/// ].into_iter()
/// )
@@ -41,13 +41,13 @@ where
/// .column_spacing(1);
/// # }
/// ```
pub struct Table<'a, 'i, T, H, I, D, R>
pub struct Table<'a, T, H, I, D, R>
where
T: Display,
H: Iterator<Item = T>,
I: Display,
D: Iterator<Item = I>,
R: Iterator<Item = Row<'i, D, I>>,
R: Iterator<Item = Row<D, I>>,
{
/// A block to wrap the widget in
block: Option<Block<'a>>,
@@ -66,15 +66,15 @@ where
rows: R,
}
impl<'a, 'i, T, H, I, D, R> Default for Table<'a, 'i, T, H, I, D, R>
impl<'a, T, H, I, D, R> Default for Table<'a, T, H, I, D, R>
where
T: Display,
H: Iterator<Item = T> + Default,
I: Display,
D: Iterator<Item = I>,
R: Iterator<Item = Row<'i, D, I>> + Default,
R: Iterator<Item = Row<D, I>> + Default,
{
fn default() -> Table<'a, 'i, T, H, I, D, R> {
fn default() -> Table<'a, T, H, I, D, R> {
Table {
block: None,
style: Style::default(),
@@ -87,15 +87,15 @@ where
}
}
impl<'a, 'i, T, H, I, D, R> Table<'a, 'i, T, H, I, D, R>
impl<'a, T, H, I, D, R> Table<'a, T, H, I, D, R>
where
T: Display,
H: Iterator<Item = T>,
I: Display,
D: Iterator<Item = I>,
R: Iterator<Item = Row<'i, D, I>>,
R: Iterator<Item = Row<D, I>>,
{
pub fn new(header: H, rows: R) -> Table<'a, 'i, T, H, I, D, R> {
pub fn new(header: H, rows: R) -> Table<'a, T, H, I, D, R> {
Table {
block: None,
style: Style::default(),
@@ -106,12 +106,12 @@ where
column_spacing: 1,
}
}
pub fn block(mut self, block: Block<'a>) -> Table<'a, 'i, T, H, I, D, R> {
pub fn block(mut self, block: Block<'a>) -> Table<'a, T, H, I, D, R> {
self.block = Some(block);
self
}
pub fn header<II>(mut self, header: II) -> Table<'a, 'i, T, H, I, D, R>
pub fn header<II>(mut self, header: II) -> Table<'a, T, H, I, D, R>
where
II: IntoIterator<Item = T, IntoIter = H>,
{
@@ -119,42 +119,42 @@ where
self
}
pub fn header_style(mut self, style: Style) -> Table<'a, 'i, T, H, I, D, R> {
pub fn header_style(mut self, style: Style) -> Table<'a, T, H, I, D, R> {
self.header_style = style;
self
}
pub fn widths(mut self, widths: &'a [u16]) -> Table<'a, 'i, T, H, I, D, R> {
pub fn widths(mut self, widths: &'a [u16]) -> Table<'a, T, H, I, D, R> {
self.widths = widths;
self
}
pub fn rows<II>(mut self, rows: II) -> Table<'a, 'i, T, H, I, D, R>
pub fn rows<II>(mut self, rows: II) -> Table<'a, T, H, I, D, R>
where
II: IntoIterator<Item = Row<'i, D, I>, IntoIter = R>,
II: IntoIterator<Item = Row<D, I>, IntoIter = R>,
{
self.rows = rows.into_iter();
self
}
pub fn style(mut self, style: Style) -> Table<'a, 'i, T, H, I, D, R> {
pub fn style(mut self, style: Style) -> Table<'a, T, H, I, D, R> {
self.style = style;
self
}
pub fn column_spacing(mut self, spacing: u16) -> Table<'a, 'i, T, H, I, D, R> {
pub fn column_spacing(mut self, spacing: u16) -> Table<'a, T, H, I, D, R> {
self.column_spacing = spacing;
self
}
}
impl<'a, 'i, T, H, I, D, R> Widget for Table<'a, 'i, T, H, I, D, R>
impl<'a, T, H, I, D, R> Widget for Table<'a, T, H, I, D, R>
where
T: Display,
H: Iterator<Item = T>,
I: Display,
D: Iterator<Item = I>,
R: Iterator<Item = Row<'i, D, I>>,
R: Iterator<Item = Row<D, I>>,
{
fn draw(&mut self, area: Rect, buf: &mut Buffer) {
// Render block if necessary and get the drawing area
@@ -185,7 +185,7 @@ where
if y < table_area.bottom() {
x = table_area.left();
for (w, t) in widths.iter().zip(self.header.by_ref()) {
buf.set_string(x, y, &format!("{}", t), &self.header_style);
buf.set_string(x, y, format!("{}", t), self.header_style);
x += *w + self.column_spacing;
}
}
@@ -197,12 +197,12 @@ where
let remaining = (table_area.bottom() - y) as usize;
for (i, row) in self.rows.by_ref().take(remaining).enumerate() {
let (data, style) = match row {
Row::Data(d) => (d, &default_style),
Row::Data(d) => (d, default_style),
Row::StyledData(d, s) => (d, s),
};
x = table_area.left();
for (w, elt) in widths.iter().zip(data) {
buf.set_stringn(x, y + i as u16, &format!("{}", elt), *w as usize, style);
buf.set_stringn(x, y + i as u16, format!("{}", elt), *w as usize, style);
x += *w + self.column_spacing;
}
}

View File

@@ -105,9 +105,9 @@ where
let mut x = tabs_area.left();
for (title, style) in self.titles.iter().enumerate().map(|(i, t)| {
if i == self.selected {
(t, &self.highlight_style)
(t, self.highlight_style)
} else {
(t, &self.style)
(t, self.style)
}
}) {
x += 1;