Add support for text styling

This commit is contained in:
Florian Dehau 2016-11-06 18:49:57 +01:00
parent 0b950de09f
commit 224eb2d8e0
19 changed files with 605 additions and 495 deletions

View file

@ -5,13 +5,13 @@ use std::io;
use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, border};
use tui::layout::{Group, Direction, Size};
use tui::style::Color;
fn main() {
let mut terminal = Terminal::new().unwrap();
let mut terminal = Terminal::new(TermionBackend::new().unwrap()).unwrap();
let stdin = io::stdin();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
@ -26,41 +26,43 @@ fn main() {
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal) {
fn draw(t: &mut Terminal<TermionBackend>) {
let size = t.size().unwrap();
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Fixed(7), Size::Min(0), Size::Fixed(7)])
.render(t, &Terminal::size().unwrap(), |t, chunks| {
.render(t, &size, |t, chunks| {
Block::default()
.title("Top")
.title_color(Color::Magenta)
.background_color(Color::White)
.border_color(Color::Magenta)
.borders(border::BOTTOM)
.render(&chunks[0], t);
.render(t, &chunks[0]);
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Fixed(7), Size::Min(0), Size::Fixed(7)])
.render(t, &chunks[1], |t, chunks| {
Block::default().title("Left").title_color(Color::Yellow).render(&chunks[0], t);
Block::default().title("Left").title_color(Color::Yellow).render(t, &chunks[0]);
Block::default()
.title("Middle")
.title_color(Color::Cyan)
.border_color(Color::Cyan)
.borders(border::LEFT | border::RIGHT)
.render(&chunks[1], t);
.render(t, &chunks[1]);
Block::default()
.title("Right")
.title_color(Color::Green)
.render(&chunks[2], t);
.render(t, &chunks[2]);
});
Block::default()
.title("Bottom")
.title_color(Color::Red)
.border_color(Color::Red)
.borders(border::TOP)
.render(&chunks[2], t);
.render(t, &chunks[2]);
});
t.draw().unwrap();

View file

@ -1,6 +1,6 @@
extern crate tui;
use tui::Terminal;
use tui::{Terminal, TermionBackend};
use tui::widgets::Widget;
use tui::buffer::Buffer;
use tui::layout::Rect;
@ -34,8 +34,9 @@ impl<'a> Label<'a> {
}
fn main() {
let mut terminal = Terminal::new().unwrap();
let mut terminal = Terminal::new(TermionBackend::new().unwrap()).unwrap();
let size = terminal.size().unwrap();
terminal.clear().unwrap();
Label::default().text("Test").render(&Terminal::size().unwrap(), &mut terminal);
Label::default().text("Test").render(&mut terminal, &size);
terminal.draw().unwrap();
}

View file

@ -22,11 +22,11 @@ use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Root};
use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset,
use tui::widgets::{Widget, Block, 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::Color;
use tui::style::{Style, Color, Modifier};
#[derive(Clone)]
struct RandomSignal {
@ -330,10 +330,10 @@ fn draw(t: &mut Terminal<TermionBackend>, app: &App) -> Result<(), io::Error> {
Tabs::default()
.block(Block::default().borders(border::ALL).title("Tabs"))
.titles(&app.tabs.titles)
.color(Color::Green)
.highlight_color(Color::Yellow)
.style(Style::default().fg(Color::Green))
.highlight_style(Style::default().fg(Color::Yellow))
.select(app.tabs.selection)
.render(&chunks[0], t);
.render(t, &chunks[0]);
match app.tabs.selection {
0 => {
draw_main(t, app, &chunks[1]);
@ -348,13 +348,13 @@ fn draw(t: &mut Terminal<TermionBackend>, app: &App) -> Result<(), io::Error> {
.title("Servers")
.borders(border::ALL))
.header(&["Server", "Location", "Status"])
.header_color(Color::Red)
.header_style(Style::default().fg(Color::Red))
.widths(&[15, 15, 10])
.rows(app.servers
.iter()
.map(|s| vec![s.name, s.location, s.status])
.collect::<Vec<Vec<&str>>>())
.render(&chunks[0], t);
.render(t, &chunks[0]);
Canvas::default()
.block(Block::default().title("World").borders(border::ALL))
@ -386,7 +386,7 @@ fn draw(t: &mut Terminal<TermionBackend>, app: &App) -> Result<(), io::Error> {
})
.x_bounds([-180.0, 180.0])
.y_bounds([-90.0, 90.0])
.render(&chunks[1], t);
.render(t, &chunks[1]);
})
}
_ => {}
@ -404,7 +404,7 @@ fn draw_main(t: &mut Terminal<TermionBackend>, app: &App, area: &Rect) {
Block::default()
.borders(border::ALL)
.title("Graphs")
.render(&chunks[0], t);
.render(t, &chunks[0]);
Group::default()
.direction(Direction::Vertical)
.margin(1)
@ -412,14 +412,14 @@ fn draw_main(t: &mut Terminal<TermionBackend>, app: &App, area: &Rect) {
.render(t, &chunks[0], |t, chunks| {
Gauge::default()
.block(Block::default().title("Gauge:"))
.color(Color::Magenta)
.style(Style::default().fg(Color::Magenta).bg(Color::Black).modifier(Modifier::Italic))
.percent(app.progress)
.render(&chunks[0], t);
.render(t, &chunks[0]);
Sparkline::default()
.block(Block::default().title("Sparkline:"))
.color(Color::Green)
.style(Style::default().fg(Color::Green))
.data(&app.data)
.render(&chunks[1], t);
.render(t, &chunks[1]);
});
let sizes = if app.show_chart {
vec![Size::Percent(50), Size::Percent(50)]
@ -444,16 +444,16 @@ fn draw_main(t: &mut Terminal<TermionBackend>, app: &App, area: &Rect) {
.title("List"))
.items(&app.items)
.select(app.selected)
.highlight_color(Color::Yellow)
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(&chunks[0], t);
.render(t, &chunks[0]);
List::default()
.block(Block::default()
.borders(border::ALL)
.title("List"))
.items(&app.items2)
.color(Color::Gray)
.render(&chunks[1], t);
.style(Style::default().fg(Color::Gray))
.render(t, &chunks[1]);
});
BarChart::default()
.block(Block::default()
@ -462,50 +462,52 @@ fn draw_main(t: &mut Terminal<TermionBackend>, app: &App, area: &Rect) {
.data(&app.data4)
.bar_width(3)
.bar_gap(2)
.bar_color(Color::Green)
.value_color(Color::Black)
.label_color(Color::Yellow)
.render(&chunks[1], t);
.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"))
.block(Block::default().title("Chart").borders(border::ALL))
.x_axis(Axis::default()
.title("X Axis")
.color(Color::Gray)
.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")
.color(Color::Gray)
.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)
.color(Color::Cyan)
.style(Style::default().fg(Color::Cyan))
.data(&app.data2),
Dataset::default()
.name("data3")
.marker(Marker::Braille)
.color(Color::Yellow)
.style(Style::default().fg(Color::Yellow))
.data(&app.data3)])
.render(&chunks[1], t);
.render(t, &chunks[1]);
}
});
Text::default()
Paragraph::default()
.block(Block::default().borders(border::ALL).title("Footer"))
.wrap(true)
.color(app.colors[app.color_index])
.style(Style::default().fg(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 =).\nOne more thing is that it should display unicode \
\\{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(&chunks[2], t);
.render(t, &chunks[2]);
});
}

