mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-10 07:04:17 +00:00
chore: self contained examples
This commit is contained in:
parent
e00df22588
commit
c8c03294e1
16 changed files with 424 additions and 284 deletions
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
|
@ -9,7 +9,7 @@ on:
|
|||
name: CI
|
||||
|
||||
env:
|
||||
CI_CARGO_MAKE_VERSION: 0.32.9
|
||||
CI_CARGO_MAKE_VERSION: 0.35.6
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
|
@ -38,16 +38,10 @@ jobs:
|
|||
- name: "Install cargo-make"
|
||||
if: steps.cache-cargo-make.outputs.cache-hit != 'true'
|
||||
run: cargo install cargo-make --version ${{ env.CI_CARGO_MAKE_VERSION }}
|
||||
- name: "Format"
|
||||
run: cargo make fmt
|
||||
- name: "Check"
|
||||
run: cargo make check
|
||||
- name: "Test"
|
||||
run: cargo make test
|
||||
- name: "Format / Build / Test"
|
||||
run: cargo make ci
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
- name: "Clippy"
|
||||
run: cargo make clippy
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
|
@ -73,13 +67,7 @@ jobs:
|
|||
- name: "Install cargo-make"
|
||||
if: steps.cache-cargo-make.outputs.cache-hit != 'true'
|
||||
run: cargo install cargo-make --version ${{ env.CI_CARGO_MAKE_VERSION }}
|
||||
- name: "Format"
|
||||
run: cargo make fmt
|
||||
- name: "Check"
|
||||
run: cargo make check
|
||||
- name: "Test"
|
||||
run: cargo make test
|
||||
- name: "Format / Build / Test"
|
||||
run: cargo make ci
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
- name: "Clippy"
|
||||
run: cargo make clippy
|
||||
|
|
57
Cargo.toml
57
Cargo.toml
|
@ -33,6 +33,57 @@ rand = "0.8"
|
|||
argh = "0.1"
|
||||
|
||||
[[example]]
|
||||
name = "termion_demo"
|
||||
path = "examples/termion_demo.rs"
|
||||
required-features = ["termion"]
|
||||
name = "barchart"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "block"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "canvas"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "chart"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "custom_widget"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "gauge"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "layout"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "list"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "paragraph"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "popup"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "sparkline"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "table"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "tabs"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "user_input"
|
||||
required-features = ["crossterm"]
|
||||
|
|
113
Makefile.toml
113
Makefile.toml
|
@ -1,28 +1,34 @@
|
|||
[config]
|
||||
skip_core_tasks = true
|
||||
|
||||
[env.TUI_FEATURES]
|
||||
source = "${CARGO_MAKE_RUST_TARGET_OS}"
|
||||
default_value = "unknown"
|
||||
|
||||
[env.TUI_FEATURES.mapping]
|
||||
linux = "serde,crossterm,termion"
|
||||
macos = "serde,crossterm,termion"
|
||||
windows = "serde,crossterm"
|
||||
|
||||
[tasks.default]
|
||||
dependencies = [
|
||||
"check",
|
||||
]
|
||||
|
||||
[tasks.ci]
|
||||
dependencies = [
|
||||
"fmt",
|
||||
"check",
|
||||
"test",
|
||||
"clippy",
|
||||
run_task = [
|
||||
{ name = "ci-unix", condition = { platforms = ["linux", "mac"] } },
|
||||
{ name = "ci-windows", condition = { platforms = ["windows"] } },
|
||||
]
|
||||
|
||||
[tasks.ci-unix]
|
||||
private = true
|
||||
dependencies = [
|
||||
"fmt",
|
||||
"check-crossterm",
|
||||
"check-termion",
|
||||
"test-crossterm",
|
||||
"test-termion",
|
||||
"clippy-crossterm",
|
||||
"clippy-termion",
|
||||
"test-doc",
|
||||
]
|
||||
|
||||
[tasks.ci-windows]
|
||||
private = true
|
||||
dependencies = [
|
||||
"fmt",
|
||||
"check-crossterm",
|
||||
"test-crossterm",
|
||||
"clippy-crossterm",
|
||||
"test-doc",
|
||||
]
|
||||
|
||||
[tasks.fmt]
|
||||
command = "cargo"
|
||||
|
@ -33,8 +39,17 @@ args = [
|
|||
"--check",
|
||||
]
|
||||
|
||||
[tasks.check-crossterm]
|
||||
env = { TUI_FEATURES = "serde,crossterm" }
|
||||
run_task = "check"
|
||||
|
||||
[tasks.check-termion]
|
||||
env = { TUI_FEATURES = "serde,termion" }
|
||||
run_task = "check"
|
||||
|
||||
[tasks.check]
|
||||
command = "cargo"
|
||||
condition = { env_set = ["TUI_FEATURES"] }
|
||||
args = [
|
||||
"check",
|
||||
"--no-default-features",
|
||||
|
@ -43,8 +58,17 @@ args = [
|
|||
"--all-targets",
|
||||
]
|
||||
|
||||
[tasks.build-crossterm]
|
||||
env = { TUI_FEATURES = "serde,crossterm" }
|
||||
run_task = "build"
|
||||
|
||||
[tasks.build-termion]
|
||||
env = { TUI_FEATURES = "serde,termion" }
|
||||
run_task = "build"
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
condition = { env_set = ["TUI_FEATURES"] }
|
||||
args = [
|
||||
"build",
|
||||
"--no-default-features",
|
||||
|
@ -53,8 +77,17 @@ args = [
|
|||
"--all-targets",
|
||||
]
|
||||
|
||||
[tasks.clippy-crossterm]
|
||||
env = { TUI_FEATURES = "serde,crossterm" }
|
||||
run_task = "clippy"
|
||||
|
||||
[tasks.clippy-termion]
|
||||
env = { TUI_FEATURES = "serde,termion" }
|
||||
run_task = "clippy"
|
||||
|
||||
[tasks.clippy]
|
||||
command = "cargo"
|
||||
condition = { env_set = ["TUI_FEATURES"] }
|
||||
args = [
|
||||
"clippy",
|
||||
"--no-default-features",
|
||||
|
@ -65,48 +98,50 @@ args = [
|
|||
"warnings",
|
||||
]
|
||||
|
||||
[tasks.test-crossterm]
|
||||
env = { TUI_FEATURES = "serde,crossterm" }
|
||||
run_task = "test"
|
||||
|
||||
[tasks.test-termion]
|
||||
env = { TUI_FEATURES = "serde,termion" }
|
||||
run_task = "test"
|
||||
|
||||
[tasks.test]
|
||||
command = "cargo"
|
||||
condition = { env_set = ["TUI_FEATURES"] }
|
||||
args = [
|
||||
"test",
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
"${TUI_FEATURES}"
|
||||
"${TUI_FEATURES}",
|
||||
"--lib",
|
||||
"--tests",
|
||||
"--examples",
|
||||
]
|
||||
|
||||
[tasks.test-doc]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"test",
|
||||
"--doc",
|
||||
]
|
||||
|
||||
[tasks.run-example]
|
||||
private = true
|
||||
condition = { env_set = ["TUI_EXAMPLE_NAME", "TUI_FEATURES"] }
|
||||
condition = { env_set = ["TUI_EXAMPLE_NAME"] }
|
||||
command = "cargo"
|
||||
args = [
|
||||
"run",
|
||||
"--features",
|
||||
"${TUI_FEATURES}",
|
||||
"--release",
|
||||
"--example",
|
||||
"${TUI_EXAMPLE_NAME}"
|
||||
]
|
||||
|
||||
[tasks.run-example-windows]
|
||||
private = true
|
||||
condition = { env = {"TUI_EXAMPLE_NAME" = "crossterm_demo"} }
|
||||
run_task = "run-example"
|
||||
|
||||
[tasks.run-example-router]
|
||||
private = true
|
||||
run_task = [
|
||||
{ name = "run-example-windows", condition = { platforms = ["window"] } },
|
||||
{ name = "run-example" }
|
||||
]
|
||||
|
||||
[tasks.build-examples]
|
||||
condition = { env_set = ["TUI_FEATURES"] }
|
||||
command = "cargo"
|
||||
args = [
|
||||
"build",
|
||||
"--examples",
|
||||
"--features",
|
||||
"${TUI_FEATURES}",
|
||||
"--release"
|
||||
]
|
||||
|
||||
|
@ -119,6 +154,6 @@ for file in ${files}
|
|||
name = basename ${file}
|
||||
name = substring ${name} -3
|
||||
set_env TUI_EXAMPLE_NAME ${name}
|
||||
cm_run_task run-example-router
|
||||
cm_run_task run-example
|
||||
end
|
||||
'''
|
||||
|
|
35
README.md
35
README.md
|
@ -11,15 +11,9 @@ user interfaces and dashboards. It is heavily inspired by the `Javascript`
|
|||
library [blessed-contrib](https://github.com/yaronn/blessed-contrib) and the
|
||||
`Go` library [termui](https://github.com/gizak/termui).
|
||||
|
||||
The library itself supports four different backends to draw to the terminal. You
|
||||
can either choose from:
|
||||
|
||||
The library supports multiple backends:
|
||||
- [crossterm](https://github.com/crossterm-rs/crossterm) [default]
|
||||
- [termion](https://github.com/ticki/termion)
|
||||
- [rustbox](https://github.com/gchp/rustbox)
|
||||
- [crossterm](https://github.com/crossterm-rs/crossterm)
|
||||
- [pancurses](https://github.com/ihalila/pancurses)
|
||||
|
||||
However, some features may only be available in one of the four.
|
||||
|
||||
The library is based on the principle of immediate rendering with intermediate
|
||||
buffers. This means that at each new frame you should build all widgets that are
|
||||
|
@ -34,18 +28,19 @@ you may rely on the previously cited libraries to achieve such features.
|
|||
|
||||
### Rust version requirements
|
||||
|
||||
Since version 0.10.0, `tui` requires **rustc version 1.44.0 or greater**.
|
||||
Since version 0.17.0, `tui` requires **rustc version 1.52.1 or greater**.
|
||||
|
||||
### [Documentation](https://docs.rs/tui)
|
||||
|
||||
### Demo
|
||||
|
||||
The demo shown in the gif can be run with all available backends
|
||||
(`examples/*_demo.rs` files). For example to see the `termion` version one could
|
||||
run:
|
||||
The demo shown in the gif can be run with all available backends.
|
||||
|
||||
```
|
||||
cargo run --example termion_demo --release -- --tick-rate 200
|
||||
# crossterm
|
||||
cargo run --example demo --release -- --tick-rate 200
|
||||
# termion
|
||||
cargo run --example demo --no-default-features --features=termion --release -- --tick-rate 200
|
||||
```
|
||||
|
||||
where `tick-rate` is the UI refresh rate in ms.
|
||||
|
@ -53,18 +48,11 @@ where `tick-rate` is the UI refresh rate in ms.
|
|||
The UI code is in [examples/demo/ui.rs](https://github.com/fdehau/tui-rs/blob/v0.16.0/examples/demo/ui.rs) while the
|
||||
application state is in [examples/demo/app.rs](https://github.com/fdehau/tui-rs/blob/v0.16.0/examples/demo/app.rs).
|
||||
|
||||
Beware that the `termion_demo` only works on Unix platforms. If you are a Windows user,
|
||||
you can see the same demo using the `crossterm` backend with the following command:
|
||||
|
||||
```
|
||||
cargo run --example crossterm_demo --no-default-features --features="crossterm" --release -- --tick-rate 200
|
||||
```
|
||||
|
||||
If the user interface contains glyphs that are not displayed correctly by your terminal, you may want to run
|
||||
the demo without those symbols:
|
||||
|
||||
```
|
||||
cargo run --example crossterm_demo --no-default-features --features="crossterm" --release -- --tick-rate 200 --enhanced-graphics false
|
||||
cargo run --example demo --release -- --tick-rate 200 --enhanced-graphics false
|
||||
```
|
||||
|
||||
### Widgets
|
||||
|
@ -83,9 +71,10 @@ The library comes with the following list of widgets:
|
|||
* [Tabs](https://github.com/fdehau/tui-rs/blob/v0.16.0/examples/tabs.rs)
|
||||
|
||||
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`.
|
||||
cargo (e.g. to run the gauge example `cargo run --example gauge`), and quit by pressing `q`.
|
||||
|
||||
You can run all examples by running `make run-examples`.
|
||||
You can run all examples by running `cargo make run-examples` (require
|
||||
`cargo-make` that can be installed with `cargo install cargo-make`).
|
||||
|
||||
### Third-party widgets
|
||||
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
#[allow(dead_code)]
|
||||
mod util;
|
||||
|
||||
use crate::util::SinSignal;
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
|
@ -33,6 +29,34 @@ const DATA2: [(f64, f64); 7] = [
|
|||
(60.0, 3.0),
|
||||
];
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SinSignal {
|
||||
x: f64,
|
||||
interval: f64,
|
||||
period: f64,
|
||||
scale: f64,
|
||||
}
|
||||
|
||||
impl SinSignal {
|
||||
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
|
||||
SinSignal {
|
||||
x: 0.0,
|
||||
interval,
|
||||
period,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SinSignal {
|
||||
type Item = (f64, f64);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let point = (self.x, (self.x * 1.0 / self.period).sin() * self.scale);
|
||||
self.x += self.interval;
|
||||
Some(point)
|
||||
}
|
||||
}
|
||||
|
||||
struct App {
|
||||
signal1: SinSignal,
|
||||
data1: Vec<(f64, f64)>,
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use crate::util::{RandomSignal, SinSignal, StatefulList, TabsState};
|
||||
use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
rngs::ThreadRng,
|
||||
};
|
||||
use tui::widgets::ListState;
|
||||
|
||||
const TASKS: [&str; 24] = [
|
||||
"Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10",
|
||||
|
@ -62,6 +66,120 @@ const EVENTS: [(&str, u64); 24] = [
|
|||
("B24", 5),
|
||||
];
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RandomSignal {
|
||||
distribution: Uniform<u64>,
|
||||
rng: ThreadRng,
|
||||
}
|
||||
|
||||
impl RandomSignal {
|
||||
pub fn new(lower: u64, upper: u64) -> RandomSignal {
|
||||
RandomSignal {
|
||||
distribution: Uniform::new(lower, upper),
|
||||
rng: rand::thread_rng(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RandomSignal {
|
||||
type Item = u64;
|
||||
fn next(&mut self) -> Option<u64> {
|
||||
Some(self.distribution.sample(&mut self.rng))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SinSignal {
|
||||
x: f64,
|
||||
interval: f64,
|
||||
period: f64,
|
||||
scale: f64,
|
||||
}
|
||||
|
||||
impl SinSignal {
|
||||
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
|
||||
SinSignal {
|
||||
x: 0.0,
|
||||
interval,
|
||||
period,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SinSignal {
|
||||
type Item = (f64, f64);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let point = (self.x, (self.x * 1.0 / self.period).sin() * self.scale);
|
||||
self.x += self.interval;
|
||||
Some(point)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TabsState<'a> {
|
||||
pub titles: Vec<&'a str>,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl<'a> TabsState<'a> {
|
||||
pub fn new(titles: Vec<&'a str>) -> TabsState {
|
||||
TabsState { titles, index: 0 }
|
||||
}
|
||||
pub fn next(&mut self) {
|
||||
self.index = (self.index + 1) % self.titles.len();
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
if self.index > 0 {
|
||||
self.index -= 1;
|
||||
} else {
|
||||
self.index = self.titles.len() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StatefulList<T> {
|
||||
pub state: ListState,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> StatefulList<T> {
|
||||
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
||||
StatefulList {
|
||||
state: ListState::default(),
|
||||
items,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.items.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
self.items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Signal<S: Iterator> {
|
||||
source: S,
|
||||
pub points: Vec<S::Item>,
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
#[allow(dead_code)]
|
||||
mod demo;
|
||||
#[allow(dead_code)]
|
||||
mod util;
|
||||
|
||||
use crate::demo::{ui, App};
|
||||
use argh::FromArgs;
|
||||
use crate::{app::App, ui};
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
|
@ -20,20 +14,7 @@ use tui::{
|
|||
Terminal,
|
||||
};
|
||||
|
||||
/// Crossterm demo
|
||||
#[derive(Debug, FromArgs)]
|
||||
struct Cli {
|
||||
/// time in ms between two ticks.
|
||||
#[argh(option, default = "250")]
|
||||
tick_rate: u64,
|
||||
/// whether unicode symbols are used to improve the overall look of the app
|
||||
#[argh(option, default = "true")]
|
||||
enhanced_graphics: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli: Cli = argh::from_env();
|
||||
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
|
@ -42,8 +23,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let tick_rate = Duration::from_millis(cli.tick_rate);
|
||||
let app = App::new("Crossterm Demo", cli.enhanced_graphics);
|
||||
let app = App::new("Crossterm Demo", enhanced_graphics);
|
||||
let res = run_app(&mut terminal, app, tick_rate);
|
||||
|
||||
// restore terminal
|
31
examples/demo/main.rs
Normal file
31
examples/demo/main.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
mod app;
|
||||
#[cfg(feature = "crossterm")]
|
||||
mod crossterm;
|
||||
#[cfg(feature = "termion")]
|
||||
mod termion;
|
||||
mod ui;
|
||||
|
||||
#[cfg(feature = "crossterm")]
|
||||
use crate::crossterm::run;
|
||||
#[cfg(feature = "termion")]
|
||||
use crate::termion::run;
|
||||
use argh::FromArgs;
|
||||
use std::{error::Error, time::Duration};
|
||||
|
||||
/// Demo
|
||||
#[derive(Debug, FromArgs)]
|
||||
struct Cli {
|
||||
/// time in ms between two ticks.
|
||||
#[argh(option, default = "250")]
|
||||
tick_rate: u64,
|
||||
/// whether unicode symbols are used to improve the overall look of the app
|
||||
#[argh(option, default = "true")]
|
||||
enhanced_graphics: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli: Cli = argh::from_env();
|
||||
let tick_rate = Duration::from_millis(cli.tick_rate);
|
||||
run(tick_rate, cli.enhanced_graphics)?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
mod app;
|
||||
pub mod ui;
|
||||
pub use app::App;
|
|
@ -1,9 +1,4 @@
|
|||
mod demo;
|
||||
#[allow(dead_code)]
|
||||
mod util;
|
||||
|
||||
use crate::demo::{ui, App};
|
||||
use argh::FromArgs;
|
||||
use crate::{app::App, ui};
|
||||
use std::{error::Error, io, sync::mpsc, thread, time::Duration};
|
||||
use termion::{
|
||||
event::Key,
|
||||
|
@ -16,20 +11,7 @@ use tui::{
|
|||
Terminal,
|
||||
};
|
||||
|
||||
/// Termion demo
|
||||
#[derive(Debug, FromArgs)]
|
||||
struct Cli {
|
||||
/// time in ms between two ticks.
|
||||
#[argh(option, default = "250")]
|
||||
tick_rate: u64,
|
||||
/// whether unicode symbols are used to improve the overall look of the app
|
||||
#[argh(option, default = "true")]
|
||||
enhanced_graphics: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli: Cli = argh::from_env();
|
||||
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
let stdout = io::stdout().into_raw_mode()?;
|
||||
let stdout = MouseTerminal::from(stdout);
|
||||
|
@ -38,8 +20,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let tick_rate = Duration::from_millis(cli.tick_rate);
|
||||
let app = App::new("Termion demo", cli.enhanced_graphics);
|
||||
let app = App::new("Termion demo", enhanced_graphics);
|
||||
run_app(&mut terminal, app, tick_rate)?;
|
||||
|
||||
Ok(())
|
|
@ -1,4 +1,4 @@
|
|||
use crate::demo::App;
|
||||
use crate::app::App;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
|
|
1
examples/demo/util.rs
Normal file
1
examples/demo/util.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
#[allow(dead_code)]
|
||||
mod util;
|
||||
|
||||
use crate::util::StatefulList;
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
|
@ -17,10 +13,56 @@ use tui::{
|
|||
layout::{Constraint, Corner, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Span, Spans},
|
||||
widgets::{Block, Borders, List, ListItem},
|
||||
widgets::{Block, Borders, List, ListItem, ListState},
|
||||
Frame, Terminal,
|
||||
};
|
||||
|
||||
struct StatefulList<T> {
|
||||
state: ListState,
|
||||
items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> StatefulList<T> {
|
||||
fn with_items(items: Vec<T>) -> StatefulList<T> {
|
||||
StatefulList {
|
||||
state: ListState::default(),
|
||||
items,
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.items.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
|
||||
fn previous(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
self.items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
|
||||
fn unselect(&mut self) {
|
||||
self.state.select(None);
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct holds the current state of the app. In particular, it has the `items` field which is a wrapper
|
||||
/// around `ListState`. Keeping track of the items state let us render the associated widget with its state
|
||||
/// and have access to features such as natural scrolling.
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#[allow(dead_code)]
|
||||
mod util;
|
||||
|
||||
use crate::util::RandomSignal;
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
rngs::ThreadRng,
|
||||
};
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
|
@ -20,6 +20,28 @@ use tui::{
|
|||
Frame, Terminal,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RandomSignal {
|
||||
distribution: Uniform<u64>,
|
||||
rng: ThreadRng,
|
||||
}
|
||||
|
||||
impl RandomSignal {
|
||||
pub fn new(lower: u64, upper: u64) -> RandomSignal {
|
||||
RandomSignal {
|
||||
distribution: Uniform::new(lower, upper),
|
||||
rng: rand::thread_rng(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RandomSignal {
|
||||
type Item = u64;
|
||||
fn next(&mut self) -> Option<u64> {
|
||||
Some(self.distribution.sample(&mut self.rng))
|
||||
}
|
||||
}
|
||||
|
||||
struct App {
|
||||
signal: RandomSignal,
|
||||
data1: Vec<u64>,
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
#[allow(dead_code)]
|
||||
mod util;
|
||||
|
||||
use crate::util::TabsState;
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
|
@ -18,13 +14,27 @@ use tui::{
|
|||
};
|
||||
|
||||
struct App<'a> {
|
||||
tabs: TabsState<'a>,
|
||||
pub titles: Vec<&'a str>,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
fn new() -> App<'a> {
|
||||
App {
|
||||
tabs: TabsState::new(vec!["Tab0", "Tab1", "Tab2", "Tab3"]),
|
||||
titles: vec!["Tab0", "Tab1", "Tab2", "Tab3"],
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
self.index = (self.index + 1) % self.titles.len();
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
if self.index > 0 {
|
||||
self.index -= 1;
|
||||
} else {
|
||||
self.index = self.titles.len() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,8 +74,8 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
|
|||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => return Ok(()),
|
||||
KeyCode::Right => app.tabs.next(),
|
||||
KeyCode::Left => app.tabs.previous(),
|
||||
KeyCode::Right => app.next(),
|
||||
KeyCode::Left => app.previous(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +93,6 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
|||
let block = Block::default().style(Style::default().bg(Color::White).fg(Color::Black));
|
||||
f.render_widget(block, size);
|
||||
let titles = app
|
||||
.tabs
|
||||
.titles
|
||||
.iter()
|
||||
.map(|t| {
|
||||
|
@ -96,7 +105,7 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
|||
.collect();
|
||||
let tabs = Tabs::new(titles)
|
||||
.block(Block::default().borders(Borders::ALL).title("Tabs"))
|
||||
.select(app.tabs.index)
|
||||
.select(app.index)
|
||||
.style(Style::default().fg(Color::Cyan))
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
|
@ -104,7 +113,7 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
|||
.bg(Color::Black),
|
||||
);
|
||||
f.render_widget(tabs, chunks[0]);
|
||||
let inner = match app.tabs.index {
|
||||
let inner = match app.index {
|
||||
0 => Block::default().title("Inner 0").borders(Borders::ALL),
|
||||
1 => Block::default().title("Inner 1").borders(Borders::ALL),
|
||||
2 => Block::default().title("Inner 2").borders(Borders::ALL),
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
use rand::distributions::{Distribution, Uniform};
|
||||
use rand::rngs::ThreadRng;
|
||||
use tui::widgets::ListState;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RandomSignal {
|
||||
distribution: Uniform<u64>,
|
||||
rng: ThreadRng,
|
||||
}
|
||||
|
||||
impl RandomSignal {
|
||||
pub fn new(lower: u64, upper: u64) -> RandomSignal {
|
||||
RandomSignal {
|
||||
distribution: Uniform::new(lower, upper),
|
||||
rng: rand::thread_rng(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RandomSignal {
|
||||
type Item = u64;
|
||||
fn next(&mut self) -> Option<u64> {
|
||||
Some(self.distribution.sample(&mut self.rng))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SinSignal {
|
||||
x: f64,
|
||||
interval: f64,
|
||||
period: f64,
|
||||
scale: f64,
|
||||
}
|
||||
|
||||
impl SinSignal {
|
||||
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
|
||||
SinSignal {
|
||||
x: 0.0,
|
||||
interval,
|
||||
period,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SinSignal {
|
||||
type Item = (f64, f64);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let point = (self.x, (self.x * 1.0 / self.period).sin() * self.scale);
|
||||
self.x += self.interval;
|
||||
Some(point)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TabsState<'a> {
|
||||
pub titles: Vec<&'a str>,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl<'a> TabsState<'a> {
|
||||
pub fn new(titles: Vec<&'a str>) -> TabsState {
|
||||
TabsState { titles, index: 0 }
|
||||
}
|
||||
pub fn next(&mut self) {
|
||||
self.index = (self.index + 1) % self.titles.len();
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
if self.index > 0 {
|
||||
self.index -= 1;
|
||||
} else {
|
||||
self.index = self.titles.len() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StatefulList<T> {
|
||||
pub state: ListState,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> StatefulList<T> {
|
||||
pub fn new() -> StatefulList<T> {
|
||||
StatefulList {
|
||||
state: ListState::default(),
|
||||
items: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
||||
StatefulList {
|
||||
state: ListState::default(),
|
||||
items,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.items.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
self.items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
|
||||
pub fn unselect(&mut self) {
|
||||
self.state.select(None);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue