Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c38abb203 | ||
|
|
4816563452 | ||
|
|
24dc73912b | ||
|
|
f96db9c74f | ||
|
|
ef2054a45b | ||
|
|
f4052e0e71 | ||
|
|
524845cc74 | ||
|
|
4c356c5077 | ||
|
|
36dea8373f | ||
|
|
2cb823a15b | ||
|
|
169dc43565 | ||
|
|
4b53acab14 | ||
|
|
c3acac797a | ||
|
|
dd2bf0ad13 | ||
|
|
f620af1455 | ||
|
|
fcd1b7b187 | ||
|
|
d0d2f88346 | ||
|
|
c56173462a | ||
|
|
58074f23c5 | ||
|
|
f816e6bbc4 | ||
|
|
d53ecaeade | ||
|
|
299279dc2d | ||
|
|
72f9a2b460 | ||
|
|
7d273b576d | ||
|
|
3e143593ab | ||
|
|
6a3b9fb130 | ||
|
|
e53748de16 | ||
|
|
0d2e2e185e | ||
|
|
d2a4048e12 | ||
|
|
c3c5109c5a | ||
|
|
151d7e8a1c | ||
|
|
af16518650 | ||
|
|
8907ab90a1 | ||
|
|
5dd03d91ad | ||
|
|
cb8af88adf | ||
|
|
e675d6735c | ||
|
|
3012215e32 | ||
|
|
ba80889333 | ||
|
|
f24517bc5a | ||
|
|
1f285fbac0 | ||
|
|
afe5317592 | ||
|
|
20d373b5f9 | ||
|
|
b5e4ddafb4 | ||
|
|
1c0bddd9bc | ||
|
|
53d0001547 | ||
|
|
3cc3585e48 | ||
|
|
3045ac4124 | ||
|
|
80f5f9f481 | ||
|
|
71545a0aa8 | ||
|
|
295fc77df2 | ||
|
|
6eb1987650 | ||
|
|
3b8cc241ac | ||
|
|
ca3308e945 | ||
|
|
d008892e04 | ||
|
|
af6d589459 | ||
|
|
c18885d38b | ||
|
|
d6a91d1865 | ||
|
|
7749e5ee35 | ||
|
|
b8fd4a8685 | ||
|
|
41eac2aa4e | ||
|
|
89a173fe9b | ||
|
|
b1737ce667 | ||
|
|
bb61028e0c | ||
|
|
a9aa23aead | ||
|
|
d926695b17 | ||
|
|
fd1e1f22af | ||
|
|
28f0a8f216 | ||
|
|
92a3474093 | ||
|
|
7b2b1e1c80 | ||
|
|
dea9818c59 | ||
|
|
06a3c9346b | ||
|
|
c2ad42b509 | ||
|
|
419f2aadb2 | ||
|
|
28accd529a | ||
|
|
1461aaa351 | ||
|
|
d8865af559 | ||
|
|
2cb5e185eb | ||
|
|
c42ca05849 | ||
|
|
b2bb24b9d2 | ||
|
|
29db3dd722 | ||
|
|
b351ad86e4 | ||
|
|
340aa94e63 | ||
|
|
a8c623d0ce | ||
|
|
bd0dcdcc87 | ||
|
|
5e4960162d | ||
|
|
7652cc440d | ||
|
|
81cedbd6f2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
target
|
||||
Cargo.lock
|
||||
*.log
|
||||
*.rs.rustfmt
|
||||
.gdb_history
|
||||
|
||||
14
.travis.yml
14
.travis.yml
@@ -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
51
CHANGELOG.md
Normal 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`
|
||||
86
Cargo.toml
86
Cargo.toml
@@ -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
119
Makefile
@@ -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
102
README.md
@@ -1,7 +1,9 @@
|
||||
# tui-rs
|
||||
|
||||
[](https://travis-ci.org/fdehau/tui-rs)
|
||||
<img src="https://img.shields.io/crates/v/tui.svg" alt="tui’s current version badge" title="tui’s current version badge">
|
||||
[](https://crates.io/crates/tui)
|
||||
[](https://docs.rs/crate/tui/)
|
||||
|
||||
<img src="./docs/demo.gif" alt="Demo cast under Linux Termite with Inconsolata font 12pt">
|
||||
|
||||
`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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
567
examples/demo.rs
567
examples/demo.rs
@@ -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,
|
||||
|
||||
@@ -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
104
examples/layout.rs
Normal 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();
|
||||
}
|
||||
168
examples/list.rs
168
examples/list.rs
@@ -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();
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
131
examples/user_input.rs
Normal 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();
|
||||
}
|
||||
@@ -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
15
scripts/build-examples.sh
Executable 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
14
scripts/run-examples.sh
Executable 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
65
scripts/tools/install.sh
Executable 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
|
||||
9
scripts/travis/before_script.sh
Executable file
9
scripts/travis/before_script.sh
Executable 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
9
scripts/travis/script.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
make build
|
||||
make test
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||
make lint
|
||||
fi
|
||||
@@ -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>;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
133
src/buffer.rs
133
src/buffer.rs
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
157
src/lib.rs
157
src/lib.rs
@@ -1,8 +1,163 @@
|
||||
//! [tui](https://github.com/fdehau/tui-rs) is a library used to build rich
|
||||
//! terminal users interfaces and dashboards.
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! # 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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = "•";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user