Skip to main content

tui_treelistview/
edit.rs

1use smallvec::SmallVec;
2
3use crate::model::TreeModel;
4
5/// An insertion position within a child list.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum TreeInsertPosition<Id> {
8    First,
9    Last,
10    Before(Id),
11    After(Id),
12}
13
14impl<Id: PartialEq> TreeInsertPosition<Id> {
15    /// Resolves this logical position against the destination sibling list.
16    ///
17    /// Returns `None` when a `Before` or `After` anchor is absent.
18    #[must_use]
19    pub fn index_in(&self, siblings: &[Id]) -> Option<usize> {
20        match self {
21            Self::First => Some(0),
22            Self::Last => Some(siblings.len()),
23            Self::Before(anchor) => siblings.iter().position(|sibling| sibling == anchor),
24            Self::After(anchor) => siblings
25                .iter()
26                .position(|sibling| sibling == anchor)
27                .and_then(|index| index.checked_add(1)),
28        }
29    }
30}
31
32/// An atomic model editing command.
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub enum TreeEditCommand<Id> {
35    CreateChild {
36        parent: Id,
37    },
38    Rename {
39        node: Id,
40    },
41    Move {
42        nodes: SmallVec<[Id; 4]>,
43        parent: Id,
44        position: TreeInsertPosition<Id>,
45    },
46    Detach {
47        nodes: SmallVec<[Id; 4]>,
48    },
49    Delete {
50        nodes: SmallVec<[Id; 4]>,
51    },
52}
53
54/// A selection update after a successful edit.
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
56pub enum TreeSelectionUpdate<Id> {
57    #[default]
58    Keep,
59    Select(Id),
60    Clear,
61}
62
63/// The exact model changes needed to reconcile widget state.
64#[derive(Clone, Debug, Default, PartialEq, Eq)]
65pub struct TreeChangeSet<Id> {
66    pub inserted: SmallVec<[Id; 4]>,
67    pub moved: SmallVec<[Id; 4]>,
68    pub removed: SmallVec<[Id; 4]>,
69    pub selection: TreeSelectionUpdate<Id>,
70}
71
72/// Applies typed editing commands to a domain model.
73pub trait TreeEditor: TreeModel {
74    type Error;
75
76    /// Applies a command atomically and returns the actual changes.
77    ///
78    /// # Errors
79    ///
80    /// Returns a model-specific error when the command cannot be applied atomically.
81    fn apply(
82        &mut self,
83        command: TreeEditCommand<Self::Id>,
84    ) -> Result<TreeChangeSet<Self::Id>, Self::Error>;
85}
86
87#[cfg(test)]
88mod tests {
89    use super::TreeInsertPosition;
90
91    #[test]
92    fn insert_positions_resolve_against_siblings() {
93        let siblings = [10, 20, 30];
94        assert_eq!(TreeInsertPosition::First.index_in(&siblings), Some(0));
95        assert_eq!(TreeInsertPosition::Last.index_in(&siblings), Some(3));
96        assert_eq!(TreeInsertPosition::Before(20).index_in(&siblings), Some(1));
97        assert_eq!(TreeInsertPosition::After(20).index_in(&siblings), Some(2));
98        assert_eq!(TreeInsertPosition::Before(40).index_in(&siblings), None);
99    }
100}