freya_components/
tree.rs

1use std::path::PathBuf;
2
3/// Indicates the state of the item.
4#[derive(Debug, Clone, PartialEq)]
5pub enum ExpandableItemState<I, V> {
6    Open(Vec<TreeItem<I, V>>),
7    Closed,
8}
9
10/// Abstract the path matching.
11pub trait ItemPath: PartialEq {
12    fn item_starts_with(&self, other: &Self) -> bool;
13}
14
15// Implement it for PathBuf to make it easier for end users
16impl ItemPath for PathBuf {
17    fn item_starts_with(&self, other: &Self) -> bool {
18        self.starts_with(other)
19    }
20}
21
22/// Item part of a larget Tree.
23///
24/// `Expandable` can be expanded/open, e.g folders
25/// `Standalone` cannot be expanded/opened, e.g files
26#[derive(Debug, Clone, PartialEq)]
27pub enum TreeItem<I, V> {
28    Expandable {
29        id: I,
30        value: V,
31        state: ExpandableItemState<I, V>,
32    },
33    Standalone {
34        id: I,
35        value: V,
36    },
37}
38
39impl<I, V> TreeItem<I, V>
40where
41    I: ItemPath + Clone,
42    V: Clone + PartialEq,
43{
44    /// Get the ID of the item, e.g its path.
45    pub fn id(&self) -> &I {
46        match self {
47            Self::Expandable { id, .. } => id,
48            Self::Standalone { id, .. } => id,
49        }
50    }
51
52    /// Update the state of the given Expandable Item, e.g to open with more items or to simply close.
53    pub fn set_state(&mut self, item_id: &I, item_state: &ExpandableItemState<I, V>) {
54        if let TreeItem::Expandable { id, state, .. } = self {
55            if id == item_id {
56                *state = item_state.clone();
57            } else if item_id.item_starts_with(id) {
58                if let ExpandableItemState::Open(items) = state {
59                    for item in items {
60                        item.set_state(item_id, item_state);
61                    }
62                }
63            }
64        }
65    }
66
67    /// Turn all the inner items and this item itself into a flat list.
68    /// This can be useful for virtualization.
69    pub fn flat(&self, depth: usize, root_id: &I) -> Vec<FlatItem<I>> {
70        let mut flat_items = vec![self.clone().into_flat(depth, root_id.clone())];
71        if let TreeItem::Expandable {
72            state: ExpandableItemState::Open(items),
73            ..
74        } = self
75        {
76            for item in items {
77                let inner_items = item.flat(depth + 1, root_id);
78                flat_items.extend(inner_items);
79            }
80        }
81        flat_items
82    }
83
84    fn into_flat(self, depth: usize, root_id: I) -> FlatItem<I> {
85        match self {
86            TreeItem::Standalone { id, .. } => FlatItem {
87                id,
88                is_standalone: true,
89                is_open: false,
90                depth,
91                root_id,
92            },
93            TreeItem::Expandable { id, state, .. } => FlatItem {
94                id,
95                is_standalone: false,
96                is_open: state != ExpandableItemState::Closed,
97                depth,
98                root_id,
99            },
100        }
101    }
102}
103
104/// Just like a TreeItem for flattened.
105/// Use this when rendering the items.
106#[derive(Clone, Debug, PartialEq)]
107pub struct FlatItem<I> {
108    pub id: I,
109    pub is_open: bool,
110    pub is_standalone: bool,
111    pub depth: usize,
112    pub root_id: I,
113}
114
115#[cfg(test)]
116mod test {
117    use crate::FlatItem;
118
119    #[test]
120    fn tree() {
121        use std::{
122            path::PathBuf,
123            str::FromStr,
124        };
125
126        use crate::{
127            ExpandableItemState,
128            TreeItem,
129        };
130
131        let mut tree = TreeItem::Expandable {
132            id: PathBuf::from_str("/").unwrap(),
133            value: (),
134            state: ExpandableItemState::Open(vec![
135                TreeItem::Expandable {
136                    id: PathBuf::from_str("/1").unwrap(),
137                    value: (),
138                    state: ExpandableItemState::Open(vec![
139                        TreeItem::Standalone {
140                            id: PathBuf::from_str("/1/1").unwrap(),
141                            value: (),
142                        },
143                        TreeItem::Standalone {
144                            id: PathBuf::from_str("/1/2").unwrap(),
145                            value: (),
146                        },
147                    ]),
148                },
149                TreeItem::Expandable {
150                    id: PathBuf::from_str("/2").unwrap(),
151                    value: (),
152                    state: ExpandableItemState::Closed,
153                },
154                TreeItem::Standalone {
155                    id: PathBuf::from_str("/3").unwrap(),
156                    value: (),
157                },
158            ]),
159        };
160
161        tree.set_state(
162            &PathBuf::from_str("/1").unwrap(),
163            &ExpandableItemState::Closed,
164        );
165        tree.set_state(
166            &PathBuf::from_str("/2").unwrap(),
167            &ExpandableItemState::Open(vec![TreeItem::Expandable {
168                id: PathBuf::from_str("/2/1").unwrap(),
169                value: (),
170                state: ExpandableItemState::Open(vec![]),
171            }]),
172        );
173        tree.set_state(
174            &PathBuf::from_str("/2/1").unwrap(),
175            &ExpandableItemState::Closed,
176        );
177        tree.set_state(
178            &PathBuf::from_str("/3").unwrap(),
179            &ExpandableItemState::Closed,
180        );
181
182        let flat_items = tree.flat(0, &PathBuf::from_str("/").unwrap());
183
184        assert_eq!(
185            flat_items,
186            vec![
187                FlatItem {
188                    id: PathBuf::from_str("/").unwrap(),
189                    is_open: true,
190                    is_standalone: false,
191                    depth: 0,
192                    root_id: PathBuf::from_str("/").unwrap(),
193                },
194                FlatItem {
195                    id: PathBuf::from_str("/1").unwrap(),
196                    is_open: false,
197                    is_standalone: false,
198                    depth: 1,
199                    root_id: PathBuf::from_str("/").unwrap(),
200                },
201                FlatItem {
202                    id: PathBuf::from_str("/2").unwrap(),
203                    is_open: true,
204                    is_standalone: false,
205                    depth: 1,
206                    root_id: PathBuf::from_str("/").unwrap(),
207                },
208                FlatItem {
209                    id: PathBuf::from_str("/2/1").unwrap(),
210                    is_open: false,
211                    is_standalone: false,
212                    depth: 2,
213                    root_id: PathBuf::from_str("/").unwrap(),
214                },
215                FlatItem {
216                    id: PathBuf::from_str("/3").unwrap(),
217                    is_open: false,
218                    is_standalone: true,
219                    depth: 1,
220                    root_id: PathBuf::from_str("/").unwrap(),
221                },
222            ]
223        )
224    }
225}