use itertools::Itertools;
use std::collections::{HashMap, HashSet};
use std::{
collections::{BTreeMap, BTreeSet, VecDeque},
mem::{replace, take},
};
use tap::prelude::{Pipe, Tap};
use crate::{
DataTable, RowViewer, UiAction, default,
draw::tsv,
viewer::{
CellWriteContext, DecodeErrorBehavior, EmptyRowCreateContext, MoveDirection, RowCodec,
UiActionContext, UiCursorState,
},
};
mod action;
mod clipboard;
mod command;
mod model_ops;
mod selection;
mod types;
mod validation;
#[cfg(test)]
mod tests;
pub(crate) use command::Command;
pub(crate) use model_ops::DataModelOps;
pub(crate) use types::*;
pub(crate) struct UiState<R> {
viewer_type: std::any::TypeId,
viewer_filter_hash: u64,
undo_queue: VecDeque<UndoArg<R>>,
undo_cursor: usize,
clipboard: Option<Clipboard<R>>,
p: PersistData,
#[cfg(feature = "persistency")]
is_p_loaded: bool,
pub cc_rows: Vec<RowIdx>,
pub cc_row_heights: Vec<f32>,
cc_row_id_to_vis: HashMap<RowIdx, VisRowPos>,
cc_dirty: bool,
cc_cursor: CursorState<R>,
cc_num_frame_from_last_edit: usize,
cc_prev_n_columns: usize,
cc_interactive_cell: VisLinearIdx,
cc_desired_selection: Option<Vec<(RowIdx, Vec<ColumnIdx>)>>,
cci_selection: Option<(VisLinearIdx, VisLinearIdx)>,
pub cci_has_focus: bool,
pub cci_want_move_scroll: bool,
pub cci_page_row_count: usize,
}
#[cfg_attr(feature = "persistency", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Default)]
struct PersistData {
num_columns: usize,
vis_cols: Vec<ColumnIdx>,
sort: Vec<(ColumnIdx, IsAscending)>,
}
struct Clipboard<R> {
slab: Box<[R]>,
pastes: Box<[(VisRowOffset, ColumnIdx, RowSlabIndex)]>,
}
impl<R> Clipboard<R> {
pub fn sort(&mut self) {
self.pastes
.sort_by(|(a_row, a_col, ..), (b_row, b_col, ..)| {
a_row.0.cmp(&b_row.0).then(a_col.0.cmp(&b_col.0))
})
}
}
struct UndoArg<R> {
apply: Command<R>,
restore: Vec<Command<R>>,
}
impl<R> Default for UiState<R> {
fn default() -> Self {
Self {
viewer_filter_hash: 0,
clipboard: None,
viewer_type: std::any::TypeId::of::<()>(),
cc_cursor: CursorState::Select(default()),
undo_queue: VecDeque::new(),
cc_rows: Vec::new(),
cc_row_heights: Vec::new(),
cc_dirty: false,
undo_cursor: 0,
cci_selection: None,
cci_has_focus: false,
cc_interactive_cell: VisLinearIdx(0),
cc_row_id_to_vis: default(),
cc_num_frame_from_last_edit: 0,
cc_prev_n_columns: 0,
cc_desired_selection: None,
cci_want_move_scroll: false,
cci_page_row_count: 0,
p: default(),
#[cfg(feature = "persistency")]
is_p_loaded: false,
}
}
}
enum CursorState<R> {
Select(Vec<VisSelection>),
Edit {
next_focus: bool,
last_focus: VisColumnPos,
row: RowIdx,
edition: R,
},
}
impl<R> UiState<R> {
pub fn cc_is_dirty(&self) -> bool {
self.cc_dirty
}
pub fn vis_cols(&self) -> &Vec<ColumnIdx> {
&self.p.vis_cols
}
pub fn force_mark_dirty(&mut self) {
self.cc_dirty = true;
}
pub fn row_editing_cell(&mut self, row_id: RowIdx) -> Option<(bool, VisColumnPos)> {
match &mut self.cc_cursor {
CursorState::Edit {
row,
last_focus,
next_focus,
..
} if *row == row_id => Some((replace(next_focus, false), *last_focus)),
_ => None,
}
}
pub fn num_columns(&self) -> usize {
self.p.num_columns
}
pub fn sort(&self) -> &[(ColumnIdx, IsAscending)] {
&self.p.sort
}
pub fn unwrap_editing_row_data(&mut self) -> &mut R {
match &mut self.cc_cursor {
CursorState::Edit { edition, .. } => edition,
_ => unreachable!(),
}
}
pub fn is_editing(&self) -> bool {
matches!(self.cc_cursor, CursorState::Edit { .. })
}
pub fn is_interactive_row(&self, row: VisRowPos) -> Option<VisColumnPos> {
let (r, c) = self.cc_interactive_cell.row_col(self.p.vis_cols.len());
(r == row).then_some(c)
}
pub fn interactive_cell(&self) -> (VisRowPos, VisColumnPos) {
self.cc_interactive_cell.row_col(self.p.vis_cols.len())
}
pub fn has_clipboard_contents(&self) -> bool {
self.clipboard.is_some()
}
pub fn cursor_as_selection(&self) -> Option<&[VisSelection]> {
match &self.cc_cursor {
CursorState::Select(x) => Some(x),
CursorState::Edit { .. } => None,
}
}
fn try_take_edition(&mut self) -> Option<(RowIdx, R, VisColumnPos)> {
if matches!(self.cc_cursor, CursorState::Edit { .. }) {
match replace(&mut self.cc_cursor, CursorState::Select(Vec::default())) {
CursorState::Edit {
row,
edition,
last_focus,
..
} => Some((row, edition, last_focus)),
_ => unreachable!(),
}
} else {
None
}
}
pub fn ui_action_context(&self) -> UiActionContext {
UiActionContext {
cursor: match &self.cc_cursor {
CursorState::Select(x) => {
if x.is_empty() {
UiCursorState::Idle
} else if x.len() == 1 && x[0].0 == x[0].1 {
UiCursorState::SelectOne
} else {
UiCursorState::SelectMany
}
}
CursorState::Edit { .. } => UiCursorState::Editing,
},
}
}
pub fn set_interactive_cell(&mut self, row: VisRowPos, col: VisColumnPos) {
self.cc_interactive_cell = row.linear_index(self.p.vis_cols.len(), col);
}
fn queue_select_rows(&mut self, rows: impl IntoIterator<Item = RowIdx>) {
self.cc_desired_selection = Some(rows.into_iter().map(|r| (r, default())).collect());
}
fn validate_interactive_cell(&mut self, new_num_column: usize) {
let (r, c) = self.cc_interactive_cell.row_col(self.p.vis_cols.len());
let rmax = self.cc_rows.len().saturating_sub(1);
let clen = self.p.vis_cols.len();
self.cc_interactive_cell =
VisLinearIdx(r.0.min(rmax) * clen + c.0.min(new_num_column.saturating_sub(1)));
}
}