use std::{convert::TryInto, marker::PhantomData};
pub mod column;
pub use column::*;
pub mod styling;
pub use styling::*;
pub mod props;
pub use props::DataTableProps;
pub mod state;
pub use state::{DataTableState, ScrollDirection};
pub mod draw;
pub use draw::*;
pub mod data_type;
pub use data_type::*;
pub mod sortable;
pub use sortable::*;
pub struct DataTable<DataType, Header, S = Unsortable, C = Column<Header>> {
pub columns: Vec<C>,
pub state: DataTableState,
pub props: DataTableProps,
pub styling: DataTableStyling,
data: Vec<DataType>,
sort_type: S,
first_draw: bool,
_pd: PhantomData<(DataType, S, Header)>,
}
impl<DataType: DataToCell<H>, H: ColumnHeader> DataTable<DataType, H, Unsortable, Column<H>> {
pub fn new<C: Into<Vec<Column<H>>>>(
columns: C, props: DataTableProps, styling: DataTableStyling,
) -> Self {
Self {
columns: columns.into(),
state: DataTableState::default(),
props,
styling,
data: vec![],
sort_type: Unsortable,
first_draw: true,
_pd: PhantomData,
}
}
}
impl<DataType: DataToCell<H>, H: ColumnHeader, S: SortType, C: DataTableColumn<H>>
DataTable<DataType, H, S, C>
{
pub fn set_first(&mut self) {
self.state.current_index = 0;
self.state.scroll_direction = ScrollDirection::Up;
}
pub fn set_last(&mut self) {
self.state.current_index = self.data.len().saturating_sub(1);
self.state.scroll_direction = ScrollDirection::Down;
}
pub fn set_data(&mut self, data: Vec<DataType>) {
self.data = data;
let max_pos = self.data.len().saturating_sub(1);
if self.state.current_index > max_pos {
self.state.current_index = max_pos;
self.state.display_start_index = 0;
self.state.scroll_direction = ScrollDirection::Down;
}
}
pub fn increment_position(&mut self, change: i64) -> Option<usize> {
let max_index = self.data.len();
let current_index = self.state.current_index;
if change == 0
|| (change > 0 && current_index == max_index)
|| (change < 0 && current_index == 0)
{
return None;
}
let csp: Result<i64, _> = self.state.current_index.try_into();
if let Ok(csp) = csp {
let proposed: Result<usize, _> = (csp + change).try_into();
if let Ok(proposed) = proposed {
if proposed < self.data.len() {
self.state.current_index = proposed;
self.state.scroll_direction = if change < 0 {
ScrollDirection::Up
} else {
ScrollDirection::Down
};
return Some(self.state.current_index);
}
}
}
None
}
#[allow(clippy::comparison_chain)]
pub fn set_position(&mut self, new_index: usize) {
let new_index = new_index.clamp(0, self.data.len().saturating_sub(1));
if self.state.current_index < new_index {
self.state.scroll_direction = ScrollDirection::Down;
} else if self.state.current_index > new_index {
self.state.scroll_direction = ScrollDirection::Up;
}
self.state.current_index = new_index;
}
pub fn current_index(&self) -> usize {
self.state.current_index
}
pub fn current_item(&self) -> Option<&DataType> {
self.data.get(self.state.current_index)
}
pub fn tui_selected(&self) -> Option<usize> {
self.state.table_state.selected()
}
}
#[cfg(test)]
mod test {
use super::*;
#[derive(Clone, PartialEq, Eq, Debug)]
struct TestType {
index: usize,
}
impl DataToCell<&'static str> for TestType {
fn to_cell<'a>(
&'a self, _column: &&'static str, _calculated_width: u16,
) -> Option<tui::text::Text<'a>> {
None
}
fn column_widths<C: DataTableColumn<&'static str>>(
_data: &[Self], _columns: &[C],
) -> Vec<u16>
where
Self: Sized,
{
vec![]
}
}
#[test]
fn test_data_table_operations() {
let columns = [Column::hard("a", 10), Column::hard("b", 10)];
let props = DataTableProps {
title: Some("test".into()),
table_gap: 1,
left_to_right: false,
is_basic: false,
show_table_scroll_position: true,
show_current_entry_when_unfocused: false,
};
let styling = DataTableStyling::default();
let mut table = DataTable::new(columns, props, styling);
table.set_data((0..=4).map(|index| TestType { index }).collect::<Vec<_>>());
table.set_last();
assert_eq!(table.current_index(), 4);
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
table.set_first();
assert_eq!(table.current_index(), 0);
assert_eq!(table.state.scroll_direction, ScrollDirection::Up);
table.set_position(4);
assert_eq!(table.current_index(), 4);
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
table.set_position(100);
assert_eq!(table.current_index(), 4);
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
assert_eq!(table.current_item(), Some(&TestType { index: 4 }));
table.increment_position(-1);
assert_eq!(table.current_index(), 3);
assert_eq!(table.state.scroll_direction, ScrollDirection::Up);
assert_eq!(table.current_item(), Some(&TestType { index: 3 }));
table.increment_position(-3);
assert_eq!(table.current_index(), 0);
assert_eq!(table.state.scroll_direction, ScrollDirection::Up);
assert_eq!(table.current_item(), Some(&TestType { index: 0 }));
table.increment_position(-3);
assert_eq!(table.current_index(), 0);
assert_eq!(table.state.scroll_direction, ScrollDirection::Up);
assert_eq!(table.current_item(), Some(&TestType { index: 0 }));
table.increment_position(1);
assert_eq!(table.current_index(), 1);
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
assert_eq!(table.current_item(), Some(&TestType { index: 1 }));
table.increment_position(3);
assert_eq!(table.current_index(), 4);
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
assert_eq!(table.current_item(), Some(&TestType { index: 4 }));
table.increment_position(10);
assert_eq!(table.current_index(), 4);
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
assert_eq!(table.current_item(), Some(&TestType { index: 4 }));
table.set_data((0..=2).map(|index| TestType { index }).collect::<Vec<_>>());
assert_eq!(table.current_index(), 2);
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
assert_eq!(table.current_item(), Some(&TestType { index: 2 }));
}
}