Compare commits
18 Commits
v0.3.0-bet
...
v0.3.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56fc43400a | ||
|
|
7b4d35d224 | ||
|
|
a99fc115f8 | ||
|
|
d8e5f57d53 | ||
|
|
aa85e597d9 | ||
|
|
08ab92da80 | ||
|
|
5d52fd2486 | ||
|
|
4ae9850e13 | ||
|
|
e14190ae4b | ||
|
|
ce445a8096 | ||
|
|
dd71d6471c | ||
|
|
f795173886 | ||
|
|
e42ab1fed8 | ||
|
|
0544c023f5 | ||
|
|
ff47f9480b | ||
|
|
70561b7c54 | ||
|
|
559c9c75f3 | ||
|
|
6c69160d6b |
40
CHANGELOG.md
40
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
34
Makefile
34
Makefile
@@ -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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[](https://crates.io/crates/tui)
|
||||
[](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`
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
257
examples/demo.rs
257
examples/demo.rs
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
153
examples/list.rs
153
examples/list.rs
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
134
examples/tabs.rs
134
examples/tabs.rs
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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
83
examples/util/event.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
72
src/lib.rs
72
src/lib.rs
@@ -1,7 +1,7 @@
|
||||
//! [tui](https://github.com/fdehau/tui-rs) is a library used to build rich
|
||||
//! terminal users interfaces and dashboards.
|
||||
//!
|
||||
//! 
|
||||
//! 
|
||||
//!
|
||||
//! # 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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ impl<'a> Widget for Block<'a> {
|
||||
area.top(),
|
||||
title,
|
||||
width as usize,
|
||||
&self.title_style,
|
||||
self.title_style,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ where
|
||||
dx + canvas_area.left(),
|
||||
dy + canvas_area.top(),
|
||||
label.text,
|
||||
&style.fg(label.color),
|
||||
style.fg(label.color),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user