fix(table): widths() now accepts AsRef<[Constraint]> (#628)

This allows passing an array, slice or Vec of constraints, which is more
ergonomic than requiring this to always be a slice.

The following calls now all succeed:

```rust
Table::new(rows).widths([Constraint::Length(5), Constraint::Length(5)]);
Table::new(rows).widths(&[Constraint::Length(5), Constraint::Length(5)]);

// widths could also be computed at runtime
let widths = vec![Constraint::Length(5), Constraint::Length(5)];
Table::new(rows).widths(widths.clone());
Table::new(rows).widths(&widths);
```
This commit is contained in:
Josh McKinney 2023-11-15 12:34:02 -08:00 committed by GitHub
parent ec7b3872b4
commit 36d8c53645
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 89 additions and 39 deletions

View file

@ -10,6 +10,9 @@ github with a [breaking change] label.
This is a quick summary of the sections below:
- Unreleased (0.24.1)
-
- [v0.24.0](#v0240)
- MSRV is now 1.70.0
- `ScrollbarState`: `position`, `content_length`, and `viewport_content_length` are now `usize`
@ -32,6 +35,24 @@ This is a quick summary of the sections below:
- MSRV is now 1.63.0
- `List` no longer ignores empty strings
## Unreleased (v0.24.1)
### `Table::widths()` now accepts `AsRef<[Constraint]>` ([#628])
Previously `Table::widths()` took a slice (`&'a [Constraint]`). This change will introduce clippy
`needless_borrow` warnings for places where slices are passed to this method. To fix these, remove
the `&`.
E.g.
```rust
let table = Table::new(rows).widths(&[Constraint::Length(1)]);
// becomes
let table = Table::new(rows).widths([Constraint::Length(1)]);
```
[#628]: https://github.com/ratatui-org/ratatui/pull/628
## [v0.24.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.24.0)
### ScrollbarState field type changed from `u16` to `usize` ([#456])

View file

@ -296,7 +296,7 @@ fn draw_second_tab(f: &mut Frame, app: &mut App, area: Rect) {
.bottom_margin(1),
)
.block(Block::default().title("Servers").borders(Borders::ALL))
.widths(&[
.widths([
Constraint::Length(15),
Constraint::Length(15),
Constraint::Length(10),
@ -395,7 +395,7 @@ fn draw_third_tab(f: &mut Frame, _app: &mut App, area: Rect) {
.collect();
let table = Table::new(items)
.block(Block::default().title("Colors").borders(Borders::ALL))
.widths(&[
.widths([
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),

View file

@ -149,7 +149,7 @@ fn render_ingredients(selected_row: usize, area: Rect, buf: &mut Buffer) {
Table::new(rows)
.block(Block::new().style(theme.ingredients))
.header(Row::new(vec!["Qty", "Ingredient"]).style(theme.ingredients_header))
.widths(&[Constraint::Length(7), Constraint::Length(30)])
.widths([Constraint::Length(7), Constraint::Length(30)])
.highlight_style(Style::new().light_yellow()),
area,
buf,

View file

@ -52,7 +52,7 @@ fn render_hops(selected_row: usize, area: Rect, buf: &mut Buffer) {
StatefulWidget::render(
Table::new(rows)
.header(Row::new(vec!["Host", "Address"]).set_style(THEME.traceroute.header))
.widths(&[Constraint::Max(100), Constraint::Length(15)])
.widths([Constraint::Max(100), Constraint::Length(15)])
.highlight_style(THEME.traceroute.selected)
.block(block),
area,

View file

@ -142,7 +142,7 @@ fn ui(f: &mut Frame, app: &mut App) {
.block(Block::default().borders(Borders::ALL).title("Table"))
.highlight_style(selected_style)
.highlight_symbol(">> ")
.widths(&[
.widths([
Constraint::Percentage(50),
Constraint::Max(30),
Constraint::Min(10),

View file

@ -1,3 +1,6 @@
use std::iter;
use itertools::Itertools;
use strum::{Display, EnumString};
use unicode_width::UnicodeWidthStr;
@ -251,7 +254,7 @@ pub struct Table<'a> {
/// Base style for the widget
style: Style,
/// Width constraints for each column
widths: &'a [Constraint],
widths: Vec<Constraint>,
/// Space between each column
column_spacing: u16,
/// Style used to render the selected row
@ -294,7 +297,7 @@ impl<'a> Table<'a> {
Self {
block: None,
style: Style::default(),
widths: &[],
widths: vec![],
column_spacing: 1,
highlight_style: Style::default(),
highlight_symbol: None,
@ -355,7 +358,24 @@ impl<'a> Table<'a> {
self
}
pub fn widths(mut self, widths: &'a [Constraint]) -> Self {
/// Set the widths of the columns of the [`Table`] widget.
///
/// The `widths` parameter accepts `AsRef<[Constraint]>`` which can be an array, slice, or Vec.
///
/// # Examples
///
/// ```rust
/// # use ratatui::{prelude::*, widgets::*};
/// let table = Table::new(vec![]).widths([Constraint::Length(5), Constraint::Length(5)]);
/// let table = Table::new(vec![]).widths(&[Constraint::Length(5), Constraint::Length(5)]);
///
/// // widths could also be computed at runtime
/// let widths = vec![Constraint::Length(5), Constraint::Length(5)];
/// let table = Table::new(vec![]).widths(widths.clone());
/// let table = Table::new(vec![]).widths(&widths);
/// ```
pub fn widths<T: AsRef<[Constraint]>>(mut self, widths: T) -> Self {
let widths = widths.as_ref().to_vec();
let between_0_and_100 = |&w| match w {
Constraint::Percentage(p) => p <= 100,
_ => true,
@ -399,29 +419,21 @@ impl<'a> Table<'a> {
/// Get all offsets and widths of all user specified columns
/// Returns (x, width)
fn get_columns_widths(&self, max_width: u16, selection_width: u16) -> Vec<(u16, u16)> {
let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1);
constraints.push(Constraint::Length(selection_width));
for constraint in self.widths {
constraints.push(*constraint);
constraints.push(Constraint::Length(self.column_spacing));
}
if !self.widths.is_empty() {
constraints.pop();
}
let chunks = Layout::default()
let constraints = iter::once(Constraint::Length(selection_width))
.chain(Itertools::intersperse(
self.widths.iter().cloned(),
Constraint::Length(self.column_spacing),
))
.collect_vec();
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints(constraints)
.segment_size(SegmentSize::None)
.split(Rect {
x: 0,
y: 0,
width: max_width,
height: 1,
});
chunks
.split(Rect::new(0, 0, max_width, 1));
layout
.iter()
.skip(1)
.step_by(2)
.skip(1) // skip selection column
.step_by(2) // skip spacing between columns
.map(|c| (c.x, c.width))
.collect()
}
@ -661,7 +673,23 @@ mod tests {
#[test]
#[should_panic]
fn table_invalid_percentages() {
Table::new(vec![]).widths(&[Constraint::Percentage(110)]);
Table::new(vec![]).widths([Constraint::Percentage(110)]);
}
#[test]
fn widths_conversions() {
let table = Table::new(vec![]).widths([Constraint::Percentage(100)]);
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "array");
#[allow(clippy::needless_borrow)] // for backwards compatibility with existing code
let table = Table::new(vec![]).widths(&[Constraint::Percentage(100)]);
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "slice");
let table = Table::new(vec![]).widths(vec![Constraint::Percentage(100)]);
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "vec");
let table = Table::new(vec![]).widths(&vec![Constraint::Percentage(100)]);
assert_eq!(table.widths, vec![Constraint::Percentage(100)], "vec ref");
}
// test how constraints interact with table column width allocation
@ -779,7 +807,7 @@ mod tests {
Row::new(vec![Line::from("Center").alignment(Alignment::Center)]),
Row::new(vec![Line::from("Right").alignment(Alignment::Right)]),
])
.widths(&[Percentage(100)]);
.widths([Percentage(100)]);
Widget::render(table, Rect::new(0, 0, 20, 3), &mut buf);

View file

@ -27,7 +27,7 @@ fn widgets_table_column_spacing_can_be_changed() {
])
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
.block(Block::default().borders(Borders::ALL))
.widths(&[
.widths([
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(5),
@ -198,7 +198,8 @@ fn widgets_table_columns_widths_can_use_fixed_length_constraints() {
#[test]
fn widgets_table_columns_widths_can_use_percentage_constraints() {
let test_case = |widths, expected| {
#[track_caller]
fn test_case(widths: &[Constraint], expected: Buffer) {
let backend = TestBackend::new(30, 10);
let mut terminal = Terminal::new(backend).unwrap();
@ -219,7 +220,7 @@ fn widgets_table_columns_widths_can_use_percentage_constraints() {
})
.unwrap();
terminal.backend().assert_buffer(&expected);
};
}
// columns of zero width show nothing
test_case(
@ -536,7 +537,7 @@ fn widgets_table_can_have_rows_with_multi_lines() {
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
.block(Block::default().borders(Borders::ALL))
.highlight_symbol(">> ")
.widths(&[
.widths([
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(5),
@ -631,7 +632,7 @@ fn widgets_table_enable_always_highlight_spacing() {
.block(Block::default().borders(Borders::ALL))
.highlight_symbol(">> ")
.highlight_spacing(space)
.widths(&[
.widths([
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(5),
@ -772,7 +773,7 @@ fn widgets_table_can_have_elements_styled_individually() {
.block(Block::default().borders(Borders::LEFT | Borders::RIGHT))
.highlight_symbol(">> ")
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
.widths(&[
.widths([
Constraint::Length(6),
Constraint::Length(6),
Constraint::Length(6),
@ -833,7 +834,7 @@ fn widgets_table_should_render_even_if_empty() {
let table = Table::new(vec![])
.header(Row::new(vec!["Head1", "Head2", "Head3"]))
.block(Block::default().borders(Borders::LEFT | Borders::RIGHT))
.widths(&[
.widths([
Constraint::Length(6),
Constraint::Length(6),
Constraint::Length(6),
@ -873,7 +874,7 @@ fn widgets_table_columns_dont_panic() {
.block(Block::default().borders(Borders::ALL))
.highlight_symbol(">> ")
.column_spacing(1)
.widths(&[
.widths([
Constraint::Percentage(15),
Constraint::Percentage(15),
Constraint::Percentage(25),
@ -908,7 +909,7 @@ fn widgets_table_should_clamp_offset_if_rows_are_removed() {
])
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
.block(Block::default().borders(Borders::ALL))
.widths(&[
.widths([
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(5),
@ -937,7 +938,7 @@ fn widgets_table_should_clamp_offset_if_rows_are_removed() {
let table = Table::new(vec![Row::new(vec!["Row31", "Row32", "Row33"])])
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
.block(Block::default().borders(Borders::ALL))
.widths(&[
.widths([
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(5),