mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-27 13:01:13 +00:00
feat(table)!: Collect iterator of Row
into Table
(#774)
Any iterator whose item is convertible into `Row` can now be collected into a `Table`. Where previously, `Table::new` accepted `IntoIterator<Item = Row>`, it now accepts `IntoIterator<Item: Into<Row>>`. BREAKING CHANGE: The compiler can no longer infer the element type of the container passed to `Table::new()`. For example, `Table::new(vec![], widths)` will no longer compile, as the type of `vec![]` can no longer be inferred.
This commit is contained in:
parent
f29c73fb1c
commit
c69ca47922
@ -16,6 +16,7 @@ This is a quick summary of the sections below:
|
||||
- `Line` now has an extra `style` field which applies the style to the entire line
|
||||
- `Block` style methods cannot be created in a const context
|
||||
- `Tabs::new()` now accepts `IntoIterator<Item: Into<Line<'a>>>`
|
||||
- `Table::new` now accepts `IntoIterator<Item: Into<Row<'a>>>`.
|
||||
- [v0.25.0](#v0250)
|
||||
- Removed `Axis::title_style` and `Buffer::set_background`
|
||||
- `List::new()` now accepts `IntoIterator<Item = Into<ListItem<'a>>>`
|
||||
@ -47,20 +48,37 @@ This is a quick summary of the sections below:
|
||||
|
||||
## v0.26.0 (unreleased)
|
||||
|
||||
### `Table::new()` now accepts `IntoIterator<Item: Into<Row<'a>>>` ([#774])
|
||||
|
||||
[#774]: https://github.com/ratatui-org/ratatui/pull/774
|
||||
|
||||
Previously, `Table::new()` accepted `IntoIterator<Item=Row<'a>>`. The argument change to
|
||||
`IntoIterator<Item: Into<Row<'a>>>`, This allows more flexible types from calling scopes, though it
|
||||
can some break type inference in the calling scope for empty containers.
|
||||
|
||||
This can be resolved either by providing an explicit type (e.g. `Vec::<Row>::new()`), or by using
|
||||
`Table::default()`.
|
||||
|
||||
```diff
|
||||
- let table = Table::new(vec![], widths);
|
||||
// becomes
|
||||
+ let table = Table::default().widths(widths);
|
||||
```
|
||||
|
||||
### `Tabs::new()` now accepts `IntoIterator<Item: Into<Line<'a>>>` ([#776])
|
||||
|
||||
[#776]: https://github.com/ratatui-org/ratatui/pull/776
|
||||
|
||||
Previously, `Tabs::new()` accepted `Vec<T>` where `T: Into<Line<'a>>`. This allows more flexible
|
||||
types from calling scopes, though it can break type inference when the calling scope.
|
||||
types from calling scopes, though it can break some type inference in the calling scope.
|
||||
|
||||
This typically occurs when collecting an iterator prior to calling `Tabs::new`, and can be resolved
|
||||
by removing the call to `.collect()`.
|
||||
|
||||
```diff
|
||||
- let table = Tabs::new((0.3).map(|i| format!("{i}")).collect());
|
||||
- let tabs = Tabs::new((0.3).map(|i| format!("{i}")).collect());
|
||||
// becomes
|
||||
+ let table = Tabs::new((0.3).map(|i| format!("{i}")));
|
||||
+ let tabs = Tabs::new((0.3).map(|i| format!("{i}")));
|
||||
```
|
||||
|
||||
### Table::default() now sets segment_size to None and column_spacing to ([#751])
|
||||
|
@ -146,7 +146,7 @@ fn render_recipe(area: Rect, buf: &mut Buffer) {
|
||||
|
||||
fn render_ingredients(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = TableState::default().with_selected(Some(selected_row));
|
||||
let rows = INGREDIENTS.iter().map(|&i| i.into()).collect_vec();
|
||||
let rows = INGREDIENTS.iter().cloned();
|
||||
let theme = THEME.recipe;
|
||||
StatefulWidget::render(
|
||||
Table::new(rows, [Constraint::Length(7), Constraint::Length(30)])
|
||||
|
@ -132,6 +132,22 @@ use crate::{
|
||||
/// Cell::from(Text::from("text"));
|
||||
/// ```
|
||||
///
|
||||
/// Just as rows can be collected from iterators of `Cell`s, tables can be collected from iterators
|
||||
/// of `Row`s. This will create a table with column widths evenly dividing the space available.
|
||||
/// These default columns widths can be overridden using the `Table::widths` method.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// let text = "Mary had a\nlittle lamb.";
|
||||
///
|
||||
/// let table = text
|
||||
/// .split("\n")
|
||||
/// .map(|line: &str| -> Row { line.split_ascii_whitespace().collect() })
|
||||
/// .collect::<Table>()
|
||||
/// .widths([Constraint::Length(10); 3]);
|
||||
/// ```
|
||||
///
|
||||
/// `Table` also implements the [`Styled`] trait, which means you can use style shorthands from
|
||||
/// the [`Stylize`] trait to set the style of the widget more concisely.
|
||||
///
|
||||
@ -250,14 +266,17 @@ impl<'a> Table<'a> {
|
||||
/// ```
|
||||
pub fn new<R, C>(rows: R, widths: C) -> Self
|
||||
where
|
||||
R: IntoIterator<Item = Row<'a>>,
|
||||
R: IntoIterator,
|
||||
R::Item: Into<Row<'a>>,
|
||||
C: IntoIterator,
|
||||
C::Item: Into<Constraint>,
|
||||
{
|
||||
let widths = widths.into_iter().map(Into::into).collect_vec();
|
||||
ensure_percentages_less_than_100(&widths);
|
||||
|
||||
let rows = rows.into_iter().map(Into::into).collect();
|
||||
Self {
|
||||
rows: rows.into_iter().collect(),
|
||||
rows,
|
||||
widths,
|
||||
..Default::default()
|
||||
}
|
||||
@ -534,11 +553,10 @@ impl<'a> Table<'a> {
|
||||
/// to the last column.
|
||||
#[cfg_attr(feature = "unstable", doc = " ```")]
|
||||
#[cfg_attr(not(feature = "unstable"), doc = " ```ignore")]
|
||||
/// # use ratatui::layout::Constraint;
|
||||
/// # use ratatui::layout::SegmentSize;
|
||||
/// # use ratatui::widgets::Table;
|
||||
/// # use ratatui::layout::{Constraint, SegmentSize};
|
||||
/// # use ratatui::widgets::{Table, Row};
|
||||
/// let widths = [Constraint::Min(10), Constraint::Min(10), Constraint::Min(10)];
|
||||
/// let table = Table::new([], widths)
|
||||
/// let table = Table::new(Vec::<Row>::new(), widths)
|
||||
/// .segment_size(SegmentSize::LastTakesRemainder);
|
||||
/// ```
|
||||
#[stability::unstable(
|
||||
@ -807,6 +825,20 @@ impl<'a> Styled for Table<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Item> FromIterator<Item> for Table<'a>
|
||||
where
|
||||
Item: Into<Row<'a>>,
|
||||
{
|
||||
/// Collects an iterator of rows into a table.
|
||||
///
|
||||
/// When collecting from an iterator into a table, the user must provide the widths using
|
||||
/// `Table::widths` after construction.
|
||||
fn from_iter<Iter: IntoIterator<Item = Item>>(rows: Iter) -> Self {
|
||||
let widths: [Constraint; 0] = [];
|
||||
Table::new(rows, widths)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::vec;
|
||||
@ -853,6 +885,24 @@ mod tests {
|
||||
assert_eq!(table.segment_size, SegmentSize::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect() {
|
||||
let table = (0..4)
|
||||
.map(|i| -> Row { (0..4).map(|j| format!("{i}*{j} = {}", i * j)).collect() })
|
||||
.collect::<Table>()
|
||||
.widths([Constraint::Percentage(25); 4]);
|
||||
|
||||
let expected_rows: Vec<Row> = vec![
|
||||
Row::new(["0*0 = 0", "0*1 = 0", "0*2 = 0", "0*3 = 0"]),
|
||||
Row::new(["1*0 = 0", "1*1 = 1", "1*2 = 2", "1*3 = 3"]),
|
||||
Row::new(["2*0 = 0", "2*1 = 2", "2*2 = 4", "2*3 = 6"]),
|
||||
Row::new(["3*0 = 0", "3*1 = 3", "3*2 = 6", "3*3 = 9"]),
|
||||
];
|
||||
|
||||
assert_eq!(table.rows, expected_rows);
|
||||
assert_eq!(table.widths, [Constraint::Percentage(25); 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widths() {
|
||||
let table = Table::default().widths([Constraint::Length(100)]);
|
||||
@ -934,24 +984,24 @@ mod tests {
|
||||
#[test]
|
||||
fn widths_conversions() {
|
||||
let array = [Constraint::Percentage(100)];
|
||||
let table = Table::new(vec![], array);
|
||||
let table = Table::new(Vec::<Row>::new(), array);
|
||||
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "array");
|
||||
|
||||
let array_ref = &[Constraint::Percentage(100)];
|
||||
let table = Table::new(vec![], array_ref);
|
||||
let table = Table::new(Vec::<Row>::new(), array_ref);
|
||||
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "array ref");
|
||||
|
||||
let vec = vec![Constraint::Percentage(100)];
|
||||
let slice = vec.as_slice();
|
||||
let table = Table::new(vec![], slice);
|
||||
let table = Table::new(Vec::<Row>::new(), slice);
|
||||
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "slice");
|
||||
|
||||
let vec = vec![Constraint::Percentage(100)];
|
||||
let table = Table::new(vec![], vec);
|
||||
let table = Table::new(Vec::<Row>::new(), vec);
|
||||
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "vec");
|
||||
|
||||
let vec_ref = &vec![Constraint::Percentage(100)];
|
||||
let table = Table::new(vec![], vec_ref);
|
||||
let table = Table::new(Vec::<Row>::new(), vec_ref);
|
||||
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "vec ref");
|
||||
}
|
||||
|
||||
@ -1120,7 +1170,7 @@ mod tests {
|
||||
#[test]
|
||||
fn render_with_overflow_does_not_panic() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 3));
|
||||
let table = Table::new(vec![], [Constraint::Min(20); 1])
|
||||
let table = Table::new(Vec::<Row>::new(), [Constraint::Min(20); 1])
|
||||
.header(Row::new([Line::from("").alignment(Alignment::Right)]))
|
||||
.footer(Row::new([Line::from("").alignment(Alignment::Right)]));
|
||||
Widget::render(table, Rect::new(0, 0, 20, 3), &mut buf);
|
||||
@ -1161,7 +1211,7 @@ mod tests {
|
||||
selection_width: u16,
|
||||
expected: &[(u16, u16)],
|
||||
) {
|
||||
let table = Table::new(vec![], constraints).segment_size(segment_size);
|
||||
let table = Table::new(Vec::<Row>::new(), constraints).segment_size(segment_size);
|
||||
|
||||
let widths = table.get_columns_widths(available_width, selection_width);
|
||||
assert_eq!(widths, expected);
|
||||
|
2
tests/widgets_table.rs
Normal file → Executable file
2
tests/widgets_table.rs
Normal file → Executable file
@ -849,7 +849,7 @@ fn widgets_table_should_render_even_if_empty() {
|
||||
.draw(|f| {
|
||||
let size = f.size();
|
||||
let table = Table::new(
|
||||
vec![],
|
||||
Vec::<Row>::new(),
|
||||
[
|
||||
Constraint::Length(6),
|
||||
Constraint::Length(6),
|
||||
|
Loading…
x
Reference in New Issue
Block a user