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#[derive(Default, Clone, Debug, SerBin, DeBin)]
23pub struct FileTreeData {
24 pub root_path: String,
26 pub root: FileNodeData,
28}
29
30#[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#[derive(Clone, Debug, SerBin, DeBin)]
46pub struct DirectoryEntry {
47 pub name: String,
49 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 let entry = entry.map_err( | error | FileError::Unknown(error.to_string())) ?;
199 let entry_path = entry.path();
201 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 continue;
212 }
213 }
214 else {
215 continue;
217 }
218 entries.push(DirectoryEntry {
220 name: entry.file_name().to_string_lossy().to_string(),
221 node: if entry_path.is_dir() {
222 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 continue
243 },
244 });
245 }
246
247 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}