54
examples/rustbox.rs Normal file
View file

@ -0,0 +1,54 @@
extern crate tui;
extern crate rustbox;
use std::error::Error;
use rustbox::Key;
use tui::{Terminal, RustboxBackend};
use tui::widgets::{Widget, Block, border, Paragraph};
use tui::layout::{Group, Direction, Size};
use tui::style::{Style, Color, Modifier};
fn main() {
let mut terminal = Terminal::new(RustboxBackend::new().unwrap()).unwrap();
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
draw(&mut terminal);
loop {
match terminal.backend().rustbox().poll_event(false) {
Ok(rustbox::Event::KeyEvent(key)) => {
match key {
Key::Char('q') => {
break;
}
_ => {}
}
}
Err(e) => panic!("{}", e.description()),
_ => {}
};
draw(&mut terminal);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<RustboxBackend>) {
let size = t.size().unwrap();
Group::default()
.direction(Direction::Vertical)
.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)))
.text("It {yellow works}!")
.render(t, &chunks[0]);
});
t.draw().unwrap();
}

View file

@ -4,22 +4,52 @@ use std::usize;
use unicode_segmentation::UnicodeSegmentation;
use layout::Rect;
use style::Color;
use style::{Style, Color, Modifier};
/// A buffer cell
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
pub fg: Color,
pub bg: Color,
pub symbol: String,
pub style: Style,
}
impl Cell {
pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
self.symbol.clear();
self.symbol.push_str(symbol);
self
}
pub fn set_char(&mut self, ch: char) -> &mut Cell {
self.symbol.clear();
self.symbol.push(ch);
self
}
pub fn set_fg(&mut self, color: Color) -> &mut Cell {
self.style.fg = color;
self
}
pub fn set_bg(&mut self, color: Color) -> &mut Cell {
self.style.bg = color;
self
}
pub fn set_modifier(&mut self, modifier: Modifier) -> &mut Cell {
self.style.modifier = modifier;
self
}
pub fn set_style(&mut self, style: Style) -> &mut Cell {
self.style = style;
self
}
pub fn reset(&mut self) {
self.symbol.clear();
self.symbol.push(' ');
self.fg = Color::Reset;
self.bg = Color::Reset;
self.style.reset();
}
}
@ -27,8 +57,7 @@ impl Default for Cell {
fn default() -> Cell {
Cell {
symbol: " ".into(),
fg: Color::Reset,
bg: Color::Reset,
style: Default::default(),
}
}
}
@ -46,19 +75,20 @@ impl Default for Cell {
/// # extern crate tui;
/// use tui::buffer::{Buffer, Cell};
/// use tui::layout::Rect;
/// use tui::style::Color;
/// use tui::style::{Color, Style};
///
/// # 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.get_mut(0, 2).set_symbol("x");
/// assert_eq!(buf.get(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");
/// assert_eq!(buf.get(5, 0), &Cell{
/// symbol: String::from("r"),
/// fg: Color::Red,
/// bg: Color::White,
/// style: Style::Reset});
/// buf.get_mut(5, 0).set_char('x');
/// assert_eq!(buf.get(5, 0).symbol, "x");
/// # }
/// ```
#[derive(Debug, Clone)]
@ -110,11 +140,17 @@ impl Buffer {
}
/// Returns a reference to Cell at the given coordinates
pub fn at(&self, x: u16, y: u16) -> &Cell {
pub fn get(&self, x: u16, y: u16) -> &Cell {
let i = self.index_of(x, y);
&self.content[i]
}
/// Returns a mutable reference to Cell at the given coordinates
pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
let i = self.index_of(x, y);
&mut 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 {
debug_assert!(x >= self.area.left() && x < self.area.right() && y >= self.area.top() &&
@ -136,76 +172,25 @@ impl Buffer {
(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);
pub fn set_string(&mut self, x: u16, y: u16, string: &str, style: &Style) {
self.set_stringn(x, y, string, usize::MAX, style);
}
/// 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,
string: &str,
limit: usize,
fg: Color,
bg: Color) {
pub fn set_stringn(&mut self, x: u16, y: u16, string: &str, limit: usize, style: &Style) {
let mut index = self.index_of(x, y);
let graphemes = UnicodeSegmentation::graphemes(string, true).collect::<Vec<&str>>();
let max_index = min((self.area.width - x) as usize, limit);
let max_index = min((self.area.right() - x) as usize, limit);
for s in graphemes.into_iter().take(max_index) {
self.content[index].symbol.clear();
self.content[index].symbol.push_str(s);
self.content[index].fg = fg;
self.content[index].bg = bg;
self.content[index].style = style.clone();
index += 1;
}
}
/// 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();
self.content[i].symbol.push_str(symbol);
self.content[i].fg = fg;
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)
{
let i = self.index_of(x, y);
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) {

View file

@ -7,7 +7,7 @@ use cassowary::strength::{REQUIRED, WEAK};
use terminal::{Terminal, Backend};
#[derive(Debug, Hash, PartialEq)]
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum Direction {
Horizontal,
Vertical,
@ -109,7 +109,7 @@ impl Rect {
}
}
#[derive(Debug, Clone, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Size {
Fixed(u16),
Percent(u16),
@ -222,6 +222,17 @@ 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 {
Direction::Vertical => {
last.height = dest_area.bottom() - last.y;
}
Direction::Horizontal => {
last.width = dest_area.right() - last.x;
}
}
}
results
}
@ -274,7 +285,7 @@ impl Element {
/// .sizes(&[Size::Percent(50), Size::Percent(50)]);
/// # }
/// ```
#[derive(Debug, Hash)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Group {
pub direction: Direction,
pub margin: u16,

View file

@ -21,6 +21,64 @@ pub enum Color {
Rgb(u8, u8, u8),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Modifier {
Blink,
Bold,
CrossedOut,
Faint,
Framed,
Invert,
Italic,
NoBlink,
NoBold,
NoCrossedOut,
NoFaint,
NoInvert,
NoItalic,
NoUnderline,
Reset,
Underline,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
pub fg: Color,
pub bg: Color,
pub modifier: Modifier,
}
impl Default for Style {
fn default() -> Style {
Style {
fg: Color::Reset,
bg: Color::Reset,
modifier: Modifier::Reset,
}
}
}
impl Style {
pub fn reset(&mut self) {
self.fg = Color::Reset;
self.bg = Color::Reset;
self.modifier = Modifier::Reset;
}
pub fn fg(mut self, color: Color) -> Style {
self.fg = color;
self
}
pub fn bg(mut self, color: Color) -> Style {
self.bg = color;
self
}
pub fn modifier(mut self, modifier: Modifier) -> Style {
self.modifier = modifier;
self
}
}
macro_rules! termion_fg {
($color:ident) => (format!("{}", termion::color::Fg(termion::color::$color)));
}
@ -37,6 +95,10 @@ macro_rules! termion_bg_rgb {
($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));
}
impl Color {
pub fn termion_fg(&self) -> String {
match *self {
@ -80,6 +142,10 @@ impl Color {
}
}
fn rgb_to_byte(r: u8, g: u8, b: u8) -> u16 {
(((r & 255 & 0xC0) + (g & 255 & 0xE0) >> 2 + (b & 0xE0) >> 5) & 0xFF) as u16
}
impl Into<rustbox::Color> for Color {
fn into(self) -> rustbox::Color {
match self {
@ -98,7 +164,42 @@ impl Into<rustbox::Color> for Color {
Color::LightMagenta => rustbox::Color::Magenta,
Color::LightCyan => rustbox::Color::Cyan,
Color::White => rustbox::Color::White,
Color::Rgb(r, g, b) => rustbox::Color::Default,
Color::Rgb(r, g, b) => rustbox::Color::Byte(rgb_to_byte(r, g, b)),
}
}
}
impl Modifier {
pub fn termion_modifier(&self) -> String {
match *self {
Modifier::Blink => termion_modifier!(Blink),
Modifier::Bold => termion_modifier!(Bold),
Modifier::CrossedOut => termion_modifier!(CrossedOut),
Modifier::Faint => termion_modifier!(Faint),
Modifier::Framed => termion_modifier!(Framed),
Modifier::Invert => termion_modifier!(Invert),
Modifier::Italic => termion_modifier!(Italic),
Modifier::NoBlink => termion_modifier!(NoBlink),
Modifier::NoBold => termion_modifier!(NoBold),
Modifier::NoCrossedOut => termion_modifier!(NoCrossedOut),
Modifier::NoFaint => termion_modifier!(NoFaint),
Modifier::NoInvert => termion_modifier!(NoInvert),
Modifier::NoItalic => termion_modifier!(NoItalic),
Modifier::NoUnderline => termion_modifier!(NoUnderline),
Modifier::Reset => termion_modifier!(Reset),
Modifier::Underline => termion_modifier!(Underline),
}
}
}
impl Into<rustbox::Style> for Modifier {
fn into(self) -> rustbox::Style {
match self {
Modifier::Reset => rustbox::RB_NORMAL,
Modifier::Bold => rustbox::RB_BOLD,
Modifier::Underline => rustbox::RB_UNDERLINE,
Modifier::Invert => rustbox::RB_REVERSE,
_ => rustbox::RB_NORMAL,
}
}
}

View file

@ -10,7 +10,7 @@ use rustbox;
use buffer::{Buffer, Cell};
use layout::{Rect, Group, split};
use widgets::Widget;
use style::Color;
use style::{Color, Modifier, Style};
use util::hash;
pub trait Backend {
@ -61,8 +61,7 @@ impl Backend for TermionBackend {
where I: Iterator<Item = (u16, u16, &'a Cell)>
{
let mut string = String::with_capacity(content.size_hint().0 * 3);
let mut fg = Color::Reset;
let mut bg = Color::Reset;
let mut style = Style::default();
let mut last_y = 0;
let mut last_x = 0;
let mut inst = 0;
@ -73,14 +72,23 @@ impl Backend for TermionBackend {
}
last_x = x;
last_y = y;
if cell.fg != fg {
string.push_str(&cell.fg.termion_fg());
fg = cell.fg;
if cell.style.modifier != style.modifier {
string.push_str(&cell.style.modifier.termion_modifier());
style.modifier = cell.style.modifier;
if style.modifier == Modifier::Reset {
style.bg = Color::Reset;
style.fg = Color::Reset;
}
inst += 1;
}
if cell.bg != bg {
string.push_str(&cell.bg.termion_bg());
bg = cell.bg;
if cell.style.fg != style.fg {
string.push_str(&cell.style.fg.termion_fg());
style.fg = cell.style.fg;
inst += 1;
}
if cell.style.bg != style.bg {
string.push_str(&cell.style.bg.termion_bg());
style.bg = cell.style.bg;
inst += 1;
}
string.push_str(&cell.symbol);
@ -88,10 +96,11 @@ impl Backend for TermionBackend {
}
debug!("{} instructions outputed.", inst);
try!(write!(self.stdout,
"{}{}{}",
"{}{}{}{}",
string,
Color::Reset.termion_fg(),
Color::Reset.termion_bg()));
Color::Reset.termion_bg(),
Modifier::Reset.termion_modifier()));
Ok(())
}
@ -121,6 +130,10 @@ impl RustboxBackend {
let rustbox = try!(rustbox::RustBox::init(Default::default()));
Ok(RustboxBackend { rustbox: rustbox })
}
pub fn rustbox(&self) -> &rustbox::RustBox {
&self.rustbox
}
}
impl Backend for RustboxBackend {
@ -132,9 +145,9 @@ impl Backend for RustboxBackend {
inst += 1;
self.rustbox.print(x as usize,
y as usize,
rustbox::RB_NORMAL,
cell.fg.into(),
cell.bg.into(),
cell.style.modifier.into(),
cell.style.fg.into(),
cell.style.bg.into(),
&cell.symbol);
}
debug!("{} instructions outputed", inst);

View file

@ -5,7 +5,7 @@ use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block};
use buffer::Buffer;
use layout::Rect;
use style::Color;
use style::Style;
use symbols::bar;
/// Display multiple bars in a single widgets
@ -36,14 +36,12 @@ pub struct BarChart<'a> {
bar_width: u16,
/// The gap between each bar
bar_gap: u16,
/// Color of the bars
bar_color: Color,
/// Color of the values printed at the bottom of each bar
value_color: Color,
/// Color of the labels printed under each bar
label_color: Color,
/// Background color for the widget
background_color: Color,
/// Style of the values printed at the bottom of each bar
value_style: Style,
/// Style of the labels printed under each bar
label_style: Style,
/// Style for the widget
style: Style,
/// Slice of (label, value) pair to plot on the chart
data: &'a [(&'a str, u64)],
/// Value necessary for a bar to reach the maximum height (if no value is specified,
@ -62,10 +60,9 @@ impl<'a> Default for BarChart<'a> {
values: Vec::new(),
bar_width: 1,
bar_gap: 1,
bar_color: Color::Reset,
value_color: Color::Reset,
label_color: Color::Reset,
background_color: Color::Reset,
value_style: Default::default(),
label_style: Default::default(),
style: Default::default(),
}
}
}
@ -97,20 +94,16 @@ impl<'a> BarChart<'a> {
self.bar_gap = gap;
self
}
pub fn bar_color(&'a mut self, color: Color) -> &mut BarChart<'a> {
self.bar_color = color;
pub fn value_style(&'a mut self, style: Style) -> &mut BarChart<'a> {
self.value_style = style;
self
}
pub fn value_color(&'a mut self, color: Color) -> &mut BarChart<'a> {
self.value_color = color;
pub fn label_style(&'a mut self, style: Style) -> &mut BarChart<'a> {
self.label_style = style;
self
}
pub fn label_color(&'a mut self, color: Color) -> &mut BarChart<'a> {
self.label_color = color;
self
}
pub fn background_color(&'a mut self, color: Color) -> &mut BarChart<'a> {
self.background_color = color;
pub fn style(&'a mut self, style: Style) -> &mut BarChart<'a> {
self.style = style;
self
}
}
@ -129,9 +122,7 @@ impl<'a> Widget for BarChart<'a> {
return;
}
if self.background_color != Color::Reset {
self.background(&chart_area, buf, self.background_color);
}
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,
@ -156,12 +147,11 @@ impl<'a> Widget for BarChart<'a> {
};
for x in 0..self.bar_width {
buf.set_cell(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) +
buf.get_mut(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) +
x,
chart_area.top() + j,
symbol,
self.bar_color,
self.background_color);
chart_area.top() + j)
.set_symbol(symbol)
.set_style(self.style);
}
if d.1 > 8 {
@ -182,16 +172,14 @@ impl<'a> Widget for BarChart<'a> {
(self.bar_width - width) / 2,
chart_area.bottom() - 2,
value_label,
self.value_color,
self.bar_color);
&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_color,
self.background_color);
&self.label_style);
}
}
}

View file

@ -1,6 +1,6 @@
use buffer::Buffer;
use layout::Rect;
use style::Color;
use style::Style;
use widgets::{Widget, border};
use symbols::line;
@ -26,24 +26,24 @@ use symbols::line;
pub struct Block<'a> {
/// Optional title place on the upper left of the block
title: Option<&'a str>,
/// Title color
title_color: Color,
/// Title style
title_style: Style,
/// Visible borders
borders: border::Flags,
/// Borders color
border_color: Color,
/// Background color
background_color: Color,
/// Border style
border_style: Style,
/// Widget style
style: Style,
}
impl<'a> Default for Block<'a> {
fn default() -> Block<'a> {
Block {
title: None,
title_color: Color::Reset,
title_style: Default::default(),
borders: border::NONE,
border_color: Color::Reset,
background_color: Color::Reset,
border_style: Default::default(),
style: Default::default(),
}
}
}
@ -54,18 +54,18 @@ impl<'a> Block<'a> {
self
}
pub fn title_color(mut self, color: Color) -> Block<'a> {
self.title_color = color;
pub fn title_style(mut self, style: Style) -> Block<'a> {
self.title_style = style;
self
}
pub fn border_color(mut self, color: Color) -> Block<'a> {
self.border_color = color;
pub fn border_style(mut self, style: Style) -> Block<'a> {
self.border_style = style;
self
}
pub fn background_color(mut self, color: Color) -> Block<'a> {
self.background_color = color;
pub fn style(mut self, style: Style) -> Block<'a> {
self.style = style;
self
}
@ -105,62 +105,60 @@ impl<'a> Widget for Block<'a> {
return;
}
if self.background_color != Color::Reset {
self.background(area, buf, self.background_color)
}
self.background(area, buf, self.style.bg);
// Sides
if self.borders.intersects(border::LEFT) {
for y in area.top()..area.bottom() {
buf.set_cell(area.left(),
y,
line::VERTICAL,
self.border_color,
self.background_color);
buf.get_mut(area.left(), y)
.set_symbol(line::VERTICAL)
.set_style(self.border_style);
}
}
if self.borders.intersects(border::TOP) {
for x in area.left()..area.right() {
buf.set_cell(x,
area.top(),
line::HORIZONTAL,
self.border_color,
self.background_color);
buf.get_mut(x, area.top())
.set_symbol(line::HORIZONTAL)
.set_style(self.border_style);
}
}
if self.borders.intersects(border::RIGHT) {
let x = area.right() - 1;
for y in area.top()..area.bottom() {
buf.set_cell(x,
y,
line::VERTICAL,
self.border_color,
self.background_color);
buf.get_mut(x, y)
.set_symbol(line::VERTICAL)
.set_style(self.border_style);
}
}
if self.borders.intersects(border::BOTTOM) {
let y = area.bottom() - 1;
for x in area.left()..area.right() {
buf.set_cell(x,
y,
line::HORIZONTAL,
self.border_color,
self.background_color);
buf.get_mut(x, y)
.set_symbol(line::HORIZONTAL)
.set_style(self.border_style);
}
}
// Corners
if self.borders.contains(border::LEFT | border::TOP) {
buf.set_symbol(area.left(), area.top(), line::TOP_LEFT);
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) {
buf.set_symbol(area.right() - 1, area.top(), line::TOP_RIGHT);
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) {
buf.set_symbol(area.left(), area.bottom() - 1, line::BOTTOM_LEFT);
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) {
buf.set_symbol(area.right() - 1, area.bottom() - 1, line::BOTTOM_RIGHT);
buf.get_mut(area.right() - 1, area.bottom() - 1)
.set_symbol(line::BOTTOM_RIGHT)
.set_style(self.border_style);
}
if area.width > 2 {
@ -180,8 +178,7 @@ impl<'a> Widget for Block<'a> {
area.top(),
title,
width as usize,
self.title_color,
self.background_color);
&self.title_style);
}
}
}

