use super::structures::{ListSong, ListSongDisplayableField, Percentage};
use crate::widgets::{ScrollingListState, ScrollingTableState};
use rat_text::text_input::TextInputState;
use ratatui::Frame;
use ratatui::prelude::{Constraint, Rect};
use ratatui::widgets::ListState;
use std::borrow::Cow;
pub mod draw;
#[derive(Clone, Debug)]
pub struct TableSortCommand {
pub column: usize,
pub direction: SortDirection,
}
#[derive(Default, Clone, Copy, Debug, PartialEq)]
pub enum SortDirection {
#[default]
Asc,
Desc,
}
#[derive(Clone, Debug)]
pub enum TableFilterCommand {
All(Filter),
Column { filter: Filter, column: usize },
}
#[derive(Clone, Debug)]
pub enum Filter {
Contains(FilterString),
NotContains(FilterString),
Equal(FilterString),
}
#[derive(Clone, Debug)]
pub enum FilterString {
CaseSensitive(String),
CaseInsensitive(String),
}
impl TableFilterCommand {
fn as_readable(&self) -> String {
match self {
TableFilterCommand::All(f) => format!("ALL{}", f.as_readable()),
TableFilterCommand::Column { filter, column } => {
format!("COL{}{}", column, filter.as_readable())
}
}
}
pub fn matches_row<const N: usize>(
&self,
row: &ListSong,
fields_in_table: [ListSongDisplayableField; N],
filterable_colums: &[usize],
) -> bool {
let fields = row.get_fields(fields_in_table);
match self {
TableFilterCommand::All(filter) => match filter {
Filter::Contains(filter_string) => filterable_colums
.iter()
.any(|col| filter_string.is_in(fields[*col].as_ref())),
Filter::NotContains(filter_string) => filterable_colums
.iter()
.all(|col| !filter_string.is_in(fields[*col].as_ref())),
Filter::Equal(filter_string) => filterable_colums
.iter()
.any(|col| filter_string.is_equal(fields[*col].as_ref())),
},
TableFilterCommand::Column { filter, column } => match filter {
Filter::Contains(filter_string) => filter_string.is_in(fields[*column].as_ref()),
Filter::NotContains(filter_string) => {
!filter_string.is_in(fields[*column].as_ref())
}
Filter::Equal(filter_string) => filter_string.is_equal(fields[*column].as_ref()),
},
}
}
}
impl Filter {
fn as_readable(&self) -> String {
match self {
Filter::Contains(f) => format!("~{}", f.as_readable()),
Filter::NotContains(f) => format!("!={}", f.as_readable()),
Filter::Equal(f) => format!("={}", f.as_readable()),
}
}
}
impl FilterString {
fn as_readable(&self) -> String {
match self {
FilterString::CaseSensitive(s) => format!("a=a:{s}"),
FilterString::CaseInsensitive(s) => format!("a=A:{s}"),
}
}
pub fn is_in<S: AsRef<str>>(&self, test_str: S) -> bool {
match self {
FilterString::CaseSensitive(s) => test_str.as_ref().contains(s),
FilterString::CaseInsensitive(s) => test_str
.as_ref()
.to_ascii_lowercase()
.contains(s.to_ascii_lowercase().as_str()),
}
}
pub fn is_equal<S: AsRef<str>>(&self, test_str: S) -> bool {
match self {
FilterString::CaseSensitive(s) => test_str.as_ref() == s,
FilterString::CaseInsensitive(s) => {
test_str.as_ref().to_ascii_lowercase() == s.to_ascii_uppercase()
}
}
}
}
pub enum BasicConstraint {
Length(u16),
Percentage(Percentage),
}
pub fn basic_constraints_to_table_constraints(
basic_constraints: &[BasicConstraint],
length: u16,
margin: u16,
) -> Vec<Constraint> {
let sum_lengths = basic_constraints.iter().fold(0, |acc, c| {
acc + match c {
BasicConstraint::Length(l) => *l,
BasicConstraint::Percentage(_) => 0,
} + margin
});
basic_constraints
.iter()
.map(|bc| match bc {
BasicConstraint::Length(l) => Constraint::Length(*l),
BasicConstraint::Percentage(p) => {
Constraint::Length(p.0 as u16 * length.saturating_sub(sum_lengths) / 100)
}
})
.collect()
}
pub trait TableView {
fn get_state(&self) -> &ScrollingTableState;
fn get_mut_state(&mut self) -> &mut ScrollingTableState;
fn get_selected_item(&self) -> usize;
fn get_layout(&self) -> &[BasicConstraint];
fn get_highlighted_row(&self) -> Option<usize>;
fn get_items(&self) -> impl ExactSizeIterator<Item = impl Iterator<Item = Cow<'_, str>> + '_>;
fn get_headings(&self) -> impl Iterator<Item = &'static str>;
}
pub trait AdvancedTableView: TableView {
fn get_mut_filter_state(&mut self) -> &mut TextInputState;
fn filter_popup_shown(&self) -> bool;
fn get_filterable_columns(&self) -> &[usize];
fn get_filtered_items(&self) -> impl Iterator<Item = impl Iterator<Item = Cow<'_, str>> + '_>;
fn get_filter_commands(&self) -> &[TableFilterCommand];
fn push_filter_command(&mut self, filter_command: TableFilterCommand);
fn clear_filter_commands(&mut self);
fn get_sort_popup_cur(&self) -> usize;
fn sort_popup_shown(&self) -> bool;
fn get_sort_state(&self) -> &ListState;
fn get_mut_sort_state(&mut self) -> &mut ListState;
fn push_sort_command(&mut self, sort_command: TableSortCommand) -> anyhow::Result<()>;
fn clear_sort_commands(&mut self);
fn get_sortable_columns(&self) -> &[usize];
fn get_sort_commands(&self) -> &[TableSortCommand];
}
pub trait ListView {
fn get_selected_item(&self) -> usize;
fn get_state(&self) -> &ScrollingListState;
fn get_mut_state(&mut self) -> &mut ScrollingListState;
fn get_items(&self) -> impl ExactSizeIterator<Item = Cow<'_, str>> + '_;
fn len(&self) -> usize {
self.get_items().len()
}
}
pub trait Drawable {
fn draw_chunk(&self, f: &mut Frame, chunk: Rect, selected: bool);
}
pub trait DrawableMut {
fn draw_mut_chunk(&mut self, f: &mut Frame, chunk: Rect, selected: bool, cur_tick: u64);
}
pub trait Loadable {
fn is_loading(&self) -> bool;
}
pub trait HasTitle {
fn get_title(&self) -> Cow<'_, str>;
}
pub trait HasTabs {
fn tabs_block_title(&'_ self) -> Cow<'_, str>;
fn tab_items(&'_ self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> + '_;
fn selected_tab_idx(&self) -> usize;
}
#[cfg(test)]
mod tests {
use super::{BasicConstraint, basic_constraints_to_table_constraints};
use crate::app::structures::Percentage;
use ratatui::prelude::Constraint;
#[test]
fn test_constraints() {
let basic_constraints = &[
BasicConstraint::Length(5),
BasicConstraint::Length(5),
BasicConstraint::Percentage(Percentage(100)),
];
let constraints = vec![
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(10),
];
let converted = basic_constraints_to_table_constraints(basic_constraints, 20, 0);
assert_eq!(converted, constraints);
let basic_constraints = &[
BasicConstraint::Length(5),
BasicConstraint::Length(5),
BasicConstraint::Percentage(Percentage(50)),
BasicConstraint::Percentage(Percentage(50)),
];
let constraints = vec![
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(5),
Constraint::Length(5),
];
let converted = basic_constraints_to_table_constraints(basic_constraints, 20, 0);
assert_eq!(converted, constraints);
}
}