use std::sync::Arc;
use crate::signal::{GenericSignal, Signal};
use crate::widget::view_widgets::list_view::ListModel;
use crate::widget::view_widgets::table_widget::TableModel;
use crate::widget::view_widgets::tree_view::TreeModel;
pub const MODEL_INDEX_MAX_DEPTH: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ModelIndex {
pub parent: Option<usize>,
pub row: usize,
pub column: usize,
pub ancestry: [Option<usize>; MODEL_INDEX_MAX_DEPTH],
}
impl ModelIndex {
pub fn root(row: usize, column: usize) -> Self {
Self { parent: None, row, column, ancestry: [None; MODEL_INDEX_MAX_DEPTH] }
}
pub fn child(parent: usize, row: usize, column: usize) -> Self {
Self { parent: Some(parent), row, column, ancestry: [None; MODEL_INDEX_MAX_DEPTH] }
}
pub fn nested(path: &[usize], column: usize) -> Self {
match path.split_last() {
None => Self::root(0, column),
Some((row, rest)) => {
let parent = rest.last().copied();
let above_parent = &rest[..rest.len().saturating_sub(1)];
let start = above_parent.len().saturating_sub(MODEL_INDEX_MAX_DEPTH);
let mut ancestry = [None; MODEL_INDEX_MAX_DEPTH];
for (slot, value) in ancestry.iter_mut().zip(above_parent[start..].iter().copied())
{
*slot = Some(value);
}
Self { parent, row: *row, column, ancestry }
}
}
}
pub fn path(&self) -> Vec<usize> {
let mut path = Vec::with_capacity(1 + self.ancestry.len());
path.extend(self.ancestry.iter().flatten().copied());
if let Some(parent) = self.parent {
path.push(parent);
}
path.push(self.row);
path
}
pub fn depth(&self) -> usize {
self.ancestry.iter().flatten().count() + usize::from(self.parent.is_some())
}
pub fn is_root(&self) -> bool {
self.parent.is_none() && self.ancestry.iter().all(Option::is_none)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SortOrder {
Ascending,
Descending,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CellChange {
pub row: usize,
pub column: usize,
pub role: Option<DataRole>,
}
impl CellChange {
pub fn display(row: usize, column: usize) -> Self {
Self { row, column, role: Some(DataRole::Display) }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RowRange {
pub start: usize,
pub count: usize,
}
impl RowRange {
pub fn new(start: usize, count: usize) -> Self {
Self { start, count }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DataRole {
Display,
AccessibleText,
Edit,
Decoration,
CheckState,
User(u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CellFlags {
pub selectable: bool,
pub editable: bool,
pub enabled: bool,
pub checkable: bool,
}
impl Default for CellFlags {
fn default() -> Self {
Self { selectable: true, editable: false, enabled: true, checkable: false }
}
}
impl CellFlags {
pub const EDITABLE: Self =
Self { selectable: true, editable: true, enabled: true, checkable: false };
}
pub trait IncrementalTableDataSource: Send + Sync {
fn row_count(&self) -> usize;
fn column_count(&self) -> usize;
fn column_name(&self, column: usize) -> Option<String> {
let _ = column;
None
}
fn data(&self, row: usize, column: usize) -> Option<String>;
fn data_role(&self, row: usize, column: usize, role: DataRole) -> Option<String> {
match role {
DataRole::Display => self.data(row, column),
_ => None,
}
}
fn data_at(&self, index: ModelIndex, role: DataRole) -> Option<String> {
if index.is_root() {
self.data_role(index.row, index.column, role)
} else {
None
}
}
fn parent_index(&self, row: usize) -> Option<usize> {
let _ = row;
None
}
fn set_data(&mut self, row: usize, column: usize, value: &str) -> bool {
let _ = (row, column, value);
false
}
fn flags(&self, row: usize, column: usize) -> CellFlags {
let _ = (row, column);
CellFlags::default()
}
fn mime_data(&self, row: usize, column: usize) -> Option<String> {
let _ = (row, column);
None
}
fn cell_changed_signal(&self) -> Option<&GenericSignal> {
None
}
fn cell_changed_detail_signal(&self) -> Option<&Signal<CellChange>> {
None
}
fn rows_inserted_signal(&self) -> Option<&GenericSignal> {
None
}
fn rows_inserted_detail_signal(&self) -> Option<&Signal<RowRange>> {
None
}
fn rows_removed_signal(&self) -> Option<&GenericSignal> {
None
}
fn rows_removed_detail_signal(&self) -> Option<&Signal<RowRange>> {
None
}
fn rows_reset_signal(&self) -> Option<&GenericSignal> {
None
}
fn rows_reset_detail_signal(&self) -> Option<&Signal<()>> {
None
}
fn insert_row(&mut self, row: usize) -> bool {
let _ = row;
false
}
fn remove_row(&mut self, row: usize) -> bool {
let _ = row;
false
}
fn move_row(&mut self, from: usize, to: usize) -> bool {
let _ = (from, to);
false
}
fn can_fetch_more(&self) -> bool {
false
}
fn can_fetch_more_path(&self, path: &[usize]) -> bool {
let _ = path;
self.can_fetch_more()
}
fn fetch_more(&mut self, path: &[usize]) {
let _ = path;
}
fn can_insert_rows(&self) -> bool {
false
}
fn can_remove_rows(&self) -> bool {
false
}
fn can_move_rows(&self) -> bool {
false
}
fn can_sort(&self) -> bool {
false
}
fn sort(&mut self, column: usize, order: SortOrder) -> bool {
let _ = (column, order);
false
}
fn sort_indicator(&self) -> Option<(usize, SortOrder)> {
None
}
fn fill_cell(&self, row: usize, column: usize, out: &mut String) -> bool {
match self.data(row, column) {
Some(value) => {
out.push_str(&value);
true
}
None => false,
}
}
fn revision(&self) -> u64 {
0
}
fn data_changed_signal(&self) -> Option<&GenericSignal> {
None
}
fn fetch_window(
&self,
row_start: usize,
row_len: usize,
column_start: usize,
column_len: usize,
) -> Vec<Vec<Option<String>>> {
let total_rows = self.row_count();
let total_columns = self.column_count();
if row_start >= total_rows || column_start >= total_columns {
return Vec::new();
}
let row_end = row_start.saturating_add(row_len).min(total_rows);
let col_end = column_start.saturating_add(column_len).min(total_columns);
let mut window = Vec::with_capacity(row_end.saturating_sub(row_start));
for row in row_start..row_end {
let mut line = Vec::with_capacity(col_end.saturating_sub(column_start));
for column in column_start..col_end {
line.push(self.data(row, column));
}
window.push(line);
}
window
}
}
pub struct ListModelDataSource {
model: Arc<dyn ListModel>,
}
impl ListModelDataSource {
pub fn new(model: Arc<dyn ListModel>) -> Self {
Self { model }
}
pub fn model_ref(&self) -> &Arc<dyn ListModel> {
&self.model
}
}
impl IncrementalTableDataSource for ListModelDataSource {
fn row_count(&self) -> usize {
self.model.row_count()
}
fn column_count(&self) -> usize {
1
}
fn data(&self, row: usize, column: usize) -> Option<String> {
if column == 0 {
self.model.data(row)
} else {
None
}
}
fn fill_cell(&self, row: usize, column: usize, out: &mut String) -> bool {
if column == 0 {
self.model.fill_data(row, out)
} else {
false
}
}
fn data_changed_signal(&self) -> Option<&GenericSignal> {
self.model.data_changed_signal()
}
}
pub struct TreeModelDataSource {
model: Arc<dyn TreeModel>,
}
impl TreeModelDataSource {
pub fn new(model: Arc<dyn TreeModel>) -> Self {
Self { model }
}
pub fn model_ref(&self) -> &Arc<dyn TreeModel> {
&self.model
}
}
impl IncrementalTableDataSource for TreeModelDataSource {
fn row_count(&self) -> usize {
self.model.node_count()
}
fn column_count(&self) -> usize {
1
}
fn data(&self, row: usize, column: usize) -> Option<String> {
if column == 0 {
self.model.node_path(row)
} else {
None
}
}
fn fill_cell(&self, row: usize, column: usize, out: &mut String) -> bool {
if column == 0 {
self.model.fill_node_path(row, out)
} else {
false
}
}
fn data_changed_signal(&self) -> Option<&GenericSignal> {
self.model.data_changed_signal()
}
}
pub struct TableModelDataSource {
model: Arc<dyn TableModel>,
}
impl TableModelDataSource {
pub fn new(model: Arc<dyn TableModel>) -> Self {
Self { model }
}
pub fn model_ref(&self) -> &Arc<dyn TableModel> {
&self.model
}
}
impl IncrementalTableDataSource for TableModelDataSource {
fn row_count(&self) -> usize {
self.model.row_count()
}
fn column_count(&self) -> usize {
self.model.column_count()
}
fn data(&self, row: usize, column: usize) -> Option<String> {
self.model.data(row, column)
}
fn fill_cell(&self, row: usize, column: usize, out: &mut String) -> bool {
self.model.fill_data(row, column, out)
}
fn data_changed_signal(&self) -> Option<&GenericSignal> {
self.model.data_changed_signal()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct StaticList;
impl ListModel for StaticList {
fn row_count(&self) -> usize {
3
}
fn data(&self, row: usize) -> Option<String> {
match row {
0 => Some("A".to_string()),
1 => Some("B".to_string()),
2 => Some("C".to_string()),
_ => None,
}
}
}
struct StaticTree;
impl TreeModel for StaticTree {
fn node_count(&self) -> usize {
2
}
fn node_path(&self, index: usize) -> Option<String> {
match index {
0 => Some("root".to_string()),
1 => Some("root/child".to_string()),
_ => None,
}
}
}
struct StaticTable;
impl TableModel for StaticTable {
fn row_count(&self) -> usize {
2
}
fn column_count(&self) -> usize {
3
}
fn data(&self, row: usize, column: usize) -> Option<String> {
Some(format!("{}:{}", row, column))
}
}
#[test]
fn list_adapter_projects_single_column_window() {
let source = ListModelDataSource::new(Arc::new(StaticList));
assert_eq!(source.row_count(), 3);
assert_eq!(source.column_count(), 1);
assert_eq!(source.data(1, 0), Some("B".to_string()));
assert_eq!(source.data(1, 1), None);
let window = source.fetch_window(1, 4, 0, 1);
assert_eq!(window.len(), 2);
assert_eq!(window[0][0], Some("B".to_string()));
assert_eq!(window[1][0], Some("C".to_string()));
}
#[test]
fn fill_cell_agrees_with_data() {
let source = ListModelDataSource::new(Arc::new(StaticList));
let mut out = String::new();
assert!(source.fill_cell(1, 0, &mut out));
assert_eq!(out, "B");
out.clear();
assert!(!source.fill_cell(9, 0, &mut out));
assert!(out.is_empty(), "an absent cell must not append anything");
out.clear();
assert!(!source.fill_cell(0, 1, &mut out));
assert!(out.is_empty(), "a column the adapter does not project is absent");
let table = TableModelDataSource::new(Arc::new(StaticTable));
out.clear();
assert!(table.fill_cell(1, 2, &mut out));
assert_eq!(out, "1:2");
}
#[test]
fn tree_adapter_projects_single_column_window() {
let source = TreeModelDataSource::new(Arc::new(StaticTree));
assert_eq!(source.row_count(), 2);
assert_eq!(source.column_count(), 1);
assert_eq!(source.data(0, 0), Some("root".to_string()));
let window = source.fetch_window(0, 2, 0, 1);
assert_eq!(window.len(), 2);
assert_eq!(window[1][0], Some("root/child".to_string()));
}
#[test]
fn table_adapter_fetches_bounded_window() {
let source = TableModelDataSource::new(Arc::new(StaticTable));
assert_eq!(source.row_count(), 2);
assert_eq!(source.column_count(), 3);
assert_eq!(source.data(1, 2), Some("1:2".to_string()));
let window = source.fetch_window(0, 2, 1, 5);
assert_eq!(window.len(), 2);
assert_eq!(window[0].len(), 2);
assert_eq!(window[0][0], Some("0:1".to_string()));
assert_eq!(window[1][1], Some("1:2".to_string()));
let empty = source.fetch_window(10, 2, 0, 1);
assert!(empty.is_empty());
}
#[test]
fn default_revision_is_zero() {
struct NoRevision;
impl IncrementalTableDataSource for NoRevision {
fn row_count(&self) -> usize {
5
}
fn column_count(&self) -> usize {
3
}
fn data(&self, row: usize, column: usize) -> Option<String> {
Some(format!("{}:{}", row, column))
}
}
let source = NoRevision;
assert_eq!(source.revision(), 0);
assert!(source.data_changed_signal().is_none());
}
#[test]
fn data_changed_signal_default_is_none() {
struct NoSignalSource;
impl IncrementalTableDataSource for NoSignalSource {
fn row_count(&self) -> usize {
3
}
fn column_count(&self) -> usize {
1
}
fn data(&self, row: usize, _: usize) -> Option<String> {
Some(format!("row-{}", row))
}
}
let source = NoSignalSource;
assert!(source.data_changed_signal().is_none());
}
#[test]
fn fetch_window_bounds_min_window() {
struct SmallSource;
impl IncrementalTableDataSource for SmallSource {
fn row_count(&self) -> usize {
1
}
fn column_count(&self) -> usize {
1
}
fn data(&self, row: usize, column: usize) -> Option<String> {
Some(format!("{}:{}", row, column))
}
}
let source = SmallSource;
let window = source.fetch_window(0, 1, 0, 1);
assert_eq!(window.len(), 1);
assert_eq!(window[0][0], Some("0:0".to_string()));
let window2 = source.fetch_window(0, 10, 0, 10);
assert_eq!(window2.len(), 1);
assert_eq!(window2[0].len(), 1);
}
#[test]
fn fetch_window_column_offset_window() {
struct WideSource;
impl IncrementalTableDataSource for WideSource {
fn row_count(&self) -> usize {
2
}
fn column_count(&self) -> usize {
5
}
fn data(&self, row: usize, column: usize) -> Option<String> {
Some(format!("{}:{}", row, column))
}
}
let source = WideSource;
let window = source.fetch_window(0, 2, 2, 2);
assert_eq!(window.len(), 2);
assert_eq!(window[0].len(), 2);
assert_eq!(window[0][0], Some("0:2".to_string()));
assert_eq!(window[1][1], Some("1:3".to_string()));
let empty = source.fetch_window(0, 2, 10, 2);
assert!(empty.is_empty());
}
#[test]
fn list_model_adapter_model_ref() {
use crate::widget::view_widgets::list_view::ListModel;
struct MyList;
impl ListModel for MyList {
fn row_count(&self) -> usize {
2
}
fn data(&self, row: usize) -> Option<String> {
Some(format!("item-{}", row))
}
}
let inner = Arc::new(MyList);
let adapter = ListModelDataSource::new(inner.clone());
let recovered = adapter.model_ref();
assert_eq!(recovered.row_count(), 2);
assert_eq!(recovered.data(0), Some("item-0".to_string()));
}
#[test]
fn table_model_adapter_model_ref() {
use crate::widget::view_widgets::table_widget::TableModel;
struct MyTable;
impl TableModel for MyTable {
fn row_count(&self) -> usize {
3
}
fn column_count(&self) -> usize {
2
}
fn data(&self, row: usize, column: usize) -> Option<String> {
Some(format!("{}-{}", row, column))
}
}
let inner = Arc::new(MyTable);
let adapter = TableModelDataSource::new(inner.clone());
let recovered = adapter.model_ref();
assert_eq!(recovered.row_count(), 3);
assert_eq!(recovered.data(1, 1), Some("1-1".to_string()));
}
#[test]
fn tree_model_adapter_model_ref() {
use crate::widget::view_widgets::tree_view::TreeModel;
struct MyTree;
impl TreeModel for MyTree {
fn node_count(&self) -> usize {
2
}
fn node_path(&self, index: usize) -> Option<String> {
Some(format!("node-{}", index))
}
}
let inner = Arc::new(MyTree);
let adapter = TreeModelDataSource::new(inner.clone());
let recovered = adapter.model_ref();
assert_eq!(recovered.node_count(), 2);
assert_eq!(recovered.node_path(1), Some("node-1".to_string()));
}
#[test]
fn roles_and_model_indices_are_addressable() {
use super::{CellFlags, DataRole, ModelIndex};
struct Editable {
cells: Vec<Vec<String>>,
}
impl IncrementalTableDataSource for Editable {
fn row_count(&self) -> usize {
self.cells.len()
}
fn column_count(&self) -> usize {
self.cells.first().map(Vec::len).unwrap_or(0)
}
fn data(&self, row: usize, column: usize) -> Option<String> {
self.cells.get(row).and_then(|line| line.get(column)).cloned()
}
fn flags(&self, _row: usize, _column: usize) -> CellFlags {
CellFlags::EDITABLE
}
fn set_data(&mut self, row: usize, column: usize, value: &str) -> bool {
match self.cells.get_mut(row).and_then(|line| line.get_mut(column)) {
Some(cell) => {
*cell = value.to_string();
true
}
None => false,
}
}
}
let mut source = Editable { cells: vec![vec!["a".into(), "b".into()]] };
assert_eq!(source.data_at(ModelIndex::root(0, 1), DataRole::Display), Some("b".into()));
assert_eq!(source.data_at(ModelIndex::child(0, 0, 0), DataRole::Display), None);
assert_eq!(source.data_role(0, 0, DataRole::AccessibleText), None);
assert!(source.flags(0, 0).editable);
assert!(source.set_data(0, 0, "changed"));
assert_eq!(source.data(0, 0), Some("changed".into()));
assert!(!source.set_data(9, 9, "nope"));
assert!(source.parent_index(0).is_none());
assert!(source.mime_data(0, 0).is_none());
assert!(source.cell_changed_signal().is_none());
assert!(source.cell_changed_detail_signal().is_none());
assert!(!source.can_fetch_more());
assert!(!source.can_sort());
assert!(!source.insert_row(0));
assert!(!source.remove_row(0));
assert!(!source.move_row(0, 1));
}
#[test]
fn a_model_index_carries_the_whole_ancestor_path() {
use super::ModelIndex;
let deep = ModelIndex::nested(&[9, 4, 2, 7], 3);
assert_eq!(deep.path(), vec![9, 4, 2, 7]);
assert_eq!(deep.parent, Some(2));
assert_eq!(deep.row, 7);
assert_eq!(deep.column, 3);
assert_eq!(deep.depth(), 3);
assert!(!deep.is_root());
assert_eq!(deep.ancestry[0], Some(9));
assert_eq!(deep.ancestry[1], Some(4));
assert_eq!(deep.ancestry[2], None);
let root = ModelIndex::root(5, 1);
assert_eq!(root.path(), vec![5]);
assert!(root.is_root());
assert_eq!(root.depth(), 0);
let long: Vec<usize> = (0..20).collect();
let capped = ModelIndex::nested(&long, 0);
assert_eq!(capped.row, 19);
assert_eq!(capped.parent, Some(18));
assert_eq!(capped.path().last().copied(), Some(19));
assert_eq!(capped.depth(), super::MODEL_INDEX_MAX_DEPTH + 1);
}
struct FlatSource {
can_fetch: bool,
}
impl IncrementalTableDataSource for FlatSource {
fn row_count(&self) -> usize {
1
}
fn column_count(&self) -> usize {
1
}
fn data(&self, row: usize, column: usize) -> Option<String> {
(row == 0 && column == 0).then(|| "cell".to_string())
}
fn can_fetch_more(&self) -> bool {
self.can_fetch
}
}
#[test]
fn path_fetch_defaults_follow_the_source_wide_flag() {
let finite = FlatSource { can_fetch: false };
assert!(!finite.can_fetch_more_path(&[0]));
let mut streaming = FlatSource { can_fetch: true };
assert!(streaming.can_fetch_more_path(&[0]));
streaming.fetch_more(&[0]);
assert_eq!(streaming.row_count(), 1);
}
}