View file

@ -7,7 +7,7 @@ pub use self::points::Points;
pub use self::line::Line;
pub use self::map::{Map, MapResolution};
use style::Color;
use style::{Style, Color};
use buffer::Buffer;
use widgets::{Block, Widget};
use layout::Rect;
@ -254,19 +254,16 @@ impl<'a, F> Widget for Canvas<'a, F>
.enumerate() {
if ch != BRAILLE_BLANK {
let (x, y) = (i % width, i / width);
buf.update_cell(x as u16 + canvas_area.left(),
y as u16 + canvas_area.top(),
|c| {
c.symbol.clear();
c.symbol.push(ch);
c.fg = color;
c.bg = self.background_color;
});
buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top())
.set_char(ch)
.set_fg(color)
.set_bg(self.background_color);
}
}
}
// 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])
@ -278,8 +275,7 @@ impl<'a, F> Widget for Canvas<'a, F>
buf.set_string(dx + canvas_area.left(),
dy + canvas_area.top(),
label.text,
label.color,
self.background_color);
&style.fg(label.color));
}
}
}

View file

@ -6,34 +6,34 @@ use widgets::{Widget, Block, border};
use widgets::canvas::{Canvas, Points};
use buffer::Buffer;
use layout::Rect;
use style::Color;
use style::Style;
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,
/// Style of the title
title_style: Style,
/// 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,
/// 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> {
Axis {
title: None,
title_color: Color::Reset,
title_style: Default::default(),
bounds: [0.0, 0.0],
labels: None,
labels_color: Color::Reset,
color: Color::Reset,
labels_style: Default::default(),
style: Default::default(),
}
}
}
@ -44,8 +44,8 @@ impl<'a> Axis<'a> {
self
}
pub fn title_color(mut self, color: Color) -> Axis<'a> {
self.title_color = color;
pub fn title_style(mut self, style: Style) -> Axis<'a> {
self.title_style = style;
self
}
@ -59,13 +59,13 @@ impl<'a> Axis<'a> {
self
}
pub fn labels_color(mut self, color: Color) -> Axis<'a> {
self.labels_color = color;
pub fn labels_style(mut self, style: Style) -> Axis<'a> {
self.labels_style = style;
self
}
pub fn color(mut self, color: Color) -> Axis<'a> {
self.color = color;
pub fn style(mut self, style: Style) -> Axis<'a> {
self.style = style;
self
}
}
@ -86,8 +86,8 @@ pub struct Dataset<'a> {
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,
/// Style used to plot this dataset
style: Style,
}
impl<'a> Default for Dataset<'a> {
@ -96,7 +96,7 @@ impl<'a> Default for Dataset<'a> {
name: "",
data: &[],
marker: Marker::Dot,
color: Color::Reset,
style: Style::default(),
}
}
}
@ -117,8 +117,8 @@ impl<'a> Dataset<'a> {
self
}
pub fn color(mut self, color: Color) -> Dataset<'a> {
self.color = color;
pub fn style(mut self, style: Style) -> Dataset<'a> {
self.style = style;
self
}
}
@ -193,8 +193,8 @@ pub struct Chart<'a> {
y_axis: Axis<'a>,
/// A reference to the datasets
datasets: &'a [Dataset<'a>],
/// The background color
background_color: Color,
/// The widget base style
style: Style,
}
impl<'a> Default for Chart<'a> {
@ -203,7 +203,7 @@ impl<'a> Default for Chart<'a> {
block: None,
x_axis: Axis::default(),
y_axis: Axis::default(),
background_color: Color::Reset,
style: Default::default(),
datasets: &[],
}
}
@ -215,8 +215,8 @@ impl<'a> Chart<'a> {
self
}
pub fn background_color(&mut self, background_color: Color) -> &mut Chart<'a> {
self.background_color = background_color;
pub fn style(&mut self, style: Style) -> &mut Chart<'a> {
self.style = style;
self
}
@ -318,18 +318,16 @@ impl<'a> Widget for Chart<'a> {
return;
}
if self.background_color != Color::Reset {
self.background(&chart_area, buf, self.background_color);
}
self.background(&chart_area, buf, self.style.bg);
if let Some((x, y)) = layout.title_x {
let title = self.x_axis.title.unwrap();
buf.set_string(x, y, title, self.x_axis.title_color, self.background_color);
buf.set_string(x, y, title, &self.x_axis.style);
}
if let Some((x, y)) = layout.title_y {
let title = self.y_axis.title.unwrap();
buf.set_string(x, y, title, self.y_axis.title_color, self.background_color);
buf.set_string(x, y, title, &self.y_axis.style);
}
if let Some(y) = layout.label_x {
@ -343,8 +341,7 @@ impl<'a> Widget for Chart<'a> {
label.width() as u16,
y,
label,
self.x_axis.labels_color,
self.background_color);
&self.x_axis.labels_style);
}
}
}
@ -358,39 +355,32 @@ impl<'a> Widget for Chart<'a> {
buf.set_string(x,
graph_area.bottom() - 1 - dy,
label,
self.y_axis.labels_color,
self.background_color);
&self.y_axis.labels_style);
}
}
}
if let Some(y) = layout.axis_x {
for x in graph_area.left()..graph_area.right() {
buf.set_cell(x,
y,
symbols::line::HORIZONTAL,
self.x_axis.color,
self.background_color);
buf.get_mut(x, y)
.set_symbol(symbols::line::HORIZONTAL)
.set_style(self.x_axis.style);
}
}
if let Some(x) = layout.axis_y {
for y in graph_area.top()..graph_area.bottom() {
buf.set_cell(x,
y,
symbols::line::VERTICAL,
self.y_axis.color,
self.background_color);
buf.get_mut(x, y)
.set_symbol(symbols::line::VERTICAL)
.set_style(self.y_axis.style);
}
}
if let Some(y) = layout.axis_x {
if let Some(x) = layout.axis_y {
buf.set_cell(x,
y,
symbols::line::BOTTOM_LEFT,
self.x_axis.color,
self.background_color);
buf.get_mut(x, y)
.set_symbol(symbols::line::BOTTOM_LEFT)
.set_style(self.x_axis.style);
}
}
@ -409,22 +399,21 @@ impl<'a> Widget for Chart<'a> {
(self.x_axis.bounds[1] -
self.x_axis.bounds[0])) as u16;
buf.set_cell(graph_area.left() + dx,
graph_area.top() + dy,
symbols::DOT,
dataset.color,
self.background_color);
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.background_color)
.background_color(self.style.bg)
.x_bounds(self.x_axis.bounds)
.y_bounds(self.y_axis.bounds)
.paint(|ctx| {
ctx.draw(&Points {
coords: dataset.data,
color: dataset.color,
color: dataset.style.fg,
});
})
.draw(&graph_area, buf);
@ -440,8 +429,7 @@ impl<'a> Widget for Chart<'a> {
buf.set_string(legend_area.x + 1,
legend_area.y + 1 + i as u16,
dataset.name,
dataset.color,
self.background_color);
&dataset.style);
}
}
}

