Compare commits

...

87 Commits

Author SHA1 Message Date
Florian Dehau
3c38abb203 Release 0.2.2 2018-05-06 12:34:26 +02:00
Florian Dehau
4816563452 Update CHANGELOG 2018-05-06 11:49:32 +02:00
Florian Dehau
24dc73912b [examples] Update table example
Modify example to use variables outside of the closure scope
2018-05-06 11:49:32 +02:00
Florian Dehau
f96db9c74f [layout] Replace FnMut with FnOnce in Group::render
As the function does not need to mutate state and be run multiple times.
2018-05-06 11:49:32 +02:00
Florian Dehau
ef2054a45b [lib] Derive Debug on Terminal 2018-04-15 22:09:36 +02:00
Xavier Bestel
f4052e0e71 fix colors use, add missing Blue 2018-04-04 08:45:58 +02:00
Florian Dehau
524845cc74 Publish v0.2.1 2018-04-01 19:49:10 +02:00
Florian Dehau
4c356c5077 Update CHANGELOG 2018-04-01 19:03:49 +02:00
Florian Dehau
36dea8373f [widgets][paragraph] Fix text wrapping 2018-04-01 19:03:49 +02:00
Florian Dehau
2cb823a15b [lib] Fix conversion from cassowary-rs results to internal layouts
The library used to compute the layout may returned negative results
given strange contraints. To avoid overflows on unsigned integers operations,
those results will be converted to 0 instead of being converted as is.
2018-04-01 18:28:17 +02:00
Florian Dehau
169dc43565 [examples] Add layout example 2018-04-01 18:28:17 +02:00
Florian Dehau
4b53acab14 [doc] Fix layout example in documentation 2018-04-01 18:28:17 +02:00
Florian Dehau
c3acac797a Update CHANGELOG 2018-04-01 12:50:03 +02:00
Florian Dehau
dd2bf0ad13 Update CHANGELOG 2018-04-01 12:36:11 +02:00
Florian Dehau
f620af1455 [examples][list] Change style of first list 2018-04-01 12:36:11 +02:00
Florian Dehau
fcd1b7b187 [widgets][list] Set the style of the underlying list 2018-04-01 12:36:11 +02:00
Magnus Bergmark
d0d2f88346 BUG: Buffer::pos_of panics on inside-bounds index
- Add tests for this behavior.
- Extend documentation of Buffer::pos_of and Buffer::index_of
  - Clarify that the coordinates should be in global coordinate-space
  (rather than local).
  - Document panics.
  - Add examples.
2018-04-01 11:13:35 +02:00
Rafael Escobar
c56173462a Export AlternateScreenBackend. 2018-01-30 22:16:41 +01:00
Florian Dehau
58074f23c5 Update CHANGELOG 2018-01-30 22:16:41 +01:00
Florian Dehau
f816e6bbc4 [backends] Improve termion backend
* Add AlternateScreenBackend
* Add a way to create a TermionBackend with a custom config
* Improve return values of several methods
2018-01-30 22:16:41 +01:00
Florian Dehau
d53ecaeade [examples] Clean user input example 2018-01-27 10:46:58 +01:00
Florian Dehau
299279dc2d [examples] Add user input example 2018-01-27 10:46:58 +01:00
Florian Dehau
72f9a2b460 Bump version in CHANGELOG 2017-12-26 22:16:24 +01:00
Florian Dehau
7d273b576d Bump version 2017-12-26 22:13:40 +01:00
Florian Dehau
3e143593ab [changelog] Add border change 2017-12-26 21:45:46 +01:00
Florian Dehau
6a3b9fb130 [src][chart] fmt 2017-12-26 21:45:46 +01:00
Florian Dehau
e53748de16 Add CHANGELOG 2017-12-26 21:45:46 +01:00
Florian Dehau
0d2e2e185e [scripts] Allow pipefail for convenience 2017-12-26 21:45:46 +01:00
Florian Dehau
d2a4048e12 [scripts] Update installation of dev tools 2017-12-26 21:45:46 +01:00
Florian Dehau
c3c5109c5a [src] Fix build and tests 2017-12-26 21:45:46 +01:00
Florian Dehau
151d7e8a1c [src] Update dependencies
* Update all dependencies to their latest versions
* Change border to Borders to match v1.0 of bitflags
2017-12-26 21:45:46 +01:00
Florian Dehau
af16518650 [src] Run rustfmt 2017-12-26 21:45:46 +01:00
Florian Dehau
8907ab90a1 [doc] Move "Get started" from README to src/lib.rs 2017-12-26 21:45:46 +01:00
Florian Dehau
5dd03d91ad [widgets] Fix a bug in chart layout 2017-12-26 20:41:40 +01:00
Emmanuel Sales
cb8af88adf Add Copy trait for layout enums 2017-12-26 09:21:12 +01:00
Florian Dehau
e675d6735c [dependencies] Replace log4rs by stderrlog 2017-10-31 00:08:29 +01:00
Florian Dehau
3012215e32 [widgets][tabs] Use generic type for tabs titles 2017-10-30 23:38:17 +01:00
Florian Dehau
ba80889333 [clippy] Fix warnings 2017-10-30 23:14:57 +01:00
Florian Dehau
f24517bc5a [examples] Run cargo fmt 2017-10-30 23:14:57 +01:00
Florian Dehau
1f285fbac0 [src] Run cargo fmt 2017-10-30 23:14:57 +01:00
Florian Dehau
afe5317592 [travis] Fix unbound variable 2017-10-30 22:51:34 +01:00
Florian Dehau
20d373b5f9 [travis] Fix shared libs loading 2017-10-30 22:51:34 +01:00
Florian Dehau
b5e4ddafb4 [Makefile] Fix fmt rule 2017-10-30 22:51:34 +01:00
Florian Dehau
1c0bddd9bc [scripts] Fix library path 2017-10-30 22:51:34 +01:00
Florian Dehau
53d0001547 [tools] Update tools install scripts 2017-10-30 22:51:34 +01:00
Florian Dehau
3cc3585e48 [widgets][chart] Use generic type for labels 2017-10-30 21:18:31 +01:00
talha
3045ac4124 Minor codestyle fixes 2017-10-30 17:15:28 +01:00
William Bush
80f5f9f481 add note to readme about running/quitting examples 2017-10-30 17:08:10 +01:00
Florian Dehau
71545a0aa8 Run cargo fmt 2017-09-11 08:15:39 +02:00
Florian Dehau
295fc77df2 [Makefile] Bump clippy version 2017-09-11 08:15:39 +02:00
Florian Dehau
6eb1987650 [Makefile] Bump rustfmt version 2017-09-11 08:15:39 +02:00
Florian Dehau
3b8cc241ac [widgets] Fix table doc test 2017-09-11 07:43:19 +02:00
Florian Dehau
ca3308e945 [widgets] Fix Row export 2017-09-11 07:43:19 +02:00
Florian Dehau
d008892e04 [examples] Fix Demo example 2017-09-11 07:43:19 +02:00
Florian Dehau
af6d589459 [examples] Fix Table example 2017-09-11 07:43:19 +02:00
Florian Dehau
c18885d38b [widgets] Refactor Table Widget
* Add Row enum
* Move to a generic definition of table which allow iterators to be
passed as arguments and therefore reduce allocations
2017-09-11 07:43:19 +02:00
Florian Dehau
d6a91d1865 Fix List lifetime issues
Add a new lifetime for the items inside the list
2017-09-10 21:38:17 +02:00
Florian Dehau
7749e5ee35 Fix custom_widget example 2017-09-10 21:38:17 +02:00
Florian Dehau
b8fd4a8685 Fix List example 2017-09-10 21:38:17 +02:00
Florian Dehau
41eac2aa4e Refactor demo example 2017-09-10 21:38:17 +02:00
Florian Dehau
89a173fe9b Refactor List Widget
* Add a generic item object for the list. The item can either be some data
alone or some data and its style
* The List widget now store an Iterator over those element. This should
avoid the need of allocating memory to store its elements
* The List consume the Iterator when it is drawn
2017-09-10 21:38:17 +02:00
Florian Dehau
b1737ce667 Update Widget Trait
The draw function now take a &mut reference to the widget, allowing the
widget to modify itself when drawn. This change the semantic of the draw
call since the widget should now be considered "consumed" after it.
2017-09-10 21:38:17 +02:00
Florian Dehau
bb61028e0c Fix examples 2017-09-07 08:32:08 +02:00
Florian Dehau
a9aa23aead Add mouse support in TermionBackend
* Old TermionBackend is replaced by RawBackend
* Add TermionBackend with mouse support called MouseBackend
2017-09-07 08:32:08 +02:00
Florian Dehau
d926695b17 Release v0.1.3 2017-06-15 09:00:00 +02:00
Florian Dehau
fd1e1f22af Add specification for all examples in Cargo.toml 2017-06-15 08:57:07 +02:00
Florian Dehau
28f0a8f216 Bump dependencies 2017-06-15 08:57:07 +02:00
Florian Dehau
92a3474093 Bump clippy version 2017-06-15 08:57:07 +02:00
Florian Dehau
7b2b1e1c80 Add documentation for pipelines rules 2017-06-15 08:57:07 +02:00
Florian Dehau
dea9818c59 Update README 2017-06-15 08:57:07 +02:00
Florian Dehau
06a3c9346b Add pipelines rules to build && test a channel quickly 2017-06-15 08:57:07 +02:00
Florian Dehau
c2ad42b509 Fix clippy rule in Makefile 2017-06-15 08:57:07 +02:00
Florian Dehau
419f2aadb2 Refactor Cargo.toml to only enable "termion" feature by default 2017-06-15 08:57:07 +02:00
Florian Dehau
28accd529a Update badges 2017-05-21 14:21:29 +02:00
Florian Dehau
1461aaa351 Add travis badge to Cargo.toml 2017-05-21 14:07:44 +02:00
Florian Dehau
d8865af559 Update dependencies 2017-05-21 14:01:53 +02:00
Florian Dehau
2cb5e185eb [CI] Fix missing build and test command in scripts/travis/script.sh 2017-05-21 13:58:36 +02:00
Florian Dehau
c42ca05849 Refactor Makefile install rule
Check for older installation of the tools and only install them if
missing or if the versions don't match
2017-05-21 12:49:12 +02:00
Florian Dehau
b2bb24b9d2 Fix rustfmt and clippy errors 2017-05-21 12:49:12 +02:00
Florian Dehau
29db3dd722 [CI] Add lint stage
* Add scripts for Travis under ./scripts/travis
* Install rustfmt and clippy on nightly
* Lint code for formatting problems and common errors
2017-05-21 12:49:12 +02:00
Florian Dehau
b351ad86e4 Merge pull request #5 from fdehau/ft-refactor-makefile
Refactor Makefile
2017-05-21 09:52:49 +02:00
Florian Dehau
340aa94e63 Refactor Makefile
* Restructure old rules to make them generic. They now also take in
consideration the developer setup (using rustup only if available)
* Defines rules to format and lint the source code in order to provide
this project some kind of a coding style (Rust default style)
2017-05-21 09:40:31 +02:00
Florian Dehau
a8c623d0ce [Travis] Allow failure in beta 2017-05-17 07:57:34 +02:00
Florian Dehau
bd0dcdcc87 Merge pull request #4 from wose/bug/example-crash
fixes panic when terminal was resized
2017-05-17 07:42:57 +02:00
Florian Dehau
5e4960162d Merge pull request #3 from wose/master
fixes scroll handling for paragraph widget
2017-05-17 07:35:56 +02:00
Sebastian Woetzel
7652cc440d fixes panic when terminal was resized 2017-05-08 21:34:41 +02:00
Sebastian Woetzel
81cedbd6f2 fixes scroll handling for paragraph widget 2017-04-30 19:10:46 +02:00
50 changed files with 8739 additions and 7791 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
target
Cargo.lock
*.log
*.rs.rustfmt
.gdb_history

View File

@@ -1,8 +1,22 @@
language: rust
rust:
- stable
- beta
- nightly
env:
- NO_RUSTUP=1
cache: cargo
matrix:
allow_failures:
- rust: nightly
- rust: beta
before_script:
- ./scripts/travis/before_script.sh
script:
- ./scripts/travis/script.sh

51
CHANGELOG.md Normal file
View File

@@ -0,0 +1,51 @@
# Changelog
## To be released
## v0.2.2 - 2018-05-06
### Added
* `Terminal` implements `Debug`
### Changed
* Use `FnOnce` instead of `FnMut` in Group::render
## v0.2.1 - 2018-04-01
### Added
* Add `AlternateScreenBackend` in `termion` backend
* Add `TermionBackend::with_stdout` in order to let an user of the library
provides its own termion struct
* Add tests and documentation for `Buffer::pos_of`
* Remove leading whitespaces when wrapping text
### Fixed
* Fix `debug_assert` in `Buffer::pos_of`
* Pass the style of `SelectableList` to the underlying `List`
* Fix missing character when wrapping text
* Fix panic when specifying layout constraints
## v0.2.0 - 2017-12-26
### Added
* Add `MouseBackend` in `termion` backend to handle scroll and mouse events
* Add generic `Item` for items in a `List`
### Changed
* Rename `TermionBackend` to `RawBackend` (to distinguish it from the `MouseBackend`)
* Generic parameters for `List` to allow passing iterators as items
* Generic parameters for `Table` to allow using iterators as rows and header
* Generic parameters for `Tabs`
* Rename `border` bitflags to `Borders`
* Run latest `rustfmt` on all sources
### Removed
* Drop `log4rs` as a dev-dependencies in favor of `stderrlog`

View File

