Documentation

pull/3/head
Florian Dehau 8 years ago
parent 9e5195096a
commit 93cc237007

@ -4,3 +4,6 @@ test:
cargo test
watch:
watchman-make -p 'src/**/*.rs' -t build -p 'test/**/*.rs' -t test
watch-test:
watchman-make -p 'src/**/*.rs' 'tests/**/*.rs' -t test

@ -13,8 +13,8 @@ use tui::style::Color;
fn main() {
let mut terminal = Terminal::new().unwrap();
let stdin = io::stdin();
terminal.clear();
terminal.hide_cursor();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal);
for c in stdin.keys() {
draw(&mut terminal);
@ -23,7 +23,7 @@ fn main() {
break;
}
}
terminal.show_cursor();
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal) {
@ -63,5 +63,5 @@ fn draw(t: &mut Terminal) {
.render(&chunks[2], t);
});
t.finish();
t.draw().unwrap();
}

@ -35,7 +35,7 @@ impl<'a> Label<'a> {
fn main() {
let mut terminal = Terminal::new().unwrap();
terminal.clear();
terminal.clear().unwrap();
Label::default().text("Test").render(&Terminal::size().unwrap(), &mut terminal);
terminal.finish();
terminal.draw().unwrap();
}

@ -5,11 +5,11 @@ extern crate log4rs;
extern crate termion;
extern crate rand;
use std::io;
use std::thread;
use std::env;
use std::time;
use std::sync::mpsc;
use std::io::stdin;
use rand::distributions::{IndependentSample, Range};
@ -24,7 +24,7 @@ use log4rs::config::{Appender, Config, Root};
use tui::Terminal;
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset,
BarChart, Marker, Tabs, Table};
use tui::widgets::canvas::{Canvas, Line, Shape, Map, MapResolution, Label};
use tui::widgets::canvas::{Canvas, Shape, Map, MapResolution, Label};
use tui::layout::{Group, Direction, Size, Rect};
use tui::style::Color;
@ -209,7 +209,7 @@ fn main() {
}
thread::spawn(move || {
let stdin = stdin();
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
@ -228,16 +228,16 @@ fn main() {
});
let mut terminal = Terminal::new().unwrap();
terminal.clear();
terminal.hide_cursor();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
loop {
let size = Terminal::size().unwrap();
if size != app.size {
terminal.resize(size);
terminal.resize(size).unwrap();
app.size = size;
}
draw(&mut terminal, &app);
draw(&mut terminal, &app).unwrap();
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => {
@ -295,10 +295,10 @@ fn main() {
}
}
}
terminal.show_cursor();
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal, app: &App) {
fn draw(t: &mut Terminal, app: &App) -> Result<(), io::Error> {
Group::default()
.direction(Direction::Vertical)
@ -364,7 +364,8 @@ fn draw(t: &mut Terminal, app: &App) {
_ => {}
};
});
t.finish();
try!(t.draw());
Ok(())
}
fn draw_main(t: &mut Terminal, app: &App, area: &Rect) {
@ -423,6 +424,7 @@ fn draw_main(t: &mut Terminal, app: &App, area: &Rect) {
.borders(border::ALL)
.title("List"))
.items(&app.items2)
.color(Color::Gray)
.render(&chunks[1], t);
});
BarChart::default()
@ -469,12 +471,13 @@ fn draw_main(t: &mut Terminal, app: &App, area: &Rect) {
.block(Block::default().borders(border::ALL).title("Footer"))
.wrap(true)
.color(app.colors[app.color_index])
.text("This is a paragraph with several lines.\nYou can change the \
color.\nUse \\{[color] [text]} to highlight the text with a \
color. For example, {red u}{green n}{yellow d}{magenta e}{cyan r} \
{gray t}{light_gray h}{light_red e} {light_green r}{light_yellow \
a}{light_magenta i}{light_cyan n}{white b}{red o}{green w}.\nOh, \
and if you didn't notice you can automatically wrap your text =).")
.text("This is a paragraph with several lines.\nYou can change the color.\nUse \
\\{[color] [text]} to highlight the text with a color. For example, {red \
u}{green n}{yellow d}{magenta e}{cyan r} {gray t}{light_gray h}{light_red \
e} {light_green r}{light_yellow a}{light_magenta i}{light_cyan n}{white \
b}{red o}{green w}.\nOh, and if you didn't notice you can automatically \
wrap your text =).\nOne more thing is that it should display unicode \
characters properly: , ٩(-̮̮̃-̃)۶ ٩(̮̮̃̃)۶ ٩(̯͡͡)۶ ٩(-̮̮̃̃).")
.render(&chunks[2], t);
});
}

