Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe17165c39 | ||
|
|
e18671c1e4 | ||
|
|
b5f6219d39 | ||
|
|
5ed82aac5f | ||
|
|
f6a0a91a23 | ||
|
|
5645d0de03 | ||
|
|
ffaaf5e39c | ||
|
|
567cf7b8e5 | ||
|
|
5f8dd38135 | ||
|
|
a74d335cb4 | ||
|
|
6d594143ed | ||
|
|
7a5ad3fbdb | ||
|
|
584f7688f4 | ||
|
|
4436110c44 | ||
|
|
8a7c9d49b2 | ||
|
|
b5d41caace | ||
|
|
206813d560 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ Cargo.lock
|
||||
*.log
|
||||
*.rs.rustfmt
|
||||
.gdb_history
|
||||
.idea/
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -2,9 +2,21 @@
|
||||
|
||||
## To be released
|
||||
|
||||
## v0.5.0 - 2019-03-10
|
||||
## v0.6.0 - 2019-05-18
|
||||
|
||||
### Added
|
||||
### Changed
|
||||
|
||||
* Update crossterm backend
|
||||
|
||||
## v0.5.1 - 2019-04-14
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix a panic in the Sparkline widget
|
||||
|
||||
## v0.5.0 - 2019-03-10
|
||||
|
||||
### Added
|
||||
|
||||
* Add a new curses backend (with Windows support thanks to `pancurses`).
|
||||
* Add `Backend::get_cursor` and `Backend::set_cursor` methods to query and
|
||||
@@ -14,7 +26,7 @@ set the position of the cursor.
|
||||
* Add `Ratio` as a new variant of layout `Constraint`. It can be used to define
|
||||
exact ratios constraints.
|
||||
|
||||
### Changed
|
||||
### Changed
|
||||
|
||||
* Add support for multiple modifiers on the same `Style` by changing `Modifier`
|
||||
from an enum to a bitflags struct.
|
||||
@@ -33,14 +45,14 @@ let style = Style::default().modifier(Modifier::ITALIC);
|
||||
let style = Style::default().modifier(Modifier::ITALIC | Modifier::BOLD);
|
||||
```
|
||||
|
||||
### Fixed
|
||||
### Fixed
|
||||
|
||||
* Ensure correct behavoir of the alternate screens with the `Crossterm` backend.
|
||||
* Fix out of bounds panic when two `Buffer` are merged.
|
||||
|
||||
## v0.4.0 - 2019-02-03
|
||||
|
||||
### Added
|
||||
### Added
|
||||
|
||||
* Add a new canvas shape: `Rectangle`.
|
||||
* Official support of `Crossterm` backend.
|
||||
@@ -49,11 +61,11 @@ let style = Style::default().modifier(Modifier::ITALIC | Modifier::BOLD);
|
||||
* The gauge widget accepts a ratio (f64 between 0 and 1) in addition of a
|
||||
percentage.
|
||||
|
||||
### Changed
|
||||
### Changed
|
||||
|
||||
* Upgrade to Rust 2018 edition.
|
||||
|
||||
### Fixed
|
||||
### Fixed
|
||||
|
||||
* Fix rendering of double-width characters.
|
||||
* Fix race condition on the size of the terminal and expose a size that is
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tui"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
authors = ["Florian Dehau <work@fdehau.com>"]
|
||||
description = """
|
||||
A library to build rich terminal user interfaces or dashboards
|
||||
@@ -30,7 +30,7 @@ unicode-segmentation = "1.2"
|
||||
unicode-width = "0.1"
|
||||
termion = { version = "1.5", optional = true }
|
||||
rustbox = { version = "0.11", optional = true }
|
||||
crossterm = { version = "0.6", optional = true }
|
||||
crossterm = { version = "^0.9", optional = true }
|
||||
easycurses = { version = "0.12.2", optional = true }
|
||||
pancurses = { version = "0.16.1", optional = true, features = ["win32a"] }
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ can either choose from:
|
||||
- [crossterm](https://github.com/TimonPost/crossterm)
|
||||
- [pancurses](https://github.com/ihalila/pancurses)
|
||||
|
||||
However, some features may only be available in one of the three.
|
||||
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
|
||||
@@ -56,7 +56,7 @@ The library comes with the following list of widgets:
|
||||
* [Gauge](examples/gauge.rs)
|
||||
* [Sparkline](examples/sparkline.rs)
|
||||
* [Chart](examples/chart.rs)
|
||||
* [BarChart](examples/bar_chart.rs)
|
||||
* [BarChart](examples/barchart.rs)
|
||||
* [List](examples/list.rs)
|
||||
* [Table](examples/table.rs)
|
||||
* [Paragraph](examples/paragraph.rs)
|
||||
|
||||
@@ -31,9 +31,7 @@ fn main() -> Result<(), failure::Error> {
|
||||
let cli = Cli::from_args();
|
||||
stderrlog::new().quiet(!cli.log).verbosity(4).init()?;
|
||||
|
||||
let screen = crossterm::Screen::default();
|
||||
let alternate_screen = screen.enable_alternate_modes(true)?;
|
||||
let backend = CrosstermBackend::with_alternate_screen(alternate_screen)?;
|
||||
let backend = CrosstermBackend::new();
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.hide_cursor()?;
|
||||
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
use std::io;
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::buffer::Cell;
|
||||
use crate::layout::Rect;
|
||||
use crate::style::{Color, Modifier};
|
||||
use crossterm::error::ErrorKind;
|
||||
use crate::{buffer::Cell, layout::Rect};
|
||||
use crossterm::{Crossterm, ErrorKind};
|
||||
use std::io::{stdout, Write};
|
||||
|
||||
pub struct CrosstermBackend {
|
||||
screen: Option<crossterm::Screen>,
|
||||
crossterm: crossterm::Crossterm,
|
||||
// Need to keep the AlternateScreen around even when not using it directly,
|
||||
// see https://github.com/TimonPost/crossterm/issues/88
|
||||
alternate_screen: Option<crossterm::AlternateScreen>,
|
||||
crossterm: Crossterm,
|
||||
}
|
||||
|
||||
impl Default for CrosstermBackend {
|
||||
fn default() -> CrosstermBackend {
|
||||
let screen = crossterm::Screen::default();
|
||||
let crossterm = crossterm::Crossterm::from_screen(&screen);
|
||||
CrosstermBackend {
|
||||
screen: Some(screen),
|
||||
crossterm,
|
||||
crossterm: Crossterm::new(),
|
||||
alternate_screen: None,
|
||||
}
|
||||
}
|
||||
@@ -31,33 +25,15 @@ impl CrosstermBackend {
|
||||
CrosstermBackend::default()
|
||||
}
|
||||
|
||||
pub fn with_screen(screen: crossterm::Screen) -> CrosstermBackend {
|
||||
let crossterm = crossterm::Crossterm::from_screen(&screen);
|
||||
CrosstermBackend {
|
||||
screen: Some(screen),
|
||||
crossterm,
|
||||
alternate_screen: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_alternate_screen(
|
||||
alternate_screen: crossterm::AlternateScreen,
|
||||
) -> Result<CrosstermBackend, io::Error> {
|
||||
let crossterm = crossterm::Crossterm::from_screen(&alternate_screen.screen);
|
||||
Ok(CrosstermBackend {
|
||||
screen: None,
|
||||
crossterm,
|
||||
crossterm: Crossterm::new(),
|
||||
alternate_screen: Some(alternate_screen),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn screen(&self) -> Option<&crossterm::Screen> {
|
||||
match &self.screen {
|
||||
Some(screen) => Some(&screen),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alternate_screen(&self) -> Option<&crossterm::AlternateScreen> {
|
||||
match &self.alternate_screen {
|
||||
Some(alt_screen) => Some(&alt_screen),
|
||||
@@ -89,9 +65,7 @@ fn convert_error(error: ErrorKind) -> io::Error {
|
||||
impl Backend for CrosstermBackend {
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
let terminal = self.crossterm.terminal();
|
||||
terminal
|
||||
.clear(crossterm::ClearType::All)
|
||||
.map_err(convert_error)?;
|
||||
terminal.clear(crossterm::ClearType::All)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -135,6 +109,10 @@ impl Backend for CrosstermBackend {
|
||||
let mut last_y = 0;
|
||||
let mut last_x = 0;
|
||||
let mut first = true;
|
||||
|
||||
let stdout = stdout();
|
||||
let mut handle = stdout.lock();
|
||||
|
||||
for (x, y, cell) in content {
|
||||
if y != last_y || x != last_x + 1 || first {
|
||||
cursor.goto(x, y).map_err(convert_error)?;
|
||||
@@ -151,7 +129,7 @@ impl Backend for CrosstermBackend {
|
||||
}
|
||||
s.object_style.attrs = cell.style.modifier.into();
|
||||
|
||||
self.crossterm.paint(s).map_err(convert_error)?;
|
||||
write!(handle, "{}", s)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ impl Backend for CursesBackend {
|
||||
for (col, row, cell) in content {
|
||||
// eprintln!("{:?}", cell);
|
||||
if row != last_row || col != last_col + 1 {
|
||||
self.curses.move_rc(row as i32, col as i32);
|
||||
self.curses.move_rc(i32::from(row), i32::from(col));
|
||||
}
|
||||
last_col = col;
|
||||
last_row = row;
|
||||
@@ -111,7 +111,7 @@ impl Backend for CursesBackend {
|
||||
Ok((x as u16, y as u16))
|
||||
}
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
self.curses.move_rc(x as i32, y as i32);
|
||||
self.curses.move_rc(i32::from(x), i32::from(y));
|
||||
Ok(())
|
||||
}
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
|
||||
@@ -103,7 +103,7 @@ impl Into<rustbox::Color> for Color {
|
||||
Color::Cyan | Color::LightCyan => rustbox::Color::Cyan,
|
||||
Color::White => rustbox::Color::White,
|
||||
Color::Blue | Color::LightBlue => rustbox::Color::Blue,
|
||||
Color::Indexed(i) => rustbox::Color::Byte(i as u16),
|
||||
Color::Indexed(i) => rustbox::Color::Byte(u16::from(i)),
|
||||
Color::Rgb(r, g, b) => rustbox::Color::Byte(rgb_to_byte(r, g, b)),
|
||||
}
|
||||
}
|
||||
|
||||
70
src/lib.rs
70
src/lib.rs
@@ -5,12 +5,34 @@
|
||||
//!
|
||||
//! # Get started
|
||||
//!
|
||||
//! ## Adding `tui` as a dependency
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! tui = "0.5"
|
||||
//! termion = "1.5"
|
||||
//! ```
|
||||
//!
|
||||
//! The crate is using the `termion` backend by default but if for some reason you might want to use
|
||||
//! the `rustbox` backend instead, you need the to replace your dependency specification by:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! rustbox = "0.11"
|
||||
//!
|
||||
//! [dependencies.tui]
|
||||
//! version = "0.5"
|
||||
//! default-features = false
|
||||
//! features = ['rustbox']
|
||||
//! ```
|
||||
//!
|
||||
//! The same logic applies for all other available backends.
|
||||
//!
|
||||
//! ## 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.
|
||||
//! 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.
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use std::io;
|
||||
@@ -26,20 +48,10 @@
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! 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.3.0"
|
||||
//! default-features = false
|
||||
//! features = ['rustbox']
|
||||
//! ```
|
||||
//!
|
||||
//! and then create the terminal in a similar way:
|
||||
//! If you had previously chosen `rustbox` as a backend, the terminal can be created in a similar
|
||||
//! way:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//!
|
||||
//! use tui::Terminal;
|
||||
//! use tui::backend::RustboxBackend;
|
||||
//!
|
||||
@@ -50,21 +62,22 @@
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! You may also refer to the examples to find out how to create a `Terminal` for each available
|
||||
//! backend.
|
||||
//!
|
||||
//! ## 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.
|
||||
//! 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 `Frame` instance and an area to draw
|
||||
//! to.
|
||||
//! 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 `Frame`
|
||||
//! instance and an area to draw to.
|
||||
//!
|
||||
//! The following example renders a block of the size of the terminal:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//!
|
||||
//! use std::io;
|
||||
//! use termion::raw::IntoRawMode;
|
||||
//! use tui::Terminal;
|
||||
@@ -93,7 +106,6 @@
|
||||
//! full customization. And `Layout` is no exception:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//!
|
||||
//! use std::io;
|
||||
//! use termion::raw::IntoRawMode;
|
||||
//! use tui::Terminal;
|
||||
@@ -129,10 +141,10 @@
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! This let you describe responsive terminal UI by nesting layouts. 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 constraint and don't use the corresponding area.
|
||||
//! This let you describe responsive terminal UI by nesting layouts. 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 constraint and don't use the
|
||||
//! corresponding area.
|
||||
|
||||
pub mod backend;
|
||||
pub mod buffer;
|
||||
|
||||
@@ -26,7 +26,7 @@ pub enum Color {
|
||||
impl Color {
|
||||
/// Returns a short code associated with the color, used for debug purpose
|
||||
/// only
|
||||
pub(crate) fn code(&self) -> &str {
|
||||
pub(crate) fn code(self) -> &'static str {
|
||||
match self {
|
||||
Color::Reset => "X",
|
||||
Color::Black => "b",
|
||||
@@ -68,7 +68,7 @@ bitflags! {
|
||||
impl Modifier {
|
||||
/// Returns a short code associated with the color, used for debug purpose
|
||||
/// only
|
||||
pub(crate) fn code(&self) -> String {
|
||||
pub(crate) fn code(self) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut result = String::new();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::iter;
|
||||
use std::iter::Iterator;
|
||||
use std::convert::AsRef;
|
||||
use std::iter::{self, Iterator};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
@@ -166,7 +166,7 @@ impl<'b> SelectableList<'b> {
|
||||
where
|
||||
I: AsRef<str> + 'b,
|
||||
{
|
||||
self.items = items.iter().map(|i| i.as_ref()).collect::<Vec<&str>>();
|
||||
self.items = items.iter().map(AsRef::as_ref).collect::<Vec<&str>>();
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,13 @@ impl<'a> Widget for Sparkline<'a> {
|
||||
.data
|
||||
.iter()
|
||||
.take(max_index)
|
||||
.map(|e| e * u64::from(spark_area.height) * 8 / max)
|
||||
.map(|e| {
|
||||
if max != 0 {
|
||||
e * u64::from(spark_area.height) * 8 / max
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
.collect::<Vec<u64>>();
|
||||
for j in (0..spark_area.height).rev() {
|
||||
for (i, d) in data.iter_mut().enumerate() {
|
||||
@@ -118,3 +124,24 @@ impl<'a> Widget for Sparkline<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_does_not_panic_if_max_is_zero() {
|
||||
let mut widget = Sparkline::default().data(&[0, 0, 0]);
|
||||
let area = Rect::new(0, 0, 3, 1);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
widget.draw(area, &mut buffer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_does_not_panic_if_max_is_set_to_zero() {
|
||||
let mut widget = Sparkline::default().data(&[0, 1, 2]).max(0);
|
||||
let area = Rect::new(0, 0, 3, 1);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
widget.draw(area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,8 @@ fn paragraph_render_double_width() {
|
||||
let backend = TestBackend::new(10, 10);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
let s = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点では、";
|
||||
let s =
|
||||
"コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点では、";
|
||||
terminal
|
||||
.draw(|mut f| {
|
||||
let size = f.size();
|
||||
|
||||
Reference in New Issue
Block a user