View file

@ -2,7 +2,7 @@ use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block};
use buffer::Buffer;
use style::Color;
use style::{Style, Color};
use layout::Rect;
/// A widget to display a task progress.
@ -12,11 +12,11 @@ use layout::Rect;
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Widget, Gauge, Block, border};
/// # use tui::style::Color;
/// # use tui::style::{Style, Color, ;
/// # fn main() {
/// Gauge::default()
/// .block(Block::default().borders(border::ALL).title("Progress"))
/// .color(Color::White)
/// .style(Style::default().fg(Color::White).bg(Color::Black).modifier(Modifier::Italic))
/// .background_color(Color::Black)
/// .percent(20);
/// # }
@ -24,8 +24,7 @@ use layout::Rect;
pub struct Gauge<'a> {
block: Option<Block<'a>>,
percent: u16,
color: Color,
background_color: Color,
style: Style,
}
impl<'a> Default for Gauge<'a> {
@ -33,14 +32,13 @@ impl<'a> Default for Gauge<'a> {
Gauge {
block: None,
percent: 0,
color: Color::Reset,
background_color: Color::Reset,
style: Default::default(),
}
}
}
impl<'a> Gauge<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut Gauge<'a> {
pub fn block(&mut self, block: Block<'a>) -> &mut Gauge<'a> {
self.block = Some(block);
self
}
@ -50,13 +48,8 @@ impl<'a> Gauge<'a> {
self
}
pub fn color(&mut self, color: Color) -> &mut Gauge<'a> {
self.color = color;
self
}
pub fn background_color(&mut self, color: Color) -> &mut Gauge<'a> {
self.background_color = color;
pub fn style(&mut self, style: Style) -> &mut Gauge<'a> {
self.style = style;
self
}
}
@ -74,8 +67,8 @@ impl<'a> Widget for Gauge<'a> {
return;
}
if self.background_color != Color::Reset {
self.background(&gauge_area, buf, self.background_color);
if self.style.bg != Color::Reset {
self.background(&gauge_area, buf, self.style.bg);
}
// Gauge
@ -83,21 +76,18 @@ impl<'a> Widget for Gauge<'a> {
let end = gauge_area.left() + width;
for x in gauge_area.left()..end {
buf.set_symbol(x, gauge_area.top(), " ");
buf.get_mut(x, gauge_area.top())
.set_symbol(" ");
}
// Label
let label = format!("{}%", self.percent);
let label_width = label.width() as u16;
let middle = (gauge_area.width - label_width) / 2 + gauge_area.left();
buf.set_string(middle,
gauge_area.top(),
&label,
self.color,
self.background_color);
buf.set_string(middle, gauge_area.top(), &label, &self.style);
for x in gauge_area.left()..end {
buf.set_colors(x, gauge_area.top(), self.background_color, self.color);
buf.get_mut(x, gauge_area.top()).set_fg(self.style.bg).set_bg(self.style.fg);
}
}
}

