1use {
2 std::{
3 collections::{HashSet},
4 },
5 crate::{
6 makepad_derive_widget::*,
7 check_box::*,
8 makepad_draw::*,
9 widget::*,
10 scroll_shadow::DrawScrollShadow,
11 scroll_bars::ScrollBars
12 }
13};
14
15live_design!{
16 DrawBgQuad = {{DrawBgQuad}} {}
17 DrawNameText = {{DrawNameText}} {}
18 DrawIconQuad = {{DrawIconQuad}} {}
19 FileTreeNodeBase = {{FileTreeNode}} {}
20 FileTreeBase = {{FileTree}} {}
21}
22
23#[derive(Live, LiveHook)]#[repr(C)]
25struct DrawBgQuad {
26 #[deref] draw_super: DrawQuad,
27 #[live] is_even: f32,
28 #[live] scale: f32,
29 #[live] is_folder: f32,
30 #[live] focussed: f32,
31 #[live] selected: f32,
32 #[live] hover: f32,
33 #[live] opened: f32,
34}
35
36#[derive(Live, LiveHook)]#[repr(C)]
37struct DrawNameText {
38 #[deref] draw_super: DrawText,
39 #[live] is_even: f32,
40 #[live] scale: f32,
41 #[live] is_folder: f32,
42 #[live] focussed: f32,
43 #[live] selected: f32,
44 #[live] hover: f32,
45 #[live] opened: f32,
46}
47
48#[derive(Live, LiveHook)]#[repr(C)]
49struct DrawIconQuad {
50 #[deref] draw_super: DrawQuad,
51 #[live] is_even: f32,
52 #[live] scale: f32,
53 #[live] is_folder: f32,
54 #[live] focussed: f32,
55 #[live] selected: f32,
56 #[live] hover: f32,
57 #[live] opened: f32,
58}
59
60#[derive(Live, LiveHook)]
61pub struct FileTreeNode {
62 #[live] draw_bg: DrawBgQuad,
63 #[live] draw_icon: DrawIconQuad,
64 #[live] draw_name: DrawNameText,
65 #[live] check_box: CheckBox,
66 #[layout] layout: Layout,
67
68 #[animator] animator: Animator,
69
70 #[live] indent_width: f64,
71
72 #[live] icon_walk: Walk,
73
74 #[live] is_folder: bool,
75 #[live] min_drag_distance: f64,
76
77 #[live] opened: f32,
78 #[live] focussed: f32,
79 #[live] hover: f32,
80 #[live] selected: f32,
81}
82
83#[derive(Live)]
84pub struct FileTree {
85 #[live] scroll_bars: ScrollBars,
86 #[live] file_node: Option<LivePtr>,
87 #[live] folder_node: Option<LivePtr>,
88 #[walk] walk: Walk,
89 #[layout] layout: Layout,
90 #[live] filler: DrawBgQuad,
91
92 #[live] node_height: f64,
93
94 #[live] draw_scroll_shadow: DrawScrollShadow,
95
96 #[rust] draw_state: DrawStateWrap<()>,
97
98 #[rust] dragging_node_id: Option<FileNodeId>,
99 #[rust] selected_node_id: Option<FileNodeId>,
100 #[rust] open_nodes: HashSet<FileNodeId>,
101
102 #[rust] tree_nodes: ComponentMap<FileNodeId, (FileTreeNode, LiveId)>,
103
104 #[rust] count: usize,
105 #[rust] stack: Vec<f64>,
106}
107
108impl LiveHook for FileTree {
109 fn before_live_design(cx:&mut Cx){
110 register_widget!(cx, FileTree)
111 }
112
113 fn after_apply(&mut self, cx: &mut Cx, from: ApplyFrom, index: usize, nodes: &[LiveNode]) {
114 for (_, (tree_node, id)) in self.tree_nodes.iter_mut() {
115 if let Some(index) = nodes.child_by_name(index, id.as_field()) {
116 tree_node.apply(cx, from, index, nodes);
117 }
118 }
119 self.scroll_bars.redraw(cx);
120 }
121}
122
123#[derive(Clone, WidgetAction)]
124pub enum FileTreeAction {
125 None,
126 FileClicked(FileNodeId),
127 FolderClicked(FileNodeId),
128 ShouldFileStartDrag(FileNodeId),
129}
130
131pub enum FileTreeNodeAction {
132 None,
133 WasClicked,
134 Opening,
135 Closing,
136 ShouldStartDrag
137}
138
139impl FileTreeNode {
140 pub fn set_draw_state(&mut self, is_even: f32, scale: f64) {
141 self.draw_bg.scale = scale as f32;
142 self.draw_bg.is_even = is_even;
143 self.draw_name.scale = scale as f32;
144 self.draw_name.is_even = is_even;
145 self.draw_icon.scale = scale as f32;
146 self.draw_icon.is_even = is_even;
147 self.draw_name.font_scale = scale;
148 }
149
150 pub fn draw_folder(&mut self, cx: &mut Cx2d, name: &str, is_even: f32, node_height: f64, depth: usize, scale: f64) {
151 self.set_draw_state(is_even, scale);
152
153 self.draw_bg.begin(cx, Walk::size(Size::Fill, Size::Fixed(scale * node_height)), self.layout);
154
155 cx.walk_turtle(self.indent_walk(depth));
156
157 self.draw_icon.draw_walk(cx, self.icon_walk);
158
159 self.draw_name.draw_walk(cx, Walk::fit(), Align::default(), name);
160 self.draw_bg.end(cx);
161 }
162
163 pub fn draw_file(&mut self, cx: &mut Cx2d, name: &str, is_even: f32, node_height: f64, depth: usize, scale: f64) {
164 self.set_draw_state(is_even, scale);
165
166 self.draw_bg.begin(cx, Walk::size(Size::Fill, Size::Fixed(scale * node_height)), self.layout);
167
168 cx.walk_turtle(self.indent_walk(depth));
169
170 self.draw_name.draw_walk(cx, Walk::fit(), Align::default(), name);
171 self.draw_bg.end(cx);
172 }
173
174 fn indent_walk(&self, depth: usize) -> Walk {
175 Walk {
176 abs_pos: None,
177 width: Size::Fixed(depth as f64 * self.indent_width),
178 height: Size::Fixed(0.0),
179 margin: Margin {
180 left: depth as f64 * 1.0,
181 top: 0.0,
182 right: depth as f64 * 4.0,
183 bottom: 0.0,
184 },
185 }
186 }
187
188 fn set_is_selected(&mut self, cx: &mut Cx, is: bool, animate: Animate) {
189 self.animator_toggle(cx, is, animate, id!(select.on), id!(select.off))
190 }
191
192 fn set_is_focussed(&mut self, cx: &mut Cx, is: bool, animate: Animate) {
193 self.animator_toggle(cx, is, animate, id!(focus.on), id!(focus.off))
194 }
195
196 pub fn set_folder_is_open(&mut self, cx: &mut Cx, is: bool, animate: Animate) {
197 self.animator_toggle(cx, is, animate, id!(open.on), id!(open.off));
198 }
199
200 pub fn handle_event_with(
201 &mut self,
202 cx: &mut Cx,
203 event: &Event,
204 dispatch_action: &mut dyn FnMut(&mut Cx, FileTreeNodeAction),
205 ) {
206 if self.animator_handle_event(cx, event).must_redraw() {
207 self.draw_bg.redraw(cx);
208 }
209 match event.hits(cx, self.draw_bg.area()) {
210 Hit::FingerHoverIn(_) => {
211 self.animator_play(cx, id!(hover.on));
212 }
213 Hit::FingerHoverOut(_) => {
214 self.animator_play(cx, id!(hover.off));
215 }
216 Hit::FingerMove(f) => {
217 if f.abs.distance(&f.abs_start) >= self.min_drag_distance {
218 dispatch_action(cx, FileTreeNodeAction::ShouldStartDrag);
219 }
220 }
221 Hit::FingerDown(_) => {
222 self.animator_play(cx, id!(select.on));
223 if self.is_folder {
224 if self.animator_in_state(cx, id!(open.on)) {
225 self.animator_play(cx, id!(open.off));
226 dispatch_action(cx, FileTreeNodeAction::Closing);
227 }
228 else {
229 self.animator_play(cx, id!(open.on));
230 dispatch_action(cx, FileTreeNodeAction::Opening);
231 }
232
233 }
234 dispatch_action(cx, FileTreeNodeAction::WasClicked);
235 }
236 _ => {}
237 }
238 }
239}
240
241
242impl FileTree {
243
244 pub fn begin(&mut self, cx: &mut Cx2d, walk: Walk) {
245 self.scroll_bars.begin(cx, walk, self.layout);
246 self.count = 0;
247 }
248
249 pub fn end(&mut self, cx: &mut Cx2d) {
250 let height_left = cx.turtle().height_left();
252 let mut walk = 0.0;
253 while walk < height_left {
254 self.count += 1;
255 self.filler.is_even = Self::is_even(self.count);
256 self.filler.draw_walk(cx, Walk::size(Size::Fill, Size::Fixed(self.node_height.min(height_left - walk))));
257 walk += self.node_height.max(1.0);
258 }
259
260 self.draw_scroll_shadow.draw(cx, dvec2(0., 0.));
261 self.scroll_bars.end(cx);
262
263 let selected_node_id = self.selected_node_id;
264 self.tree_nodes.retain_visible_and( | node_id, _ | Some(*node_id) == selected_node_id);
265 }
266
267 pub fn is_even(count: usize) -> f32 {
268 if count % 2 == 1 {0.0}else {1.0}
269 }
270
271 pub fn should_node_draw(&mut self, cx: &mut Cx2d) -> bool {
272 let scale = self.stack.last().cloned().unwrap_or(1.0);
273 let height = self.node_height * scale;
274 let walk = Walk::size(Size::Fill, Size::Fixed(height));
275 if scale > 0.01 && cx.walk_turtle_would_be_visible(walk) {
276 return true
277 }
278 else {
279 cx.walk_turtle(walk);
280 return false
281 }
282 }
283
284 pub fn begin_folder(
285 &mut self,
286 cx: &mut Cx2d,
287 node_id: FileNodeId,
288 name: &str,
289 ) -> Result<(), ()> {
290 let scale = self.stack.last().cloned().unwrap_or(1.0);
291
292 if scale > 0.2 {
293 self.count += 1;
294 }
295
296 let is_open = self.open_nodes.contains(&node_id);
297
298 if self.should_node_draw(cx) {
299 let folder_node = self.folder_node;
300 let (tree_node, _) = self.tree_nodes.get_or_insert(cx, node_id, | cx | {
301 let mut tree_node = FileTreeNode::new_from_ptr(cx, folder_node);
302 if is_open {
303 tree_node.set_folder_is_open(cx, true, Animate::No)
304 }
305 (tree_node, live_id!(folder_node))
306 });
307
308 tree_node.draw_folder(cx, name, Self::is_even(self.count), self.node_height, self.stack.len(), scale);
309 self.stack.push(tree_node.opened as f64 * scale);
310 if tree_node.opened == 0.0 {
311 self.end_folder();
312 return Err(());
313 }
314 }
315 else {
316 if is_open {
317 self.stack.push(scale * 1.0);
318 }
319 else {
320 return Err(());
321 }
322 }
323 Ok(())
324 }
325
326 pub fn end_folder(&mut self) {
327 self.stack.pop();
328 }
329
330 pub fn file(&mut self, cx: &mut Cx2d, node_id: FileNodeId, name: &str) {
331 let scale = self.stack.last().cloned().unwrap_or(1.0);
332
333 if scale > 0.2 {
334 self.count += 1;
335 }
336 if self.should_node_draw(cx) {
337 let file_node = self.file_node;
338 let (tree_node, _) = self.tree_nodes.get_or_insert(cx, node_id, | cx | {
339 (FileTreeNode::new_from_ptr(cx, file_node), live_id!(file_node))
340 });
341 tree_node.draw_file(cx, name, Self::is_even(self.count), self.node_height, self.stack.len(), scale);
342 }
343 }
344
345 pub fn forget(&mut self) {
346 self.tree_nodes.clear();
347 }
348
349 pub fn forget_node(&mut self, file_node_id: FileNodeId) {
350 self.tree_nodes.remove(&file_node_id);
351 }
352
353 pub fn is_folder(&mut self, file_node_id: FileNodeId)->bool {
354 if let Some((node,_)) = self.tree_nodes.get(&file_node_id){
355 node.is_folder
356 }
357 else{
358 false
359 }
360 }
361
362 pub fn set_folder_is_open(
363 &mut self,
364 cx: &mut Cx,
365 node_id: FileNodeId,
366 is_open: bool,
367 animate: Animate,
368 ) {
369 if is_open {
370 self.open_nodes.insert(node_id);
371 }
372 else {
373 self.open_nodes.remove(&node_id);
374 }
375 if let Some((tree_node, _)) = self.tree_nodes.get_mut(&node_id) {
376 tree_node.set_folder_is_open(cx, is_open, animate);
377 }
378 }
379
380 pub fn start_dragging_file_node(
381 &mut self,
382 cx: &mut Cx,
383 node_id: FileNodeId,
384 items: Vec<DragItem>,
385 ) {
386 self.dragging_node_id = Some(node_id);
387
388 log!("makepad: start_dragging_file_node");
389
390 cx.start_dragging(items);
391 }
392
393 pub fn handle_event(&mut self, cx: &mut Cx, event: &Event) -> Vec<FileTreeAction> {
394 let mut a = Vec::new();
395 self.handle_event_with(cx, event, &mut | _, v | a.push(v));
396 a
397 }
398
399 pub fn handle_event_with(
400 &mut self,
401 cx: &mut Cx,
402 event: &Event,
403 dispatch_action: &mut dyn FnMut(&mut Cx, FileTreeAction),
404 ) {
405 self.scroll_bars.handle_event_with(cx, event, &mut | _, _ | {});
406
407 match event {
408 Event::DragEnd => self.dragging_node_id = None,
409 _ => ()
410 }
411
412 let mut actions = Vec::new();
413 for (node_id, (node, _)) in self.tree_nodes.iter_mut() {
414 node.handle_event_with(cx, event, &mut | _, e | actions.push((*node_id, e)));
415 }
416
417 for (node_id, action) in actions {
418 match action {
419 FileTreeNodeAction::Opening => {
420 self.open_nodes.insert(node_id);
421 }
422 FileTreeNodeAction::Closing => {
423 self.open_nodes.remove(&node_id);
424 }
425 FileTreeNodeAction::WasClicked => {
426 cx.set_key_focus(self.scroll_bars.area());
427 if let Some(last_selected) = self.selected_node_id {
428 if last_selected != node_id {
429 self.tree_nodes.get_mut(&last_selected).unwrap().0.set_is_selected(cx, false, Animate::Yes);
430 }
431 }
432 self.selected_node_id = Some(node_id);
433 if self.is_folder(node_id){
434 dispatch_action(cx, FileTreeAction::FolderClicked(node_id));
435 }
436 else{
437 dispatch_action(cx, FileTreeAction::FileClicked(node_id));
438 }
439 }
440 FileTreeNodeAction::ShouldStartDrag => {
441 if self.dragging_node_id.is_none() {
442 dispatch_action(cx, FileTreeAction::ShouldFileStartDrag(node_id));
443 }
444 }
445 _ => ()
446 }
447 }
448
449 match event.hits(cx, self.scroll_bars.area()) {
450 Hit::KeyFocus(_) => {
451 if let Some(node_id) = self.selected_node_id {
452 self.tree_nodes.get_mut(&node_id).unwrap().0.set_is_focussed(cx, true, Animate::Yes);
453 }
454 }
455 Hit::KeyFocusLost(_) => {
456 if let Some(node_id) = self.selected_node_id {
457 self.tree_nodes.get_mut(&node_id).unwrap().0.set_is_focussed(cx, false, Animate::Yes);
458 }
459 }
460 _ => ()
461 }
462 }
463}
464
465#[derive(Clone, Debug, Default, Eq, Hash, Copy, PartialEq, FromLiveId)]
466pub struct FileNodeId(pub LiveId);
467
468impl Widget for FileTree {
469 fn redraw(&mut self, cx: &mut Cx) {
470 self.scroll_bars.redraw(cx);
471 }
472
473 fn handle_widget_event_with(&mut self, cx: &mut Cx, event: &Event, dispatch_action: &mut dyn FnMut(&mut Cx, WidgetActionItem)) {
474 let uid = self.widget_uid();
475 self.handle_event_with(cx, event, &mut | cx, action | {
476 dispatch_action(cx, WidgetActionItem::new(action.into(), uid))
477 });
478 }
479
480 fn walk(&mut self, _cx:&mut Cx) -> Walk {self.walk}
481
482 fn draw_walk_widget(&mut self, cx: &mut Cx2d, walk: Walk) -> WidgetDraw {
483 if self.draw_state.begin(cx, ()) {
484 self.begin(cx, walk);
485 return WidgetDraw::hook_above()
486 }
487 if let Some(()) = self.draw_state.get() {
488 self.end(cx);
489 self.draw_state.end();
490 }
491 WidgetDraw::done()
492 }
493}
494
495#[derive(Debug, Clone, Default, PartialEq, WidgetRef)]
496pub struct FileTreeRef(WidgetRef);
497
498impl FileTreeRef{
499 pub fn should_file_start_drag(&self, actions: &WidgetActions) -> Option<FileNodeId> {
500 if let Some(item) = actions.find_single_action(self.widget_uid()) {
501 if let FileTreeAction::ShouldFileStartDrag(file_id) = item.action() {
502 return Some(file_id)
503 }
504 }
505 None
506 }
507
508 pub fn file_clicked(&self, actions: &WidgetActions) -> Option<FileNodeId> {
509 if let Some(item) = actions.find_single_action(self.widget_uid()) {
510 if let FileTreeAction::FileClicked(file_id) = item.action() {
511 return Some(file_id)
512 }
513 }
514 None
515 }
516
517
518 pub fn file_start_drag(&self, cx: &mut Cx, _file_id: FileNodeId, item: DragItem) {
519 cx.start_dragging(vec![item]);
520 }
521}
522
523#[derive(Clone, Default, WidgetSet)]
524pub struct FileTreeSet(WidgetSet);