use crate::column::Column;
use crate::style::ColumnConstraint::*;
use crate::style::{CellAlignment, ColumnConstraint, ContentArrangement};
use crate::table::Table;
use crate::utils::borders::{
should_draw_left_border, should_draw_right_border, should_draw_vertical_lines,
};
pub struct ColumnDisplayInfo {
pub padding: (u16, u16),
max_content_width: u16,
content_width: u16,
fixed: bool,
pub constraint: Option<ColumnConstraint>,
pub hidden: bool,
pub cell_alignment: Option<CellAlignment>,
}
impl ColumnDisplayInfo {
fn new(column: &Column) -> Self {
ColumnDisplayInfo {
padding: column.padding,
max_content_width: column.max_content_width,
content_width: 0,
fixed: false,
constraint: None::<ColumnConstraint>,
hidden: false,
cell_alignment: column.cell_alignment,
}
}
pub fn padding_width(&self) -> u16 {
self.padding.0 + self.padding.1
}
pub fn content_width(&self) -> u16 {
self.content_width
}
pub fn set_content_width(&mut self, width: u16) {
if width == 0 {
self.content_width = 1;
return;
}
self.content_width = width;
}
pub fn max_width(&self) -> u16 {
self.max_content_width + self.padding.0 + self.padding.1
}
pub fn width(&self) -> u16 {
self.content_width + self.padding.0 + self.padding.1
}
fn without_padding(&self, width: u16) -> u16 {
let padding = self.padding_width();
if padding >= width {
return 1;
}
width - padding
}
}
pub fn arrange_content(table: &Table) -> Vec<ColumnDisplayInfo> {
let table_width = table.get_table_width();
let mut display_infos = Vec::new();
for column in table.columns.iter() {
let mut info = ColumnDisplayInfo::new(column);
if let Some(ColumnConstraint::Hidden) = column.constraint.as_ref() {
continue;
} else if let Some(constraint) = column.constraint.as_ref() {
evaluate_constraint(&mut info, constraint, table_width);
}
display_infos.push(info);
}
if table_width.is_none() {
disabled_arrangement(&mut display_infos);
return display_infos;
}
match &table.arrangement {
ContentArrangement::Disabled => disabled_arrangement(&mut display_infos),
ContentArrangement::Dynamic => {
dynamic_arrangement(table, &mut display_infos, table_width.unwrap());
}
}
display_infos
}
fn evaluate_constraint(
info: &mut ColumnDisplayInfo,
constraint: &ColumnConstraint,
table_width: Option<u16>,
) {
match constraint {
Hidden => info.hidden = true,
Width(width) => {
let width = info.without_padding(*width);
info.set_content_width(width);
info.fixed = true;
}
Percentage(percent) => {
if let Some(table_width) = table_width {
let mut width = table_width * percent / 100;
width = info.without_padding(width as u16);
info.set_content_width(width);
info.fixed = true;
}
}
ContentWidth => {
info.set_content_width(info.max_content_width);
info.fixed = true;
}
MaxWidth(max_width) => info.constraint = Some(MaxWidth(*max_width)),
MinWidth(min_width) => {
if info.max_content_width <= *min_width {
let width = info.without_padding(*min_width);
info.set_content_width(width);
info.fixed = true;
}
}
}
}
fn disabled_arrangement(infos: &mut Vec<ColumnDisplayInfo>) {
for info in infos.iter_mut() {
if info.fixed {
continue;
}
if let Some(ColumnConstraint::MaxWidth(max_width)) = info.constraint {
if max_width < info.max_width() {
let width = info.without_padding(max_width);
info.set_content_width(width);
info.fixed = true;
continue;
}
}
info.set_content_width(info.max_content_width);
info.fixed = true;
}
}
fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_width: u16) {
let mut remaining_width = table_width as i32;
if should_draw_left_border(table) {
remaining_width -= 1;
}
if should_draw_right_border(table) {
remaining_width -= 1;
}
if should_draw_vertical_lines(table) {
remaining_width -= infos.len() as i32 - 1;
}
let mut checked = Vec::new();
for (id, info) in infos.iter().enumerate() {
if info.hidden {
checked.push(id);
}
if info.fixed {
remaining_width -= info.width() as i32;
checked.push(id);
}
}
let mut found_smaller = true;
while found_smaller {
found_smaller = false;
let remaining_columns = infos.len() - checked.len();
if remaining_columns == 0 {
break;
}
let average_space = remaining_width / remaining_columns as i32;
if average_space <= 0 {
break;
}
for (id, info) in infos.iter_mut().enumerate() {
if checked.contains(&id) {
continue;
}
if let Some(ColumnConstraint::MaxWidth(max_width)) = info.constraint {
if max_width as i32 <= average_space && info.max_width() >= max_width {
let width = info.without_padding(max_width);
info.set_content_width(width);
info.fixed = true;
remaining_width -= info.width() as i32;
checked.push(id);
found_smaller = true;
continue;
}
}
if (info.max_width() as i32) < average_space {
info.set_content_width(info.max_content_width);
info.fixed = true;
remaining_width -= info.width() as i32;
checked.push(id);
found_smaller = true;
}
}
}
let remaining_columns = infos.len() - checked.len();
if remaining_columns == 0 {
return;
}
if remaining_width < remaining_columns as i32 {
remaining_width = remaining_columns as i32;
}
let remaining_width = remaining_width as u16;
let average_space = remaining_width / remaining_columns as u16;
let mut excess = remaining_width - (average_space * remaining_columns as u16);
for (id, info) in infos.iter_mut().enumerate() {
if checked.contains(&id) {
continue;
}
let mut width = if excess > 0 {
excess -= 1;
average_space + 1
} else {
average_space
};
width = info.without_padding(width);
info.set_content_width(width);
info.fixed = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_disabled_arrangement() {
let mut table = Table::new();
table.set_header(&vec!["head", "head", "head"]);
table.add_row(&vec!["four", "fivef", "sixsix"]);
let display_infos = arrange_content(&table);
let max_widths: Vec<u16> = display_infos
.iter()
.map(|info| info.max_content_width)
.collect();
assert_eq!(max_widths, vec![4, 5, 6]);
let widths: Vec<u16> = display_infos.iter().map(|info| info.width()).collect();
assert_eq!(widths, vec![6, 7, 8]);
}
}