use crate::widget::row::{ScrollAmount, ScrollDirection};
use ratatui::widgets::TableState as TuiState;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TableSize {
Normal,
Compact,
Minimized,
}
impl TableSize {
pub fn set_minimized(&mut self, minimized: bool) {
*self = if minimized {
Self::Minimized
} else {
Self::Normal
}
}
pub fn next(&self) -> Self {
match self {
Self::Normal => Self::Compact,
Self::Compact => Self::Minimized,
_ => Self::Normal,
}
}
}
#[derive(Clone, Debug)]
pub struct TableState {
pub tui: TuiState,
pub scroll: ScrollAmount,
pub size: TableSize,
pub minimize_threshold: u16,
}
impl Default for TableState {
fn default() -> Self {
Self {
tui: TuiState::default(),
scroll: ScrollAmount::default(),
size: TableSize::Normal,
minimize_threshold: 90,
}
}
}
#[derive(Clone, Debug)]
pub struct StatefulTable<T: Clone> {
pub default_items: Vec<T>,
pub items: Vec<T>,
pub state: TableState,
}
impl<T: Clone> StatefulTable<T> {
pub fn new(items: Vec<T>, mut state: TableState) -> StatefulTable<T> {
state.tui.select(Some(0));
Self {
default_items: items.clone(),
items,
state,
}
}
pub fn with_items(items: Vec<T>) -> StatefulTable<T> {
Self::new(items, TableState::default())
}
pub fn selected(&self) -> Option<&T> {
self.items.get(self.state.tui.selected()?)
}
pub fn next(&mut self) {
let i = match self.state.tui.selected() {
Some(i) => {
if i >= self.items.len().checked_sub(1).unwrap_or(i) {
0
} else {
i + 1
}
}
None => 0,
};
self.state.tui.select(Some(i));
self.reset_scroll();
}
pub fn previous(&mut self) {
let i = match self.state.tui.selected() {
Some(i) => {
if i == 0 {
self.items.len().checked_sub(1).unwrap_or(i)
} else {
i - 1
}
}
None => 0,
};
self.state.tui.select(Some(i));
self.reset_scroll();
}
pub fn scroll_row(&mut self, direction: ScrollDirection) {
match direction {
ScrollDirection::Up(value) => {
self.state.scroll.vertical =
self.state.scroll.vertical.saturating_sub(value);
}
ScrollDirection::Right(value) => {
self.state.scroll.horizontal = self
.state
.scroll
.horizontal
.checked_add(value)
.unwrap_or(self.state.scroll.horizontal)
}
ScrollDirection::Down(value) => {
self.state.scroll.vertical = self
.state
.scroll
.vertical
.checked_add(value)
.unwrap_or(self.state.scroll.vertical)
}
ScrollDirection::Left(value) => {
self.state.scroll.horizontal =
self.state.scroll.horizontal.saturating_sub(value);
}
_ => {}
}
}
pub fn reset_state(&mut self) {
self.items.clone_from(&self.default_items);
self.state.tui.select(Some(0));
}
pub fn reset_scroll(&mut self) {
self.state.scroll = ScrollAmount::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_widget_table() {
let mut table =
StatefulTable::with_items(vec!["data1", "data2", "data3"]);
table.state.tui.select(Some(1));
assert_eq!(Some(&"data2"), table.selected());
table.next();
assert_eq!(Some(2), table.state.tui.selected());
table.previous();
assert_eq!(Some(1), table.state.tui.selected());
table.reset_scroll();
assert_eq!(
"ScrollAmount { vertical: 0, horizontal: 0 }",
&format!("{:?}", table.state.scroll)
);
table.scroll_row(ScrollDirection::Down(3));
table.scroll_row(ScrollDirection::Right(2));
assert_eq!(
"ScrollAmount { vertical: 3, horizontal: 2 }",
&format!("{:?}", table.state.scroll)
);
table.scroll_row(ScrollDirection::Up(1));
table.scroll_row(ScrollDirection::Left(1));
assert_eq!(
"ScrollAmount { vertical: 2, horizontal: 1 }",
&format!("{:?}", table.state.scroll)
);
table.reset_state();
assert_eq!(Some(0), table.state.tui.selected());
assert_eq!(table.default_items, table.items);
assert_eq!(TableSize::Normal, table.state.size);
table.state.size = TableSize::Minimized;
table.state.size.set_minimized(false);
assert_eq!(TableSize::Compact, table.state.size.next());
}
}