View file

@ -5,7 +5,7 @@ use unicode_width::UnicodeWidthStr;
use buffer::Buffer;
use widgets::{Widget, Block};
use layout::Rect;
use style::Color;
use style::Style;
/// A widget to display several items among which one can be selected (optional)
///
@ -31,12 +31,10 @@ pub struct List<'a> {
items: &'a [&'a str],
/// 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,
/// Base style of the widget
style: Style,
/// 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>,
}
@ -47,9 +45,8 @@ impl<'a> Default for List<'a> {
block: None,
items: &[],
selected: None,
color: Color::Reset,
background_color: Color::Reset,
highlight_color: Color::Reset,
style: Default::default(),
highlight_style: Default::default(),
highlight_symbol: None,
}
}
@ -66,13 +63,8 @@ impl<'a> List<'a> {
self
}
pub fn color(&'a mut self, color: Color) -> &mut List<'a> {
self.color = color;
self
}
pub fn background_color(&'a mut self, color: Color) -> &mut List<'a> {
self.background_color = color;
pub fn style(&'a mut self, style: Style) -> &mut List<'a> {
self.style = style;
self
}
@ -81,8 +73,8 @@ impl<'a> List<'a> {
self
}
pub fn highlight_color(&'a mut self, highlight_color: Color) -> &mut List<'a> {
self.highlight_color = highlight_color;
pub fn highlight_style(&'a mut self, highlight_style: Style) -> &mut List<'a> {
self.highlight_style = highlight_style;
self
}
@ -107,52 +99,50 @@ impl<'a> Widget for List<'a> {
return;
}
if self.background_color != Color::Reset {
self.background(&list_area, buf, self.background_color);
}
self.background(&list_area, buf, self.style.bg);
let list_length = self.items.len();
let list_height = list_area.height as usize;
let bound = min(list_height, list_length);
let (selected, highlight_color) = match self.selected {
Some(i) => (i, self.highlight_color),
None => (0, self.color),
// Use highlight_style only if something is selected
let (selected, highlight_style) = match self.selected {
Some(i) => (i, &self.highlight_style),
None => (0, &self.style),
};
// Make sure the list show the selected item
let offset = if selected >= list_height {
selected - list_height + 1
} else {
0
};
// Move items to the right if a highlight symbol was provided
let x = match self.highlight_symbol {
Some(s) => (s.width() + 1) as u16 + list_area.left(),
None => list_area.left(),
};
// Render items
if x < list_area.right() {
let width = (list_area.right() - x) as usize;
for i in 0..bound {
let max_index = min(list_height, list_length);
for i in 0..max_index {
let index = i + offset;
let item = self.items[index];
let color = if index == selected {
highlight_color
let style = if index == selected {
highlight_style
} else {
self.color
&self.style
};
buf.set_stringn(x,
list_area.top() + i as u16,
item,
width,
color,
self.background_color);
buf.set_stringn(x, list_area.top() + i as u16, item, width, style);
}
if let Some(s) = self.highlight_symbol {
buf.set_string(list_area.left(),
list_area.top() + (selected - offset) as u16,
s,
self.highlight_color,
self.background_color);
&self.highlight_style);
}
}
}

View file

@ -1,5 +1,5 @@
mod block;
mod text;
mod paragraph;
mod list;
mod gauge;
mod sparkline;
@ -10,7 +10,7 @@ mod table;
pub mod canvas;
pub use self::block::Block;
pub use self::text::Text;
pub use self::paragraph::Paragraph;
pub use self::list::List;
pub use self::gauge::Gauge;
pub use self::sparkline::Sparkline;
@ -53,12 +53,12 @@ pub trait Widget {
fn background(&self, area: &Rect, buf: &mut Buffer, color: Color) {
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
buf.set_bg(x, y, color);
buf.get_mut(x, y).set_bg(color);
}
}
}
/// Helper method that can be chained with a widget's builder methods to render it.
fn render<B>(&self, area: &Rect, t: &mut Terminal<B>)
fn render<B>(&self, t: &mut Terminal<B>, area: &Rect)
where Self: Sized,
B: Backend
{

View file

@ -4,7 +4,7 @@ use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block};
use buffer::Buffer;
use layout::Rect;
use style::Color;
use style::{Style, Color, Modifier};
/// A widget to display some text. You can specify colors using commands embedded in
/// the text such as "{[color] [text]}".
@ -13,64 +13,56 @@ use style::Color;
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, Text};
/// # use tui::widgets::{Block, border, Paragraph};
/// # use tui::style::Color;
/// # fn main() {
/// Text::default()
/// .block(Block::default().title("Text").borders(border::ALL))
/// Paragraph::default()
/// .block(Block::default().title("Paragraph").borders(border::ALL))
/// .color(Color::White)
/// .background_color(Color::Black)
/// .wrap(true)
/// .text("First line\nSecond line\n{red Colored text}.");
/// # }
/// ```
pub struct Text<'a> {
pub struct Paragraph<'a> {
/// A block to wrap the widget in
block: Option<Block<'a>>,
/// The base color used to render the text
color: Color,
/// Background color of the widget
background_color: Color,
/// Widget style
style: Style,
/// Wrap the text or not
wrapping: bool,
/// The text to display
text: &'a str,
}
impl<'a> Default for Text<'a> {
fn default() -> Text<'a> {
Text {
impl<'a> Default for Paragraph<'a> {
fn default() -> Paragraph<'a> {
Paragraph {
block: None,
color: Color::Reset,
background_color: Color::Reset,
style: Default::default(),
wrapping: false,
text: "",
}
}
}
impl<'a> Text<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut Text<'a> {
impl<'a> Paragraph<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut Paragraph<'a> {
self.block = Some(block);
self
}
pub fn text(&mut self, text: &'a str) -> &mut Text<'a> {
pub fn text(&mut self, text: &'a str) -> &mut Paragraph<'a> {
self.text = text;
self
}
pub fn background_color(&mut self, background_color: Color) -> &mut Text<'a> {
self.background_color = background_color;
pub fn style(&mut self, style: Style) -> &mut Paragraph<'a> {
self.style = style;
self
}
pub fn color(&mut self, color: Color) -> &mut Text<'a> {
self.color = color;
self
}
pub fn wrap(&mut self, flag: bool) -> &mut Text<'a> {
pub fn wrap(&mut self, flag: bool) -> &mut Paragraph<'a> {
self.wrapping = flag;
self
}
@ -79,28 +71,54 @@ impl<'a> Text<'a> {
struct Parser<'a> {
text: Vec<&'a str>,
mark: bool,
color_string: String,
color: Color,
base_color: Color,
cmd_string: String,
style: Style,
base_style: Style,
escaping: bool,
coloring: bool,
styling: bool,
}
impl<'a> Parser<'a> {
fn new(text: &'a str, base_color: Color) -> Parser<'a> {
fn new(text: &'a str, base_style: Style) -> Parser<'a> {
Parser {
text: UnicodeSegmentation::graphemes(text, true).rev().collect::<Vec<&str>>(),
mark: false,
color_string: String::from(""),
color: base_color,
base_color: base_color,
cmd_string: String::from(""),
style: base_style,
base_style: base_style,
escaping: false,
coloring: false,
styling: false,
}
}
fn update_color(&mut self) {
self.color = match &*self.color_string {
fn update_style(&mut self) {
for cmd in self.cmd_string.split(';') {
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::str_to_color(snd);
}
}
"bg" => {
if let Some(snd) = args.get(1) {
self.style.bg = Parser::str_to_color(snd);
}
}
"mod" => {
if let Some(snd) = args.get(1) {
self.style.modifier = Parser::str_to_modifier(snd);
}
}
_ => {}
}
}
}
}
fn str_to_color(string: &str) -> Color {
match string {
"black" => Color::Black,
"red" => Color::Red,
"green" => Color::Green,
@ -119,22 +137,32 @@ impl<'a> Parser<'a> {
}
}
fn str_to_modifier(string: &str) -> Modifier {
match string {
"bold" => Modifier::Bold,
"italic" => Modifier::Italic,
"underline" => Modifier::Underline,
"invert" => Modifier::Invert,
_ => Modifier::Reset,
}
}
fn reset(&mut self) {
self.coloring = false;
self.styling = false;
self.mark = false;
self.color = self.base_color;
self.color_string.clear();
self.style = self.base_style;
self.cmd_string.clear();
}
}
impl<'a> Iterator for Parser<'a> {
type Item = (&'a str, Color);
fn next(&mut self) -> Option<(&'a str, Color)> {
type Item = (&'a str, Style);
fn next(&mut self) -> Option<Self::Item> {
match self.text.pop() {
Some(s) => {
if s == "\\" {
if self.escaping {
Some((s, self.color))
Some((s, self.style))
} else {
self.escaping = true;
self.next()
@ -142,9 +170,9 @@ impl<'a> Iterator for Parser<'a> {
} else if s == "{" {
if self.escaping {
self.escaping = false;
Some((s, self.color))
Some((s, self.style))
} else {
self.color = self.base_color;
self.style = self.base_style;
self.mark = true;
self.next()
}
@ -152,14 +180,14 @@ impl<'a> Iterator for Parser<'a> {
self.reset();
self.next()
} else if s == " " && self.mark {
self.coloring = true;
self.update_color();
self.styling = true;
self.update_style();
self.next()
} else if self.mark && !self.coloring {
self.color_string.push_str(s);
} else if self.mark && !self.styling {
self.cmd_string.push_str(s);
self.next()
} else {
Some((s, self.color))
Some((s, self.style))
}
}
None => None,
@ -167,7 +195,7 @@ impl<'a> Iterator for Parser<'a> {
}
}
impl<'a> Widget for Text<'a> {
impl<'a> Widget for Paragraph<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
let text_area = match self.block {
Some(ref b) => {
@ -181,14 +209,12 @@ impl<'a> Widget for Text<'a> {
return;
}
if self.background_color != Color::Reset {
self.background(&text_area, buf, self.background_color);
}
self.background(&text_area, buf, self.style.bg);
let mut x = 0;
let mut y = 0;
for (s, c) in Parser::new(self.text, self.color) {
if s == "\n" {
for (string, style) in Parser::new(self.text, self.style) {
if string == "\n" {
x = 0;
y += 1;
continue;
@ -205,12 +231,10 @@ impl<'a> Widget for Text<'a> {
break;
}
buf.set_cell(text_area.left() + x,
text_area.top() + y,
s,
c,
self.background_color);
x += s.width() as u16;
buf.get_mut(text_area.left() + x, text_area.top() + y)
.set_symbol(string)
.set_style(style);
x += string.width() as u16;
}
}
}

View file

@ -3,7 +3,7 @@ use std::cmp::min;
use layout::Rect;
use buffer::Buffer;
use widgets::{Widget, Block};
use style::Color;
use style::Style;
use symbols::bar;
/// Widget to render a sparkline over one or more lines.
@ -26,10 +26,8 @@ use symbols::bar;
pub struct Sparkline<'a> {
/// A block to wrap the widget in
block: Option<Block<'a>>,
/// Color of the bars
color: Color,
/// Background color of the widget
background_color: Color,
/// Widget style
style: Style,
/// A slice of the data to display
data: &'a [u64],
/// The maximum value to take to compute the maximum bar height (if nothing is specified, the
@ -41,8 +39,7 @@ impl<'a> Default for Sparkline<'a> {
fn default() -> Sparkline<'a> {
Sparkline {
block: None,
color: Color::Reset,
background_color: Color::Reset,
style: Default::default(),
data: &[],
max: None,
}
@ -55,17 +52,11 @@ impl<'a> Sparkline<'a> {
self
}
pub fn color(&mut self, color: Color) -> &mut Sparkline<'a> {
self.color = color;
pub fn style(&mut self, style: Style) -> &mut Sparkline<'a> {
self.style = style;
self
}
pub fn background_color(&mut self, color: Color) -> &mut Sparkline<'a> {
self.background_color = color;
self
}
pub fn data(&mut self, data: &'a [u64]) -> &mut Sparkline<'a> {
self.data = data;
self
@ -114,11 +105,10 @@ impl<'a> Widget for Sparkline<'a> {
7 => bar::SEVEN_EIGHTHS,
_ => bar::FULL,
};
buf.set_cell(spark_area.left() + i as u16,
spark_area.top() + j,
symbol,
self.color,
self.background_color);
buf.get_mut(spark_area.left() + i as u16, spark_area.top() + j)
.set_symbol(symbol)
.set_fg(self.style.fg)
.set_bg(self.style.bg);
if *d > 8 {
*d -= 8;

View file

@ -6,7 +6,7 @@ use unicode_width::UnicodeWidthStr;
use buffer::Buffer;
use widgets::{Widget, Block};
use layout::Rect;
use style::Color;
use style::Style;
/// A widget to display data in formatted column
///
@ -32,10 +32,12 @@ use style::Color;
pub struct Table<'a> {
/// 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],
/// Color of the text in the header
header_color: Color,
/// Style for the header
header_style: Style,
/// Width of each column (if the total width is greater than the widget width some columns may
/// not be displayed)
widths: &'a [u16],
@ -43,23 +45,21 @@ pub struct Table<'a> {
column_spacing: u16,
/// Data to display in each row
rows: Vec<Cow<'a, [&'a str]>>,
/// Color of the text
color: Color,
/// Background color for the widget
background_color: Color,
/// Style for each row
row_style: Style,
}
impl<'a> Default for Table<'a> {
fn default() -> Table<'a> {
Table {
block: None,
style: Style::default(),
header: &[],
header_color: Color::Reset,
header_style: Style::default(),
widths: &[],
rows: Vec::new(),
color: Color::Reset,
row_style: Style::default(),
column_spacing: 1,
background_color: Color::Reset,
}
}
}
@ -75,8 +75,8 @@ impl<'a> Table<'a> {
self
}
pub fn header_color(&mut self, color: Color) -> &mut Table<'a> {
self.header_color = color;
pub fn header_style(&mut self, style: Style) -> &mut Table<'a> {
self.header_style = style;
self
}
@ -92,8 +92,8 @@ impl<'a> Table<'a> {
self
}
pub fn color(&mut self, color: Color) -> &mut Table<'a> {
self.color = color;
pub fn row_style(&mut self, style: Style) -> &mut Table<'a> {
self.row_style = style;
self
}
@ -101,11 +101,6 @@ impl<'a> Table<'a> {
self.column_spacing = spacing;
self
}
pub fn background_color(&mut self, color: Color) -> &mut Table<'a> {
self.background_color = color;
self
}
}
impl<'a> Widget for Table<'a> {
@ -121,9 +116,7 @@ impl<'a> Widget for Table<'a> {
};
// Set the background
if self.background_color != Color::Reset {
self.background(&table_area, buf, self.background_color);
}
self.background(&table_area, buf, self.style.bg);
let mut x = 0;
let mut widths = Vec::with_capacity(self.widths.len());
@ -137,10 +130,11 @@ impl<'a> Widget for Table<'a> {
let mut y = table_area.top();
// 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_color, self.background_color);
buf.set_string(x, y, t, &self.header_style);
x += *w + self.column_spacing;
}
}
@ -151,12 +145,7 @@ impl<'a> Widget for Table<'a> {
for (i, row) in self.rows.iter().take(remaining).enumerate() {
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,
self.color,
self.background_color);
buf.set_stringn(x, y + i as u16, elt, *w as usize, &self.row_style);
x += *w + self.column_spacing;
}
}

View file

@ -3,7 +3,7 @@ use unicode_width::UnicodeWidthStr;
use widgets::{Block, Widget};
use buffer::Buffer;
use layout::Rect;
use style::Color;
use style::Style;
use symbols::line;
/// A widget to display available tabs in a multiple panels context.
@ -30,12 +30,10 @@ pub struct Tabs<'a> {
titles: &'a [&'a str],
/// The index of the selected tabs
selected: usize,
/// The color used to draw the text
color: Color,
/// The background color of this widget
background_color: Color,
/// The color used to display the selected item
highlight_color: Color,
/// The style used to draw the text
style: Style,
/// The style used to display the selected item
highlight_style: Style,
}
impl<'a> Default for Tabs<'a> {
@ -44,9 +42,8 @@ impl<'a> Default for Tabs<'a> {
block: None,
titles: &[],
selected: 0,
color: Color::Reset,
background_color: Color::Reset,
highlight_color: Color::Reset,
style: Default::default(),
highlight_style: Default::default(),
}
}
}
@ -67,18 +64,13 @@ impl<'a> Tabs<'a> {
self
}
pub fn color(&mut self, color: Color) -> &mut Tabs<'a> {
self.color = color;
pub fn style(&mut self, style: Style) -> &mut Tabs<'a> {
self.style = style;
self
}
pub fn background_color(&mut self, color: Color) -> &mut Tabs<'a> {
self.background_color = color;
self
}
pub fn highlight_color(&mut self, color: Color) -> &mut Tabs<'a> {
self.highlight_color = color;
pub fn highlight_style(&mut self, style: Style) -> &mut Tabs<'a> {
self.highlight_style = style;
self
}
}
@ -98,30 +90,27 @@ impl<'a> Widget for Tabs<'a> {
return;
}
if self.background_color != Color::Reset {
self.background(&tabs_area, buf, self.background_color);
}
self.background(&tabs_area, buf, self.style.bg);
let mut x = tabs_area.left();
for (title, color) in self.titles.iter().enumerate().map(|(i, t)| if i == self.selected {
(t, self.highlight_color)
for (title, style) in self.titles.iter().enumerate().map(|(i, t)| if i == self.selected {
(t, &self.highlight_style)
} else {
(t, self.color)
(t, &self.style)
}) {
x += 1;
if x > tabs_area.right() {
break;
} else {
buf.set_string(x, tabs_area.top(), title, color, self.background_color);
buf.set_string(x, tabs_area.top(), title, style);
x += title.width() as u16 + 1;
if x >= tabs_area.right() {
break;
} else {
buf.set_cell(x,
tabs_area.top(),
line::VERTICAL,
self.color,
self.background_color);
buf.get_mut(x, tabs_area.top())
.set_symbol(line::VERTICAL)
.set_fg(self.style.fg)
.set_bg(self.style.bg);
x += 1;
}
}