diff --git a/examples/block.rs b/examples/block.rs index 2ae88687..f6209da6 100644 --- a/examples/block.rs +++ b/examples/block.rs @@ -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) { + + 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(); diff --git a/examples/custom_widget.rs b/examples/custom_widget.rs index c3daabe9..52a4fcec 100644 --- a/examples/custom_widget.rs +++ b/examples/custom_widget.rs @@ -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(); } diff --git a/examples/demo.rs b/examples/demo.rs index 8612bdcf..ff1165cf 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -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, 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, 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::>>()) - .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, 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, 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, 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, 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, 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]); }); } diff --git a/examples/rustbox.rs b/examples/rustbox.rs new file mode 100644 index 00000000..b12817d9 --- /dev/null +++ b/examples/rustbox.rs @@ -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) { + + 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(); +} diff --git a/src/buffer.rs b/src/buffer.rs index c1afeb5f..e78c4693 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -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 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::>(); - 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(&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) { diff --git a/src/layout.rs b/src/layout.rs index 4540a194..54a75ca9 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -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 {} } } + // 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, diff --git a/src/style.rs b/src/style.rs index b4e0b8d7..54ee4e96 100644 --- a/src/style.rs +++ b/src/style.rs @@ -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 for Color { fn into(self) -> rustbox::Color { match self { @@ -98,7 +164,42 @@ impl Into 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 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, } } } diff --git a/src/terminal.rs b/src/terminal.rs index 8be6edd5..dc5fa5fd 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -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 { 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); diff --git a/src/widgets/barchart.rs b/src/widgets/barchart.rs index 0ebab7a5..7030944e 100644 --- a/src/widgets/barchart.rs +++ b/src/widgets/barchart.rs @@ -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); } } } diff --git a/src/widgets/block.rs b/src/widgets/block.rs index 75402d1f..93f91cc5 100644 --- a/src/widgets/block.rs +++ b/src/widgets/block.rs @@ -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); } } } diff --git a/src/widgets/canvas/mod.rs b/src/widgets/canvas/mod.rs index 787ad19a..1de7b371 100644 --- a/src/widgets/canvas/mod.rs +++ b/src/widgets/canvas/mod.rs @@ -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)); } } } diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index 931aef3a..2225e65e 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -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); } } } diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs index cb76358c..a1d21fb9 100644 --- a/src/widgets/gauge.rs +++ b/src/widgets/gauge.rs @@ -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>, 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); } } } diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 4f32603f..6883172f 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -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, - /// 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); } } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 49e22a1c..3dae9ae4 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -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(&self, area: &Rect, t: &mut Terminal) + fn render(&self, t: &mut Terminal, area: &Rect) where Self: Sized, B: Backend { diff --git a/src/widgets/text.rs b/src/widgets/paragraph.rs similarity index 52% rename from src/widgets/text.rs rename to src/widgets/paragraph.rs index aa7fe907..49d6f49e 100644 --- a/src/widgets/text.rs +++ b/src/widgets/paragraph.rs @@ -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>, - /// 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::>(), 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::>(); + 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 { 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; } } } diff --git a/src/widgets/sparkline.rs b/src/widgets/sparkline.rs index 639e2754..276baf28 100644 --- a/src/widgets/sparkline.rs +++ b/src/widgets/sparkline.rs @@ -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>, - /// 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; diff --git a/src/widgets/table.rs b/src/widgets/table.rs index d5f59ee9..c9936cad 100644 --- a/src/widgets/table.rs +++ b/src/widgets/table.rs @@ -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>, + /// 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>, - /// 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; } } diff --git a/src/widgets/tabs.rs b/src/widgets/tabs.rs index 0268b857..d380464f 100644 --- a/src/widgets/tabs.rs +++ b/src/widgets/tabs.rs @@ -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; } }