From ad239ef23cd4795808ebd2813a67f2c363278598 Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Mon, 7 Nov 2016 11:56:08 +0100 Subject: [PATCH] Update list and table to be more flexible * Move List to SelectableList * Create a more generic List * Change the way to pass items to the table widget * Update demo --- examples/demo.rs | 70 +++++++++++++++---- src/widgets/list.rs | 159 ++++++++++++++++++++++++++++--------------- src/widgets/mod.rs | 2 +- src/widgets/table.rs | 33 ++++----- 4 files changed, 176 insertions(+), 88 deletions(-) diff --git a/examples/demo.rs b/examples/demo.rs index ec32f61..4cceafb 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -22,8 +22,8 @@ use log4rs::encode::pattern::PatternEncoder; use log4rs::config::{Appender, Config, Root}; use tui::{Terminal, TermionBackend}; -use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Paragraph, border, Chart, Axis, Dataset, - BarChart, Marker, Tabs, Table}; +use tui::widgets::{Widget, Block, SelectableList, 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::{Style, Color, Modifier}; @@ -105,7 +105,7 @@ impl MyTabs { struct App<'a> { size: Rect, items: Vec<&'a str>, - items2: Vec<&'a str>, + events: Vec<(&'a str, &'a str)>, selected: usize, tabs: MyTabs, show_chart: bool, @@ -155,10 +155,32 @@ fn main() { items: vec!["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10", "Item11", "Item12", "Item13", "Item14", "Item15", "Item16", "Item17", "Item18", "Item19", "Item20", "Item21", "Item22", "Item23", "Item24"], - items2: vec!["Event1", "Event2", "Event3", "Event4", "Event5", "Event6", "Event7", - "Event8", "Event9", "Event10", "Event11", "Event12", "Event13", "Event14", - "Event15", "Event16", "Event17", "Event18", "Event19", "Event20", "Event21", - "Event22", "Event23", "Event24", "Event25", "Event26"], + events: vec![("Event1", "INFO"), + ("Event2", "INFO"), + ("Event3", "CRITICAL"), + ("Event4", "ERROR"), + ("Event5", "INFO"), + ("Event6", "INFO"), + ("Event7", "WARNING"), + ("Event8", "INFO"), + ("Event9", "INFO"), + ("Event10", "INFO"), + ("Event11", "CRITICAL"), + ("Event12", "INFO"), + ("Event13", "INFO"), + ("Event14", "INFO"), + ("Event15", "INFO"), + ("Event16", "INFO"), + ("Event17", "ERROR"), + ("Event18", "ERROR"), + ("Event19", "INFO"), + ("Event20", "INFO"), + ("Event21", "WARNING"), + ("Event22", "INFO"), + ("Event23", "INFO"), + ("Event24", "WARNING"), + ("Event25", "INFO"), + ("Event26", "INFO")], selected: 0, tabs: MyTabs { titles: ["Tab0", "Tab1"], @@ -309,8 +331,8 @@ fn main() { app.data4.insert(0, i); app.window[0] += 1.0; app.window[1] += 1.0; - let i = app.items2.pop().unwrap(); - app.items2.insert(0, i); + let i = app.events.pop().unwrap(); + app.events.insert(0, i); app.color_index += 1; if app.color_index >= app.colors.len() { app.color_index = 0; @@ -343,17 +365,26 @@ fn draw(t: &mut Terminal, app: &App) -> Result<(), io::Error> { .direction(Direction::Horizontal) .sizes(&[Size::Percent(30), Size::Percent(70)]) .render(t, &chunks[1], |t, chunks| { + let up_style = Style::default().fg(Color::Green); + let failure_style = Style::default().fg(Color::Red); Table::default() .block(Block::default() .title("Servers") .borders(border::ALL)) .header(&["Server", "Location", "Status"]) - .header_style(Style::default().fg(Color::Red)) + .header_style(Style::default().fg(Color::Yellow)) .widths(&[15, 15, 10]) - .rows(app.servers + .rows(&app.servers .iter() - .map(|s| vec![s.name, s.location, s.status]) - .collect::>>()) + .map(|s| { + (vec![s.name, s.location, s.status], + if s.status == "Up" { + &up_style + } else { + &failure_style + }) + }) + .collect::, &Style)>>()) .render(t, &chunks[0]); Canvas::default() @@ -438,7 +469,7 @@ fn draw_main(t: &mut Terminal, app: &App, area: &Rect) { .direction(Direction::Horizontal) .sizes(&[Size::Percent(50), Size::Percent(50)]) .render(t, &chunks[0], |t, chunks| { - List::default() + SelectableList::default() .block(Block::default() .borders(border::ALL) .title("List")) @@ -447,11 +478,20 @@ fn draw_main(t: &mut Terminal, app: &App, area: &Rect) { .highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold)) .highlight_symbol(">") .render(t, &chunks[0]); + let info_style = Style::default().fg(Color::White); + let warning_style = Style::default().fg(Color::Yellow); + let error_style = Style::default().fg(Color::Magenta); + let critical_style = Style::default().fg(Color::Red); List::default() .block(Block::default() .borders(border::ALL) .title("List")) - .items(&app.items2) + .items(&app.events.iter().map(|&(evt, level)| (format!("{}: {}", level, evt), match level { + "ERROR" => &error_style, + "CRITICAL" => &critical_style, + "WARNING" => &warning_style, + _ => &info_style, + })).collect::>()) .render(t, &chunks[1]); }); BarChart::default() diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 9baea77..e563235 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -1,3 +1,4 @@ +use std::iter; use std::cmp::min; use unicode_width::UnicodeWidthStr; @@ -7,17 +8,88 @@ use widgets::{Widget, Block}; use layout::Rect; use style::Style; + +pub struct List<'a, T> + where T: AsRef + 'a +{ + block: Option>, + items: &'a [(T, &'a Style)], + style: Style, +} + +impl<'a, T> Default for List<'a, T> + where T: AsRef + 'a +{ + fn default() -> List<'a, T> { + List { + block: None, + items: &[], + style: Default::default(), + } + } +} + +impl<'a, T> List<'a, T> + where T: AsRef + 'a +{ + pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a, T> { + self.block = Some(block); + self + } + + pub fn items(&'a mut self, items: &'a [(T, &'a Style)]) -> &mut List<'a, T> { + self.items = items; + self + } + + pub fn style(&'a mut self, style: Style) -> &mut List<'a, T> { + self.style = style; + self + } +} + +impl<'a, T> Widget for List<'a, T> + where T: AsRef + 'a +{ + fn draw(&self, area: &Rect, buf: &mut Buffer) { + let list_area = match self.block { + Some(ref b) => { + b.draw(area, buf); + b.inner(area) + } + None => *area, + }; + + if list_area.width < 1 || list_area.height < 1 { + return; + } + + self.background(&list_area, buf, self.style.bg); + + let max_index = min(self.items.len(), list_area.height as usize); + for (i, &(ref item, style)) in self.items.iter().enumerate().take(max_index) { + buf.set_stringn(list_area.left(), + list_area.top() + i as u16, + item.as_ref(), + list_area.width as usize, + &style); + } + } +} + + + /// A widget to display several items among which one can be selected (optional) /// /// # Examples /// /// ``` /// # extern crate tui; -/// # use tui::widgets::{Block, border, List}; +/// # use tui::widgets::{Block, border, SelectableList}; /// # use tui::style::{Style, Color, Modifier}; /// # fn main() { -/// List::default() -/// .block(Block::default().title("List").borders(border::ALL)) +/// SelectableList::default() +/// .block(Block::default().title("SelectableList").borders(border::ALL)) /// .items(&["Item 1", "Item 2", "Item 3"]) /// .select(1) /// .style(Style::default().fg(Color::White)) @@ -25,7 +97,7 @@ use style::Style; /// .highlight_symbol(">>"); /// # } /// ``` -pub struct List<'a> { +pub struct SelectableList<'a> { block: Option>, /// Items to be displayed items: &'a [&'a str], @@ -39,9 +111,9 @@ pub struct List<'a> { highlight_symbol: Option<&'a str>, } -impl<'a> Default for List<'a> { - fn default() -> List<'a> { - List { +impl<'a> Default for SelectableList<'a> { + fn default() -> SelectableList<'a> { + SelectableList { block: None, items: &[], selected: None, @@ -52,56 +124,46 @@ impl<'a> Default for List<'a> { } } -impl<'a> List<'a> { - pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a> { +impl<'a> SelectableList<'a> { + pub fn block(&'a mut self, block: Block<'a>) -> &mut SelectableList<'a> { self.block = Some(block); self } - pub fn items(&'a mut self, items: &'a [&'a str]) -> &mut List<'a> { + pub fn items(&'a mut self, items: &'a [&'a str]) -> &mut SelectableList<'a> { self.items = items; self } - pub fn style(&'a mut self, style: Style) -> &mut List<'a> { + pub fn style(&'a mut self, style: Style) -> &mut SelectableList<'a> { self.style = style; self } - pub fn highlight_symbol(&'a mut self, highlight_symbol: &'a str) -> &mut List<'a> { + pub fn highlight_symbol(&'a mut self, highlight_symbol: &'a str) -> &mut SelectableList<'a> { self.highlight_symbol = Some(highlight_symbol); self } - pub fn highlight_style(&'a mut self, highlight_style: Style) -> &mut List<'a> { + pub fn highlight_style(&'a mut self, highlight_style: Style) -> &mut SelectableList<'a> { self.highlight_style = highlight_style; self } - pub fn select(&'a mut self, index: usize) -> &'a mut List<'a> { + pub fn select(&'a mut self, index: usize) -> &'a mut SelectableList<'a> { self.selected = Some(index); self } } -impl<'a> Widget for List<'a> { +impl<'a> Widget for SelectableList<'a> { fn draw(&self, area: &Rect, buf: &mut Buffer) { let list_area = match self.block { - Some(ref b) => { - b.draw(area, buf); - b.inner(area) - } + Some(ref b) => b.inner(area), None => *area, }; - if list_area.width < 1 || list_area.height < 1 { - return; - } - - self.background(&list_area, buf, self.style.bg); - - let list_length = self.items.len(); let list_height = list_area.height as usize; // Use highlight_style only if something is selected @@ -109,41 +171,30 @@ impl<'a> Widget for List<'a> { Some(i) => (i, &self.highlight_style), None => (0, &self.style), }; - + let highlight_symbol = self.highlight_symbol.unwrap_or(""); + let blank_symbol = iter::repeat(" ").take(highlight_symbol.width()).collect::(); // 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(), - }; + let items = self.items + .iter() + .cloned() + .enumerate() + .map(|(i, item)| if i == selected { + (format!("{} {}", highlight_symbol, item), highlight_style) + } else { + (format!("{} {}", blank_symbol, item), &self.style) + }) + .skip(offset as usize) + .collect::>(); // Render items - if x < list_area.right() { - let width = (list_area.right() - x) as usize; - let max_index = min(list_height, list_length); - for i in 0..max_index { - let index = i + offset; - let item = self.items[index]; - let style = if index == selected { - highlight_style - } else { - &self.style - }; - 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_style); - } - } + List::default() + .block(self.block.unwrap_or(Default::default())) + .items(&items) + .draw(area, buf); } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 3dae9ae..a64db94 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -11,7 +11,7 @@ pub mod canvas; pub use self::block::Block; pub use self::paragraph::Paragraph; -pub use self::list::List; +pub use self::list::{List, SelectableList}; pub use self::gauge::Gauge; pub use self::sparkline::Sparkline; pub use self::chart::{Chart, Axis, Dataset, Marker}; diff --git a/src/widgets/table.rs b/src/widgets/table.rs index 7e12998..78b828b 100644 --- a/src/widgets/table.rs +++ b/src/widgets/table.rs @@ -1,5 +1,4 @@ use std::cmp::max; -use std::borrow::Cow; use unicode_width::UnicodeWidthStr; @@ -23,11 +22,12 @@ use style::Style; /// .widths(&[5, 5, 10]) /// .style(Style::default().fg(Color::White)) /// .column_spacing(1) -/// .rows(vec![["Row11", "Row12", "Row13"].as_ref(), -/// ["Row21", "Row22", "Row23"].as_ref(), -/// ["Row31", "Row32", "Row33"].as_ref()]); +/// .rows(&[&["Row11", "Row12", "Row13"], +/// &["Row21", "Row22", "Row23"], +/// &["Row31", "Row32", "Row33"]]); /// # } /// ``` + pub struct Table<'a> { /// A block to wrap the widget in block: Option>, @@ -43,9 +43,7 @@ pub struct Table<'a> { /// Space between each column column_spacing: u16, /// Data to display in each row - rows: Vec>, - /// Style for each row - row_style: Style, + rows: Vec<(Vec<&'a str>, &'a Style)>, } impl<'a> Default for Table<'a> { @@ -57,7 +55,6 @@ impl<'a> Default for Table<'a> { header_style: Style::default(), widths: &[], rows: Vec::new(), - row_style: Style::default(), column_spacing: 1, } } @@ -84,15 +81,15 @@ impl<'a> Table<'a> { self } - pub fn rows(&mut self, rows: Vec) -> &mut Table<'a> - where R: Into> + pub fn rows(&mut self, rows: &'a [(R, &'a Style)]) -> &mut Table<'a> + where S: AsRef + 'a, + R: AsRef<[S]> + 'a { - self.rows = rows.into_iter().map(|r| r.into()).collect::>>(); - self - } - - pub fn row_style(&mut self, style: Style) -> &mut Table<'a> { - self.row_style = style; + self.rows = rows.iter() + .map(|&(ref r, style)| { + (r.as_ref().iter().map(|i| i.as_ref()).collect::>(), style) + }) + .collect::, &'a Style)>>(); self } @@ -147,10 +144,10 @@ impl<'a> Widget for Table<'a> { if y < table_area.bottom() { let remaining = (table_area.bottom() - y) as usize; - for (i, row) in self.rows.iter().take(remaining).enumerate() { + for (i, &(ref row, style)) 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.row_style); + buf.set_stringn(x, y + i as u16, elt, *w as usize, style); x += *w + self.column_spacing; } }