@ -6,6 +6,7 @@ use unicode_segmentation::UnicodeSegmentation;
use layout::Rect;
use style::Color;
/// A buffer cell
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
pub fg: Color,
@ -32,9 +33,40 @@ impl Default for Cell {
}
}
/// A buffer that maps to the desired content of the terminal after the draw call
///
/// No widget in the library interacts directly with the terminal. Instead each of them is required
/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains
/// a grapheme, a foreground color and a background color. This grid will then be used to output
/// the appropriate escape sequences and characters to draw the UI as the user has defined it.
///
/// # Examples:
///
/// ```
/// # extern crate tui;
/// use tui::buffer::{Buffer, Cell};
/// use tui::layout::Rect;
/// use tui::style::Color;
///
/// # fn main() {
/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
/// buf.set_symbol(0, 2, "x");
/// assert_eq!(buf.at(0, 2).symbol, "x");
/// buf.set_string(3, 0, "string", Color::Red, Color::White);
/// assert_eq!(buf.at(5, 0), &Cell{symbol: String::from("r"), fg: Color::Red, bg: Color::White});
/// buf.update_cell(5, 0, |c| {
/// c.symbol.clear();
/// c.symbol.push('x');
/// });
/// assert_eq!(buf.at(5, 0).symbol, "x");
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct Buffer {
/// The area represented by this buffer
pub area: Rect,
/// The content of the buffer. The length of this Vec should always be equal to area.width *
/// area.height
pub content: Vec<Cell>,
}
@ -48,11 +80,13 @@ impl Default for Buffer {
}
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)
}
/// Returns a Buffer with all cells initialized with the attributes of the given Cell
pub fn filled(area: Rect, cell: Cell) -> Buffer {
let size = area.area() as usize;
let mut content = Vec::with_capacity(size);
@ -65,53 +99,69 @@ impl Buffer {
}
}
/// Returns the content of the buffer as a slice
pub fn content(&self) -> &[Cell] {
&self.content
}
/// Returns the area covered by this buffer
pub fn area(&self) -> &Rect {
&self.area
}
/// Returns a reference to Cell at the given coordinates
pub fn at(&self, x: u16, y: u16) -> &Cell {
let i = self.index_of(x, y);
&self.content[i]
}
/// Returns the index in the Vec<Cell> for the given (x, y)
pub fn index_of(&self, x: u16, y: u16) -> usize {
let index = (y * self.area.width + x) as usize;
debug_assert!(index < self.content.len(),
"Trying to access position x:{}, y:{} in buffer {:?}",
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);
let index = ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize;
index
}
/// Returns the coordinates of a cell given its index
pub fn pos_of(&self, i: usize) -> (u16, u16) {
debug_assert!(self.area.width > 0);
(i as u16 % self.area.width, 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)
}
/// Update the symbol of a cell at (x, y)
pub fn set_symbol(&mut self, x: u16, y: u16, symbol: &str) {
let i = self.index_of(x, y);
self.content[i].symbol.clear();
self.content[i].symbol.push_str(symbol);
}
/// Update the foreground color of a cell at (x, y)
pub fn set_fg(&mut self, x: u16, y: u16, color: Color) {
let i = self.index_of(x, y);
self.content[i].fg = color;
}
/// Update the background color of a cell at (x, y)
pub fn set_bg(&mut self, x: u16, y: u16, color: Color) {
let i = self.index_of(x, y);
self.content[i].bg = color;
}
/// Print a string, starting at the position (x, y)
pub fn set_string(&mut self, x: u16, y: u16, string: &str, fg: Color, bg: Color) {
self.set_stringn(x, y, string, usize::MAX, fg, bg);
}
/// Print at most the first n characters of a string if enough space is available
/// until the end of the line
pub fn set_stringn(&mut self,
x: u16,
y: u16,
@ -132,12 +182,14 @@ impl Buffer {
}
/// Update both the foreground and the background colors in a single method call
pub fn set_colors(&mut self, x: u16, y: u16, fg: Color, bg: Color) {
let i = self.index_of(x, y);
self.content[i].fg = fg;
self.content[i].bg = bg;
}
/// Update all attributes of a cell at the given position
pub fn set_cell(&mut self, x: u16, y: u16, symbol: &str, fg: Color, bg: Color) {
let i = self.index_of(x, y);
self.content[i].symbol.clear();
@ -146,6 +198,7 @@ impl Buffer {
self.content[i].bg = bg;
}
/// Update a cell using the closure passed as last argument
pub fn update_cell<F>(&mut self, x: u16, y: u16, f: F)
where F: Fn(&mut Cell)
{
@ -153,6 +206,8 @@ impl Buffer {
f(&mut self.content[i]);
}
/// Resize the buffer so that the mapped area matches the given area and that the buffer
/// length is equal to area.width * area.height
pub fn resize(&mut self, area: Rect) {
let length = area.area() as usize;
if self.content.len() > length {
@ -163,12 +218,14 @@ impl Buffer {
self.area = area;
}
/// Reset all cells in the buffer
pub fn reset(&mut self) {
for c in &mut self.content {
c.reset();
}
}
/// Merge an other buffer into this one
pub fn merge(&mut self, other: Buffer) {
let area = self.area.union(&other.area);
let cell: Cell = Default::default();

@ -13,6 +13,8 @@ pub enum Direction {
Vertical,
}
/// A simple rectangle used in the computation of the layout and to give widgets an hint about the
/// area they are supposed to render to.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct Rect {
pub x: u16,
@ -115,20 +117,24 @@ pub enum Size {
Min(u16),
}
/// Wrapper function around the cassowary-rs solver to be able to split a given
/// area into smaller ones based on the preferred widths or heights and the direction.
///
/// # Examples
/// ```
/// extern crate tui;
/// use tui::layout::{Rect, Size, Direction, split};
/// # extern crate tui;
/// # use tui::layout::{Rect, Size, Direction, split};
///
/// fn main() {
/// # fn main() {
/// let chunks = split(&Rect{x: 2, y: 2, width: 10, height: 10},
/// &Direction::Vertical,
/// 0,
/// &[Size::Fixed(5), Size::Min(5)]);
/// }
/// &[Size::Fixed(5), Size::Min(0)]);
/// assert_eq!(chunks, vec![Rect{x:2, y: 2, width: 10, height: 5},
/// Rect{x: 2, y: 7, width: 10, height: 5}])
/// # }
///
/// ```
#[allow(unused_variables)]
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();
@ -219,6 +225,7 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
results
}
/// A container used by the solver inside split
struct Element {
x: Variable,
y: Variable,
@ -253,6 +260,20 @@ impl Element {
}
}
/// Describes a layout and may be used to group widgets in a specific area of the terminal
///
/// # Examples
///
/// ```
/// # extern crate tui;
/// use tui::layout::{Group, Direction, Size};
/// # fn main() {
/// Group::default()
/// .direction(Direction::Vertical)
/// .margin(0)
/// .sizes(&[Size::Percent(50), Size::Percent(50)]);
/// # }
/// ```
#[derive(Debug, Hash)]
pub struct Group {
pub direction: Direction,

@ -11,20 +11,29 @@ use widgets::Widget;
use style::Color;
use util::hash;
/// Holds a computed layout and keeps track of its use between successive draw calls
#[derive(Debug)]
pub struct LayoutEntry {
chunks: Vec<Rect>,
hot: bool,
}
/// Interface to the terminal backed by Termion
pub struct Terminal {
/// Raw mode termion terminal
stdout: RawTerminal<io::Stdout>,
/// Cache to prevent the layout to be computed at each draw call
layout_cache: HashMap<u64, LayoutEntry>,
/// Holds the results of the current and previous draw calls. The two are compared at the end
/// of each draw pass to output the necessary updates to the terminal
buffers: [Buffer; 2],
/// Index of the current buffer in the previous array
current: usize,
}
impl Terminal {
/// Wrapper around Termion initialization. Each buffer is initialized with a blank string and
/// default colors for the foreground and the background
pub fn new() -> Result<Terminal, io::Error> {
let stdout = try!(io::stdout().into_raw_mode());
let size = try!(Terminal::size());
@ -36,6 +45,7 @@ impl Terminal {
})
}
/// Return the size of the terminal
pub fn size() -> Result<Rect, io::Error> {
let terminal = try!(termion::terminal_size());
Ok(Rect {
@ -46,6 +56,9 @@ impl Terminal {
})
}
/// Check if we have already computed a layout for a given group, otherwise it creates one and
/// add it to the layout cache. Moreover the function marks the queried entries so that we can
/// clean outdated ones at the end of the draw call.
pub fn compute_layout(&mut self, group: &Group, area: &Rect) -> Vec<Rect> {
let hash = hash(group, area);
let entry = self.layout_cache
@ -65,9 +78,11 @@ impl Terminal {
entry.chunks.clone()
}
pub fn flush(&mut self) {
/// Builds a string representing the minimal escape sequences and characters set necessary to
/// update the UI and writes it to stdout.
pub fn flush(&mut self) -> Result<(), io::Error> {
let width = self.buffers[self.current].area.width;
let mut string = String::with_capacity(self.buffers[self.current].content.len());
let mut string = String::with_capacity(self.buffers[self.current].content.len() * 3);
let mut fg = Color::Reset;
let mut bg = Color::Reset;
let mut last_y = 0;
@ -106,29 +121,38 @@ impl Terminal {
string.push_str(&cell.symbol);
inst += 1;
}
string.push_str(&format!("{}{}", Color::Reset.fg(), Color::Reset.bg()));
debug!("{} instructions outputed.", inst);
write!(self.stdout, "{}", string).unwrap();
try!(write!(self.stdout,
"{}{}{}",
string,
Color::Reset.fg(),
Color::Reset.bg()));
Ok(())
}
/// 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
{
widget.draw(area, &mut self.buffers[self.current]);
}
pub fn resize(&mut self, area: Rect) {
/// Updates the interface so that internal buffers matches the current size of the terminal.
/// This leads to a full redraw of the screen.
pub fn resize(&mut self, area: Rect) -> Result<(), io::Error> {
self.buffers[self.current].resize(area);
self.buffers[1 - self.current].resize(area);
self.buffers[1 - self.current].reset();
self.layout_cache.clear();
self.clear();
try!(self.clear());
Ok(())
}
pub fn finish(&mut self) {
/// 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();
try!(self.flush());
// Clean layout cache
let to_remove = self.layout_cache
@ -149,21 +173,29 @@ impl Terminal {
self.current = 1 - self.current;
// Flush
self.stdout.flush().unwrap();
try!(self.stdout.flush());
Ok(())
}
pub fn clear(&mut self) {
write!(self.stdout, "{}", termion::clear::All).unwrap();
write!(self.stdout, "{}", termion::cursor::Goto(1, 1)).unwrap();
self.stdout.flush().unwrap();
/// Clears the entire screen and move the cursor to the top left of the screen
pub fn clear(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::clear::All));
try!(write!(self.stdout, "{}", termion::cursor::Goto(1, 1)));
try!(self.stdout.flush());
Ok(())
}
pub fn hide_cursor(&mut self) {
write!(self.stdout, "{}", termion::cursor::Hide).unwrap();
self.stdout.flush().unwrap();
/// Hides cursor
pub fn hide_cursor(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::cursor::Hide));
try!(self.stdout.flush());
Ok(())
}
pub fn show_cursor(&mut self) {
write!(self.stdout, "{}", termion::cursor::Show).unwrap();
self.stdout.flush().unwrap();
/// Shows cursor
pub fn show_cursor(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::cursor::Show));
try!(self.stdout.flush());
Ok(())
}
}

@ -9,12 +9,19 @@ use layout::Rect;
use style::Color;
use symbols;
/// An X or Y axis for the chart widget
pub struct Axis<'a> {
/// Title displayed next to axis end
title: Option<&'a str>,
/// Color of the title
title_color: Color,
/// 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]>,
/// The labels' color
labels_color: Color,
/// The color used to draw the axis itself
color: Color,
}
@ -63,15 +70,21 @@ impl<'a> Axis<'a> {
}
}
/// Marker to use when plotting data points
pub enum Marker {
Dot,
Braille,
}
/// A group of data points
pub struct Dataset<'a> {
/// Name of the dataset (used in the legend if shown)
name: &'a str,
/// A reference to the actual data
data: &'a [(f64, f64)],
/// Symbol used for each points of this dataset
marker: Marker,
/// Color of the corresponding points and of the legend entry
color: Color,
}
@ -108,6 +121,8 @@ impl<'a> Dataset<'a> {
}
}
/// A container that holds all the infos about where to display each elements of the chart (axis,
/// labels, legend, ...).
#[derive(Debug)]
struct ChartLayout {
title_x: Option<(u16, u16)>,
@ -135,11 +150,17 @@ impl Default for ChartLayout {
}
}
/// A widget to plot one or more dataset in a cartesian coordinate system
pub struct Chart<'a> {
/// A block to display around the widget eventually
block: Option<Block<'a>>,
/// The horizontal axis
x_axis: Axis<'a>,
/// The vertical axis
y_axis: Axis<'a>,
/// A reference to the datasets
datasets: &'a [Dataset<'a>],
/// The background color
background_color: Color,
}
@ -182,6 +203,8 @@ impl<'a> Chart<'a> {
}
/// 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 {
let mut layout = ChartLayout::default();
if area.height == 0 || area.width == 0 {

@ -5,19 +5,22 @@ use buffer::Buffer;
use style::Color;
use layout::Rect;
/// Progress bar widget
/// A widget to display a task progress.
///
/// # Examples:
///
/// ```
/// extern crate tui;
/// use tui::widgets::{Widget, Gauge, Block, border};
/// # extern crate tui;
/// # use tui::widgets::{Widget, Gauge, Block, border};
/// # use tui::style::Color;
///
/// fn main() {
/// # fn main() {
/// Gauge::default()
/// .block(Block::default().borders(border::ALL).title("Progress"))
/// .color(Color::White)
/// .background_color(Color::Black)
/// .percent(20);
/// }
/// # }
/// ```
pub struct Gauge<'a> {
block: Option<Block<'a>>,

@ -7,13 +7,37 @@ use widgets::{Widget, Block};
use layout::Rect;
use style::Color;
/// A widget to display several items among which one can be selected (optional)
///
/// # Examples
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, List};
/// # use tui::style::Color;
/// # fn main() {
/// List::default()
/// .block(Block::default().title("List").borders(border::ALL))
/// .items(&["Item 1", "Item 2", "Item 3"])
/// .select(1)
/// .color(Color::White)
/// .highlight_color(Color::Yellow)
/// .highlight_symbol(">>");
/// # }
/// ```
pub struct List<'a> {
block: Option<Block<'a>>,
/// Items to be displayed
items: &'a [&'a str],
selected: usize,
/// Index of the one selected
selected: Option<usize>,
/// Color used to render non selected items
color: Color,
/// Background color of the widget
background_color: Color,
/// Color used to render selected item
highlight_color: Color,
/// Symbol in front of the selected item (Shift all items to the right)
highlight_symbol: Option<&'a str>,
}
@ -22,7 +46,7 @@ impl<'a> Default for List<'a> {
List {
block: None,
items: &[],
selected: 0,
selected: None,
color: Color::Reset,
background_color: Color::Reset,
highlight_color: Color::Reset,
@ -63,7 +87,7 @@ impl<'a> List<'a> {
}
pub fn select(&'a mut self, index: usize) -> &'a mut List<'a> {
self.selected = index;
self.selected = Some(index);
self
}
}
@ -90,8 +114,12 @@ impl<'a> Widget for List<'a> {
let list_length = self.items.len();
let list_height = list_area.height as usize;
let bound = min(list_height, list_length);
let offset = if self.selected >= list_height {
self.selected - list_height + 1
let (selected, highlight_color) = match self.selected {
Some(i) => (i, self.highlight_color),
None => (0, self.color),
};
let offset = if selected >= list_height {
selected - list_height + 1
} else {
0
};
@ -106,8 +134,8 @@ impl<'a> Widget for List<'a> {
for i in 0..bound {
let index = i + offset;
let item = self.items[index];
let color = if index == self.selected {
self.highlight_color
let color = if index == selected {
highlight_color
} else {
self.color
};
@ -121,7 +149,7 @@ impl<'a> Widget for List<'a> {
if let Some(s) = self.highlight_symbol {
buf.set_string(list_area.left(),
list_area.top() + (self.selected - offset) as u16,
list_area.top() + (selected - offset) as u16,
s,
self.highlight_color,
self.background_color);

@ -24,6 +24,7 @@ use layout::Rect;
use terminal::Terminal;
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 {
@ -37,8 +38,12 @@ pub mod border {
}
}
/// Base requirements for a Widget
pub trait Widget {
/// Draws the current state of the widget in the given buffer. That the only method required to
/// implement a custom widget.
fn draw(&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() {
for x in area.left()..area.right() {
@ -46,6 +51,7 @@ pub trait Widget {
}
}
}
/// Helper method that can be chained with a widget's builder methods to render it.
fn render(&self, area: &Rect, t: &mut Terminal)
where Self: Sized
{

Loading…
Cancel
Save