use std::cmp::Ordering;
use std::sync::Arc;
use ratatui::layout::Constraint;
pub trait TableRow: Clone {
fn cells(&self) -> Vec<String>;
}
pub type SortComparator = Arc<dyn Fn(&str, &str) -> Ordering + Send + Sync>;
#[derive(Clone)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Column {
header: String,
#[cfg_attr(feature = "serialization", serde(skip))]
width: Constraint,
sortable: bool,
editable: bool,
visible: bool,
#[cfg_attr(feature = "serialization", serde(skip))]
comparator: Option<SortComparator>,
}
impl std::fmt::Debug for Column {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Column")
.field("header", &self.header)
.field("width", &self.width)
.field("sortable", &self.sortable)
.field("editable", &self.editable)
.field("visible", &self.visible)
.field("comparator", &self.comparator.as_ref().map(|_| ".."))
.finish()
}
}
impl PartialEq for Column {
fn eq(&self, other: &Self) -> bool {
self.header == other.header
&& self.width == other.width
&& self.sortable == other.sortable
&& self.editable == other.editable
&& self.visible == other.visible
}
}
impl Column {
pub fn new(header: impl Into<String>, width: Constraint) -> Self {
Self {
header: header.into(),
width,
sortable: false,
editable: true,
visible: true,
comparator: None,
}
}
pub fn sortable(mut self) -> Self {
self.sortable = true;
self
}
pub fn header(&self) -> &str {
&self.header
}
pub fn width(&self) -> Constraint {
self.width
}
pub fn fixed(header: impl Into<String>, width: u16) -> Self {
Self::new(header, Constraint::Length(width))
}
pub fn min(header: impl Into<String>, width: u16) -> Self {
Self::new(header, Constraint::Min(width))
}
pub fn percent(header: impl Into<String>, percent: u16) -> Self {
Self::new(header, Constraint::Percentage(percent))
}
pub fn is_sortable(&self) -> bool {
self.sortable
}
pub fn with_editable(mut self, editable: bool) -> Self {
self.editable = editable;
self
}
pub fn is_editable(&self) -> bool {
self.editable
}
pub fn with_visible(mut self, visible: bool) -> Self {
self.visible = visible;
self
}
pub fn is_visible(&self) -> bool {
self.visible
}
pub fn set_visible(&mut self, visible: bool) {
self.visible = visible;
}
pub fn set_editable(&mut self, editable: bool) {
self.editable = editable;
}
pub fn with_comparator(mut self, comparator: SortComparator) -> Self {
self.comparator = Some(comparator);
self.sortable = true;
self
}
pub fn comparator(&self) -> Option<&SortComparator> {
self.comparator.as_ref()
}
pub fn set_width(&mut self, width: Constraint) {
self.width = width;
}
}
pub fn numeric_comparator() -> SortComparator {
Arc::new(|a: &str, b: &str| {
let pa = a.parse::<f64>();
let pb = b.parse::<f64>();
match (pa, pb) {
(Ok(va), Ok(vb)) => va.partial_cmp(&vb).unwrap_or(Ordering::Equal),
(Ok(_), Err(_)) => Ordering::Less,
(Err(_), Ok(_)) => Ordering::Greater,
(Err(_), Err(_)) => a.cmp(b),
}
})
}
pub fn date_comparator() -> SortComparator {
Arc::new(|a: &str, b: &str| {
let pa = parse_date(a);
let pb = parse_date(b);
match (pa, pb) {
(Some(va), Some(vb)) => va.cmp(&vb),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => a.cmp(b),
}
})
}
fn parse_date(s: &str) -> Option<(i32, u32, u32)> {
let parts: Vec<&str> = s.split('-').collect();
if parts.len() != 3 {
return None;
}
let year = parts[0].parse::<i32>().ok()?;
let month = parts[1].parse::<u32>().ok()?;
let day = parts[2].parse::<u32>().ok()?;
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
return None;
}
Some((year, month, day))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum SortDirection {
#[default]
Ascending,
Descending,
}
impl SortDirection {
pub fn toggle(self) -> Self {
match self {
SortDirection::Ascending => SortDirection::Descending,
SortDirection::Descending => SortDirection::Ascending,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TableMessage {
Up,
Down,
First,
Last,
PageUp(usize),
PageDown(usize),
Select,
SortBy(usize),
AddSort(usize),
ClearSort,
IncreaseColumnWidth(usize),
DecreaseColumnWidth(usize),
SetFilter(String),
ClearFilter,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TableOutput<T: Clone> {
Selected(T),
SelectionChanged(usize),
Sorted {
column: usize,
direction: SortDirection,
},
SortCleared,
FilterChanged(String),
ColumnResized {
column: usize,
width: u16,
},
}