use crate::{ColumnFilter, RowSource, SortDirection, SortState};
#[derive(Clone, Debug, Default)]
pub struct HeaderSortState {
pub column: Option<usize>,
pub ascending: bool,
}
impl HeaderSortState {
pub fn new() -> Self {
Self {
column: None,
ascending: true,
}
}
pub fn toggle(&mut self, col: usize) {
match self.column {
Some(c) if c == col => {
if self.ascending {
self.ascending = false;
} else {
self.column = None;
self.ascending = true;
}
}
_ => {
self.column = Some(col);
self.ascending = true;
}
}
}
pub fn indicator(&self, col: usize) -> &'static str {
match self.column {
Some(c) if c == col => {
if self.ascending {
"▲"
} else {
"▼"
}
}
_ => "",
}
}
pub fn as_sort_state(&self) -> Option<SortState> {
self.column.map(|col| {
let dir = if self.ascending {
SortDirection::Ascending
} else {
SortDirection::Descending
};
SortState::new(col, dir)
})
}
}
pub fn move_column(order: &mut Vec<usize>, from: usize, to: usize) {
if from >= order.len() || to >= order.len() {
return;
}
let col = order.remove(from);
order.insert(to, col);
}
#[derive(Clone, Debug)]
pub struct TableIndex {
sort_index: Vec<usize>,
filter_index: Vec<usize>,
sort_dirty: bool,
filter_dirty: bool,
}
impl TableIndex {
pub fn new() -> Self {
Self {
sort_index: Vec::new(),
filter_index: Vec::new(),
sort_dirty: true,
filter_dirty: true,
}
}
pub fn invalidate_sort(&mut self) {
self.sort_dirty = true;
self.filter_dirty = true;
}
pub fn invalidate_filter(&mut self) {
self.filter_dirty = true;
}
pub fn is_sort_dirty(&self) -> bool {
self.sort_dirty
}
pub fn is_filter_dirty(&self) -> bool {
self.filter_dirty
}
pub fn sort_index(&mut self, rows: &dyn RowSource, sort: &HeaderSortState) -> &[usize] {
if self.sort_dirty {
self.sort_index = match sort.as_sort_state() {
Some(st) => crate::sort_indices(rows, st.column, st.direction),
None => (0..rows.row_count()).collect(),
};
self.sort_dirty = false;
}
&self.sort_index
}
pub fn filter_index(
&mut self,
rows: &dyn RowSource,
sort: &HeaderSortState,
filters: &[ColumnFilter],
) -> &[usize] {
self.sort_index(rows, sort);
if self.filter_dirty {
let active: Vec<&ColumnFilter> = filters.iter().filter(|f| !f.is_inactive()).collect();
if active.is_empty() {
self.filter_index = self.sort_index.clone();
} else {
self.filter_index = self
.sort_index
.iter()
.copied()
.filter(|&i| {
let row = rows.row(i);
active.iter().all(|f| f.matches(&row))
})
.collect();
}
self.filter_dirty = false;
}
&self.filter_index
}
}
impl Default for TableIndex {
fn default() -> Self {
Self::new()
}
}
use crate::SelectionModel;
pub fn handle_row_click(
selection: &mut SelectionModel,
row: usize,
ctrl: bool,
shift: bool,
last_clicked: &mut Option<usize>,
) {
if shift {
selection.shift_click(row);
} else if ctrl {
selection.ctrl_click(row);
} else {
selection.click(row);
}
*last_clicked = Some(row);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Cell, ColumnDef, SelectionMode};
#[test]
fn sort_toggle_new_column() {
let mut s = HeaderSortState::new();
s.toggle(0);
assert_eq!(s.column, Some(0));
assert!(s.ascending);
}
#[test]
fn sort_toggle_same_column() {
let mut s = HeaderSortState::new();
s.toggle(0); s.toggle(0); assert_eq!(s.column, Some(0));
assert!(!s.ascending);
s.toggle(0); assert_eq!(s.column, None);
}
#[test]
fn sort_indicator_active() {
let mut s = HeaderSortState::new();
s.toggle(0);
assert_eq!(s.indicator(0), "▲");
s.toggle(0);
assert_eq!(s.indicator(0), "▼");
}
#[test]
fn sort_indicator_inactive() {
let mut s = HeaderSortState::new();
s.toggle(0);
assert_eq!(s.indicator(1), "");
}
#[test]
fn move_column_forward() {
let mut order = vec![0usize, 1, 2];
move_column(&mut order, 0, 2);
assert_eq!(order, vec![1, 2, 0]);
}
#[test]
fn move_column_backward() {
let mut order = vec![0usize, 1, 2];
move_column(&mut order, 2, 0);
assert_eq!(order, vec![2, 0, 1]);
}
#[test]
fn move_column_no_op() {
let mut order = vec![0usize, 1, 2];
move_column(&mut order, 1, 1);
assert_eq!(order, vec![0, 1, 2]);
}
struct SimpleData {
rows: Vec<Vec<Cell>>,
cols: Vec<ColumnDef>,
}
impl RowSource for SimpleData {
fn row_count(&self) -> usize {
self.rows.len()
}
fn row(&self, i: usize) -> Vec<Cell> {
self.rows[i].clone()
}
fn column_defs(&self) -> &[ColumnDef] {
&self.cols
}
}
fn make_data() -> SimpleData {
SimpleData {
rows: vec![vec![Cell::Int(3)], vec![Cell::Int(1)], vec![Cell::Int(2)]],
cols: vec![],
}
}
#[test]
fn sort_index_invalidate() {
let mut idx = TableIndex::new();
let data = make_data();
let sort = HeaderSortState::new();
let _ = idx.sort_index(&data, &sort);
assert!(!idx.is_sort_dirty());
idx.invalidate_sort();
assert!(idx.is_sort_dirty());
}
#[test]
fn filter_index_invalidate() {
let mut idx = TableIndex::new();
let data = make_data();
let sort = HeaderSortState::new();
let _ = idx.filter_index(&data, &sort, &[]);
assert!(!idx.is_filter_dirty());
idx.invalidate_filter();
assert!(idx.is_filter_dirty());
}
#[test]
fn handle_click_selects_row() {
let mut sel = SelectionModel::new(SelectionMode::Multi);
let mut last = None;
handle_row_click(&mut sel, 3, false, false, &mut last);
assert!(sel.is_selected(3));
assert_eq!(last, Some(3));
}
#[test]
fn handle_ctrl_click_toggles() {
let mut sel = SelectionModel::new(SelectionMode::Multi);
let mut last = None;
handle_row_click(&mut sel, 3, true, false, &mut last);
assert!(sel.is_selected(3));
handle_row_click(&mut sel, 3, true, false, &mut last);
assert!(!sel.is_selected(3));
}
#[test]
fn handle_shift_click_range() {
let mut sel = SelectionModel::new(SelectionMode::Multi);
let mut last = None;
handle_row_click(&mut sel, 2, false, false, &mut last);
handle_row_click(&mut sel, 5, false, true, &mut last);
assert_eq!(sel.selected_sorted(), vec![2, 3, 4, 5]);
}
}