Skip to main content

makepad_example_ui_zoo/
demofiletree.rs

1
2use std::{cmp::Ordering, collections::HashMap, fs, path::{Path, PathBuf}};
3
4use makepad_widgets::makepad_micro_serde::*;
5
6use crate::{
7        makepad_widgets::*,
8        makepad_widgets::file_tree::*,
9    };
10
11live_design!{
12    use link::theme::*;
13    use link::shaders::*;
14    use link::widgets::*;
15       
16    pub DemoFileTree = {{DemoFileTree}}{
17        file_tree: <FileTree>{}
18    }
19} 
20
21/// A type for representing data about a file tree.
22#[derive(Default, Clone, Debug, SerBin, DeBin)]
23pub struct FileTreeData {
24    /// The path to the root of this file tree.
25    pub root_path: String,
26    /// Data about the root of this file tree.
27    pub root: FileNodeData,
28}
29
30/// A type for representing data about a node in a file tree.
31/// 
32/// Each node is either a directory a file. Directories form the internal nodes of the file tree.
33/// They consist of one or more named entries, each of which is another node. Files form the leaves
34/// of the file tree, and do not contain any further nodes.
35#[derive(Default, Clone, Debug, SerBin, DeBin)]
36pub enum FileNodeData {
37   
38    Directory { entries: Vec<DirectoryEntry> },
39    File { data: Option<Vec<u8>> },
40    #[default] Nothing
41
42}
43
44/// A type for representing an entry in a directory.
45#[derive(Clone, Debug, SerBin, DeBin)]
46pub struct DirectoryEntry {
47    /// The name of this entry.
48    pub name: String,
49    /// The node for this entry.
50    pub node: FileNodeData,
51}
52
53
54#[derive(Debug)]
55pub struct FileEdge {
56    pub name: String,
57    pub file_node_id: LiveId,
58}
59
60
61#[derive(Debug)]
62pub struct FileNode {
63    pub parent_edge: Option<FileEdge>,
64    pub name: String,
65    pub child_edges: Option<Vec<FileEdge >>,
66}
67
68impl FileNode {
69    pub fn is_file(&self) -> bool {
70        self.child_edges.is_none()
71    }
72}
73
74#[derive(Live, LiveHook, Widget)] 
75pub struct DemoFileTree{
76    #[wrap] #[live] pub file_tree: FileTree,
77    #[rust] pub file_nodes: LiveIdMap<LiveId, FileNode>,
78    #[rust] pub root_path: String,
79    #[rust] pub path_to_file_node_id:  HashMap<String, LiveId>
80}
81
82impl DemoFileTree{
83    pub fn draw_file_node(cx: &mut Cx2d, file_node_id: LiveId, file_tree:&mut FileTree, file_nodes: &LiveIdMap<LiveId, FileNode>) {
84        if let Some(file_node) = file_nodes.get(&file_node_id) {
85            match &file_node.child_edges {
86                Some(child_edges) => {
87                    if file_tree.begin_folder(cx, file_node_id, &file_node.name).is_ok() {
88                        for child_edge in child_edges {
89                            Self::draw_file_node(cx, child_edge.file_node_id, file_tree, file_nodes);
90                        }
91                        file_tree.end_folder();
92                    }
93                }
94                None => {
95                    file_tree.file(cx, file_node_id, &file_node.name);
96                }
97            }
98        }
99    }
100
101
102    pub fn load_file_tree(&mut self, tree_data: FileTreeData) {
103        fn create_file_node(
104            file_node_id: Option<LiveId>,
105            node_path: String,
106            path_to_file_id: &mut HashMap<String, LiveId>,
107            file_nodes: &mut LiveIdMap<LiveId, FileNode>,
108            parent_edge: Option<FileEdge>,
109            node: FileNodeData,
110        ) -> LiveId {
111            let file_node_id = file_node_id.unwrap_or(LiveId::from_str(&node_path).into());
112            let name = parent_edge.as_ref().map_or_else(
113                || String::from("root"),
114                | edge | edge.name.clone(),
115            );
116            let node = FileNode {
117                parent_edge,
118                name,
119                child_edges: match node {
120                    FileNodeData::Directory {entries} => Some(
121                        entries
122                            .into_iter()
123                            .map( | entry | FileEdge {
124                            name: entry.name.clone(),
125                            file_node_id: create_file_node(
126                                None,
127                                if node_path.len()>0 {
128                                    format!("{}/{}", node_path, entry.name.clone())
129                                }
130                                else {
131                                    format!("{}", entry.name.clone())
132                                },
133                                path_to_file_id,
134                                file_nodes,
135                                Some(FileEdge {
136                                    name: entry.name,
137                                    file_node_id,
138                                }),
139                                entry.node,
140                            ),
141                        })
142                            .collect::<Vec<_ >> (),
143                    ),
144                    FileNodeData::File {..} => None,
145                    _ => None
146                },
147            };
148            path_to_file_id.insert(node_path, file_node_id);
149            file_nodes.insert(file_node_id, node);
150            file_node_id
151        }
152        
153        self.root_path = tree_data.root_path;
154        
155        
156        self.file_nodes.clear();
157        
158        create_file_node(
159            Some(live_id!(root).into()),
160            "".to_string(),
161            &mut self.path_to_file_node_id,
162            &mut self.file_nodes,
163            None,
164            tree_data.root,
165        );
166    }
167}
168
169#[derive(Clone, Debug, SerBin, DeBin)]
170pub enum FileError {
171    Unknown(String),
172    CannotOpen(String)
173}
174
175impl Widget for DemoFileTree {
176    fn draw_walk(&mut self, cx: &mut Cx2d, scope:&mut Scope, walk:Walk)->DrawStep{
177        while self.file_tree.draw_walk(cx, scope, walk).is_step() {
178            self.file_tree.set_folder_is_open(cx, live_id!(root).into(), true, Animate::No);
179             Self::draw_file_node(
180                cx,
181                live_id!(root).into(),
182                &mut self.file_tree,
183                &self.file_nodes
184            );
185        }
186        DrawStep::done()
187    }
188    
189    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope){
190        match event{
191
192            Event::Startup=>
193            {
194                fn get_directory_entries(path: &Path, with_data: bool) -> Result<Vec<DirectoryEntry>, FileError> {
195                    let mut entries = Vec::new();
196                    for entry in fs::read_dir(path).map_err( | error | FileError::Unknown(error.to_string())) ? {
197                        // We can't get the entry for some unknown reason. Raise an error.
198                        let entry = entry.map_err( | error | FileError::Unknown(error.to_string())) ?;
199                        // Get the path for the entry.
200                        let entry_path = entry.path();
201                        // Get the file name for the entry.
202                        let name = entry.file_name();
203                        if let Ok(name_string) = name.into_string() {
204                            if entry_path.is_dir() && name_string == "target"
205                                || name_string.starts_with('.') {
206                                // Skip over directories called "target". This is sort of a hack. The reason
207                                // it's here is that the "target" directory for Rust projects is huge, and
208                                // our current implementation of the file tree widget is not yet fast enough
209                                // to display vast numbers of nodes. We paper over this by pretending the
210                                // "target" directory does not exist.
211                                continue;
212                            }
213                        }
214                        else {
215                            // Skip over entries with a non UTF-8 file name.
216                            continue;
217                        }
218                        // Create a `DirectoryEntry` for this entry and add it to the list of entries.
219                        entries.push(DirectoryEntry {
220                            name: entry.file_name().to_string_lossy().to_string(),
221                            node: if entry_path.is_dir() {
222                                // If this entry is a subdirectory, recursively create `DirectoryEntry`'s
223                                // for its entries as well.
224                                FileNodeData::Directory {
225                                    entries: get_directory_entries(&entry_path, with_data) ?,
226                                }
227                            } else if entry_path.is_file() {
228                                if with_data {
229                                    let bytes: Vec<u8> = fs::read(&entry_path).map_err(
230                                        | error | FileError::Unknown(error.to_string())
231                                    ) ?;
232                                    FileNodeData::File {data: Some(bytes)}
233                                }
234                                else {
235                                    FileNodeData::File {data: None}
236                                }
237                            }
238                            else {
239                                // If this entry is neither a directory or a file, skip it. This ignores
240                                // things such as symlinks, for which we are not yet sure how we want to
241                                // handle them.
242                                continue
243                            },
244                        });
245                    }
246                    
247                    // Sort all the entries by name, directories first, and files second.
248                    entries.sort_by( | entry_0, entry_1 | {
249                        match &entry_0.node {
250                            FileNodeData::Directory {..} => match &entry_1.node {
251                                FileNodeData::Directory {..} => entry_0.name.cmp(&entry_1.name),
252                                FileNodeData::File {..} => Ordering::Less,
253                                _ => Ordering::Less
254                            }
255                            FileNodeData::File {..} => match &entry_1.node {
256                                FileNodeData::Directory {..} => Ordering::Greater,
257                                FileNodeData::File {..} => entry_0.name.cmp(&entry_1.name),
258                                _ => Ordering::Less
259                            },
260                            _ => Ordering::Less
261                            
262                        }
263                    });
264                    Ok(entries)
265                }
266                
267                
268                #[cfg(target_arch="wasm32")]{
269                    let file_tree_data=  FileTreeData { root_path: "".into(), root:FileNodeData::Directory{
270                        entries:vec![DirectoryEntry{
271                            name: "empty".to_string(),
272                            node: FileNodeData::Directory{entries:vec![]}
273                        },
274                        DirectoryEntry{
275                            name: "on".to_string(),
276                            node: FileNodeData::Directory{entries:vec![DirectoryEntry{
277                                name: "empty".to_string(),
278                                node: FileNodeData::Directory{entries:vec![]}
279                            },
280                            DirectoryEntry{
281                                name: "on".to_string(),
282                                node: FileNodeData::Directory{entries:vec![]}
283                            },
284                            DirectoryEntry{
285                                name: "web".to_string(),
286                                node: FileNodeData::Directory{entries:vec![]}
287                            }]}
288                        },
289                        DirectoryEntry{
290                            name: "web".to_string(),
291                            node: FileNodeData::Directory{entries:vec![]}
292                        }]
293                    }};
294                    self.load_file_tree(file_tree_data);
295                }
296                #[cfg(not(target_arch="wasm32"))]{
297                    let root_path: PathBuf  = PathBuf::from(".");
298                    let root = FileNodeData::Directory {
299                        entries: get_directory_entries(&root_path, false).unwrap(),
300                    };
301                    let file_tree_data=  FileTreeData { root_path: "".into(), root:root  };
302                    self.load_file_tree(file_tree_data);
303                }
304            }
305            _ => {}
306        }
307
308        self.file_tree.handle_event(cx, event, scope);
309    }
310}