mirror of
https://github.com/fdehau/tui-rs.git
synced 2024-11-11 01:10:24 +00:00
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:
parent
d70e2d1678
commit
ad239ef23c
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user