[widgets] Refactor Table Widget

* Add Row enum
* Move to a generic definition of table which allow iterators to be
passed as arguments and therefore reduce allocations
This commit is contained in:
Florian Dehau 2017-09-10 23:01:05 +02:00
parent d6a91d1865
commit c18885d38b

View file

@ -1,12 +1,20 @@
use std::cmp::max;
use unicode_width::UnicodeWidthStr;
use std::fmt::Display;
use std::iter::Iterator;
use buffer::Buffer;
use widgets::{Widget, Block};
use layout::Rect;
use style::Style;
pub enum Row<'i, D, I>
where
D: Iterator<Item = I>,
I: Display,
{
Data(D),
StyledData(D, &'i Style),
}
/// A widget to display data in formatted column
///
/// # Examples
@ -28,14 +36,20 @@ use style::Style;
/// (&["Row31", "Row32", "Row33"], &row_style)]);
/// # }
/// ```
pub struct Table<'a> {
pub struct Table<'a, 'i, T, H, I, D, R>
where
T: Display,
H: Iterator<Item = T>,
I: Display,
D: Iterator<Item = I>,
R: Iterator<Item = Row<'i, D, I>>,
{
/// A block to wrap the widget in
block: Option<Block<'a>>,
/// Base style for the widget
style: Style,
/// Header row for all columns
header: &'a [&'a str],
header: H,
/// Style for the header
header_style: Style,
/// Width of each column (if the total width is greater than the widget width some columns may
@ -44,73 +58,101 @@ pub struct Table<'a> {
/// Space between each column
column_spacing: u16,
/// Data to display in each row
rows: Vec<(Vec<&'a str>, &'a Style)>,
rows: R,
}
impl<'a> Default for Table<'a> {
fn default() -> Table<'a> {
impl<'a, 'i, T, H, I, D, R> Default for Table<'a, 'i, T, H, I, D, R>
where
T: Display,
H: Iterator<Item = T> + Default,
I: Display,
D: Iterator<Item = I>,
R: Iterator<Item = Row<'i, D, I>>
+ Default,
{
fn default() -> Table<'a, 'i, T, H, I, D, R> {
Table {
block: None,
style: Style::default(),
header: &[],
header: H::default(),
header_style: Style::default(),
widths: &[],
rows: Vec::new(),
rows: R::default(),
column_spacing: 1,
}
}
}
impl<'a> Table<'a> {
pub fn block(&'a mut self, block: Block<'a>) -> &mut Table<'a> {
impl<'a, 'i, T, H, I, D, R> Table<'a, 'i, T, H, I, D, R>
where
T: Display,
H: Iterator<Item = T>,
I: Display,
D: Iterator<Item = I>,
R: Iterator<Item = Row<'i, D, I>>,
{
pub fn new(header: H, rows: R) -> Table<'a, 'i, T, H, I, D, R> {
Table {
block: None,
style: Style::default(),
header: header,
header_style: Style::default(),
widths: &[],
rows: rows,
column_spacing: 1,
}
}
pub fn block(&'a mut self, block: Block<'a>) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.block = Some(block);
self
}
pub fn header(&mut self, header: &'a [&'a str]) -> &mut Table<'a> {
self.header = header;
pub fn header<II>(&mut self, header: II) -> &mut Table<'a, 'i, T, H, I, D, R>
where
II: IntoIterator<Item = T, IntoIter = H>,
{
self.header = header.into_iter();
self
}
pub fn header_style(&mut self, style: Style) -> &mut Table<'a> {
pub fn header_style(&mut self, style: Style) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.header_style = style;
self
}
pub fn widths(&mut self, widths: &'a [u16]) -> &mut Table<'a> {
pub fn widths(&mut self, widths: &'a [u16]) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.widths = widths;
self
}
pub fn rows<S, R>(&mut self, rows: &'a [(R, &'a Style)]) -> &mut Table<'a>
where S: AsRef<str> + 'a,
R: AsRef<[S]> + 'a
pub fn rows<II>(&mut self, rows: II) -> &mut Table<'a, 'i, T, H, I, D, R>
where
II: IntoIterator<Item = Row<'i, D, I>, IntoIter = R>,
{
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.rows = rows.into_iter();
self
}
pub fn style(&mut self, style: Style) -> &mut Table<'a> {
pub fn style(&mut self, style: Style) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.style = style;
self
}
pub fn column_spacing(&mut self, spacing: u16) -> &mut Table<'a> {
pub fn column_spacing(&mut self, spacing: u16) -> &mut Table<'a, 'i, T, H, I, D, R> {
self.column_spacing = spacing;
self
}
}
impl<'a> Widget for Table<'a> {
impl<'a, 'i, T, H, I, D, R> Widget for Table<'a, 'i, T, H, I, D, R>
where
T: Display,
H: Iterator<Item = T>,
I: Display,
D: Iterator<Item = I>,
R: Iterator<Item = Row<'i, D, I>>,
{
fn draw(&mut self, area: &Rect, buf: &mut Buffer) {
// Render block if necessary and get the drawing area
@ -125,34 +167,40 @@ impl<'a> Widget for Table<'a> {
// Set the background
self.background(&table_area, buf, self.style.bg);
// Save the widths of the columns that will fit in the given area
let mut x = 0;
let mut widths = Vec::with_capacity(self.widths.len());
for (width, title) in self.widths.iter().zip(self.header.iter()) {
let w = max(title.width() as u16, *width);
if x + w < table_area.width {
widths.push(w);
for width in self.widths.iter() {
if x + width < table_area.width {
widths.push(*width);
}
x += w;
x += *width;
}
let mut y = table_area.top();
// Header
// Draw the 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_style);
for (w, t) in widths.iter().zip(self.header.by_ref()) {
buf.set_string(x, y, &format!("{}", t), &self.header_style);
x += *w + self.column_spacing;
}
}
y += 2;
// Draw rows
let default_style = Style::default();
if y < table_area.bottom() {
let remaining = (table_area.bottom() - y) as usize;
for (i, &(ref row, style)) in self.rows.iter().take(remaining).enumerate() {
for (i, row) in self.rows.by_ref().take(remaining).enumerate() {
let (data, style) = match row {
Row::Data(d) => (d, &default_style),
Row::StyledData(d, s) => (d, s),
};
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, style);
for (w, elt) in widths.iter().zip(data) {
buf.set_stringn(x, y + i as u16, &format!("{}", elt), *w as usize, style);
x += *w + self.column_spacing;
}
}