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 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<TermionBackend>, 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::<Vec<Vec<&str>>>())
.map(|s| {
(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]);
Canvas::default()
@ -438,7 +469,7 @@ fn draw_main(t: &mut Terminal<TermionBackend>, 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<TermionBackend>, 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::<Vec<(String, &Style)>>())
.render(t, &chunks[1]);
});
BarChart::default()

View File

@ -1,3 +1,4 @@
use std::iter;
use std::cmp::min;
use unicode_width::UnicodeWidthStr;
@ -7,86 +8,50 @@ use widgets::{Widget, Block};
use layout::Rect;
use style::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::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> {
pub struct List<'a, T>
where T: AsRef<str> + '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
items: &'a [(T, &'a 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> {
fn default() -> List<'a> {
impl<'a, T> Default for List<'a, T>
where T: AsRef<str> + 'a
{
fn default() -> List<'a, T> {
List {
block: None,
items: &[],
selected: None,
style: Default::default(),
highlight_style: Default::default(),
highlight_symbol: None,
}
}
}
impl<'a> List<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a> {
impl<'a, T> List<'a, T>
where T: AsRef<str> + '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 [&'a str]) -> &mut List<'a> {
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> {
pub fn style(&'a mut self, style: Style) -> &mut List<'a, T> {
self.style = style;
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) {
let list_area = match self.block {
Some(ref b) => {
b.draw(area, buf);
@ -101,7 +66,104 @@ impl<'a> Widget for List<'a> {
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;
// 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::<String>();
// 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::<Vec<(String, &Style)>>();
// 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);
}
}

View File

@ -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};

View File

@ -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<Block<'a>>,
@ -43,9 +43,7 @@ pub struct Table<'a> {
/// Space between each column
column_spacing: u16,
/// Data to display in each row
rows: Vec<Cow<'a, [&'a str]>>,
/// 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<R>(&mut self, rows: Vec<R>) -> &mut Table<'a>
where R: Into<Cow<'a, [&'a str]>>
pub fn rows<S, R>(&mut self, rows: &'a [(R, &'a Style)]) -> &mut Table<'a>
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
}
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::<Vec<&'a str>>(), style)
})
.collect::<Vec<(Vec<&'a str>, &'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;
}
}