use ratatui::layout::Constraint;
use super::text_width;
const GAP: usize = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct Column<K> {
pub(super) kind: K,
pub(super) header: String,
pub(super) width: usize,
}
impl<K: Copy + PartialEq> Column<K> {
pub(super) fn new(kind: K, header: impl Into<String>) -> Self {
let header = header.into();
let width = text_width(&header);
Self {
kind,
header,
width,
}
}
pub(super) fn fit(&mut self, width: usize) {
self.width = self.width.max(width);
}
}
pub(super) fn total<K>(columns: &[Column<K>]) -> usize {
let cells: usize = columns.iter().map(|column| column.width).sum();
cells + GAP * columns.len().saturating_sub(1)
}
pub(super) fn shrink<K: Copy + PartialEq>(
columns: &mut [Column<K>],
kind: K,
room: usize,
min: usize,
) {
let over = total(columns).saturating_sub(room);
if let Some(column) = columns.iter_mut().find(|column| column.kind == kind) {
column.width = column.width.saturating_sub(over).max(min.min(column.width));
}
}
pub(super) fn insert_if_room<K>(
columns: &mut Vec<Column<K>>,
at: usize,
column: Column<K>,
room: usize,
) -> bool {
if total(columns) + GAP + column.width > room {
return false;
}
columns.insert(at.min(columns.len()), column);
true
}
pub(super) fn insert_narrowed<K>(
columns: &mut Vec<Column<K>>,
at: usize,
mut column: Column<K>,
room: usize,
min: usize,
) -> bool {
let left = room.saturating_sub(total(columns) + GAP);
if left < min.min(column.width) {
return false;
}
column.width = column.width.min(left);
columns.insert(at.min(columns.len()), column);
true
}
pub(super) fn constraints<K>(columns: &[Column<K>]) -> Vec<Constraint> {
columns
.iter()
.map(|column| Constraint::Length(column.width as u16))
.collect()
}
pub(super) fn headers<K>(columns: &[Column<K>]) -> Vec<String> {
columns.iter().map(|column| column.header.clone()).collect()
}
pub(super) const SPACING: u16 = GAP as u16;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn columns_take_their_widest_cell_and_shrink_one_column_to_fit() {
let mut columns = vec![Column::new(1, "Id"), Column::new(2, "Vendor")];
columns[0].fit(9);
columns[1].fit(30);
assert_eq!(total(&columns), 9 + 1 + 30);
shrink(&mut columns, 2, 20, 6);
assert_eq!(columns[1].width, 10);
shrink(&mut columns, 2, 5, 6);
assert_eq!(columns[1].width, 6, "never below the minimum");
assert!(!insert_if_room(&mut columns, 1, Column::new(3, "Via"), 18));
assert!(insert_if_room(&mut columns, 1, Column::new(3, "Via"), 20));
assert_eq!(columns[1].kind, 3);
}
#[test]
fn a_narrowed_column_takes_the_room_left_down_to_its_minimum() {
let base = || vec![Column::new(1, "Id")];
let name = |width| {
let mut column = Column::new(2, "Name");
column.fit(width);
column
};
let mut columns = base();
assert!(insert_narrowed(&mut columns, 1, name(20), 40, 8));
assert_eq!(columns[1].width, 20);
let mut columns = base();
assert!(insert_narrowed(&mut columns, 1, name(20), 13, 8));
assert_eq!(columns[1].width, 10);
let mut columns = base();
assert!(!insert_narrowed(&mut columns, 1, name(20), 10, 8));
let mut columns = base();
assert!(insert_narrowed(&mut columns, 1, name(5), 8, 8));
assert_eq!((columns.len(), columns[1].width), (2, 5));
}
}