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
This commit is contained in:
Florian Dehau 2016-11-07 11:56:08 +01:00
parent d70e2d1678
commit ad239ef23c
4 changed files with 205 additions and 117 deletions

View File

@ -22,8 +22,8 @@ use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Root}; use log4rs::config::{Appender, Config, Root};
use tui::{Terminal, TermionBackend}; use tui::{Terminal, TermionBackend};
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Paragraph, border, Chart, Axis, Dataset, use tui::widgets::{Widget, Block, SelectableList, List, Gauge, Sparkline, Paragraph, border,
BarChart, Marker, Tabs, Table}; Chart, Axis, Dataset, BarChart, Marker, Tabs, Table};
use tui::widgets::canvas::{Canvas, Map, MapResolution, Line}; use tui::widgets::canvas::{Canvas, Map, MapResolution, Line};
use tui::layout::{Group, Direction, Size, Rect}; use tui::layout::{Group, Direction, Size, Rect};
use tui::style::{Style, Color, Modifier}; use tui::style::{Style, Color, Modifier};
@ -105,7 +105,7 @@ impl MyTabs {
struct App<'a> { struct App<'a> {
size: Rect, size: Rect,
items: Vec<&'a str>, items: Vec<&'a str>,
items2: Vec<&'a str>, events: Vec<(&'a str, &'a str)>,
selected: usize, selected: usize,
tabs: MyTabs, tabs: MyTabs,
show_chart: bool, show_chart: bool,
@ -155,10 +155,32 @@ fn main() {
items: vec!["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", items: vec!["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8",
"Item9", "Item10", "Item11", "Item12", "Item13", "Item14", "Item15", "Item16", "Item9", "Item10", "Item11", "Item12", "Item13", "Item14", "Item15", "Item16",
"Item17", "Item18", "Item19", "Item20", "Item21", "Item22", "Item23", "Item24"], "Item17", "Item18", "Item19", "Item20", "Item21", "Item22", "Item23", "Item24"],
items2: vec!["Event1", "Event2", "Event3", "Event4", "Event5", "Event6", "Event7", events: vec![("Event1", "INFO"),
"Event8", "Event9", "Event10", "Event11", "Event12", "Event13", "Event14", ("Event2", "INFO"),
"Event15", "Event16", "Event17", "Event18", "Event19", "Event20", "Event21", ("Event3", "CRITICAL"),
"Event22", "Event23", "Event24", "Event25", "Event26"], ("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, selected: 0,
tabs: MyTabs { tabs: MyTabs {
titles: ["Tab0", "Tab1"], titles: ["Tab0", "Tab1"],
@ -309,8 +331,8 @@ fn main() {
app.data4.insert(0, i); app.data4.insert(0, i);
app.window[0] += 1.0; app.window[0] += 1.0;
app.window[1] += 1.0; app.window[1] += 1.0;
let i = app.items2.pop().unwrap(); let i = app.events.pop().unwrap();
app.items2.insert(0, i); app.events.insert(0, i);
app.color_index += 1; app.color_index += 1;
if app.color_index >= app.colors.len() { if app.color_index >= app.colors.len() {
app.color_index = 0; app.color_index = 0;
@ -343,17 +365,26 @@ fn draw(t: &mut Terminal<TermionBackend>, app: &App) -> Result<(), io::Error> {
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.sizes(&[Size::Percent(30), Size::Percent(70)]) .sizes(&[Size::Percent(30), Size::Percent(70)])
.render(t, &chunks[1], |t, chunks| { .render(t, &chunks[1], |t, chunks| {
let up_style = Style::default().fg(Color::Green);
let failure_style = Style::default().fg(Color::Red);
Table::default() Table::default()
.block(Block::default() .block(Block::default()
.title("Servers") .title("Servers")
.borders(border::ALL)) .borders(border::ALL))
.header(&["Server", "Location", "Status"]) .header(&["Server", "Location", "Status"])
.header_style(Style::default().fg(Color::Red)) .header_style(Style::default().fg(Color::Yellow))
.widths(&[15, 15, 10]) .widths(&[15, 15, 10])
.rows(app.servers .rows(&app.servers
.iter() .iter()
.map(|s| vec![s.name, s.location, s.status]) .map(|s| {
.collect::<Vec<Vec<&str>>>()) (vec![s.name, s.location, s.status],
if s.status == "Up" {
&up_style
} else {
&failure_style
})
})
.collect::<Vec<(Vec<&str>, &Style)>>())
.render(t, &chunks[0]); .render(t, &chunks[0]);
Canvas::default() Canvas::default()
@ -438,7 +469,7 @@ fn draw_main(t: &mut Terminal<TermionBackend>, app: &App, area: &Rect) {
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)]) .sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[0], |t, chunks| { .render(t, &chunks[0], |t, chunks| {
List::default() SelectableList::default()
.block(Block::default() .block(Block::default()
.borders(border::ALL) .borders(border::ALL)
.title("List")) .title("List"))
@ -447,11 +478,20 @@ fn draw_main(t: &mut Terminal<TermionBackend>, app: &App, area: &Rect) {
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold)) .highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.highlight_symbol(">") .highlight_symbol(">")
.render(t, &chunks[0]); .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() List::default()
.block(Block::default() .block(Block::default()
.borders(border::ALL) .borders(border::ALL)
.title("List")) .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::<Vec<(String, &Style)>>())
.render(t, &chunks[1]); .render(t, &chunks[1]);
}); });
BarChart::default() BarChart::default()

View File

@ -1,3 +1,4 @@
use std::iter;
use std::cmp::min; use std::cmp::min;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -7,86 +8,50 @@ use widgets::{Widget, Block};
use layout::Rect; use layout::Rect;
use style::Style; use style::Style;
/// A widget to display several items among which one can be selected (optional)
/// pub struct List<'a, T>
/// # Examples where T: AsRef<str> + 'a
/// {
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, border, List};
/// # use tui::style::{Style, Color, Modifier};
/// # fn main() {
/// List::default()
/// .block(Block::default().title("List").borders(border::ALL))
/// .items(&["Item 1", "Item 2", "Item 3"])
/// .select(1)
/// .style(Style::default().fg(Color::White))
/// .highlight_style(Style::default().modifier(Modifier::Italic))
/// .highlight_symbol(">>");
/// # }
/// ```
pub struct List<'a> {
block: Option<Block<'a>>, block: Option<Block<'a>>,
/// Items to be displayed items: &'a [(T, &'a Style)],
items: &'a [&'a str],
/// Index of the one selected
selected: Option<usize>,
/// Base style of the widget
style: Style, 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>,
} }
impl<'a> Default for List<'a> { impl<'a, T> Default for List<'a, T>
fn default() -> List<'a> { where T: AsRef<str> + 'a
{
fn default() -> List<'a, T> {
List { List {
block: None, block: None,
items: &[], items: &[],
selected: None,
style: Default::default(), style: Default::default(),
highlight_style: Default::default(),
highlight_symbol: None,
} }
} }
} }
impl<'a> List<'a> { impl<'a, T> List<'a, T>
pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a> { where T: AsRef<str> + 'a
{
pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a, T> {
self.block = Some(block); self.block = Some(block);
self self
} }
pub fn items(&'a mut self, items: &'a [&'a str]) -> &mut List<'a> { pub fn items(&'a mut self, items: &'a [(T, &'a Style)]) -> &mut List<'a, T> {
self.items = items; self.items = items;
self self
} }
pub fn style(&'a mut self, style: Style) -> &mut List<'a> { pub fn style(&'a mut self, style: Style) -> &mut List<'a, T> {
self.style = style; self.style = style;
self self
} }
pub fn highlight_symbol(&'a mut self, highlight_symbol: &'a str) -> &mut List<'a> {
self.highlight_symbol = Some(highlight_symbol);
self
}
pub fn highlight_style(&'a mut self, highlight_style: Style) -> &mut List<'a> {
self.highlight_style = highlight_style;
self
}
pub fn select(&'a mut self, index: usize) -> &'a mut List<'a> {
self.selected = Some(index);
self
}
} }
impl<'a> Widget for List<'a> { impl<'a, T> Widget for List<'a, T>
where T: AsRef<str> + 'a
{
fn draw(&self, area: &Rect, buf: &mut Buffer) { fn draw(&self, area: &Rect, buf: &mut Buffer) {
let list_area = match self.block { let list_area = match self.block {
Some(ref b) => { Some(ref b) => {
b.draw(area, buf); b.draw(area, buf);
@ -101,7 +66,104 @@ impl<'a> Widget for List<'a> {
self.background(&list_area, buf, self.style.bg); self.background(&list_area, buf, self.style.bg);
let list_length = self.items.len(); 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, SelectableList};
/// # use tui::style::{Style, Color, Modifier};
/// # fn main() {
/// 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))
/// .highlight_style(Style::default().modifier(Modifier::Italic))
/// .highlight_symbol(">>");
/// # }
/// ```
pub struct SelectableList<'a> {
block: Option<Block<'a>>,
/// Items to be displayed
items: &'a [&'a str],
/// Index of the one selected
selected: Option<usize>,
/// 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>,
}
impl<'a> Default for SelectableList<'a> {
fn default() -> SelectableList<'a> {
SelectableList {
block: None,
items: &[],
selected: None,
style: Default::default(),
highlight_style: Default::default(),
highlight_symbol: None,
}
}
}
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 SelectableList<'a> {
self.items = items;
self
}
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 SelectableList<'a> {
self.highlight_symbol = Some(highlight_symbol);
self
}
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 SelectableList<'a> {
self.selected = Some(index);
self
}
}
impl<'a> Widget for SelectableList<'a> {
fn draw(&self, area: &Rect, buf: &mut Buffer) {
let list_area = match self.block {
Some(ref b) => b.inner(area),
None => *area,
};
let list_height = list_area.height as usize; let list_height = list_area.height as usize;
// Use highlight_style only if something is selected // Use highlight_style only if something is selected
@ -109,41 +171,30 @@ impl<'a> Widget for List<'a> {
Some(i) => (i, &self.highlight_style), Some(i) => (i, &self.highlight_style),
None => (0, &self.style), None => (0, &self.style),
}; };
let highlight_symbol = self.highlight_symbol.unwrap_or("");
let blank_symbol = iter::repeat(" ").take(highlight_symbol.width()).collect::<String>();
// Make sure the list show the selected item // Make sure the list show the selected item
let offset = if selected >= list_height { let offset = if selected >= list_height {
selected - list_height + 1 selected - list_height + 1
} else { } else {
0 0
}; };
let items = self.items
// Move items to the right if a highlight symbol was provided .iter()
let x = match self.highlight_symbol { .cloned()
Some(s) => (s.width() + 1) as u16 + list_area.left(), .enumerate()
None => list_area.left(), .map(|(i, item)| if i == selected {
}; (format!("{} {}", highlight_symbol, item), highlight_style)
} else {
(format!("{} {}", blank_symbol, item), &self.style)
})
.skip(offset as usize)
.collect::<Vec<(String, &Style)>>();
// Render items // Render items
if x < list_area.right() { List::default()
let width = (list_area.right() - x) as usize; .block(self.block.unwrap_or(Default::default()))
let max_index = min(list_height, list_length); .items(&items)
for i in 0..max_index { .draw(area, buf);
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);
}
}
} }
} }

View File

@ -11,7 +11,7 @@ pub mod canvas;
pub use self::block::Block; pub use self::block::Block;
pub use self::paragraph::Paragraph; pub use self::paragraph::Paragraph;
pub use self::list::List; pub use self::list::{List, SelectableList};
pub use self::gauge::Gauge; pub use self::gauge::Gauge;
pub use self::sparkline::Sparkline; pub use self::sparkline::Sparkline;
pub use self::chart::{Chart, Axis, Dataset, Marker}; pub use self::chart::{Chart, Axis, Dataset, Marker};

View File

@ -1,5 +1,4 @@
use std::cmp::max; use std::cmp::max;
use std::borrow::Cow;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -23,11 +22,12 @@ use style::Style;
/// .widths(&[5, 5, 10]) /// .widths(&[5, 5, 10])
/// .style(Style::default().fg(Color::White)) /// .style(Style::default().fg(Color::White))
/// .column_spacing(1) /// .column_spacing(1)
/// .rows(vec![["Row11", "Row12", "Row13"].as_ref(), /// .rows(&[&["Row11", "Row12", "Row13"],
/// ["Row21", "Row22", "Row23"].as_ref(), /// &["Row21", "Row22", "Row23"],
/// ["Row31", "Row32", "Row33"].as_ref()]); /// &["Row31", "Row32", "Row33"]]);
/// # } /// # }
/// ``` /// ```
pub struct Table<'a> { pub struct Table<'a> {
/// A block to wrap the widget in /// A block to wrap the widget in
block: Option<Block<'a>>, block: Option<Block<'a>>,
@ -43,9 +43,7 @@ pub struct Table<'a> {
/// Space between each column /// Space between each column
column_spacing: u16, column_spacing: u16,
/// Data to display in each row /// Data to display in each row
rows: Vec<Cow<'a, [&'a str]>>, rows: Vec<(Vec<&'a str>, &'a Style)>,
/// Style for each row
row_style: Style,
} }
impl<'a> Default for Table<'a> { impl<'a> Default for Table<'a> {
@ -57,7 +55,6 @@ impl<'a> Default for Table<'a> {
header_style: Style::default(), header_style: Style::default(),
widths: &[], widths: &[],
rows: Vec::new(), rows: Vec::new(),
row_style: Style::default(),
column_spacing: 1, column_spacing: 1,
} }
} }
@ -84,15 +81,15 @@ impl<'a> Table<'a> {
self self
} }
pub fn rows<R>(&mut self, rows: Vec<R>) -> &mut Table<'a> pub fn rows<S, R>(&mut self, rows: &'a [(R, &'a Style)]) -> &mut Table<'a>
where R: Into<Cow<'a, [&'a str]>> where S: AsRef<str> + 'a,
R: AsRef<[S]> + 'a
{ {
self.rows = rows.into_iter().map(|r| r.into()).collect::<Vec<Cow<'a, [&'a str]>>>(); self.rows = rows.iter()
self .map(|&(ref r, style)| {
} (r.as_ref().iter().map(|i| i.as_ref()).collect::<Vec<&'a str>>(), style)
})
pub fn row_style(&mut self, style: Style) -> &mut Table<'a> { .collect::<Vec<(Vec<&'a str>, &'a Style)>>();
self.row_style = style;
self self
} }
@ -147,10 +144,10 @@ impl<'a> Widget for Table<'a> {
if y < table_area.bottom() { if y < table_area.bottom() {
let remaining = (table_area.bottom() - y) as usize; 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(); x = table_area.left();
for (w, elt) in widths.iter().zip(row.iter()) { 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; x += *w + self.column_spacing;
} }
} }