Skip to content

Commit

Permalink
feat(table): Add a Table::segment_size method
Browse files Browse the repository at this point in the history
It works just like Layout::segment_size, but for Tables.  Previously, it
was impossible to distribute extra space anywhere in a Table.

Fixes #370
  • Loading branch information
asomers committed Dec 3, 2023
1 parent 56fc410 commit 3d2d451
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub enum Alignment {
reason = "The name for this feature is not final and may change in the future",
issue = "https://github.com/ratatui-org/ratatui/issues/536"
)]
#[derive(Debug, Default, Display, EnumString, Clone, Eq, PartialEq, Hash)]
#[derive(Copy, Debug, Default, Display, EnumString, Clone, Eq, PartialEq, Hash)]
pub enum SegmentSize {
/// prefer equal chunks if other constraints are all satisfied
EvenDistribution,
Expand Down
211 changes: 189 additions & 22 deletions src/widgets/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ pub struct Table<'a> {
rows: Vec<Row<'a>>,
/// Decides when to allocate spacing for the row selection
highlight_spacing: HighlightSpacing,
segment_size: SegmentSize,
}

impl<'a> Table<'a> {
Expand Down Expand Up @@ -304,6 +305,7 @@ impl<'a> Table<'a> {
header: None,
rows: rows.into_iter().collect(),
highlight_spacing: HighlightSpacing::default(),
segment_size: SegmentSize::None,
}
}

Expand Down Expand Up @@ -428,7 +430,7 @@ impl<'a> Table<'a> {
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints(constraints)
.segment_size(SegmentSize::None)
.segment_size(self.segment_size)
.split(Rect::new(0, 0, max_width, 1));
layout
.iter()
Expand Down Expand Up @@ -475,6 +477,21 @@ impl<'a> Table<'a> {
}
(start, end)
}

/// Builder method to control how extra space is distributed amongst columns.
///
/// This determines how the space is distributed when the constraints are satisfied. By default,
/// the extra space is not distributed at all. But this can be changed to distribute all extra
/// space to the last column or to distribute it equally.
#[stability::unstable(
feature = "segment-size",
reason = "The name for this feature is not final and may change in the future",
issue = "https://github.com/ratatui-org/ratatui/issues/536"
)]
pub const fn segment_size(mut self, segment_size: SegmentSize) -> Self {
self.segment_size = segment_size;
self
}
}