@@ -1,6 +1,6 @@
[package]
name = "tui"
version = "0.1.2"
version = "0.2.2"
authors = ["Florian Dehau <work@fdehau.com>"]
description = """
A library to build rich terminal user interfaces or dashboards
@@ -8,20 +8,84 @@ 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 = ["docs/*", ".travis.yml"]
[badges]
travis-ci = { repository = "fdehau/tui-rs" }
[features]
default = ["rustbox", "termion"]
default = ["termion"]
[dependencies]
bitflags = "0.7.0"
cassowary = "0.2.0"
log = "0.3.6"
unicode-segmentation = "0.1.3"
bitflags = "1.0.1"
cassowary = "0.3.0"
log = "0.4.1"
unicode-segmentation = "1.2.0"
unicode-width = "0.1.4"
termion = { version = "1.1.4", optional = true }
rustbox = { version = "0.9.0", optional = true }
termion = { version = "1.5.1", optional = true }
rustbox = { version = "0.11.0", optional = true }
[dev-dependencies]
log4rs = "0.5.2"
rand = "0.3.15"
stderrlog = "0.3.0"
rand = "0.4.2"
[[example]]
name = "barchart"
path = "examples/barchart.rs"
[[example]]
name = "block"
path = "examples/block.rs"
[[example]]
name = "canvas"
path = "examples/canvas.rs"
[[example]]
name = "chart"
path = "examples/chart.rs"
[[example]]
name = "custom_widget"
path = "examples/custom_widget.rs"
[[example]]
name = "demo"
path = "examples/demo.rs"
[[example]]
name = "gauge"
path = "examples/gauge.rs"
[[example]]
name = "list"
path = "examples/list.rs"
[[example]]
name = "paragraph"
path = "examples/paragraph.rs"
[[example]]
name = "rustbox"
path = "examples/rustbox.rs"
required-features = ["rustbox"]
[[example]]
name = "sparkline"
path = "examples/sparkline.rs"
[[example]]
name = "table"
path = "examples/table.rs"
[[example]]
name = "tabs"
path = "examples/tabs.rs"
[[example]]
name = "user_input"
path = "examples/user_input.rs"
[[example]]
name = "layout"
path = "examples/layout.rs"

119
Makefile
View File

@@ -1,16 +1,111 @@
build:
cargo build
test:
cargo test
doc:
cargo doc
clippy:
rustup run nightly cargo clippy
watch:
watchman-make -p 'src/**/*.rs' -t build -p 'test/**/*.rs' -t test
# Makefile for the tui-rs project (https://github.com/fdehau/tui-rs)
watch-test:
# ================================ Cargo ======================================
RUST_CHANNEL ?= stable
CARGO_FLAGS =
RUSTUP_INSTALLED = $(shell command -v rustup 2> /dev/null)
ifndef RUSTUP_INSTALLED
CARGO = cargo
else
ifdef CI
CARGO = cargo
else
CARGO = rustup run $(RUST_CHANNEL) cargo
endif
endif
# ================================ 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}'
# ================================ Tools ======================================
install-tools: install-rustfmt install-clippy ## Install tools dependencies
INSTALL_RUSTFMT = ./scripts/tools/install.sh --name=rustfmt-nightly
ifndef CI
INSTALL_RUSTFMT += --channel=nightly
endif
install-rustfmt: ## Intall rustfmt
$(INSTALL_RUSTFMT)
INSTALL_CLIPPY = ./scripts/tools/install.sh --name=clippy
ifndef CI
INSTALL_CLIPPY += --channel=nightly
endif
install-clippy: ## Intall rustfmt
$(INSTALL_CLIPPY)
# =============================== Build =======================================
check: ## Validate the project code
$(CARGO) check
build: ## Build the project in debug mode
$(CARGO) build $(CARGO_FLAGS)
release: CARGO_FLAGS += --release
release: build ## Build the project in release mode
# ================================ Lint =======================================
RUSTFMT_WRITEMODE ?= 'diff'
lint: fmt clippy ## Lint project files
fmt: ## Check the format of the source code
cargo fmt -- --write-mode=$(RUSTFMT_WRITEMODE)
clippy: RUST_CHANNEL = nightly
clippy: ## Check the style of the source code and catch common errors
$(CARGO) clippy --features="termion rustbox"
# ================================ Test =======================================
test: ## Run the tests
$(CARGO) test
# ================================ Doc ========================================
doc: ## Build the documentation (available at ./target/doc)
$(CARGO) doc
# ================================= Watch =====================================
# Requires watchman and watchman-make (https://facebook.github.io/watchman/docs/install.html)
watch: ## Watch file changes and build the project if any
watchman-make -p 'src/**/*.rs' -t check build
watch-test: ## Watch files changes and run the tests if any
watchman-make -p 'src/**/*.rs' 'tests/**/*.rs' 'examples/**/*.rs' -t test
watch-doc:
watch-doc: ## Watch file changes and rebuild the documentation if any
watchman-make -p 'src/**/*.rs' -t doc
# ================================= Pipelines =================================
stable: RUST_CHANNEL = stable
stable: build test ## Run build and tests for stable
beta: RUST_CHANNEL = beta
beta: build test ## Run build and tests for beta
nightly: RUST_CHANNEL = nightly
nightly: install-tools build lint test ## Run build, lint and tests for nightly

102
README.md
View File

@@ -1,7 +1,9 @@
# tui-rs
[![Build Status](https://travis-ci.org/fdehau/tui-rs.svg?branch=master)](https://travis-ci.org/fdehau/tui-rs)
<img src="https://img.shields.io/crates/v/tui.svg" alt="tuis current version badge" title="tuis current version badge">
[![Crate Status](https://img.shields.io/crates/v/tui.svg)](https://crates.io/crates/tui)
[![Docs Status](https://docs.rs/tui/badge.svg)](https://docs.rs/crate/tui/)
<img src="./docs/demo.gif" alt="Demo cast under Linux Termite with Inconsolata font 12pt">
`tui-rs` is a [Rust](https://www.rust-lang.org) library to build rich terminal
@@ -28,100 +30,7 @@ comes from the terminal emulator than the library itself.
Moreover, the library does not provide any input handling nor any event system and
you may rely on the previously cited libraries to achieve such features.
## Cargo.toml
```toml
[dependencies]
tui = "0.1.1"
```
## Get Started
### Create the terminal interface
The first thing to do is to choose from one of the two backends:
For Termion:
```rust
use tui::Terminal;
use tui::backend::TermionBackend;
fn main() {
let backend = TermionBackend::new().unwrap();
let mut terminal = Terminal::new(backend);
}
```
For Rustbox:
```rust
use tui::Terminal;
use tui::backend::RustboxBackend;
fn main() {
let backend = RustboxBackend::new().unwrap();
let mut terminal = Terminal::new(backend);
}
```
By default both backends are enabled but you might want to use only one. To do
so, you should disable the default-features for this library and specify the
chosen backend in features.
```toml
[dependencies.tui]
version = "0.1.1"
default-features = false
features = ['termion']
```
### Layout
The library comes with a basic yet useful layout management object called
`Group`. As you may see below and in the examples, the library makes heavy use
of the builder pattern to provide full customization. And the `Group` object is
no exception:
```rust
use tui::widgets::{Block, border};
use tui::layout::{Group, Rect, Direction};
fn draw(t: &mut Terminal<TermionBackend>) {
let size = t.size().unwrap();
Group::default()
/// You first choose a main direction for the group
.direction(Direction::Vertical)
/// An optional margin
.margin(1)
/// The preferred sizes (heights in this case)
.sizes(&[Size::Fixed(10), Size::Max(20), Size::Min(10)])
/// The computed (or cached) layout is then available as the second argument
/// of the closure
.render(t, &size, |t, chunks| {
/// Continue to describe your UI there.
Block::default()
.title("Block")
.borders(border::ALL)
.render(t, &chunks[0]);
})
```
This let you describe responsive terminal UI by nesting groups. You should note
that by default the computed layout tries to fill the available space
completely. So if for any reason you might need a blank space somewhere, try to
pass an additional size to the group and don't use the corresponding area inside
the render method.
Once you have finished to describe the UI, you just need to call:
```rust
t.draw().unwrap()
```
to actually draw to the terminal.
### [Documentation](https://docs.rs/tui)
### Widgets
@@ -138,7 +47,8 @@ The library comes with the following list of widgets:
* [Canvas (with line, point cloud, map)](examples/canvas.rs)
* [Tabs](examples/tabs.rs)
Click on each item to get an example.
Click on each item to see the source of the example. Run the examples with with
cargo (e.g. to run the demo `cargo run --example demo`), and quit by pressing `q`.
### Demo

View File

@@ -1,5 +1,5 @@
extern crate tui;
extern crate termion;
extern crate tui;
use std::io;
use std::thread;
@@ -10,42 +10,46 @@ use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, border, BarChart};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
use tui::backend::MouseBackend;
use tui::widgets::{BarChart, Block, Borders, Widget};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Modifier, Style};
struct App<'a> {
size: Rect,
data: Vec<(&'a str, u64)>,
}
impl<'a> App<'a> {
fn new() -> App<'a> {
App {
data: vec![("B1", 9),
("B2", 12),
("B3", 5),
("B4", 8),
("B5", 2),
("B6", 4),
("B7", 5),
("B8", 9),
("B9", 14),
("B10", 15),
("B11", 1),
("B12", 0),
("B13", 4),
("B14", 6),
("B15", 4),
("B16", 6),
("B17", 4),
("B18", 7),
("B19", 13),
("B20", 8),
("B21", 11),
("B22", 9),
("B23", 3),
("B24", 5)],
size: Rect::default(),
data: vec![
("B1", 9),
("B2", 12),
("B3", 5),
("B4", 8),
("B5", 2),
("B6", 4),
("B7", 5),
("B8", 9),
("B9", 14),
("B10", 15),
("B11", 1),
("B12", 0),
("B13", 4),
("B14", 6),
("B15", 4),
("B16", 6),
("B17", 4),
("B18", 7),
("B19", 13),
("B20", 8),
("B21", 11),
("B22", 9),
("B23", 3),
("B24", 5),
],
}
}
@@ -62,7 +66,7 @@ enum Event {
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
@@ -83,11 +87,9 @@ fn main() {
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
thread::spawn(move || loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
});
// App
@@ -96,16 +98,21 @@ fn main() {
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app);
loop {
let size = terminal.size().unwrap();
if app.size != size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
if input == event::Key::Char('q') {
break;
}
}
Event::Input(input) => if input == event::Key::Char('q') {
break;
},
Event::Tick => {
app.advance();
}
@@ -116,17 +123,14 @@ fn main() {
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Vertical)
.margin(2)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &size, |t, chunks| {
.render(t, &app.size, |t, chunks| {
BarChart::default()
.block(Block::default().title("Data1").borders(border::ALL))
.block(Block::default().title("Data1").borders(Borders::ALL))
.data(&app.data)
.bar_width(9)
.style(Style::default().fg(Color::Yellow))
@@ -137,7 +141,7 @@ fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[1], |t, chunks| {
BarChart::default()
.block(Block::default().title("Data2").borders(border::ALL))
.block(Block::default().title("Data2").borders(Borders::ALL))
.data(&app.data)
.bar_width(5)
.bar_gap(3)
@@ -145,7 +149,7 @@ fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
.value_style(Style::default().bg(Color::Green).modifier(Modifier::Bold))
.render(t, &chunks[0]);
BarChart::default()
.block(Block::default().title("Data3").borders(border::ALL))
.block(Block::default().title("Data3").borders(Borders::ALL))
.data(&app.data)
.style(Style::default().fg(Color::Red))
.bar_width(7)

View File

@@ -1,24 +1,31 @@
extern crate tui;
extern crate termion;
extern crate tui;
use std::io;
use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, border};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Widget};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Modifier, Style};
fn main() {
let mut terminal = Terminal::new(TermionBackend::new().unwrap()).unwrap();
let mut terminal = Terminal::new(MouseBackend::new().unwrap()).unwrap();
let stdin = io::stdin();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal);
let mut term_size = terminal.size().unwrap();
draw(&mut terminal, &term_size);
for c in stdin.keys() {
draw(&mut terminal);
let size = terminal.size().unwrap();
if term_size != size {
terminal.resize(size).unwrap();
term_size = size;
}
draw(&mut terminal, &term_size);
let evt = c.unwrap();
if evt == event::Key::Char('q') {
break;
@@ -27,21 +34,16 @@ fn main() {
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>) {
let size = t.size().unwrap();
fn draw(t: &mut Terminal<MouseBackend>, size: &Rect) {
// Wrapping block for a group
// Just draw the block and the group on the same area and build the group
// with at least a margin of 1
Block::default()
.borders(border::ALL)
.render(t, &size);
Block::default().borders(Borders::ALL).render(t, size);
Group::default()
.direction(Direction::Vertical)
.margin(4)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &size, |t, chunks| {
.render(t, size, |t, chunks| {
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
@@ -53,10 +55,12 @@ fn draw(t: &mut Terminal<TermionBackend>) {
.render(t, &chunks[0]);
Block::default()
.title("Styled title")
.title_style(Style::default()
.fg(Color::White)
.bg(Color::Red)
.modifier(Modifier::Bold))
.title_style(
Style::default()
.fg(Color::White)
.bg(Color::Red)
.modifier(Modifier::Bold),
)
.render(t, &chunks[1]);
});
Group::default()
@@ -65,12 +69,12 @@ fn draw(t: &mut Terminal<TermionBackend>) {
.render(t, &chunks[1], |t, chunks| {
Block::default()
.title("With borders")
.borders(border::ALL)
.borders(Borders::ALL)
.render(t, &chunks[0]);
Block::default()
.title("With styled borders")
.border_style(Style::default().fg(Color::Cyan))
.borders(border::LEFT | border::RIGHT)
.borders(Borders::LEFT | Borders::RIGHT)
.render(t, &chunks[1]);
});
});

View File

@@ -1,5 +1,5 @@
extern crate tui;
extern crate termion;
extern crate tui;
use std::io;
use std::thread;
@@ -10,10 +10,10 @@ use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, border};
use tui::widgets::canvas::{Canvas, Map, MapResolution, Line};
use tui::layout::{Group, Rect, Direction, Size};
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Widget};
use tui::widgets::canvas::{Canvas, Line, Map, MapResolution};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::Color;
struct App {
@@ -24,32 +24,46 @@ struct App {
playground: Rect,
vx: u16,
vy: u16,
dir_x: bool,
dir_y: bool,
}
impl App {
fn new() -> App {
App {
size: Default::default(),
x: 0.0,
y: 0.0,
ball: Rect::new(20, 20, 10, 10),
ball: Rect::new(10, 30, 10, 10),
playground: Rect::new(10, 10, 100, 100),
vx: 1,
vy: 1,
dir_x: true,
dir_y: true,
}
}
fn advance(&mut self) {
if self.ball.left() < self.playground.left() ||
self.ball.right() > self.playground.right() {
self.vx = !self.vx;
} else if self.ball.top() < self.playground.top() ||
self.ball.bottom() > self.playground.bottom() {
self.vy = !self.vy;
if self.ball.left() < self.playground.left() || self.ball.right() > self.playground.right()
{
self.dir_x = !self.dir_x;
}
if self.ball.top() < self.playground.top() || self.ball.bottom() > self.playground.bottom()
{
self.dir_y = !self.dir_y;
}
if self.dir_x {
self.ball.x += self.vx;
} else {
self.ball.x -= self.vx;
}
if self.dir_y {
self.ball.y += self.vy;
} else {
self.ball.y -= self.vy
}
self.ball.x += self.vx;
self.ball.y += self.vy;
}
}
@@ -60,7 +74,7 @@ enum Event {
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
@@ -81,11 +95,9 @@ fn main() {
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
thread::spawn(move || loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
});
// App
@@ -94,60 +106,54 @@ fn main() {
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
let size = terminal.size().unwrap();
app.size = size;
app.size = terminal.size().unwrap();
draw(&mut terminal, &app);
loop {
let size = terminal.size().unwrap();
if size != app.size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
match input {
event::Key::Char('q') => {
break;
}
event::Key::Down => {
app.y += 1.0;
}
event::Key::Up => {
app.y -= 1.0;
}
event::Key::Right => {
app.x += 1.0;
}
event::Key::Left => {
app.x -= 1.0;
}
_ => {}
Event::Input(input) => match input {
event::Key::Char('q') => {
break;
}
}
event::Key::Down => {
app.y += 1.0;
}
event::Key::Up => {
app.y -= 1.0;
}
event::Key::Right => {
app.x += 1.0;
}
event::Key::Left => {
app.x -= 1.0;
}
_ => {}
},
Event::Tick => {
app.advance();
}
}
let size = terminal.size().unwrap();
if size != app.size {
app.size = size;
terminal.resize(size).unwrap();
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &app.size, |t, chunks| {
Canvas::default()
.block(Block::default()
.borders(border::ALL)
.title("World"))
.block(Block::default().borders(Borders::ALL).title("World"))
.paint(|ctx| {
ctx.draw(&Map {
color: Color::White,
@@ -159,36 +165,34 @@ fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
.y_bounds([-90.0, 90.0])
.render(t, &chunks[0]);
Canvas::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.block(Block::default().borders(Borders::ALL).title("List"))
.paint(|ctx| {
ctx.draw(&Line {
x1: app.ball.left() as f64,
y1: app.ball.top() as f64,
x2: app.ball.right() as f64,
y2: app.ball.top() as f64,
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: app.ball.right() as f64,
y1: app.ball.top() as f64,
x2: app.ball.right() as f64,
y2: app.ball.bottom() as f64,
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: app.ball.right() as f64,
y1: app.ball.bottom() as f64,
x2: app.ball.left() as f64,
y2: app.ball.bottom() as f64,
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: app.ball.left() as f64,
y1: app.ball.bottom() as f64,
x2: app.ball.left() as f64,
y2: app.ball.top() as f64,
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,
});
})

View File

@@ -1,5 +1,5 @@
extern crate tui;
extern crate termion;
extern crate tui;
mod util;
use util::*;
@@ -13,11 +13,13 @@ use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, border, Chart, Axis, Marker, Dataset};
use tui::style::{Style, Color, Modifier};
use tui::backend::MouseBackend;
use tui::widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Widget};
use tui::layout::Rect;
use tui::style::{Color, Modifier, Style};
struct App {
size: Rect,
signal1: SinSignal,
data1: Vec<(f64, f64)>,
signal2: SinSignal,
@@ -32,6 +34,7 @@ impl App {
let data1 = signal1.by_ref().take(200).collect::<Vec<(f64, f64)>>();
let data2 = signal2.by_ref().take(200).collect::<Vec<(f64, f64)>>();
App {
size: Rect::default(),
signal1: signal1,
data1: data1,
signal2: signal2,
@@ -61,7 +64,7 @@ enum Event {
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
@@ -82,11 +85,9 @@ fn main() {
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
thread::spawn(move || loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
});
// App
@@ -95,16 +96,21 @@ fn main() {
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app);
loop {
let size = terminal.size().unwrap();
if app.size != size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
if input == event::Key::Char('q') {
break;
}
}
Event::Input(input) => if input == event::Key::Char('q') {
break;
},
Event::Tick => {
app.advance();
}
@@ -115,42 +121,47 @@ fn main() {
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Chart::default()
.block(Block::default()
.title("Chart")
.title_style(Style::default()
.fg(Color::Cyan)
.modifier(Modifier::Bold))
.borders(border::ALL))
.x_axis(Axis::default()
.title("X Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::Italic))
.bounds(app.window)
.labels(&[&format!("{}", app.window[0]),
&format!("{}", (app.window[0] + app.window[1]) / 2.0),
&format!("{}", app.window[1])]))
.y_axis(Axis::default()
.title("Y Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::Italic))
.bounds([-20.0, 20.0])
.labels(&["-20", "0", "20"]))
.datasets(&[Dataset::default()
.name("data2")
.marker(Marker::Dot)
.style(Style::default().fg(Color::Cyan))
.data(&app.data1),
Dataset::default()
.name("data3")
.marker(Marker::Braille)
.style(Style::default().fg(Color::Yellow))
.data(&app.data2)])
.render(t, &size);
.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(t, &app.size);
t.draw().unwrap();
}

View File

@@ -1,7 +1,7 @@
extern crate tui;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::backend::MouseBackend;
use tui::widgets::Widget;
use tui::buffer::Buffer;
use tui::layout::Rect;
@@ -18,7 +18,7 @@ impl<'a> Default for Label<'a> {
}
impl<'a> Widget for Label<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
buf.set_string(area.left(), area.top(), self.text, &Style::default());
}
}
@@ -31,7 +31,7 @@ impl<'a> Label<'a> {
}
fn main() {
let mut terminal = Terminal::new(TermionBackend::new().unwrap()).unwrap();
let mut terminal = Terminal::new(MouseBackend::new().unwrap()).unwrap();
let size = terminal.size().unwrap();
terminal.clear().unwrap();
Label::default().text("Test").render(&mut terminal, &size);

View File

@@ -1,14 +1,13 @@
#[macro_use]
extern crate log;
extern crate tui;
extern crate stderrlog;
extern crate termion;
extern crate tui;
mod util;
use std::io;
use std::thread;
use std::env;
use std::time;
use std::sync::mpsc;
@@ -16,12 +15,12 @@ use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, SelectableList, List, Gauge, Sparkline, Paragraph, border,
Chart, Axis, Dataset, BarChart, Marker, Tabs, Table};
use tui::widgets::canvas::{Canvas, Map, MapResolution, Line};
use tui::layout::{Group, Direction, Size, Rect};
use tui::style::{Style, Color, Modifier};
use tui::backend::MouseBackend;
use tui::widgets::{Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, Item, List, Marker,
Paragraph, Row, SelectableList, Sparkline, Table, Tabs, Widget};
use tui::widgets::canvas::{Canvas, Line, Map, MapResolution};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Modifier, Style};
use util::*;
@@ -32,7 +31,6 @@ struct Server<'a> {
status: &'a str,
}
struct App<'a> {
size: Rect,
items: Vec<&'a str>,
@@ -57,14 +55,11 @@ enum Event {
}
fn main() {
for argument in env::args() {
if argument == "--log" {
setup_log("demo.log");
}
}
stderrlog::new()
.module(module_path!())
.verbosity(4)
.init()
.unwrap();
info!("Start");
let mut rand_signal = RandomSignal::new(0, 100);
@@ -73,35 +68,39 @@ fn main() {
let mut app = App {
size: Rect::default(),
items: vec!["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8",
"Item9", "Item10", "Item11", "Item12", "Item13", "Item14", "Item15", "Item16",
"Item17", "Item18", "Item19", "Item20", "Item21", "Item22", "Item23", "Item24"],
events: vec![("Event1", "INFO"),
("Event2", "INFO"),
("Event3", "CRITICAL"),
("Event4", "ERROR"),
("Event5", "INFO"),
("Event6", "INFO"),
("Event7", "WARNING"),
("Event8", "INFO"),
("Event9", "INFO"),
("Event10", "INFO"),
("Event11", "CRITICAL"),
("Event12", "INFO"),
("Event13", "INFO"),
("Event14", "INFO"),
("Event15", "INFO"),
("Event16", "INFO"),
("Event17", "ERROR"),
("Event18", "ERROR"),
("Event19", "INFO"),
("Event20", "INFO"),
("Event21", "WARNING"),
("Event22", "INFO"),
("Event23", "INFO"),
("Event24", "WARNING"),
("Event25", "INFO"),
("Event26", "INFO")],
items: vec![
"Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9",
"Item10", "Item11", "Item12", "Item13", "Item14", "Item15", "Item16", "Item17",
"Item18", "Item19", "Item20", "Item21", "Item22", "Item23", "Item24",
],
events: vec![
("Event1", "INFO"),
("Event2", "INFO"),
("Event3", "CRITICAL"),
("Event4", "ERROR"),
("Event5", "INFO"),
("Event6", "INFO"),
("Event7", "WARNING"),
("Event8", "INFO"),
("Event9", "INFO"),
("Event10", "INFO"),
("Event11", "CRITICAL"),
("Event12", "INFO"),
("Event13", "INFO"),
("Event14", "INFO"),
("Event15", "INFO"),
("Event16", "INFO"),
("Event17", "ERROR"),
("Event18", "ERROR"),
("Event19", "INFO"),
("Event20", "INFO"),
("Event21", "WARNING"),
("Event22", "INFO"),
("Event23", "INFO"),
("Event24", "WARNING"),
("Event25", "INFO"),
("Event26", "INFO"),
],
selected: 0,
tabs: MyTabs {
titles: vec!["Tab0", "Tab1"],
@@ -112,57 +111,61 @@ fn main() {
data: rand_signal.clone().take(300).collect(),
data2: sin_signal.clone().take(100).collect(),
data3: sin_signal2.clone().take(200).collect(),
data4: vec![("B1", 9),
("B2", 12),
("B3", 5),
("B4", 8),
("B5", 2),
("B6", 4),
("B7", 5),
("B8", 9),
("B9", 14),
("B10", 15),
("B11", 1),
("B12", 0),
("B13", 4),
("B14", 6),
("B15", 4),
("B16", 6),
("B17", 4),
("B18", 7),
("B19", 13),
("B20", 8),
("B21", 11),
("B22", 9),
("B23", 3),
("B24", 5)],
data4: vec![
("B1", 9),
("B2", 12),
("B3", 5),
("B4", 8),
("B5", 2),
("B6", 4),
("B7", 5),
("B8", 9),
("B9", 14),
("B10", 15),
("B11", 1),
("B12", 0),
("B13", 4),
("B14", 6),
("B15", 4),
("B16", 6),
("B17", 4),
("B18", 7),
("B19", 13),
("B20", 8),
("B21", 11),
("B22", 9),
("B23", 3),
("B24", 5),
],
window: [0.0, 20.0],
colors: [Color::Magenta, Color::Red],
color_index: 0,
servers: vec![Server {
name: "NorthAmerica-1",
location: "New York City",
coords: (40.71, -74.00),
status: "Up",
},
Server {
name: "Europe-1",
location: "Paris",
coords: (48.85, 2.35),
status: "Failure",
},
Server {
name: "SouthAmerica-1",
location: "São Paulo",
coords: (-23.54, -46.62),
status: "Up",
},
Server {
name: "Asia-1",
location: "Singapore",
coords: (1.35, 103.86),
status: "Up",
}],
servers: vec![
Server {
name: "NorthAmerica-1",
location: "New York City",
coords: (40.71, -74.00),
status: "Up",
},
Server {
name: "Europe-1",
location: "Paris",
coords: (48.85, 2.35),
status: "Failure",
},
Server {
name: "SouthAmerica-1",
location: "São Paulo",
coords: (-23.54, -46.62),
status: "Up",
},
Server {
name: "Asia-1",
location: "Singapore",
coords: (1.35, 103.86),
status: "Up",
},
],
};
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
@@ -193,7 +196,7 @@ fn main() {
}
});
let backend = TermionBackend::new().unwrap();
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
@@ -207,33 +210,29 @@ fn main() {
draw(&mut terminal, &app).unwrap();
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
match input {
event::Key::Char('q') => {
break;
}
event::Key::Up => {
if app.selected > 0 {
app.selected -= 1
};
}
event::Key::Down => {
if app.selected < app.items.len() - 1 {
app.selected += 1;
}
}
event::Key::Left => {
app.tabs.previous();
}
event::Key::Right => {
app.tabs.next();
}
event::Key::Char('t') => {
app.show_chart = !app.show_chart;
}
_ => {}
Event::Input(input) => match input {
event::Key::Char('q') => {
break;
}
}
event::Key::Up => {
if app.selected > 0 {
app.selected -= 1
};
}
event::Key::Down => if app.selected < app.items.len() - 1 {
app.selected += 1;
},
event::Key::Left => {
app.tabs.previous();
}
event::Key::Right => {
app.tabs.next();
}
event::Key::Char('t') => {
app.show_chart = !app.show_chart;
}
_ => {}
},
Event::Tick => {
app.progress += 5;
if app.progress > 100 {
@@ -263,16 +262,16 @@ fn main() {
}
}
terminal.show_cursor().unwrap();
terminal.clear().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) -> Result<(), io::Error> {
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Fixed(3), Size::Min(0)])
.render(t, &app.size, |t, chunks| {
Tabs::default()
.block(Block::default().borders(border::ALL).title("Tabs"))
.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))
@@ -292,167 +291,199 @@ fn draw(t: &mut Terminal<TermionBackend>, app: &App) -> Result<(), io::Error> {
Ok(())
}
fn draw_first_tab(t: &mut Terminal<TermionBackend>, app: &App, area: &Rect) {
fn draw_first_tab(t: &mut Terminal<MouseBackend>, app: &App, area: &Rect) {
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Fixed(7), Size::Min(7), Size::Fixed(7)])
.render(t, area, |t, chunks| {
Block::default()
.borders(border::ALL)
.title("Graphs")
.render(t, &chunks[0]);
Group::default()
.direction(Direction::Vertical)
.margin(1)
.sizes(&[Size::Fixed(2), Size::Fixed(3)])
.render(t, &chunks[0], |t, chunks| {
Gauge::default()
.block(Block::default().title("Gauge:"))
.style(Style::default().fg(Color::Magenta).bg(Color::Black).modifier(Modifier::Italic))
.label(&format!("{} / 100", app.progress))
.percent(app.progress)
.render(t, &chunks[0]);
Sparkline::default()
.block(Block::default().title("Sparkline:"))
.style(Style::default().fg(Color::Green))
.data(&app.data)
.render(t, &chunks[1]);
});
let sizes = if app.show_chart {
vec![Size::Percent(50), Size::Percent(50)]
} else {
vec![Size::Percent(100)]
};
Group::default()
.direction(Direction::Horizontal)
.sizes(&sizes)
.render(t, &chunks[1], |t, chunks| {
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[0], |t, chunks| {
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[0], |t, chunks| {
SelectableList::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.items(&app.items)
.select(app.selected)
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(t, &chunks[0]);
let info_style = Style::default().fg(Color::White);
let warning_style = Style::default().fg(Color::Yellow);
let error_style = Style::default().fg(Color::Magenta);
let critical_style = Style::default().fg(Color::Red);
List::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.items(&app.events.iter().map(|&(evt, level)| (format!("{}: {}", level, evt), match level {
"ERROR" => &error_style,
"CRITICAL" => &critical_style,
"WARNING" => &warning_style,
_ => &info_style,
})).collect::<Vec<(String, &Style)>>())
.render(t, &chunks[1]);
});
BarChart::default()
.block(Block::default()
.borders(border::ALL)
.title("Bar chart"))
.data(&app.data4)
.bar_width(3)
.bar_gap(2)
.value_style(Style::default().fg(Color::Black).bg(Color::Green).modifier(Modifier::Italic))
.label_style(Style::default().fg(Color::Yellow))
.style(Style::default().fg(Color::Green))
.render(t, &chunks[1]);
});
if app.show_chart {
Chart::default()
.block(Block::default()
.title("Chart")
.title_style(Style::default()
.fg(Color::Cyan)
.modifier(Modifier::Bold))
.borders(border::ALL))
.x_axis(Axis::default()
.title("X Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::Italic))
.bounds(app.window)
.labels(&[&format!("{}", app.window[0]),
&format!("{}", (app.window[0] + app.window[1]) / 2.0),
&format!("{}", app.window[1])]))
.y_axis(Axis::default()
.title("Y Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::Italic))
.bounds([-20.0, 20.0])
.labels(&["-20", "0", "20"]))
.datasets(&[Dataset::default()
.name("data2")
.marker(Marker::Dot)
.style(Style::default().fg(Color::Cyan))
.data(&app.data2),
Dataset::default()
.name("data3")
.marker(Marker::Braille)
.style(Style::default().fg(Color::Yellow))
.data(&app.data3)])
.render(t, &chunks[1]);
}
});
Paragraph::default()
.block(Block::default()
.borders(border::ALL)
.title("Footer")
.title_style(Style::default().fg(Color::Magenta).modifier(Modifier::Bold)))
.wrap(true)
.text("This is a paragraph with several lines.\nYou can change the color.\nUse \
\\{fg=[color];bg=[color];mod=[modifier] [text]} to highlight the text with a color. For example, {fg=red \
u}{fg=green n}{fg=yellow d}{fg=magenta e}{fg=cyan r} {fg=gray t}{fg=light_gray h}{fg=light_red \
e} {fg=light_green r}{fg=light_yellow a}{fg=light_magenta i}{fg=light_cyan n}{fg=white \
b}{fg=red o}{fg=green w}.\nOh, and if you didn't {mod=italic notice} you can {mod=bold automatically} \
{mod=invert wrap} your {mod=underline text} =).\nOne more thing is that it should display unicode \
characters properly: 日本国, ٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃).")
.render(t, &chunks[2]);
draw_gauges(t, app, &chunks[0]);
draw_charts(t, app, &chunks[1]);
draw_text(t, &chunks[2]);
});
}
fn draw_second_tab(t: &mut Terminal<TermionBackend>, app: &App, area: &Rect) {
fn draw_gauges(t: &mut Terminal<MouseBackend>, app: &App, area: &Rect) {
Block::default()
.borders(Borders::ALL)
.title("Graphs")
.render(t, area);
Group::default()
.direction(Direction::Vertical)
.margin(1)
.sizes(&[Size::Fixed(2), Size::Fixed(3)])
.render(t, area, |t, chunks| {
Gauge::default()
.block(Block::default().title("Gauge:"))
.style(
Style::default()
.fg(Color::Magenta)
.bg(Color::Black)
.modifier(Modifier::Italic),
)
.label(&format!("{} / 100", app.progress))
.percent(app.progress)
.render(t, &chunks[0]);
Sparkline::default()
.block(Block::default().title("Sparkline:"))
.style(Style::default().fg(Color::Green))
.data(&app.data)
.render(t, &chunks[1]);
});
}
fn draw_charts(t: &mut Terminal<MouseBackend>, app: &App, area: &Rect) {
let sizes = if app.show_chart {
vec![Size::Percent(50), Size::Percent(50)]
} else {
vec![Size::Percent(100)]
};
Group::default()
.direction(Direction::Horizontal)
.sizes(&sizes)
.render(t, area, |t, chunks| {
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[0], |t, chunks| {
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[0], |t, chunks| {
SelectableList::default()
.block(Block::default().borders(Borders::ALL).title("List"))
.items(&app.items)
.select(app.selected)
.highlight_style(
Style::default().fg(Color::Yellow).modifier(Modifier::Bold),
)
.highlight_symbol(">")
.render(t, &chunks[0]);
let info_style = Style::default().fg(Color::White);
let warning_style = Style::default().fg(Color::Yellow);
let error_style = Style::default().fg(Color::Magenta);
let critical_style = Style::default().fg(Color::Red);
let events = app.events.iter().map(|&(evt, level)| {
Item::StyledData(
format!("{}: {}", level, evt),
match level {
"ERROR" => &error_style,
"CRITICAL" => &critical_style,
"WARNING" => &warning_style,
_ => &info_style,
},
)
});
List::new(events)
.block(Block::default().borders(Borders::ALL).title("List"))
.render(t, &chunks[1]);
});
BarChart::default()
.block(Block::default().borders(Borders::ALL).title("Bar chart"))
.data(&app.data4)
.bar_width(3)
.bar_gap(2)
.value_style(
Style::default()
.fg(Color::Black)
.bg(Color::Green)
.modifier(Modifier::Italic),
)
.label_style(Style::default().fg(Color::Yellow))
.style(Style::default().fg(Color::Green))
.render(t, &chunks[1]);
});
if app.show_chart {
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.data2),
Dataset::default()
.name("data3")
.marker(Marker::Braille)
.style(Style::default().fg(Color::Yellow))
.data(&app.data3),
])
.render(t, &chunks[1]);
}
});
}
fn draw_text(t: &mut Terminal<MouseBackend>, area: &Rect) {
Paragraph::default()
.block(
Block::default()
.borders(Borders::ALL)
.title("Footer")
.title_style(Style::default().fg(Color::Magenta).modifier(Modifier::Bold)),
)
.wrap(true)
.text(
"This is a paragraph with several lines.\nYou can change the color.\nUse \
\\{fg=[color];bg=[color];mod=[modifier] [text]} to highlight the text with a color. \
For example, {fg=red u}{fg=green n}{fg=yellow d}{fg=magenta e}{fg=cyan r} \
{fg=gray t}{fg=light_gray h}{fg=light_red e} {fg=light_green r}{fg=light_yellow a} \
{fg=light_magenta i}{fg=light_cyan n}{fg=white b}{fg=red o}{fg=green w}.\n\
Oh, and if you didn't {mod=italic notice} you can {mod=bold automatically} \
{mod=invert wrap} your {mod=underline text} =).\nOne more thing is that \
it should display unicode characters properly: 日本国, ٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ \
٩(-̮̮̃•̃).",
)
.render(t, area);
}
fn draw_second_tab(t: &mut Terminal<MouseBackend>, app: &App, area: &Rect) {
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(30), Size::Percent(70)])
.render(t, area, |t, chunks| {
let up_style = Style::default().fg(Color::Green);
let failure_style = Style::default().fg(Color::Red);
Table::default()
.block(Block::default()
.title("Servers")
.borders(border::ALL))
.header(&["Server", "Location", "Status"])
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))
.header_style(Style::default().fg(Color::Yellow))
.widths(&[15, 15, 10])
.rows(&app.servers
.iter()
.map(|s| {
(vec![s.name, s.location, s.status],
if s.status == "Up" {
&up_style
} else {
&failure_style
})
})
.collect::<Vec<(Vec<&str>, &Style)>>())
.render(t, &chunks[0]);
Canvas::default()
.block(Block::default().title("World").borders(border::ALL))
.block(Block::default().title("World").borders(Borders::ALL))
.paint(|ctx| {
ctx.draw(&Map {
color: Color::White,

View File

@@ -1,5 +1,5 @@
extern crate tui;
extern crate termion;
extern crate tui;
use std::io;
use std::thread;
@@ -10,12 +10,13 @@ use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, border, Gauge};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Gauge, Widget};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Modifier, Style};
struct App {
size: Rect,
progress1: u16,
progress2: u16,
progress3: u16,
@@ -25,6 +26,7 @@ struct App {
impl App {
fn new() -> App {
App {
size: Rect::default(),
progress1: 0,
progress2: 0,
progress3: 0,
@@ -59,7 +61,7 @@ enum Event {
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
@@ -80,11 +82,9 @@ fn main() {
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
thread::spawn(move || loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
});
// App
@@ -93,16 +93,21 @@ fn main() {
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app);
loop {
let size = terminal.size().unwrap();
if size != app.size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
if input == event::Key::Char('q') {
break;
}
}
Event::Input(input) => if input == event::Key::Char('q') {
break;
},
Event::Tick => {
app.advance();
}
@@ -113,33 +118,35 @@ fn main() {
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Vertical)
.margin(2)
.sizes(&[Size::Percent(25), Size::Percent(25), Size::Percent(25), Size::Percent(25)])
.render(t, &size, |t, chunks| {
.sizes(&[
Size::Percent(25),
Size::Percent(25),
Size::Percent(25),
Size::Percent(25),
])
.render(t, &app.size, |t, chunks| {
Gauge::default()
.block(Block::default().title("Gauge1").borders(border::ALL))
.block(Block::default().title("Gauge1").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow))
.percent(app.progress1)
.render(t, &chunks[0]);
Gauge::default()
.block(Block::default().title("Gauge2").borders(border::ALL))
.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(t, &chunks[1]);
Gauge::default()
.block(Block::default().title("Gauge2").borders(border::ALL))
.block(Block::default().title("Gauge2").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow))
.percent(app.progress3)
.render(t, &chunks[2]);
Gauge::default()
.block(Block::default().title("Gauge3").borders(border::ALL))
.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))

104
examples/layout.rs Normal file
View File

@@ -0,0 +1,104 @@
extern crate log;
extern crate stderrlog;
extern crate termion;
extern crate tui;
use std::io;
use std::thread;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Widget};
use tui::layout::{Direction, Group, Rect, Size};
struct App {
size: Rect,
}
impl App {
fn new() -> App {
App {
size: Rect::default(),
}
}
}
enum Event {
Input(event::Key),
}
fn main() {
stderrlog::new().verbosity(4).init().unwrap();
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// 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;
}
}
});
// 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);
loop {
let size = terminal.size().unwrap();
if size != app.size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => match input {
event::Key::Char('q') => {
break;
}
_ => {}
},
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Percent(10), Size::Percent(80), Size::Percent(10)])
.render(t, &app.size, |t, chunks| {
Block::default()
.title("Block")
.borders(Borders::ALL)
.render(t, &chunks[0]);
Block::default()
.title("Block 2")
.borders(Borders::ALL)
.render(t, &chunks[2]);
});
t.draw().unwrap();
}

View File

@@ -1,5 +1,5 @@
extern crate tui;
extern crate termion;
extern crate tui;
use std::io;
use std::thread;
@@ -10,12 +10,13 @@ use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, border, SelectableList, List};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Item, List, SelectableList, Widget};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Modifier, Style};
struct App<'a> {
size: Rect,
items: Vec<&'a str>,
selected: usize,
events: Vec<(&'a str, &'a str)>,
@@ -28,37 +29,41 @@ struct App<'a> {
impl<'a> App<'a> {
fn new() -> App<'a> {
App {
items: vec!["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8",
"Item9", "Item10", "Item11", "Item12", "Item13", "Item14", "Item15",
"Item16", "Item17", "Item18", "Item19", "Item20", "Item21", "Item22",
"Item23", "Item24"],
size: Rect::default(),
items: vec![
"Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9",
"Item10", "Item11", "Item12", "Item13", "Item14", "Item15", "Item16", "Item17",
"Item18", "Item19", "Item20", "Item21", "Item22", "Item23", "Item24",
],
selected: 0,
events: vec![("Event1", "INFO"),
("Event2", "INFO"),
("Event3", "CRITICAL"),
("Event4", "ERROR"),
("Event5", "INFO"),
("Event6", "INFO"),
("Event7", "WARNING"),
("Event8", "INFO"),
("Event9", "INFO"),
("Event10", "INFO"),
("Event11", "CRITICAL"),
("Event12", "INFO"),
("Event13", "INFO"),
("Event14", "INFO"),
("Event15", "INFO"),
("Event16", "INFO"),
("Event17", "ERROR"),
("Event18", "ERROR"),
("Event19", "INFO"),
("Event20", "INFO"),
("Event21", "WARNING"),
("Event22", "INFO"),
("Event23", "INFO"),
("Event24", "WARNING"),
("Event25", "INFO"),
("Event26", "INFO")],
events: vec![
("Event1", "INFO"),
("Event2", "INFO"),
("Event3", "CRITICAL"),
("Event4", "ERROR"),
("Event5", "INFO"),
("Event6", "INFO"),
("Event7", "WARNING"),
("Event8", "INFO"),
("Event9", "INFO"),
("Event10", "INFO"),
("Event11", "CRITICAL"),
("Event12", "INFO"),
("Event13", "INFO"),
("Event14", "INFO"),
("Event15", "INFO"),
("Event16", "INFO"),
("Event17", "ERROR"),
("Event18", "ERROR"),
("Event19", "INFO"),
("Event20", "INFO"),
("Event21", "WARNING"),
("Event22", "INFO"),
("Event23", "INFO"),
("Event24", "WARNING"),
("Event25", "INFO"),
("Event26", "INFO"),
],
info_style: Style::default().fg(Color::White),
warning_style: Style::default().fg(Color::Yellow),
error_style: Style::default().fg(Color::Magenta),
@@ -79,7 +84,7 @@ enum Event {
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
@@ -100,11 +105,9 @@ fn main() {
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
thread::spawn(move || loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
});
// App
@@ -113,32 +116,35 @@ fn main() {
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app);
loop {
let size = terminal.size().unwrap();
if size != app.size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
match input {
event::Key::Char('q') => {
break;
}
event::Key::Down => {
app.selected += 1;
if app.selected > app.items.len() - 1 {
app.selected = 0;
}
}
event::Key::Up => {
if app.selected > 0 {
app.selected -= 1;
} else {
app.selected = app.items.len() - 1;
}
}
_ => {}
Event::Input(input) => match input {
event::Key::Char('q') => {
break;
}
}
event::Key::Down => {
app.selected += 1;
if app.selected > app.items.len() - 1 {
app.selected = 0;
}
}
event::Key::Up => if app.selected > 0 {
app.selected -= 1;
} else {
app.selected = app.items.len() - 1;
},
_ => {}
},
Event::Tick => {
app.advance();
}
@@ -149,40 +155,36 @@ fn main() {
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &size, |t, chunks| {
.render(t, &app.size, |t, chunks| {
let style = Style::default().fg(Color::Black).bg(Color::White);
SelectableList::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.block(Block::default().borders(Borders::ALL).title("List"))
.items(&app.items)
.select(app.selected)
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.style(style)
.highlight_style(style.clone().fg(Color::LightGreen).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(t, &chunks[0]);
List::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.items(&app.events
.iter()
.map(|&(evt, level)| {
(format!("{}: {}", level, evt),
match level {
{
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,
})
})
.collect::<Vec<(String, &Style)>>())
.render(t, &chunks[1]);
},
)
});
List::new(events)
.block(Block::default().borders(Borders::ALL).title("List"))
.render(t, &chunks[1]);
}
});
t.draw().unwrap();

View File

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

View File

@@ -1,14 +1,14 @@
extern crate tui;
extern crate rustbox;
extern crate tui;
use std::error::Error;
use rustbox::Key;
use tui::Terminal;
use tui::backend::RustboxBackend;
use tui::widgets::{Widget, Block, border, Paragraph};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
use tui::widgets::{Block, Borders, Paragraph, Widget};
use tui::layout::{Direction, Group, Size};
use tui::style::{Color, Modifier, Style};
fn main() {
let mut terminal = Terminal::new(RustboxBackend::new().unwrap()).unwrap();
@@ -17,11 +17,9 @@ fn main() {
draw(&mut terminal);
loop {
match terminal.backend().rustbox().poll_event(false) {
Ok(rustbox::Event::KeyEvent(key)) => {
if key == Key::Char('q') {
break;
}
}
Ok(rustbox::Event::KeyEvent(key)) => if key == Key::Char('q') {
break;
},
Err(e) => panic!("{}", e.description()),
_ => {}
};
@@ -31,7 +29,6 @@ fn main() {
}
fn draw(t: &mut Terminal<RustboxBackend>) {
let size = t.size().unwrap();
Group::default()
@@ -39,11 +36,13 @@ fn draw(t: &mut Terminal<RustboxBackend>) {
.sizes(&[Size::Percent(100)])
.render(t, &size, |t, chunks| {
Paragraph::default()
.block(Block::default()
.title("Rustbox backend")
.title_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.borders(border::ALL)
.border_style(Style::default().fg(Color::Magenta)))
.block(
Block::default()
.title("Rustbox backend")
.title_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Magenta)),
)
.text("It {yellow works}!")
.render(t, &chunks[0]);
});

View File

@@ -1,5 +1,5 @@
extern crate tui;
extern crate termion;
extern crate tui;
mod util;
use util::*;
@@ -13,12 +13,13 @@ use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, border, Sparkline};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color};
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Sparkline, Widget};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Style};
struct App {
size: Rect,
signal: RandomSignal,
data1: Vec<u64>,
data2: Vec<u64>,
@@ -32,6 +33,7 @@ impl App {
let data2 = signal.by_ref().take(200).collect::<Vec<u64>>();
let data3 = signal.by_ref().take(200).collect::<Vec<u64>>();
App {
size: Rect::default(),
signal: signal,
data1: data1,
data2: data2,
@@ -59,7 +61,7 @@ enum Event {
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
@@ -80,11 +82,9 @@ fn main() {
});
// Tick
thread::spawn(move || {
loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
}
thread::spawn(move || loop {
clock_tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(500));
});
// App
@@ -93,16 +93,21 @@ fn main() {
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app);
loop {
let size = terminal.size().unwrap();
if size != app.size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
if input == event::Key::Char('q') {
break;
}
}
Event::Input(input) => if input == event::Key::Char('q') {
break;
},
Event::Tick => {
app.advance();
}
@@ -113,28 +118,37 @@ fn main() {
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Vertical)
.margin(2)
.sizes(&[Size::Fixed(3), Size::Fixed(3), Size::Fixed(7), Size::Min(0)])
.render(t, &size, |t, chunks| {
.render(t, &app.size, |t, chunks| {
Sparkline::default()
.block(Block::default().title("Data1").borders(border::LEFT | border::RIGHT))
.block(
Block::default()
.title("Data1")
.borders(Borders::LEFT | Borders::RIGHT),
)
.data(&app.data1)
.style(Style::default().fg(Color::Yellow))
.render(t, &chunks[0]);
Sparkline::default()
.block(Block::default().title("Data2").borders(border::LEFT | border::RIGHT))
.block(
Block::default()
.title("Data2")
.borders(Borders::LEFT | Borders::RIGHT),
)
.data(&app.data2)
.style(Style::default().bg(Color::Green))
.render(t, &chunks[1]);
// Multiline
Sparkline::default()
.block(Block::default().title("Data3").borders(border::LEFT | border::RIGHT))
.block(
Block::default()
.title("Data3")
.borders(Borders::LEFT | Borders::RIGHT),
)
.data(&app.data3)
.style(Style::default().fg(Color::Red))
.render(t, &chunks[2]);

View File

@@ -1,5 +1,5 @@
extern crate tui;
extern crate termion;
extern crate tui;
use std::io;
@@ -7,12 +7,13 @@ use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::TermionBackend;
use tui::widgets::{Widget, Block, border, Table};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Row, Table, Widget};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Modifier, Style};
struct App<'a> {
size: Rect,
items: Vec<Vec<&'a str>>,
selected: usize,
}
@@ -20,12 +21,15 @@ struct App<'a> {
impl<'a> App<'a> {
fn new() -> App<'a> {
App {
items: vec![vec!["Row12", "Row12", "Row13"],
vec!["Row21", "Row22", "Row23"],
vec!["Row31", "Row32", "Row33"],
vec!["Row41", "Row42", "Row43"],
vec!["Row41", "Row42", "Row43"],
vec!["Row41", "Row42", "Row43"]],
size: Rect::default(),
items: vec![
vec!["Row12", "Row12", "Row13"],
vec!["Row21", "Row22", "Row23"],
vec!["Row31", "Row32", "Row33"],
vec!["Row41", "Row42", "Row43"],
vec!["Row51", "Row52", "Row53"],
vec!["Row61", "Row62", "Row63"],
],
selected: 0,
}
}
@@ -33,21 +37,27 @@ impl<'a> App<'a> {
fn main() {
// Terminal initialization
let backend = TermionBackend::new().unwrap();
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// 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);
// Input
let stdin = io::stdin();
for c in stdin.keys() {
let size = terminal.size().unwrap();
if size != app.size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = c.unwrap();
match evt {
event::Key::Char('q') => {
@@ -59,50 +69,39 @@ fn main() {
app.selected = 0;
}
}
event::Key::Up => {
if app.selected > 0 {
app.selected -= 1;
} else {
app.selected = app.items.len() - 1;
}
}
event::Key::Up => if app.selected > 0 {
app.selected -= 1;
} else {
app.selected = app.items.len() - 1;
},
_ => {}
};
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
terminal.clear().unwrap();
}
fn draw(t: &mut Terminal<TermionBackend>, app: &App) {
let size = t.size().unwrap();
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
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)
}
});
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(100)])
.margin(5)
.render(t, &size, |t, chunks| {
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold);
let normal_style = Style::default().fg(Color::White);
Table::default()
.block(Block::default()
.borders(border::ALL)
.title("Table"))
.header(&["Header1", "Header2", "Header3"])
.render(t, &app.size, |t, chunks| {
Table::new(header.into_iter(), rows)
.block(Block::default().borders(Borders::ALL).title("Table"))
.widths(&[10, 10, 10])
.rows(&app.items
.iter()
.enumerate()
.map(|(i, item)| {
(item,
if i == app.selected {
&selected_style
} else {
&normal_style
})
})
.collect::<Vec<(&Vec<&str>, &Style)>>())
.render(t, &chunks[0]);
});

View File

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

131
examples/user_input.rs Normal file
View File

@@ -0,0 +1,131 @@
/// 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
/// started.
///
/// This is a very simple example:
/// * A input box always focused. Every character you type is registered
/// here
/// * Pressing Backspace erases a character
/// * Pressing Enter pushes the current input in the history of previous
/// messages
extern crate termion;
extern crate tui;
use std::io;
use std::thread;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Item, List, Paragraph, Widget};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Style};
struct App {
size: Rect,
input: String,
messages: Vec<String>,
}
impl App {
fn new() -> App {
App {
size: Rect::default(),
input: String::new(),
messages: Vec::new(),
}
}
}
enum Event {
Input(event::Key),
}
fn main() {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// 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;
}
}
});
// 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);
loop {
let size = terminal.size().unwrap();
if app.size != size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => match input {
event::Key::Char('q') => {
break;
}
event::Key::Char('\n') => {
app.messages.push(app.input.drain(..).collect());
}
event::Key::Char(c) => {
app.input.push(c);
}
event::Key::Backspace => {
app.input.pop();
}
_ => {}
},
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
terminal.clear().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Vertical)
.margin(2)
.sizes(&[Size::Fixed(3), Size::Min(1)])
.render(t, &app.size, |t, chunks| {
Paragraph::default()
.style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("Input"))
.text(&app.input)
.render(t, &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(t, &chunks[1]);
});
t.draw().unwrap();
}

View File

@@ -1,30 +1,9 @@
#![allow(dead_code)]
extern crate rand;
extern crate log4rs;
extern crate log;
use self::rand::distributions::{IndependentSample, Range};
use self::log::LogLevelFilter;
use self::log4rs::append::file::FileAppender;
use self::log4rs::encode::pattern::PatternEncoder;
use self::log4rs::config::{Appender, Config, Root};
pub fn setup_log(file_name: &str) {
let log = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{l} / {d(%H:%M:%S)} / \
{M}:{L}{n}{m}{n}{n}")))
.build(file_name)
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("log", Box::new(log)))
.build(Root::builder().appender("log").build(LogLevelFilter::Debug))
.unwrap();
log4rs::init_config(config).unwrap();
}
#[derive(Clone)]
pub struct RandomSignal {
range: Range<u64>,

15
scripts/build-examples.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Build all examples in examples directory
set -eu -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

14
scripts/run-examples.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
# Run all examples in examples directory
set -eu -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

65
scripts/tools/install.sh Executable file
View File

@@ -0,0 +1,65 @@
#!/bin/bash
set -e
USAGE="$0 --name=STRING [--channel=STRING]"
# Default values
CARGO="cargo"
NAME=""
CHANNEL=""
# Parse args
for i in "$@"; do
case $i in
--name=*)
NAME="${i#*=}"
shift
;;
--channel=*)
CHANNEL="${i#*=}"
shift
;;
*)
echo "$USAGE"
exit 1
;;
esac
done
current_version() {
local crate="$1"
$CARGO install --list | \
grep "$crate" | \
head -n 1 | \
cut -d ' ' -f 2 | \
sed 's/v\(.*\):/\1/g'
}
upstream_version() {
local crate="$1"
$CARGO search "$crate" | \
grep "$crate" | \
head -n 1 | \
cut -d' ' -f 3 | \
sed 's/"//g'
}
if [ "$NAME" == "" ]; then
echo "$USAGE"
exit 1
fi
if [ "$CHANNEL" != "" ]; then
CARGO+=" +$CHANNEL"
fi
CURRENT_VERSION=$(current_version "$NAME")
UPSTREAM_VERSION=$(upstream_version "$NAME")
if [[ "$CURRENT_VERSION" != "$UPSTREAM_VERSION" ]]; then
echo "WARN: Latest version of $NAME not installed: $CURRENT_VERSION -> $UPSTREAM_VERSION"
$CARGO install --force "$NAME"
else
echo "INFO: Latest version of $NAME already installed ($CURRENT_VERSION)"
fi

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -eu
export PATH="$PATH:$HOME/.cargo/bin"
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
export LD_LIBRARY_PATH="$(rustc +nightly --print sysroot)/lib:${LD_LIBRARY_PATH:-""}"
make install-tools
fi

9
scripts/travis/script.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -eu
make build
make test
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
make lint
fi

View File

@@ -11,11 +11,12 @@ pub use self::rustbox::RustboxBackend;
#[cfg(feature = "termion")]
mod termion;
#[cfg(feature = "termion")]
pub use self::termion::TermionBackend;
pub use self::termion::{MouseBackend, RawBackend, TermionBackend, AlternateScreenBackend};
pub trait Backend {
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
where I: Iterator<Item = (u16, u16, &'a Cell)>;
where
I: Iterator<Item = (u16, u16, &'a Cell)>;
fn hide_cursor(&mut self) -> Result<(), io::Error>;
fn show_cursor(&mut self) -> Result<(), io::Error>;
fn clear(&mut self) -> Result<(), io::Error>;

View File

@@ -28,17 +28,20 @@ impl RustboxBackend {
impl Backend for RustboxBackend {
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
where I: Iterator<Item = (u16, u16, &'a Cell)>
where
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
let mut inst = 0;
for (x, y, cell) in content {
inst += 1;
self.rustbox.print(x as usize,
y as usize,
cell.style.modifier.into(),
cell.style.fg.into(),
cell.style.bg.into(),
&cell.symbol);
self.rustbox.print(
x as usize,
y as usize,
cell.style.modifier.into(),
cell.style.fg.into(),
cell.style.bg.into(),
&cell.symbol,
);
}
debug!("{} instructions outputed", inst);
Ok(())
@@ -54,12 +57,12 @@ impl Backend for RustboxBackend {
Ok(())
}
fn size(&self) -> Result<Rect, io::Error> {
Ok((Rect {
Ok(Rect {
x: 0,
y: 0,
width: self.rustbox.width() as u16,
height: self.rustbox.height() as u16,
}))
})
}
fn flush(&mut self) -> Result<(), io::Error> {
self.rustbox.present();
@@ -68,7 +71,7 @@ impl Backend for RustboxBackend {
}
fn rgb_to_byte(r: u8, g: u8, b: u8) -> u16 {
((((r & 255 & 0xC0) + ((g & 255 & 0xE0) >> 2) + ((b & 0xE0) >> 5))) & 0xFF) as u16
u16::from((r & 0xC0) + ((g & 0xE0) >> 2) + ((b & 0xE0) >> 5))
}
impl Into<rustbox::Color> for Color {

View File

@@ -3,49 +3,97 @@ extern crate termion;
use std::io;
use std::io::Write;
use self::termion::raw::{IntoRawMode, RawTerminal};
use self::termion::raw::IntoRawMode;
use super::Backend;
use buffer::Cell;
use layout::Rect;
use style::{Style, Color, Modifier};
use style::{Color, Modifier, Style};
pub struct TermionBackend {
stdout: RawTerminal<io::Stdout>,
pub struct TermionBackend<W>
where
W: Write,
{
stdout: W,
}
impl TermionBackend {
pub fn new() -> Result<TermionBackend, io::Error> {
let stdout = try!(io::stdout().into_raw_mode());
Ok(TermionBackend { stdout: stdout })
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 Backend for TermionBackend {
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> {
TermionBackend { stdout }
}
}
impl<W> Write for TermionBackend<W>
where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stdout.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
}
impl<W> Backend for TermionBackend<W>
where
W: Write,
{
/// Clears the entire screen and move the cursor to the top left of the screen
fn clear(&mut self) -> Result<(), io::Error> {
fn clear(&mut self) -> io::Result<()> {
write!(self.stdout, "{}", termion::clear::All)?;
write!(self.stdout, "{}", termion::cursor::Goto(1, 1))?;
self.stdout.flush()?;
Ok(())
self.stdout.flush()
}
/// Hides cursor
fn hide_cursor(&mut self) -> Result<(), io::Error> {
fn hide_cursor(&mut self) -> io::Result<()> {
write!(self.stdout, "{}", termion::cursor::Hide)?;
self.stdout.flush()?;
Ok(())
self.stdout.flush()
}
/// Shows cursor
fn show_cursor(&mut self) -> Result<(), io::Error> {
fn show_cursor(&mut self) -> io::Result<()> {
write!(self.stdout, "{}", termion::cursor::Show)?;
self.stdout.flush()?;
Ok(())
self.stdout.flush()
}
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
where I: Iterator<Item = (u16, u16, &'a Cell)>
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
where
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
let mut string = String::with_capacity(content.size_hint().0 * 3);
let mut style = Style::default();
@@ -82,13 +130,14 @@ impl Backend for TermionBackend {
inst += 1;
}
debug!("{} instructions outputed.", inst);
write!(self.stdout,
"{}{}{}{}",
string,
Color::Reset.termion_fg(),
Color::Reset.termion_bg(),
Modifier::Reset.termion_modifier())?;
Ok(())
write!(
self.stdout,
"{}{}{}{}",
string,
Color::Reset.termion_fg(),
Color::Reset.termion_bg(),
Modifier::Reset.termion_modifier()
)
}
/// Return the size of the terminal
@@ -102,30 +151,39 @@ impl Backend for TermionBackend {
})
}
fn flush(&mut self) -> Result<(), io::Error> {
try!(self.stdout.flush());
Ok(())
fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
}
macro_rules! termion_fg {
($color:ident) => (format!("{}", termion::color::Fg(termion::color::$color)));
($color:ident) => (
format!("{}", termion::color::Fg(termion::color::$color))
);
}
macro_rules! termion_fg_rgb {
($r:expr, $g:expr, $b:expr) => (format!("{}", termion::color::Fg(termion::color::Rgb($r, $g, $b))));
($r:expr, $g:expr, $b:expr) => (
format!("{}", termion::color::Fg(termion::color::Rgb($r, $g, $b)))
);
}
macro_rules! termion_bg {
($color:ident) => (format!("{}", termion::color::Bg(termion::color::$color)));
($color:ident) => (
format!("{}", termion::color::Bg(termion::color::$color))
);
}
macro_rules! termion_bg_rgb {
($r:expr, $g:expr, $b:expr) => (format!("{}", termion::color::Bg(termion::color::Rgb($r, $g, $b))));
($r:expr, $g:expr, $b:expr) => (
format!("{}", termion::color::Bg(termion::color::Rgb($r, $g, $b)))
);
}
macro_rules! termion_modifier {
($style:ident) => (format!("{}", termion::style::$style));
($style:ident) => (
format!("{}", termion::style::$style)
);
}
impl Color {
@@ -136,16 +194,18 @@ impl Color {
Color::Red => termion_fg!(Red),
Color::Green => termion_fg!(Green),
Color::Yellow => termion_fg!(Yellow),
Color::Blue => termion_fg!(Blue),
Color::Magenta => termion_fg!(Magenta),
Color::Cyan => termion_fg!(Cyan),
Color::Gray => termion_fg_rgb!(146, 131, 116),
Color::DarkGray => termion_fg_rgb!(80, 73, 69),
Color::Gray => termion_fg!(White),
Color::DarkGray => termion_fg!(LightBlack),
Color::LightRed => termion_fg!(LightRed),
Color::LightGreen => termion_fg!(LightGreen),
Color::LightBlue => termion_fg!(LightBlue),
Color::LightYellow => termion_fg!(LightYellow),
Color::LightMagenta => termion_fg!(LightMagenta),
Color::LightCyan => termion_fg!(LightCyan),
Color::White => termion_fg!(White),
Color::White => termion_fg!(LightWhite),
Color::Rgb(r, g, b) => termion_fg_rgb!(r, g, b),
}
}
@@ -156,16 +216,18 @@ impl Color {
Color::Red => termion_bg!(Red),
Color::Green => termion_bg!(Green),
Color::Yellow => termion_bg!(Yellow),
Color::Blue => termion_bg!(Blue),
Color::Magenta => termion_bg!(Magenta),
Color::Cyan => termion_bg!(Cyan),
Color::Gray => termion_bg_rgb!(146, 131, 116),
Color::DarkGray => termion_bg_rgb!(80, 73, 69),
Color::Gray => termion_bg!(White),
Color::DarkGray => termion_bg!(LightBlack),
Color::LightRed => termion_bg!(LightRed),
Color::LightGreen => termion_bg!(LightGreen),
Color::LightBlue => termion_bg!(LightBlue),
Color::LightYellow => termion_bg!(LightYellow),
Color::LightMagenta => termion_bg!(LightMagenta),
Color::LightCyan => termion_bg!(LightCyan),
Color::White => termion_bg!(White),
Color::White => termion_bg!(LightWhite),
Color::Rgb(r, g, b) => termion_bg_rgb!(r, g, b),
}
}

View File

@@ -4,7 +4,7 @@ use std::usize;
use unicode_segmentation::UnicodeSegmentation;
use layout::Rect;
use style::{Style, Color, Modifier};
use style::{Color, Modifier, Style};
/// A buffer cell
#[derive(Debug, Clone, PartialEq)]
@@ -115,11 +115,11 @@ impl Buffer {
/// Returns a Buffer with all cells set to the default one
pub fn empty(area: Rect) -> Buffer {
let cell: Cell = Default::default();
Buffer::filled(area, cell)
Buffer::filled(area, &cell)
}
/// Returns a Buffer with all cells initialized with the attributes of the given Cell
pub fn filled(area: Rect, cell: Cell) -> Buffer {
pub fn filled(area: Rect, cell: &Cell) -> Buffer {
let size = area.area() as usize;
let mut content = Vec::with_capacity(size);
for _ in 0..size {
@@ -153,24 +153,84 @@ impl Buffer {
&mut self.content[i]
}
/// Returns the index in the Vec<Cell> for the given (x, y)
/// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
///
/// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
///
/// # Examples
///
/// ```
/// # use tui::buffer::Buffer;
/// # use tui::layout::Rect;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(rect);
/// // Global coordinates to the top corner of this buffer's area
/// assert_eq!(buffer.index_of(200, 100), 0);
/// ```
///
/// # Panics
///
/// Panics when given an coordinate that is outside of this Buffer's area.
///
/// ```should_panic
/// # use tui::buffer::Buffer;
/// # use tui::layout::Rect;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(rect);
/// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
/// // starts at (200, 100).
/// buffer.index_of(0, 0); // Panics
/// ```
pub fn index_of(&self, x: u16, y: u16) -> usize {
debug_assert!(x >= self.area.left() && x < self.area.right() && y >= self.area.top() &&
y < self.area.bottom(),
"Trying to access position outside the buffer: x={}, y={}, area={:?}",
x,
y,
self.area);
debug_assert!(
x >= self.area.left() && x < self.area.right() && y >= self.area.top()
&& y < self.area.bottom(),
"Trying to access position outside the buffer: x={}, y={}, area={:?}",
x,
y,
self.area
);
((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
}
/// Returns the coordinates of a cell given its index
/// Returns the (global) coordinates of a cell given its index
///
/// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
///
/// # Examples
///
/// ```
/// # use tui::buffer::Buffer;
/// # use tui::layout::Rect;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(rect);
/// assert_eq!(buffer.pos_of(0), (200, 100));
/// assert_eq!(buffer.pos_of(14), (204, 101));
/// ```
///
/// # Panics
///
/// Panics when given an index that is outside the Buffer's content.
///
/// ```should_panic
/// # use tui::buffer::Buffer;
/// # use tui::layout::Rect;
/// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
/// let buffer = Buffer::empty(rect);
/// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
/// buffer.pos_of(100); // Panics
/// ```
pub fn pos_of(&self, i: usize) -> (u16, u16) {
debug_assert!(i >= self.content.len(),
"Trying to get the coords of a cell outside the buffer: i={} len={}",
i,
self.content.len());
(self.area.x + i as u16 % self.area.width, self.area.y + i as u16 / self.area.width)
debug_assert!(
i < self.content.len(),
"Trying to get the coords of a cell outside the buffer: i={} len={}",
i,
self.content.len()
);
(
self.area.x + i as u16 % self.area.width,
self.area.y + i as u16 / self.area.width,
)
}
/// Print a string, starting at the position (x, y)
@@ -212,7 +272,7 @@ impl Buffer {
}
/// Merge an other buffer into this one
pub fn merge(&mut self, other: Buffer) {
pub fn merge(&mut self, other: &Buffer) {
let area = self.area.union(&other.area);
let cell: Cell = Default::default();
self.content.resize(area.area() as usize, cell.clone());
@@ -245,3 +305,42 @@ impl Buffer {
self.area = area;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_translates_to_and_from_coordinates() {
let rect = Rect::new(200, 100, 50, 80);
let buf = Buffer::empty(rect);
// First cell is at the upper left corner.
assert_eq!(buf.pos_of(0), (200, 100));
assert_eq!(buf.index_of(200, 100), 0);
// Last cell is in the lower right.
assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
}
#[test]
#[should_panic(expected = "outside the buffer")]
fn pos_of_panics_on_out_of_bounds() {
let rect = Rect::new(0, 0, 10, 10);
let buf = Buffer::empty(rect);
// There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
buf.pos_of(100);
}
#[test]
#[should_panic(expected = "outside the buffer")]
fn index_of_panics_on_out_of_bounds() {
let rect = Rect::new(0, 0, 10, 10);
let buf = Buffer::empty(rect);
// width is 10; zero-indexed means that 10 would be the 11th cell.
buf.index_of(10, 0);
}
}

View File

@@ -1,14 +1,14 @@
use std::cmp::{min, max};
use std::cmp::{max, min};
use std::collections::HashMap;
use cassowary::{Solver, Variable, Expression, Constraint};
use cassowary::{Constraint, Expression, Solver, Variable};
use cassowary::WeightedRelation::*;
use cassowary::strength::{REQUIRED, WEAK};
use terminal::Terminal;
use backend::Backend;
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Horizontal,
Vertical,
@@ -105,12 +105,12 @@ impl Rect {
}
pub fn intersects(&self, other: &Rect) -> bool {
self.x < other.x + other.width && self.x + self.width > other.x &&
self.y < other.y + other.height && self.y + self.height > other.y
self.x < other.x + other.width && self.x + self.width > other.x
&& self.y < other.y + other.height && self.y + self.height > other.y
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Size {
Fixed(u16),
Percent(u16),
@@ -139,7 +139,10 @@ pub enum Size {
pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<Rect> {
let mut solver = Solver::new();
let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
let elements = sizes.iter().map(|_| Element::new()).collect::<Vec<Element>>();
let elements = sizes
.iter()
.map(|_| Element::new())
.collect::<Vec<Element>>();
let mut results = sizes.iter().map(|_| Rect::default()).collect::<Vec<Rect>>();
let dest_area = area.inner(margin);
for (i, e) in elements.iter().enumerate() {
@@ -150,21 +153,21 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
}
let mut constraints: Vec<Constraint> = Vec::with_capacity(elements.len() * 4 + sizes.len() * 6);
for elt in &elements {
constraints.push(elt.left() | GE(REQUIRED) | dest_area.left() as f64);
constraints.push(elt.top() | GE(REQUIRED) | dest_area.top() as f64);
constraints.push(elt.right() | LE(REQUIRED) | dest_area.right() as f64);
constraints.push(elt.bottom() | LE(REQUIRED) | dest_area.bottom() as f64);
constraints.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
constraints.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
constraints.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
constraints.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
}
if let Some(first) = elements.first() {
constraints.push(match *dir {
Direction::Horizontal => first.left() | EQ(REQUIRED) | dest_area.left() as f64,
Direction::Vertical => first.top() | EQ(REQUIRED) | dest_area.top() as f64,
Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
});
}
if let Some(last) = elements.last() {
constraints.push(match *dir {
Direction::Horizontal => last.right() | EQ(REQUIRED) | dest_area.right() as f64,
Direction::Vertical => last.bottom() | EQ(REQUIRED) | dest_area.bottom() as f64,
Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
});
}
match *dir {
@@ -173,15 +176,15 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
constraints.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
}
for (i, size) in sizes.iter().enumerate() {
constraints.push(elements[i].y | EQ(REQUIRED) | dest_area.y as f64);
constraints.push(elements[i].height | EQ(REQUIRED) | dest_area.height as f64);
constraints.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
constraints.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
constraints.push(match *size {
Size::Fixed(v) => elements[i].width | EQ(WEAK) | v as f64,
Size::Fixed(v) => elements[i].width | EQ(WEAK) | f64::from(v),
Size::Percent(v) => {
elements[i].width | EQ(WEAK) | ((v * dest_area.width) as f64 / 100.0)
elements[i].width | EQ(WEAK) | (f64::from(v * dest_area.width) / 100.0)
}
Size::Min(v) => elements[i].width | GE(WEAK) | v as f64,
Size::Max(v) => elements[i].width | LE(WEAK) | v as f64,
Size::Min(v) => elements[i].width | GE(WEAK) | f64::from(v),
Size::Max(v) => elements[i].width | LE(WEAK) | f64::from(v),
});
}
}
@@ -190,15 +193,15 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
constraints.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
}
for (i, size) in sizes.iter().enumerate() {
constraints.push(elements[i].x | EQ(REQUIRED) | dest_area.x as f64);
constraints.push(elements[i].width | EQ(REQUIRED) | dest_area.width as f64);
constraints.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
constraints.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
constraints.push(match *size {
Size::Fixed(v) => elements[i].height | EQ(WEAK) | v as f64,
Size::Fixed(v) => elements[i].height | EQ(WEAK) | f64::from(v),
Size::Percent(v) => {
elements[i].height | EQ(WEAK) | ((v * dest_area.height) as f64 / 100.0)
elements[i].height | EQ(WEAK) | (f64::from(v * dest_area.height) / 100.0)
}
Size::Min(v) => elements[i].height | GE(WEAK) | v as f64,
Size::Max(v) => elements[i].height | LE(WEAK) | v as f64,
Size::Min(v) => elements[i].height | GE(WEAK) | f64::from(v),
Size::Max(v) => elements[i].height | LE(WEAK) | f64::from(v),
});
}
}
@@ -206,7 +209,11 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
solver.add_constraints(&constraints).unwrap();
for &(var, value) in solver.fetch_changes() {
let (index, attr) = vars[&var];
let value = value as u16;
let value = if value.is_sign_negative() {
0
} else {
value as u16
};
match attr {
0 => {
results[index].x = value;
@@ -223,6 +230,7 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
_ => {}
}
}
// Fix imprecision by extending the last item a bit if necessary
if let Some(last) = results.last_mut() {
match *dir {
@@ -318,9 +326,10 @@ impl Group {
self.sizes = Vec::from(sizes);
self
}
pub fn render<F, B>(&self, t: &mut Terminal<B>, area: &Rect, mut f: F)
where B: Backend,
F: FnMut(&mut Terminal<B>, &[Rect])
pub fn render<F, B>(&self, t: &mut Terminal<B>, area: &Rect, f: F)
where
B: Backend,
F: FnOnce(&mut Terminal<B>, &[Rect]),
{
let chunks = t.compute_layout(self, area);
f(t, &chunks);

View File

@@ -1,8 +1,163 @@
//! [tui](https://github.com/fdehau/tui-rs) is a library used to build rich
//! terminal users interfaces and dashboards.
//!
//! ![](https://raw.githubusercontent.com/fdehau/tui-rs/master/docs/demo.gif)
//!
//! # Get started
//!
//! ## Creating a `Terminal`
//!
//! Every application using `tui` should start by instantiating a `Terminal`. It is
//! a light abstraction over available backends that provides basic functionalities
//! such as clearing the screen, hiding the cursor, etc. By default only the `termion`
//! backend is available.
//!
//! ```rust,no_run
//! extern crate tui;
//!
//! use tui::Terminal;
//! use tui::backend::RawBackend;
//!
//! fn main() {
//! let backend = RawBackend::new().unwrap();
//! let mut terminal = Terminal::new(backend).unwrap();
//! }
//! ```
//!
//! If for some reason, you might want to use the `rustbox` backend instead, you
//! need the to replace your `tui` dependency specification by:
//!
//! ```toml
//! [dependencies.tui]
//! version = "0.2.0"
//! default-features = false
//! features = ['rustbox']
//! ```
//!
//! and then create the terminal in a similar way:
//!
//! ```rust,ignore
//! extern crate tui;
//!
//! use tui::Terminal;
//! use tui::backend::RustboxBackend;
//!
//! fn main() {
//! let backend = RustboxBackend::new().unwrap();
//! let mut terminal = Terminal::new(backend).unwrap();
//! }
//! ```
//!
//! ## Building a User Interface (UI)
//!
//! Every component of your interface will be implementing the `Widget` trait.
//! The library comes with a predefined set of widgets that should met most of
//! your use cases. You are also free to implement your owns.
//!
//! Each widget follows a builder pattern API providing a default configuration
//! along with methods to customize them. The widget is then registered using
//! its `render` method that take a `Terminal` instance and an area to draw
//! to.
//!
//! The following example renders a block of the size of the terminal:
//!
//! ```rust,no_run
//! extern crate tui;
//!
//! use std::io;
//!
//! use tui::Terminal;
//! use tui::backend::RawBackend;
//! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Group, Size, 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()?;
//!
//! Block::default()
//! .title("Block")
//! .borders(Borders::ALL)
//! .render(t, &size);
//!
//! t.draw()
//! }
//! ```
//!
//! ## Layout
//!
//! The library comes with a basic yet useful layout management object called
//! `Group`. As you may see below and in the examples, the library makes heavy
//! use of the builder pattern to provide full customization. And the `Group`
//! object is no exception:
//!
//! ```rust,no_run
//! extern crate tui;
//!
//! use std::io;
//!
//! use tui::Terminal;
//! use tui::backend::RawBackend;
//! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Group, Size, 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()?;
//!
//! Group::default()
//! .direction(Direction::Vertical)
//! .margin(1)
//! .sizes(&[Size::Percent(10), Size::Percent(80), Size::Percent(10)])
//! .render(t, &size, |t, chunks| {
//! Block::default()
//! .title("Block")
//! .borders(Borders::ALL)
//! .render(t, &chunks[0]);
//! Block::default()
//! .title("Block 2")
//! .borders(Borders::ALL)
//! .render(t, &chunks[2]);
//! });
//!
//! t.draw()
//! }
//! ```
//!
//! This let you describe responsive terminal UI by nesting groups. You should note
//! that by default the computed layout tries to fill the available space
//! completely. So if for any reason you might need a blank space somewhere, try to
//! pass an additional size to the group and don't use the corresponding area inside
//! the render method.
//!
//! Once you have finished to describe the UI, you just need to call `draw`
//! on `Terminal` to actually flush to the terminal.
#[macro_use]
extern crate bitflags;
extern crate cassowary;
#[macro_use]
extern crate log;
extern crate cassowary;
extern crate unicode_segmentation;
extern crate unicode_width;

View File

@@ -5,6 +5,7 @@ pub enum Color {
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
Gray,
@@ -12,6 +13,7 @@ pub enum Color {
LightRed,
LightGreen,
LightYellow,
LightBlue,
LightMagenta,
LightCyan,
White,

View File

@@ -1,36 +1,36 @@
pub mod block {
pub const FULL: &'static str = "";
pub const SEVEN_EIGHTHS: &'static str = "";
pub const THREE_QUATERS: &'static str = "";
pub const FIVE_EIGHTHS: &'static str = "";
pub const HALF: &'static str = "";
pub const THREE_EIGHTHS: &'static str = "";
pub const ONE_QUATER: &'static str = "";
pub const ONE_EIGHTH: &'static str = "";
pub const FULL: &str = "";
pub const SEVEN_EIGHTHS: &str = "";
pub const THREE_QUATERS: &str = "";
pub const FIVE_EIGHTHS: &str = "";
pub const HALF: &str = "";
pub const THREE_EIGHTHS: &str = "";
pub const ONE_QUATER: &str = "";
pub const ONE_EIGHTH: &str = "";
}
pub mod bar {
pub const FULL: &'static str = "";
pub const SEVEN_EIGHTHS: &'static str = "";
pub const THREE_QUATERS: &'static str = "";
pub const FIVE_EIGHTHS: &'static str = "";
pub const HALF: &'static str = "";
pub const THREE_EIGHTHS: &'static str = "";
pub const ONE_QUATER: &'static str = "";
pub const ONE_EIGHTH: &'static str = "";
pub const FULL: &str = "";
pub const SEVEN_EIGHTHS: &str = "";
pub const THREE_QUATERS: &str = "";
pub const FIVE_EIGHTHS: &str = "";
pub const HALF: &str = "";
pub const THREE_EIGHTHS: &str = "";
pub const ONE_QUATER: &str = "";
pub const ONE_EIGHTH: &str = "";
}
pub mod line {
pub const TOP_RIGHT: &'static str = "";
pub const VERTICAL: &'static str = "";
pub const HORIZONTAL: &'static str = "";
pub const TOP_LEFT: &'static str = "";
pub const BOTTOM_RIGHT: &'static str = "";
pub const BOTTOM_LEFT: &'static str = "";
pub const VERTICAL_LEFT: &'static str = "";
pub const VERTICAL_RIGHT: &'static str = "";
pub const HORIZONTAL_DOWN: &'static str = "";
pub const HORIZONTAL_UP: &'static str = "";
pub const TOP_RIGHT: &str = "";
pub const VERTICAL: &str = "";
pub const HORIZONTAL: &str = "";
pub const TOP_LEFT: &str = "";
pub const BOTTOM_RIGHT: &str = "";
pub const BOTTOM_LEFT: &str = "";
pub const VERTICAL_LEFT: &str = "";
pub const VERTICAL_RIGHT: &str = "";
pub const HORIZONTAL_DOWN: &str = "";
pub const HORIZONTAL_UP: &str = "";
}
pub const DOT: &'static str = "";
pub const DOT: &str = "";

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use backend::Backend;
use buffer::Buffer;
use layout::{Rect, Group, split};
use layout::{split, Group, Rect};
use widgets::Widget;
/// Holds a computed layout and keeps track of its use between successive draw calls
@@ -14,8 +14,10 @@ pub struct LayoutEntry {
}
/// Interface to the terminal backed by Termion
#[derive(Debug)]
pub struct Terminal<B>
where B: Backend
where
B: Backend,
{
backend: B,
/// Cache to prevent the layout to be computed at each draw call
@@ -28,7 +30,8 @@ pub struct Terminal<B>
}
impl<B> Terminal<B>
where B: Backend
where
B: Backend,
{
/// Wrapper around Termion initialization. Each buffer is initialized with a blank string and
/// default colors for the foreground and the background
@@ -58,9 +61,10 @@ impl<B> Terminal<B>
.entry((group.clone(), *area))
.or_insert_with(|| {
let chunks = split(area, &group.direction, group.margin, &group.sizes);
debug!("New layout computed:\n* Group = {:?}\n* Chunks = {:?}",
group,
chunks);
debug!(
"New layout computed:\n* Group = {:?}\n* Chunks = {:?}",
group, chunks
);
LayoutEntry {
chunks: chunks,
hot: true,
@@ -79,20 +83,23 @@ impl<B> Terminal<B>
.iter()
.zip(self.buffers[1 - self.current].content.iter())
.enumerate()
.filter_map(|(i, (c, p))| if c != p {
let i = i as u16;
let x = i % width;
let y = i / width;
Some((x, y, c))
} else {
None
.filter_map(|(i, (c, p))| {
if c != p {
let i = i as u16;
let x = i % width;
let y = i / width;
Some((x, y, c))
} else {
None
}
});
self.backend.draw(content)
}
/// Calls the draw method of a given widget on the current buffer
pub fn render<W>(&mut self, widget: &W, area: &Rect)
where W: Widget
pub fn render<W>(&mut self, widget: &mut W, area: &Rect)
where
W: Widget,
{
widget.draw(area, &mut self.buffers[self.current]);
}
@@ -109,7 +116,6 @@ impl<B> Terminal<B>
/// Flushes the current internal state and prepares the interface for the next draw call
pub fn draw(&mut self) -> Result<(), io::Error> {
// Draw to stdout
self.flush()?;
@@ -123,7 +129,7 @@ impl<B> Terminal<B>
self.layout_cache.insert(key, value);
}
for (_, e) in &mut self.layout_cache {
for e in self.layout_cache.values_mut() {
e.hot = false;
}

View File

@@ -1,8 +1,8 @@
use std::cmp::{min, max};
use std::cmp::{max, min};
use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block};
use widgets::{Block, Widget};
use buffer::Buffer;
use layout::Rect;
use style::Style;
@@ -14,11 +14,11 @@ use symbols::bar;
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, BarChart};
/// # use tui::widgets::{Block, Borders, BarChart};
/// # use tui::style::{Style, Color, Modifier};
/// # fn main() {
/// BarChart::default()
/// .block(Block::default().title("BarChart").borders(border::ALL))
/// .block(Block::default().title("BarChart").borders(Borders::ALL))
/// .bar_width(3)
/// .bar_gap(1)
/// .style(Style::default().fg(Color::Yellow).bg(Color::Red))
@@ -108,9 +108,9 @@ impl<'a> BarChart<'a> {
}
impl<'a> Widget for BarChart<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let chart_area = match self.block {
Some(ref b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -123,13 +123,16 @@ impl<'a> Widget for BarChart<'a> {
self.background(&chart_area, buf, self.style.bg);
let max = self.max.unwrap_or(self.data.iter().fold(0, |acc, &(_, v)| max(v, acc)));
let max_index = min((chart_area.width / (self.bar_width + self.bar_gap)) as usize,
self.data.len());
let max = self.max
.unwrap_or_else(|| self.data.iter().fold(0, |acc, &(_, v)| max(v, acc)));
let max_index = min(
(chart_area.width / (self.bar_width + self.bar_gap)) as usize,
self.data.len(),
);
let mut data = self.data
.iter()
.take(max_index)
.map(|&(l, v)| (l, v * chart_area.height as u64 * 8 / max))
.map(|&(l, v)| (l, v * u64::from(chart_area.height) * 8 / max))
.collect::<Vec<(&str, u64)>>();
for j in (0..chart_area.height - 1).rev() {
for (i, d) in data.iter_mut().enumerate() {
@@ -146,10 +149,10 @@ impl<'a> Widget for BarChart<'a> {
};
for x in 0..self.bar_width {
buf.get_mut(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) +
x,
chart_area.top() + j)
.set_symbol(symbol)
buf.get_mut(
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x,
chart_area.top() + j,
).set_symbol(symbol)
.set_style(self.style);
}
@@ -158,7 +161,6 @@ impl<'a> Widget for BarChart<'a> {
} else {
d.1 = 0;
}
}
}
@@ -167,18 +169,22 @@ impl<'a> Widget for BarChart<'a> {
let value_label = &self.values[i];
let width = value_label.width() as u16;
if width < self.bar_width {
buf.set_string(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) +
(self.bar_width - width) / 2,
chart_area.bottom() - 2,
value_label,
&self.value_style);
buf.set_string(
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap)
+ (self.bar_width - width) / 2,
chart_area.bottom() - 2,
value_label,
&self.value_style,
);
}
}
buf.set_stringn(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
chart_area.bottom() - 1,
label,
self.bar_width as usize,
&self.label_style);
buf.set_stringn(
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
chart_area.bottom() - 1,
label,
self.bar_width as usize,
&self.label_style,
);
}
}
}

View File

@@ -1,7 +1,7 @@
use buffer::Buffer;
use layout::Rect;
use style::Style;
use widgets::{Widget, border};
use widgets::{Borders, Widget};
use symbols::line;
/// Base widget to be used with all upper level ones. It may be used to display a box border around
@@ -11,13 +11,13 @@ use symbols::line;
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border};
/// # use tui::widgets::{Block, Borders};
/// # use tui::style::{Style, Color};
/// # fn main() {
/// Block::default()
/// .title("Block")
/// .title_style(Style::default().fg(Color::Red))
/// .borders(border::LEFT | border::RIGHT)
/// .borders(Borders::LEFT | Borders::RIGHT)
/// .border_style(Style::default().fg(Color::White))
/// .style(Style::default().bg(Color::Black));
/// # }
@@ -29,7 +29,7 @@ pub struct Block<'a> {
/// Title style
title_style: Style,
/// Visible borders
borders: border::Flags,
borders: Borders,
/// Border style
border_style: Style,
/// Widget style
@@ -41,7 +41,7 @@ impl<'a> Default for Block<'a> {
Block {
title: None,
title_style: Default::default(),
borders: border::NONE,
borders: Borders::NONE,
border_style: Default::default(),
style: Default::default(),
}
@@ -69,7 +69,7 @@ impl<'a> Block<'a> {
self
}
pub fn borders(mut self, flag: border::Flags) -> Block<'a> {
pub fn borders(mut self, flag: Borders) -> Block<'a> {
self.borders = flag;
self
}
@@ -80,18 +80,18 @@ impl<'a> Block<'a> {
return Rect::default();
}
let mut inner = *area;
if self.borders.intersects(border::LEFT) {
if self.borders.intersects(Borders::LEFT) {
inner.x += 1;
inner.width -= 1;
}
if self.borders.intersects(border::TOP) || self.title.is_some() {
if self.borders.intersects(Borders::TOP) || self.title.is_some() {
inner.y += 1;
inner.height -= 1;
}
if self.borders.intersects(border::RIGHT) {
if self.borders.intersects(Borders::RIGHT) {
inner.width -= 1;
}
if self.borders.intersects(border::BOTTOM) {
if self.borders.intersects(Borders::BOTTOM) {
inner.height -= 1;
}
inner
@@ -99,8 +99,7 @@ impl<'a> Block<'a> {
}
impl<'a> Widget for Block<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
if area.width < 2 || area.height < 2 {
return;
}
@@ -108,21 +107,21 @@ impl<'a> Widget for Block<'a> {
self.background(area, buf, self.style.bg);
// Sides
if self.borders.intersects(border::LEFT) {
if self.borders.intersects(Borders::LEFT) {
for y in area.top()..area.bottom() {
buf.get_mut(area.left(), y)
.set_symbol(line::VERTICAL)
.set_style(self.border_style);
}
}
if self.borders.intersects(border::TOP) {
if self.borders.intersects(Borders::TOP) {
for x in area.left()..area.right() {
buf.get_mut(x, area.top())
.set_symbol(line::HORIZONTAL)
.set_style(self.border_style);
}
}
if self.borders.intersects(border::RIGHT) {
if self.borders.intersects(Borders::RIGHT) {
let x = area.right() - 1;
for y in area.top()..area.bottom() {
buf.get_mut(x, y)
@@ -130,7 +129,7 @@ impl<'a> Widget for Block<'a> {
.set_style(self.border_style);
}
}
if self.borders.intersects(border::BOTTOM) {
if self.borders.intersects(Borders::BOTTOM) {
let y = area.bottom() - 1;
for x in area.left()..area.right() {
buf.get_mut(x, y)
@@ -140,22 +139,22 @@ impl<'a> Widget for Block<'a> {
}
// Corners
if self.borders.contains(border::LEFT | border::TOP) {
if self.borders.contains(Borders::LEFT | Borders::TOP) {
buf.get_mut(area.left(), area.top())
.set_symbol(line::TOP_LEFT)
.set_style(self.border_style);
}
if self.borders.contains(border::RIGHT | border::TOP) {
if self.borders.contains(Borders::RIGHT | Borders::TOP) {
buf.get_mut(area.right() - 1, area.top())
.set_symbol(line::TOP_RIGHT)
.set_style(self.border_style);
}
if self.borders.contains(border::LEFT | border::BOTTOM) {
if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
buf.get_mut(area.left(), area.bottom() - 1)
.set_symbol(line::BOTTOM_LEFT)
.set_style(self.border_style);
}
if self.borders.contains(border::RIGHT | border::BOTTOM) {
if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
buf.get_mut(area.right() - 1, area.bottom() - 1)
.set_symbol(line::BOTTOM_RIGHT)
.set_style(self.border_style);
@@ -163,22 +162,24 @@ impl<'a> Widget for Block<'a> {
if area.width > 2 {
if let Some(title) = self.title {
let lx = if self.borders.intersects(border::LEFT) {
let lx = if self.borders.intersects(Borders::LEFT) {
1
} else {
0
};
let rx = if self.borders.intersects(border::RIGHT) {
let rx = if self.borders.intersects(Borders::RIGHT) {
1
} else {
0
};
let width = area.width - lx - rx;
buf.set_stringn(area.left() + lx,
area.top(),
title,
width as usize,
&self.title_style);
buf.set_stringn(
area.left() + lx,
area.top(),
title,
width as usize,
&self.title_style,
);
}
}
}

View File

@@ -25,8 +25,10 @@ impl Iterator for LineIterator {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.end {
let pos = (self.x + (self.current * self.dx) / self.end * self.dir_x,
self.y + (self.current * self.dy) / self.end * self.dir_y);
let pos = (
self.x + (self.current * self.dx) / self.end * self.dir_x,
self.y + (self.current * self.dy) / self.end * self.dir_y,
);
self.current += 1.0;
Some(pos)
} else {

View File

@@ -7,13 +7,17 @@ pub use self::points::Points;
pub use self::line::Line;
pub use self::map::{Map, MapResolution};
use style::{Style, Color};
use style::{Color, Style};
use buffer::Buffer;
use widgets::{Block, Widget};
use layout::Rect;
pub const DOTS: [[u16; 2]; 4] =
[[0x0001, 0x0008], [0x0002, 0x0010], [0x0004, 0x0020], [0x0040, 0x0080]];
pub const DOTS: [[u16; 2]; 4] = [
[0x0001, 0x0008],
[0x0002, 0x0010],
[0x0004, 0x0020],
[0x0040, 0x0080],
];
pub const BRAILLE_OFFSET: u16 = 0x2800;
pub const BRAILLE_BLANK: char = '';
@@ -83,17 +87,20 @@ pub struct Context<'a> {
impl<'a> Context<'a> {
/// Draw any object that may implement the Shape trait
pub fn draw<'b, S>(&mut self, shape: &'b S)
where S: Shape<'b>
where
S: Shape<'b>,
{
self.dirty = true;
let left = self.x_bounds[0];
let right = self.x_bounds[1];
let bottom = self.y_bounds[0];
let top = self.y_bounds[1];
for (x, y) in shape.points()
.filter(|&(x, y)| !(x < left || x > right || y < bottom || y > top)) {
let dy = ((top - y) * (self.height - 1) as f64 * 4.0 / (top - bottom)) as usize;
let dx = ((x - left) * (self.width - 1) as f64 * 2.0 / (right - left)) as usize;
for (x, y) in shape
.points()
.filter(|&(x, y)| !(x < left || x > right || y < bottom || y > top))
{
let dy = ((top - y) * f64::from(self.height - 1) * 4.0 / (top - bottom)) as usize;
let dx = ((x - left) * f64::from(self.width - 1) * 2.0 / (right - left)) as usize;
let index = dy / 4 * self.width as usize + dx / 2;
self.grid.cells[index] |= DOTS[dy % 4][dx % 2];
self.grid.colors[index] = shape.color();
@@ -131,12 +138,12 @@ impl<'a> Context<'a> {
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border};
/// # use tui::widgets::{Block, Borders};
/// # use tui::widgets::canvas::{Canvas, Shape, Line, Map, MapResolution};
/// # use tui::style::Color;
/// # fn main() {
/// Canvas::default()
/// .block(Block::default().title("Canvas").borders(border::ALL))
/// .block(Block::default().title("Canvas").borders(Borders::ALL))
/// .x_bounds([-180.0, 180.0])
/// .y_bounds([-90.0, 90.0])
/// .paint(|ctx| {
@@ -163,7 +170,8 @@ impl<'a> Context<'a> {
/// # }
/// ```
pub struct Canvas<'a, F>
where F: Fn(&mut Context)
where
F: Fn(&mut Context),
{
block: Option<Block<'a>>,
x_bounds: [f64; 2],
@@ -173,7 +181,8 @@ pub struct Canvas<'a, F>
}
impl<'a, F> Default for Canvas<'a, F>
where F: Fn(&mut Context)
where
F: Fn(&mut Context),
{
fn default() -> Canvas<'a, F> {
Canvas {
@@ -187,7 +196,8 @@ impl<'a, F> Default for Canvas<'a, F>
}
impl<'a, F> Canvas<'a, F>
where F: Fn(&mut Context)
where
F: Fn(&mut Context),
{
pub fn block(&mut self, block: Block<'a>) -> &mut Canvas<'a, F> {
self.block = Some(block);
@@ -215,11 +225,12 @@ impl<'a, F> Canvas<'a, F>
}
impl<'a, F> Widget for Canvas<'a, F>
where F: Fn(&mut Context)
where
F: Fn(&mut Context),
{
fn draw(&self, area: &Rect, buf: &mut Buffer) {
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let canvas_area = match self.block {
Some(ref b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -230,7 +241,6 @@ impl<'a, F> Widget for Canvas<'a, F>
let height = canvas_area.height as usize;
if let Some(ref painter) = self.painter {
// Create a blank context that match the size of the terminal
let mut ctx = Context {
x_bounds: self.x_bounds,
@@ -248,10 +258,12 @@ impl<'a, F> Widget for Canvas<'a, F>
// Retreive painted points for each layer
for layer in ctx.layers {
for (i, (ch, color)) in layer.string
for (i, (ch, color)) in layer
.string
.chars()
.zip(layer.colors.into_iter())
.enumerate() {
.enumerate()
{
if ch != BRAILLE_BLANK {
let (x, y) = (i % width, i / width);
buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top())
@@ -265,17 +277,19 @@ impl<'a, F> Widget for Canvas<'a, F>
// Finally draw the labels
let style = Style::default().bg(self.background_color);
for label in ctx.labels.iter().filter(|l| {
!(l.x < self.x_bounds[0] || l.x > self.x_bounds[1] || l.y < self.y_bounds[0] ||
l.y > self.y_bounds[1])
!(l.x < self.x_bounds[0] || l.x > self.x_bounds[1] || l.y < self.y_bounds[0]
|| l.y > self.y_bounds[1])
}) {
let dy = ((self.y_bounds[1] - label.y) * (canvas_area.height - 1) as f64 /
(self.y_bounds[1] - self.y_bounds[0])) as u16;
let dx = ((label.x - self.x_bounds[0]) * (canvas_area.width - 1) as f64 /
(self.x_bounds[1] - self.x_bounds[0])) as u16;
buf.set_string(dx + canvas_area.left(),
dy + canvas_area.top(),
label.text,
&style.fg(label.color));
let dy = ((self.y_bounds[1] - label.y) * f64::from(canvas_area.height - 1)
/ (self.y_bounds[1] - self.y_bounds[0])) as u16;
let dx = ((label.x - self.x_bounds[0]) * f64::from(canvas_area.width - 1)
/ (self.x_bounds[1] - self.x_bounds[0])) as u16;
buf.set_string(
dx + canvas_area.left(),
dy + canvas_area.top(),
label.text,
&style.fg(label.color),
);
}
}
}

View File

@@ -31,7 +31,9 @@ impl<'a> IntoIterator for &'a Points<'a> {
type Item = (f64, f64);
type IntoIter = PointsIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
PointsIterator { iter: self.coords.iter() }
PointsIterator {
iter: self.coords.iter(),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ use std::cmp::max;
use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block, border};
use widgets::{Block, Borders, Widget};
use widgets::canvas::{Canvas, Points};
use buffer::Buffer;
use layout::Rect;
@@ -10,7 +10,10 @@ use style::Style;
use symbols;
/// An X or Y axis for the chart widget
pub struct Axis<'a> {
pub struct Axis<'a, L>
where
L: AsRef<str> + 'a,
{
/// Title displayed next to axis end
title: Option<&'a str>,
/// Style of the title
@@ -18,15 +21,18 @@ pub struct Axis<'a> {
/// Bounds for the axis (all data points outside these limits will not be represented)
bounds: [f64; 2],
/// A list of labels to put to the left or below the axis
labels: Option<&'a [&'a str]>,
labels: Option<&'a [L]>,
/// The labels' style
labels_style: Style,
/// The style used to draw the axis itself
style: Style,
}
impl<'a> Default for Axis<'a> {
fn default() -> Axis<'a> {
impl<'a, L> Default for Axis<'a, L>
where
L: AsRef<str>,
{
fn default() -> Axis<'a, L> {
Axis {
title: None,
title_style: Default::default(),
@@ -38,33 +44,36 @@ impl<'a> Default for Axis<'a> {
}
}
impl<'a> Axis<'a> {
pub fn title(mut self, title: &'a str) -> Axis<'a> {
impl<'a, L> Axis<'a, L>
where
L: AsRef<str>,
{
pub fn title(mut self, title: &'a str) -> Axis<'a, L> {
self.title = Some(title);
self
}
pub fn title_style(mut self, style: Style) -> Axis<'a> {
pub fn title_style(mut self, style: Style) -> Axis<'a, L> {
self.title_style = style;
self
}
pub fn bounds(mut self, bounds: [f64; 2]) -> Axis<'a> {
pub fn bounds(mut self, bounds: [f64; 2]) -> Axis<'a, L> {
self.bounds = bounds;
self
}
pub fn labels(mut self, labels: &'a [&'a str]) -> Axis<'a> {
pub fn labels(mut self, labels: &'a [L]) -> Axis<'a, L> {
self.labels = Some(labels);
self
}
pub fn labels_style(mut self, style: Style) -> Axis<'a> {
pub fn labels_style(mut self, style: Style) -> Axis<'a, L> {
self.labels_style = style;
self
}
pub fn style(mut self, style: Style) -> Axis<'a> {
pub fn style(mut self, style: Style) -> Axis<'a, L> {
self.style = style;
self
}
@@ -158,7 +167,7 @@ impl Default for ChartLayout {
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, Chart, Axis, Dataset, Marker};
/// # use tui::widgets::{Block, Borders, Chart, Axis, Dataset, Marker};
/// # use tui::style::{Style, Color};
/// # fn main() {
/// Chart::default()
@@ -186,21 +195,29 @@ impl Default for ChartLayout {
/// .style(Style::default().fg(Color::Magenta))
/// .data(&[(4.0, 5.0), (5.0, 8.0), (7.66, 13.5)])]);
/// # }
pub struct Chart<'a> {
pub struct Chart<'a, LX, LY>
where
LX: AsRef<str> + 'a,
LY: AsRef<str> + 'a,
{
/// A block to display around the widget eventually
block: Option<Block<'a>>,
/// The horizontal axis
x_axis: Axis<'a>,
x_axis: Axis<'a, LX>,
/// The vertical axis
y_axis: Axis<'a>,
y_axis: Axis<'a, LY>,
/// A reference to the datasets
datasets: &'a [Dataset<'a>],
/// The widget base style
style: Style,
}
impl<'a> Default for Chart<'a> {
fn default() -> Chart<'a> {
impl<'a, LX, LY> Default for Chart<'a, LX, LY>
where
LX: AsRef<str>,
LY: AsRef<str>,
{
fn default() -> Chart<'a, LX, LY> {
Chart {
block: None,
x_axis: Axis::default(),
@@ -211,33 +228,36 @@ impl<'a> Default for Chart<'a> {
}
}
impl<'a> Chart<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut Chart<'a> {
impl<'a, LX, LY> Chart<'a, LX, LY>
where
LX: AsRef<str>,
LY: AsRef<str>,
{
pub fn block(&'a mut self, block: Block<'a>) -> &mut Chart<'a, LX, LY> {
self.block = Some(block);
self
}
pub fn style(&mut self, style: Style) -> &mut Chart<'a> {
pub fn style(&mut self, style: Style) -> &mut Chart<'a, LX, LY> {
self.style = style;
self
}
pub fn x_axis(&mut self, axis: Axis<'a>) -> &mut Chart<'a> {
pub fn x_axis(&mut self, axis: Axis<'a, LX>) -> &mut Chart<'a, LX, LY> {
self.x_axis = axis;
self
}
pub fn y_axis(&mut self, axis: Axis<'a>) -> &mut Chart<'a> {
pub fn y_axis(&mut self, axis: Axis<'a, LY>) -> &mut Chart<'a, LX, LY> {
self.y_axis = axis;
self
}
pub fn datasets(&mut self, datasets: &'a [Dataset<'a>]) -> &mut Chart<'a> {
pub fn datasets(&mut self, datasets: &'a [Dataset<'a>]) -> &mut Chart<'a, LX, LY> {
self.datasets = datasets;
self
}
/// Compute the internal layout of the chart given the area. If the area is too small some
/// elements may be automatically hidden
fn layout(&self, area: &Rect) -> ChartLayout {
@@ -253,8 +273,16 @@ impl<'a> Chart<'a> {
y -= 1;
}
if let Some(labels) = self.y_axis.labels {
let max_width = labels.iter().fold(0, |acc, l| max(l.width(), acc)) as u16;
if let Some(y_labels) = self.y_axis.labels {
let mut max_width = y_labels
.iter()
.fold(0, |acc, l| max(l.as_ref().width(), acc))
as u16;
if let Some(x_labels) = self.x_axis.labels {
if x_labels.len() > 0 {
max_width = max(max_width, x_labels[0].as_ref().width() as u16);
}
}
if x + max_width < area.right() {
layout.label_y = Some(x);
x += max_width;
@@ -292,22 +320,29 @@ impl<'a> Chart<'a> {
if let Some(inner_width) = self.datasets.iter().map(|d| d.name.width() as u16).max() {
let legend_width = inner_width + 2;
let legend_height = self.datasets.len() as u16 + 2;
if legend_width < layout.graph_area.width / 3 &&
legend_height < layout.graph_area.height / 3 {
layout.legend_area = Some(Rect::new(layout.graph_area.right() - legend_width,
layout.graph_area.top(),
legend_width,
legend_height));
if legend_width < layout.graph_area.width / 3
&& legend_height < layout.graph_area.height / 3
{
layout.legend_area = Some(Rect::new(
layout.graph_area.right() - legend_width,
layout.graph_area.top(),
legend_width,
legend_height,
));
}
}
layout
}
}
impl<'a> Widget for Chart<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
impl<'a, LX, LY> Widget for Chart<'a, LX, LY>
where
LX: AsRef<str>,
LY: AsRef<str>,
{
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let chart_area = match self.block {
Some(ref b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -334,16 +369,17 @@ impl<'a> Widget for Chart<'a> {
if let Some(y) = layout.label_x {
let labels = self.x_axis.labels.unwrap();
let total_width = labels.iter().fold(0, |acc, l| l.width() + acc) as u16;
let total_width = labels.iter().fold(0, |acc, l| l.as_ref().width() + acc) as u16;
let labels_len = labels.len() as u16;
if total_width < graph_area.width && labels_len > 1 {
for (i, label) in labels.iter().enumerate() {
buf.set_string(graph_area.left() +
i as u16 * (graph_area.width - 1) / (labels_len - 1) -
label.width() as u16,
y,
label,
&self.x_axis.labels_style);
buf.set_string(
graph_area.left() + i as u16 * (graph_area.width - 1) / (labels_len - 1)
- label.as_ref().width() as u16,
y,
label.as_ref(),
&self.x_axis.labels_style,
);
}
}
}
@@ -354,10 +390,12 @@ impl<'a> Widget for Chart<'a> {
for (i, label) in labels.iter().enumerate() {
let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1);
if dy < graph_area.bottom() {
buf.set_string(x,
graph_area.bottom() - 1 - dy,
label,
&self.y_axis.labels_style);
buf.set_string(
x,
graph_area.bottom() - 1 - dy,
label.as_ref(),
&self.y_axis.labels_style,
);
}
}
}
@@ -388,25 +426,23 @@ impl<'a> Widget for Chart<'a> {
for dataset in self.datasets {
match dataset.marker {
Marker::Dot => {
for &(x, y) in dataset.data.iter().filter(|&&(x, y)| {
!(x < self.x_axis.bounds[0] || x > self.x_axis.bounds[1] ||
y < self.y_axis.bounds[0] ||
y > self.y_axis.bounds[1])
}) {
let dy = ((self.y_axis.bounds[1] - y) * (graph_area.height - 1) as f64 /
(self.y_axis.bounds[1] -
self.y_axis.bounds[0])) as u16;
let dx = ((x - self.x_axis.bounds[0]) * (graph_area.width - 1) as f64 /
(self.x_axis.bounds[1] -
self.x_axis.bounds[0])) as u16;
Marker::Dot => for &(x, y) in dataset.data.iter().filter(|&&(x, y)| {
!(x < self.x_axis.bounds[0] || x > self.x_axis.bounds[1]
|| y < self.y_axis.bounds[0]
|| y > self.y_axis.bounds[1])
}) {
let dy = ((self.y_axis.bounds[1] - y) * f64::from(graph_area.height - 1)
/ (self.y_axis.bounds[1] - self.y_axis.bounds[0]))
as u16;
let dx = ((x - self.x_axis.bounds[0]) * f64::from(graph_area.width - 1)
/ (self.x_axis.bounds[1] - self.x_axis.bounds[0]))
as u16;
buf.get_mut(graph_area.left() + dx, graph_area.top() + dy)
.set_symbol(symbols::DOT)
.set_fg(dataset.style.fg)
.set_bg(dataset.style.bg);
}
}
buf.get_mut(graph_area.left() + dx, graph_area.top() + dy)
.set_symbol(symbols::DOT)
.set_fg(dataset.style.fg)
.set_bg(dataset.style.bg);
},
Marker::Braille => {
Canvas::default()
.background_color(self.style.bg)
@@ -425,13 +461,15 @@ impl<'a> Widget for Chart<'a> {
if let Some(legend_area) = layout.legend_area {
Block::default()
.borders(border::ALL)
.borders(Borders::ALL)
.draw(&legend_area, buf);
for (i, dataset) in self.datasets.iter().enumerate() {
buf.set_string(legend_area.x + 1,
legend_area.y + 1 + i as u16,
dataset.name,
&dataset.style);
buf.set_string(
legend_area.x + 1,
legend_area.y + 1 + i as u16,
dataset.name,
&dataset.style,
);
}
}
}

View File

@@ -1,8 +1,8 @@
use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block};
use widgets::{Block, Widget};
use buffer::Buffer;
use style::{Style, Color};
use style::{Color, Style};
use layout::Rect;
/// A widget to display a task progress.
@@ -11,11 +11,11 @@ use layout::Rect;
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Widget, Gauge, Block, border};
/// # use tui::widgets::{Widget, Gauge, Block, Borders};
/// # use tui::style::{Style, Color, Modifier};
/// # fn main() {
/// Gauge::default()
/// .block(Block::default().borders(border::ALL).title("Progress"))
/// .block(Block::default().borders(Borders::ALL).title("Progress"))
/// .style(Style::default().fg(Color::White).bg(Color::Black).modifier(Modifier::Italic))
/// .percent(20);
/// # }
@@ -61,9 +61,9 @@ impl<'a> Gauge<'a> {
}
impl<'a> Widget for Gauge<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let gauge_area = match self.block {
Some(ref b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -81,7 +81,6 @@ impl<'a> Widget for Gauge<'a> {
let width = (gauge_area.width * self.percent) / 100;
let end = gauge_area.left() + width;
for y in gauge_area.top()..gauge_area.bottom() {
// Gauge
for x in gauge_area.left()..end {
buf.get_mut(x, y).set_symbol(" ");
@@ -98,7 +97,9 @@ impl<'a> Widget for Gauge<'a> {
// Fix colors
for x in gauge_area.left()..end {
buf.get_mut(x, y).set_fg(self.style.bg).set_bg(self.style.fg);
buf.get_mut(x, y)
.set_fg(self.style.bg)
.set_bg(self.style.fg);
}
}
}

View File

@@ -1,54 +1,80 @@
use std::iter;
use std::cmp::min;
use std::fmt::Display;
use std::iter::Iterator;
use unicode_width::UnicodeWidthStr;
use buffer::Buffer;
use widgets::{Widget, Block};
use widgets::{Block, Widget};
use layout::Rect;
use style::Style;
pub enum Item<'i, D: 'i> {
Data(D),
StyledData(D, &'i Style),
}
pub struct List<'a> {
block: Option<Block<'a>>,
items: Vec<(&'a str, &'a Style)>,
pub struct List<'b, 'i, L, D: 'i>
where
L: Iterator<Item = Item<'i, D>>,
{
block: Option<Block<'b>>,
items: L,
style: Style,
}
impl<'a> Default for List<'a> {
fn default() -> List<'a> {
impl<'b, 'i, L, D> Default for List<'b, 'i, L, D>
where
L: Iterator<Item = Item<'i, D>> + Default,
{
fn default() -> List<'b, 'i, L, D> {
List {
block: None,
items: Vec::new(),
items: L::default(),
style: Default::default(),
}
}
}
impl<'a> List<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a> {
impl<'b, 'i, L, D> List<'b, 'i, L, D>
where
L: Iterator<Item = Item<'i, D>>,
{
pub fn new(items: L) -> List<'b, 'i, L, D> {
List {
block: None,
items: items,
style: Default::default(),
}
}
pub fn block(&'b mut self, block: Block<'b>) -> &mut List<'b, 'i, L, D> {
self.block = Some(block);
self
}
pub fn items<I>(&'a mut self, items: &'a [(I, &'a Style)]) -> &mut List<'a>
where I: AsRef<str> + 'a
pub fn items<I>(&'b mut self, items: I) -> &mut List<'b, 'i, L, D>
where
I: IntoIterator<Item = Item<'i, D>, IntoIter = L>,
{
self.items =
items.iter().map(|&(ref i, s)| (i.as_ref(), s)).collect::<Vec<(&'a str, &'a Style)>>();
self.items = items.into_iter();
self
}
pub fn style(&'a mut self, style: Style) -> &mut List<'a> {
pub fn style(&'b mut self, style: Style) -> &mut List<'b, 'i, L, D> {
self.style = style;
self
}
}
impl<'a> Widget for List<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
impl<'b, 'i, L, D> Widget for List<'b, 'i, L, D>
where
L: Iterator<Item = Item<'i, D>>,
D: Display,
{
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let list_area = match self.block {
Some(ref b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -61,30 +87,46 @@ impl<'a> Widget for List<'a> {
self.background(&list_area, buf, self.style.bg);
let max_index = min(self.items.len(), list_area.height as usize);
for (i, &(ref item, style)) in self.items.iter().enumerate().take(max_index) {
buf.set_stringn(list_area.left(),
list_area.top() + i as u16,
item.as_ref(),
list_area.width as usize,
style);
for (i, item) in self.items
.by_ref()
.enumerate()
.take(list_area.height as usize)
{
match item {
Item::Data(ref v) => {
buf.set_stringn(
list_area.left(),
list_area.top() + i as u16,
&format!("{}", v),
list_area.width as usize,
&Style::default(),
);
}
Item::StyledData(ref v, s) => {
buf.set_stringn(
list_area.left(),
list_area.top() + i as u16,
&format!("{}", v),
list_area.width as usize,
s,
);
}
};
}
}
}
/// A widget to display several items among which one can be selected (optional)
///
/// # Examples
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, SelectableList};
/// # use tui::widgets::{Block, Borders, SelectableList};
/// # use tui::style::{Style, Color, Modifier};
/// # fn main() {
/// SelectableList::default()
/// .block(Block::default().title("SelectableList").borders(border::ALL))
/// .block(Block::default().title("SelectableList").borders(Borders::ALL))
/// .items(&["Item 1", "Item 2", "Item 3"])
/// .select(1)
/// .style(Style::default().fg(Color::White))
@@ -92,10 +134,10 @@ impl<'a> Widget for List<'a> {
/// .highlight_symbol(">>");
/// # }
/// ```
pub struct SelectableList<'a> {
block: Option<Block<'a>>,
pub struct SelectableList<'b> {
block: Option<Block<'b>>,
/// Items to be displayed
items: Vec<&'a str>,
items: Vec<&'b str>,
/// Index of the one selected
selected: Option<usize>,
/// Base style of the widget
@@ -103,11 +145,11 @@ pub struct SelectableList<'a> {
/// Style used to render selected item
highlight_style: Style,
/// Symbol in front of the selected item (Shift all items to the right)
highlight_symbol: Option<&'a str>,
highlight_symbol: Option<&'b str>,
}
impl<'a> Default for SelectableList<'a> {
fn default() -> SelectableList<'a> {
impl<'b> Default for SelectableList<'b> {
fn default() -> SelectableList<'b> {
SelectableList {
block: None,
items: Vec::new(),
@@ -119,45 +161,45 @@ impl<'a> Default for SelectableList<'a> {
}
}
impl<'a> SelectableList<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut SelectableList<'a> {
impl<'b> SelectableList<'b> {
pub fn block(&'b mut self, block: Block<'b>) -> &mut SelectableList<'b> {
self.block = Some(block);
self
}
pub fn items<I>(&'a mut self, items: &'a [I]) -> &mut SelectableList<'a>
where I: AsRef<str> + 'a
pub fn items<I>(&'b mut self, items: &'b [I]) -> &mut SelectableList<'b>
where
I: AsRef<str> + 'b,
{
self.items = items.iter().map(|i| i.as_ref()).collect::<Vec<&str>>();
self
}
pub fn style(&'a mut self, style: Style) -> &mut SelectableList<'a> {
pub fn style(&'b mut self, style: Style) -> &mut SelectableList<'b> {
self.style = style;
self
}
pub fn highlight_symbol(&'a mut self, highlight_symbol: &'a str) -> &mut SelectableList<'a> {
pub fn highlight_symbol(&'b mut self, highlight_symbol: &'b str) -> &mut SelectableList<'b> {
self.highlight_symbol = Some(highlight_symbol);
self
}
pub fn highlight_style(&'a mut self, highlight_style: Style) -> &mut SelectableList<'a> {
pub fn highlight_style(&'b mut self, highlight_style: Style) -> &mut SelectableList<'b> {
self.highlight_style = highlight_style;
self
}
pub fn select(&'a mut self, index: usize) -> &'a mut SelectableList<'a> {
pub fn select(&'b mut self, index: usize) -> &'b mut SelectableList<'b> {
self.selected = Some(index);
self
}
}
impl<'a> Widget for SelectableList<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
impl<'b> Widget for SelectableList<'b> {
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let list_area = match self.block {
Some(ref b) => b.inner(area),
Some(ref mut b) => b.inner(area),
None => *area,
};
@@ -169,29 +211,31 @@ impl<'a> Widget for SelectableList<'a> {
None => (0, &self.style),
};
let highlight_symbol = self.highlight_symbol.unwrap_or("");
let blank_symbol = iter::repeat(" ").take(highlight_symbol.width()).collect::<String>();
let blank_symbol = iter::repeat(" ")
.take(highlight_symbol.width())
.collect::<String>();
// Make sure the list show the selected item
let offset = if selected >= list_height {
selected - list_height + 1
} else {
0
};
let items = self.items
.iter()
.cloned()
.enumerate()
.map(|(i, item)| if i == selected {
(format!("{} {}", highlight_symbol, item), highlight_style)
} else {
(format!("{} {}", blank_symbol, item), &self.style)
})
.skip(offset as usize)
.collect::<Vec<(String, &Style)>>();
// Render items
List::default()
let items = self.items
.iter()
.enumerate()
.map(|(i, item)| {
if i == selected {
Item::StyledData(format!("{} {}", highlight_symbol, item), highlight_style)
} else {
Item::StyledData(format!("{} {}", blank_symbol, item), &self.style)
}
})
.skip(offset as usize);
List::new(items)
.block(self.block.unwrap_or_default())
.items(&items)
.style(self.style)
.draw(area, buf);
}
}

View File

@@ -11,13 +11,13 @@ pub mod canvas;
pub use self::block::Block;
pub use self::paragraph::Paragraph;
pub use self::list::{List, SelectableList};
pub use self::list::{Item, List, SelectableList};
pub use self::gauge::Gauge;
pub use self::sparkline::Sparkline;
pub use self::chart::{Chart, Axis, Dataset, Marker};
pub use self::chart::{Axis, Chart, Dataset, Marker};
pub use self::barchart::BarChart;
pub use self::tabs::Tabs;
pub use self::table::Table;
pub use self::table::{Row, Table};
use buffer::Buffer;
use layout::Rect;
@@ -26,22 +26,20 @@ use backend::Backend;
use style::Color;
/// Bitflags that can be composed to set the visible borders essentially on the block widget.
pub mod border {
bitflags! {
pub flags Flags: u32 {
/// Show no border (default)
const NONE = 0b00000001,
/// Show the top border
const TOP = 0b00000010,
/// Show the right border
const RIGHT = 0b00000100,
/// Show the bottom border
const BOTTOM = 0b0001000,
/// Show the left border
const LEFT = 0b00010000,
/// Show all borders
const ALL = TOP.bits | RIGHT.bits | BOTTOM.bits | LEFT.bits,
}
bitflags! {
pub struct Borders: u32 {
/// Show no border (default)
const NONE = 0b0000_0001;
/// Show the top border
const TOP = 0b0000_0010;
/// Show the right border
const RIGHT = 0b0000_0100;
/// Show the bottom border
const BOTTOM = 0b000_1000;
/// Show the left border
const LEFT = 0b0001_0000;
/// Show all borders
const ALL = Self::TOP.bits | Self::RIGHT.bits | Self::BOTTOM.bits | Self::LEFT.bits;
}
}
@@ -49,7 +47,7 @@ pub mod border {
pub trait Widget {
/// Draws the current state of the widget in the given buffer. That the only method required to
/// implement a custom widget.
fn draw(&self, area: &Rect, buf: &mut Buffer);
fn draw(&mut self, area: &Rect, buf: &mut Buffer);
/// Helper method to quickly set the background of all cells inside the specified area.
fn background(&self, area: &Rect, buf: &mut Buffer, color: Color) {
for y in area.top()..area.bottom() {
@@ -59,9 +57,10 @@ pub trait Widget {
}
}
/// Helper method that can be chained with a widget's builder methods to render it.
fn render<B>(&self, t: &mut Terminal<B>, area: &Rect)
where Self: Sized,
B: Backend
fn render<B>(&mut self, t: &mut Terminal<B>, area: &Rect)
where
Self: Sized,
B: Backend,
{
t.render(self, area);
}

View File

@@ -1,10 +1,10 @@
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block};
use widgets::{Block, Widget};
use buffer::Buffer;
use layout::Rect;
use style::{Style, Color, Modifier};
use style::{Color, Modifier, Style};
/// A widget to display some text. You can specify colors using commands embedded in
/// the text such as "{[color] [text]}".
@@ -13,11 +13,11 @@ use style::{Style, Color, Modifier};
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, Paragraph};
/// # use tui::widgets::{Block, Borders, Paragraph};
/// # use tui::style::{Style, Color};
/// # fn main() {
/// Paragraph::default()
/// .block(Block::default().title("Paragraph").borders(border::ALL))
/// .block(Block::default().title("Paragraph").borders(Borders::ALL))
/// .style(Style::default().fg(Color::White).bg(Color::Black))
/// .wrap(true)
/// .text("First line\nSecond line\n{red Colored text}.");
@@ -84,7 +84,8 @@ impl<'a> Paragraph<'a> {
}
struct Parser<'a, T>
where T: Iterator<Item = &'a str>
where
T: Iterator<Item = &'a str>,
{
text: T,
mark: bool,
@@ -96,7 +97,8 @@ struct Parser<'a, T>
}
impl<'a, T> Parser<'a, T>
where T: Iterator<Item = &'a str>
where
T: Iterator<Item = &'a str>,
{
fn new(text: T, base_style: Style) -> Parser<'a, T> {
Parser {
@@ -115,21 +117,15 @@ impl<'a, T> Parser<'a, T>
let args = cmd.split('=').collect::<Vec<&str>>();
if let Some(first) = args.get(0) {
match *first {
"fg" => {
if let Some(snd) = args.get(1) {
self.style.fg = Parser::<T>::str_to_color(snd);
}
}
"bg" => {
if let Some(snd) = args.get(1) {
self.style.bg = Parser::<T>::str_to_color(snd);
}
}
"mod" => {
if let Some(snd) = args.get(1) {
self.style.modifier = Parser::<T>::str_to_modifier(snd);
}
}
"fg" => if let Some(snd) = args.get(1) {
self.style.fg = Parser::<T>::str_to_color(snd);
},
"bg" => if let Some(snd) = args.get(1) {
self.style.bg = Parser::<T>::str_to_color(snd);
},
"mod" => if let Some(snd) = args.get(1) {
self.style.modifier = Parser::<T>::str_to_modifier(snd);
},
_ => {}
}
}
@@ -142,12 +138,14 @@ impl<'a, T> Parser<'a, T>
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" => Color::Magenta,
"cyan" => Color::Cyan,
"gray" => Color::Gray,
"dark_gray" => Color::DarkGray,
"light_red" => Color::LightRed,
"light_green" => Color::LightGreen,
"light_blue" => Color::LightBlue,
"light_yellow" => Color::LightYellow,
"light_magenta" => Color::LightMagenta,
"light_cyan" => Color::LightCyan,
@@ -176,59 +174,56 @@ impl<'a, T> Parser<'a, T>
}
impl<'a, T> Iterator for Parser<'a, T>
where T: Iterator<Item = &'a str>
where
T: Iterator<Item = &'a str>,
{
type Item = (&'a str, Style);
fn next(&mut self) -> Option<Self::Item> {
match self.text.next() {
Some(s) => {
if s == "\\" {
if self.escaping {
Some((s, self.style))
} else {
self.escaping = true;
self.next()
}
} else if s == "{" {
if self.escaping {
self.escaping = false;
Some((s, self.style))
} else {
if self.mark {
Some((s, self.style))
} else {
self.style = self.base_style;
self.mark = true;
self.next()
}
}
} else if s == "}" && self.mark {
self.reset();
self.next()
} else if s == " " && self.mark {
if self.styling {
Some((s, self.style))
} else {
self.styling = true;
self.update_style();
self.next()
}
} else if self.mark && !self.styling {
self.cmd_string.push_str(s);
self.next()
} else {
Some(s) => if s == "\\" {
if self.escaping {
Some((s, self.style))
} else {
self.escaping = true;
self.next()
}
}
} else if s == "{" {
if self.escaping {
self.escaping = false;
Some((s, self.style))
} else if self.mark {
Some((s, self.style))
} else {
self.style = self.base_style;
self.mark = true;
self.next()
}
} else if s == "}" && self.mark {
self.reset();
self.next()
} else if s == " " && self.mark {
if self.styling {
Some((s, self.style))
} else {
self.styling = true;
self.update_style();
self.next()
}
} else if self.mark && !self.styling {
self.cmd_string.push_str(s);
self.next()
} else {
Some((s, self.style))
},
None => None,
}
}
}
impl<'a> Widget for Paragraph<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let text_area = match self.block {
Some(ref b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -250,6 +245,7 @@ impl<'a> Widget for Paragraph<'a> {
Box::new(Parser::new(graphemes, self.style))
};
let mut remove_leading_whitespaces = false;
for (string, style) in styled {
if string == "\n" {
x = 0;
@@ -260,11 +256,16 @@ impl<'a> Widget for Paragraph<'a> {
if self.wrapping {
x = 0;
y += 1;
remove_leading_whitespaces = true
}
continue;
}
if y > text_area.height - 1 {
if remove_leading_whitespaces && string == " " {
continue;
}
remove_leading_whitespaces = false;
if y > text_area.height + self.scroll - 1 {
break;
}

View File

@@ -2,7 +2,7 @@ use std::cmp::min;
use layout::Rect;
use buffer::Buffer;
use widgets::{Widget, Block};
use widgets::{Block, Widget};
use style::Style;
use symbols::bar;
@@ -12,11 +12,11 @@ use symbols::bar;
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, Sparkline};
/// # use tui::widgets::{Block, Borders, Sparkline};
/// # use tui::style::{Style, Color};
/// # fn main() {
/// Sparkline::default()
/// .block(Block::default().title("Sparkline").borders(border::ALL))
/// .block(Block::default().title("Sparkline").borders(Borders::ALL))
/// .data(&[0, 2, 3, 4, 1, 4, 10])
/// .max(5)
/// .style(Style::default().fg(Color::Red).bg(Color::White));
@@ -68,9 +68,9 @@ impl<'a> Sparkline<'a> {
}
impl<'a> Widget for Sparkline<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let spark_area = match self.block {
Some(ref b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -89,7 +89,7 @@ impl<'a> Widget for Sparkline<'a> {
let mut data = self.data
.iter()
.take(max_index)
.map(|e| e * spark_area.height as u64 * 8 / max)
.map(|e| e * u64::from(spark_area.height) * 8 / max)
.collect::<Vec<u64>>();
for j in (0..spark_area.height).rev() {
for (i, d) in data.iter_mut().enumerate() {

View File

@@ -1,41 +1,60 @@
use std::cmp::max;
use unicode_width::UnicodeWidthStr;
use std::fmt::Display;
use std::iter::Iterator;
use buffer::Buffer;
use widgets::{Widget, Block};
use widgets::{Block, Widget};
use layout::Rect;
use style::Style;
/// A widget to display data in formatted column
/// Holds data to be displayed in a Table widget
pub enum Row<'i, D, I>
where
D: Iterator<Item = I>,
I: Display,
{
Data(D),
StyledData(D, &'i Style),
}
/// A widget to display data in formatted columns
///
/// # Examples
///
/// ```
/// # use tui::widgets::{Block, border, Table};
/// # use tui::widgets::{Block, Borders, Table, Row};
/// # use tui::style::{Style, Color};
/// # fn main() {
/// let row_style = Style::default().fg(Color::White);
/// Table::default()
/// 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::Data(["Row41", "Row42", "Row43"].into_iter())
/// ].into_iter()
/// )
/// .block(Block::default().title("Table"))
/// .header(&["Col1", "Col2", "Col3"])
/// .header_style(Style::default().fg(Color::Yellow))
/// .widths(&[5, 5, 10])
/// .style(Style::default().fg(Color::White))
/// .column_spacing(1)
/// .rows(&[(&["Row11", "Row12", "Row13"], &row_style),
/// (&["Row21", "Row22", "Row23"], &row_style),
/// (&["Row31", "Row32", "Row33"], &row_style)]);
/// .column_spacing(1);
/// # }
/// ```
pub struct Table<'a> {
pub struct Table<'a, 'i, 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>>,
{
/// A block to wrap the widget in
block: Option<Block<'a>>,
/// Base style for the widget
style: Style,
/// Header row for all columns
header: &'a [&'a str],
header: H,
/// Style for the header
header_style: Style,
/// Width of each column (if the total width is greater than the widget width some columns may
@@ -44,74 +63,103 @@ pub struct Table<'a> {
/// Space between each column
column_spacing: u16,
/// Data to display in each row
rows: Vec<(Vec<&'a str>, &'a Style)>,
rows: R,
}
impl<'a> Default for Table<'a> {
fn default() -> Table<'a> {
impl<'a, 'i, T, H, I, D, R> Default for Table<'a, 'i, 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,
{
fn default() -> Table<'a, 'i, T, H, I, D, R> {
Table {
block: None,
style: Style::default(),
header: &[],
header: H::default(),
header_style: Style::default(),
widths: &[],
rows: Vec::new(),
rows: R::default(),
column_spacing: 1,
}
}
}
impl<'a> Table<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut Table<'a> {
impl<'a, 'i, T, H, I, D, R> Table<'a, 'i, 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>>,
{
pub fn new(header: H, rows: R) -> Table<'a, 'i, T, H, I, D, R> {
Table {
block: None,
style: Style::default(),
header: header,
header_style: Style::default(),
widths: &[],
rows: rows,
column_spacing: 1,
}
}
pub fn block(&'a mut self, block: Block<'a>) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.block = Some(block);
self
}
pub fn header(&mut self, header: &'a [&'a str]) -> &mut Table<'a> {
self.header = header;
pub fn header<II>(&mut self, header: II) -> &mut Table<'a, 'i, T, H, I, D, R>
where
II: IntoIterator<Item = T, IntoIter = H>,
{
self.header = header.into_iter();
self
}
pub fn header_style(&mut self, style: Style) -> &mut Table<'a> {
pub fn header_style(&mut self, style: Style) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.header_style = style;
self
}
pub fn widths(&mut self, widths: &'a [u16]) -> &mut Table<'a> {
pub fn widths(&mut self, widths: &'a [u16]) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.widths = widths;
self
}
pub fn rows<S, R>(&mut self, rows: &'a [(R, &'a Style)]) -> &mut Table<'a>
where S: AsRef<str> + 'a,
R: AsRef<[S]> + 'a
pub fn rows<II>(&mut self, rows: II) -> &mut Table<'a, 'i, T, H, I, D, R>
where
II: IntoIterator<Item = Row<'i, D, I>, IntoIter = R>,
{
self.rows = rows.iter()
.map(|&(ref r, style)| {
(r.as_ref().iter().map(|i| i.as_ref()).collect::<Vec<&'a str>>(), style)
})
.collect::<Vec<(Vec<&'a str>, &'a Style)>>();
self.rows = rows.into_iter();
self
}
pub fn style(&mut self, style: Style) -> &mut Table<'a> {
pub fn style(&mut self, style: Style) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.style = style;
self
}
pub fn column_spacing(&mut self, spacing: u16) -> &mut Table<'a> {
pub fn column_spacing(&mut self, spacing: u16) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.column_spacing = spacing;
self
}
}
impl<'a> Widget for Table<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
impl<'a, 'i, T, H, I, D, R> Widget for Table<'a, 'i, 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>>,
{
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
// Render block if necessary and get the drawing area
let table_area = match self.block {
Some(ref b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -121,34 +169,40 @@ impl<'a> Widget for Table<'a> {
// Set the background
self.background(&table_area, buf, self.style.bg);
// Save widths of the columns that will fit in the given area
let mut x = 0;
let mut widths = Vec::with_capacity(self.widths.len());
for (width, title) in self.widths.iter().zip(self.header.iter()) {
let w = max(title.width() as u16, *width);
if x + w < table_area.width {
widths.push(w);
for width in self.widths.iter() {
if x + width < table_area.width {
widths.push(*width);
}
x += w;
x += *width;
}
let mut y = table_area.top();
// Header
// Draw header
if y < table_area.bottom() {
x = table_area.left();
for (w, t) in widths.iter().zip(self.header.iter()) {
buf.set_string(x, y, t, &self.header_style);
for (w, t) in widths.iter().zip(self.header.by_ref()) {
buf.set_string(x, y, &format!("{}", t), &self.header_style);
x += *w + self.column_spacing;
}
}
y += 2;
// Draw rows
let default_style = Style::default();
if y < table_area.bottom() {
let remaining = (table_area.bottom() - y) as usize;
for (i, &(ref row, style)) in self.rows.iter().take(remaining).enumerate() {
for (i, row) in self.rows.by_ref().take(remaining).enumerate() {
let (data, style) = match row {
Row::Data(d) => (d, &default_style),
Row::StyledData(d, s) => (d, s),
};
x = table_area.left();
for (w, elt) in widths.iter().zip(row.iter()) {
buf.set_stringn(x, y + i as u16, elt, *w as usize, style);
for (w, elt) in widths.iter().zip(data) {
buf.set_stringn(x, y + i as u16, &format!("{}", elt), *w as usize, style);
x += *w + self.column_spacing;
}
}

View File

@@ -12,21 +12,24 @@ use symbols::line;
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, Tabs};
/// # use tui::widgets::{Block, Borders, Tabs};
/// # use tui::style::{Style, Color};
/// # fn main() {
/// Tabs::default()
/// .block(Block::default().title("Tabs").borders(border::ALL))
/// .block(Block::default().title("Tabs").borders(Borders::ALL))
/// .titles(&["Tab1", "Tab2", "Tab3", "Tab4"])
/// .style(Style::default().fg(Color::White))
/// .highlight_style(Style::default().fg(Color::Yellow));
/// # }
/// ```
pub struct Tabs<'a> {
pub struct Tabs<'a, T>
where
T: AsRef<str> + 'a,
{
/// A block to wrap this widget in if necessary
block: Option<Block<'a>>,
/// One title for each tab
titles: &'a [&'a str],
titles: &'a [T],
/// The index of the selected tabs
selected: usize,
/// The style used to draw the text
@@ -35,8 +38,11 @@ pub struct Tabs<'a> {
highlight_style: Style,
}
impl<'a> Default for Tabs<'a> {
fn default() -> Tabs<'a> {
impl<'a, T> Default for Tabs<'a, T>
where
T: AsRef<str>,
{
fn default() -> Tabs<'a, T> {
Tabs {
block: None,
titles: &[],
@@ -47,38 +53,43 @@ impl<'a> Default for Tabs<'a> {
}
}
impl<'a> Tabs<'a> {
pub fn block(&mut self, block: Block<'a>) -> &mut Tabs<'a> {
impl<'a, T> Tabs<'a, T>
where
T: AsRef<str>,
{
pub fn block(&mut self, block: Block<'a>) -> &mut Tabs<'a, T> {
self.block = Some(block);
self
}
pub fn titles(&mut self, titles: &'a [&'a str]) -> &mut Tabs<'a> {
pub fn titles(&mut self, titles: &'a [T]) -> &mut Tabs<'a, T> {
self.titles = titles;
self
}
pub fn select(&mut self, selected: usize) -> &mut Tabs<'a> {
pub fn select(&mut self, selected: usize) -> &mut Tabs<'a, T> {
self.selected = selected;
self
}
pub fn style(&mut self, style: Style) -> &mut Tabs<'a> {
pub fn style(&mut self, style: Style) -> &mut Tabs<'a, T> {
self.style = style;
self
}
pub fn highlight_style(&mut self, style: Style) -> &mut Tabs<'a> {
pub fn highlight_style(&mut self, style: Style) -> &mut Tabs<'a, T> {
self.highlight_style = style;
self
}
}
impl<'a> Widget for Tabs<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
impl<'a, T> Widget for Tabs<'a, T>
where
T: AsRef<str>,
{
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
let tabs_area = match self.block {
Some(b) => {
Some(ref mut b) => {
b.draw(area, buf);
b.inner(area)
}
@@ -92,17 +103,19 @@ impl<'a> Widget for Tabs<'a> {
self.background(&tabs_area, buf, self.style.bg);
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)
} else {
(t, &self.style)
for (title, style) in self.titles.iter().enumerate().map(|(i, t)| {
if i == self.selected {
(t, &self.highlight_style)
} else {
(t, &self.style)
}
}) {
x += 1;
if x > tabs_area.right() {
break;
} else {
buf.set_string(x, tabs_area.top(), title, style);
x += title.width() as u16 + 1;
buf.set_string(x, tabs_area.top(), title.as_ref(), style);
x += title.as_ref().width() as u16 + 1;
if x >= tabs_area.right() {
break;
} else {