impl<'a> Styled for Table<'a> {
Expand Down Expand Up @@ -709,11 +726,14 @@ mod tests {
#[track_caller]
fn test(
constraints: &[Constraint],
segment_size: SegmentSize,
available_width: u16,
selection_width: u16,
expected: &[(u16, u16)],
) {
let table = Table::new(vec![]).widths(constraints);
let table = Table::new(vec![])
.segment_size(segment_size)
.widths(constraints);

let widths = table.get_columns_widths(available_width, selection_width);
assert_eq!(widths, expected);
Expand All @@ -722,31 +742,79 @@ mod tests {
#[test]
fn length_constraint() {
// without selection, more than needed width
test(&[Length(4), Length(4)], 20, 0, &[(0, 4), (5, 4)]);
test(
&[Length(4), Length(4)],
SegmentSize::None,
20,
0,
&[(0, 4), (5, 4)],
);

// with selection, more than needed width
test(&[Length(4), Length(4)], 20, 3, &[(3, 4), (8, 4)]);
test(
&[Length(4), Length(4)],
SegmentSize::None,
20,
3,
&[(3, 4), (8, 4)],
);

// without selection, less than needed width
test(&[Length(4), Length(4)], 7, 0, &[(0, 4), (5, 2)]);
test(
&[Length(4), Length(4)],
SegmentSize::None,
7,
0,
&[(0, 4), (5, 2)],
);

// with selection, less than needed width
test(&[Length(4), Length(4)], 7, 3, &[(3, 4), (7, 0)]);
test(
&[Length(4), Length(4)],
SegmentSize::None,
7,
3,
&[(3, 4), (7, 0)],
);
}

#[test]
fn max_constraint() {
// without selection, more than needed width
test(&[Max(4), Max(4)], 20, 0, &[(0, 4), (5, 4)]);
test(
&[Max(4), Max(4)],
SegmentSize::None,
20,
0,
&[(0, 4), (5, 4)],
);

// with selection, more than needed width
test(&[Max(4), Max(4)], 20, 3, &[(3, 4), (8, 4)]);
test(
&[Max(4), Max(4)],
SegmentSize::None,
20,
3,
&[(3, 4), (8, 4)],
);

// without selection, less than needed width
test(&[Max(4), Max(4)], 7, 0, &[(0, 4), (5, 2)]);
test(
&[Max(4), Max(4)],
SegmentSize::None,
7,
0,
&[(0, 4), (5, 2)],
);

// with selection, less than needed width
test(&[Max(4), Max(4)], 7, 3, &[(3, 3), (7, 0)]);
test(
&[Max(4), Max(4)],
SegmentSize::None,
7,
3,
&[(3, 3), (7, 0)],
);
}

#[test]
Expand All @@ -756,54 +824,153 @@ mod tests {
// constraint and not split it with all available constraints

// without selection, more than needed width
test(&[Min(4), Min(4)], 20, 0, &[(0, 4), (5, 4)]);
test(
&[Min(4), Min(4)],
SegmentSize::None,
20,
0,
&[(0, 4), (5, 4)],
);

// with selection, more than needed width
test(&[Min(4), Min(4)], 20, 3, &[(3, 4), (8, 4)]);
test(
&[Min(4), Min(4)],
SegmentSize::None,
20,
3,
&[(3, 4), (8, 4)],
);

// without selection, less than needed width
// allocates no spacer
test(&[Min(4), Min(4)], 7, 0, &[(0, 4), (4, 3)]);
test(
&[Min(4), Min(4)],
SegmentSize::None,
7,
0,
&[(0, 4), (4, 3)],
);

// with selection, less than needed width
// allocates no selection and no spacer
test(&[Min(4), Min(4)], 7, 3, &[(0, 4), (4, 3)]);
test(
&[Min(4), Min(4)],
SegmentSize::None,
7,
3,
&[(0, 4), (4, 3)],
);
}

#[test]
fn percentage_constraint() {
// without selection, more than needed width
test(&[Percentage(30), Percentage(30)], 20, 0, &[(0, 6), (7, 6)]);
test(
&[Percentage(30), Percentage(30)],
SegmentSize::None,
20,
0,
&[(0, 6), (7, 6)],
);

// with selection, more than needed width
test(&[Percentage(30), Percentage(30)], 20, 3, &[(3, 6), (10, 6)]);
test(
&[Percentage(30), Percentage(30)],
SegmentSize::None,
20,
3,
&[(3, 6), (10, 6)],
);

// without selection, less than needed width
// rounds from positions: [0.0, 0.0, 2.1, 3.1, 5.2, 7.0]
test(&[Percentage(30), Percentage(30)], 7, 0, &[(0, 2), (3, 2)]);
test(
&[Percentage(30), Percentage(30)],
SegmentSize::None,
7,
0,
&[(0, 2), (3, 2)],
);

// with selection, less than needed width
// rounds from positions: [0.0, 3.0, 5.1, 6.1, 7.0, 7.0]
test(&[Percentage(30), Percentage(30)], 7, 3, &[(3, 2), (6, 1)]);
test(
&[Percentage(30), Percentage(30)],
SegmentSize::None,
7,
3,
&[(3, 2), (6, 1)],
);
}

#[test]
fn ratio_constraint() {
// without selection, more than needed width
// rounds from positions: [0.00, 0.00, 6.67, 7.67, 14.33]
test(&[Ratio(1, 3), Ratio(1, 3)], 20, 0, &[(0, 7), (8, 6)]);
test(
&[Ratio(1, 3), Ratio(1, 3)],
SegmentSize::None,
20,
0,
&[(0, 7), (8, 6)],
);

// with selection, more than needed width
// rounds from positions: [0.00, 3.00, 10.67, 17.33, 20.00]
test(&[Ratio(1, 3), Ratio(1, 3)], 20, 3, &[(3, 7), (11, 6)]);
test(
&[Ratio(1, 3), Ratio(1, 3)],
SegmentSize::None,
20,
3,
&[(3, 7), (11, 6)],
);

// without selection, less than needed width
// rounds from positions: [0.00, 2.33, 3.33, 5.66, 7.00]
test(&[Ratio(1, 3), Ratio(1, 3)], 7, 0, &[(0, 2), (3, 3)]);
test(
&[Ratio(1, 3), Ratio(1, 3)],
SegmentSize::None,
7,
0,
&[(0, 2), (3, 3)],
);

// with selection, less than needed width
// rounds from positions: [0.00, 3.00, 5.33, 6.33, 7.00, 7.00]
test(&[Ratio(1, 3), Ratio(1, 3)], 7, 3, &[(3, 2), (6, 1)]);
test(
&[Ratio(1, 3), Ratio(1, 3)],
SegmentSize::None,
7,
3,
&[(3, 2), (6, 1)],
);
}

/// When more width is available than requested, the behavior is controlled by segment_size
#[test]
fn underconstrained() {
let widths = [Min(10), Min(10), Min(1)];
test(
&widths[..],
SegmentSize::None,
62,
0,
&[(0, 10), (11, 10), (22, 1)],
);
test(
&widths[..],
SegmentSize::LastTakesRemainder,
62,
0,
&[(0, 10), (11, 10), (22, 40)],
);
test(
&widths[..],
SegmentSize::EvenDistribution,
62,
0,
&[(0, 20), (21, 20), (42, 20)],
);
}
}

Expand Down

0 comments on commit 3d2d451

Please sign in to comment.