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 link widgets;
17 use link::theme::*;
18 use crate::scroll_bars::ScrollBars;
19 use link::shaders::*;
20
21 DrawBgQuad = {{DrawBgQuad}} {}
22 DrawNameText = {{DrawNameText}} {}
23 DrawIconQuad = {{DrawIconQuad}} {}
24
25 pub FileTreeNodeBase = {{FileTreeNode}} {}
26 pub FileTreeBase = {{FileTree}} {}
27
28 pub FileTreeNode = <FileTreeNodeBase> {
29 align: { y: 0.5 }
30 padding: { left: (THEME_SPACE_2) },
31 is_folder: false,
32 indent_width: (THEME_SPACE_2)
33 min_drag_distance: 10.0
34
35 draw_bg: {
36 uniform color_1: (THEME_COLOR_BG_EVEN)
37 uniform color_2: (THEME_COLOR_BG_ODD)
38 uniform color_active: (THEME_COLOR_HIGHLIGHT)
39
40 fn pixel(self) -> vec4 {
41 let sdf = Sdf2d::viewport(self.pos * self.rect_size);
42 sdf.box(
43 0.,
44 -2.,
45 self.rect_size.x,
46 self.rect_size.y + 3.0,
47 1.
48 )
49 sdf.fill_keep(
50 mix(
51 mix(
52 self.color_1,
53 self.color_2,
54 self.is_even
55 ),
56 self.color_active,
57 self.active
58 )
59 )
60 return sdf.result
61 }
62 }
63
64 draw_icon: {
65 uniform color: (THEME_COLOR_LABEL_INNER)
66 uniform color_active: (THEME_COLOR_LABEL_INNER_ACTIVE)
67
68 fn pixel(self) -> vec4 {
69 let sdf = Sdf2d::viewport(self.pos * self.rect_size);
70 let w = self.rect_size.x;
71 let h = self.rect_size.y;
72 sdf.box(0. * w, 0.35 * h, 0.87 * w, 0.39 * h, 0.75);
73 sdf.box(0. * w, 0.28 * h, 0.5 * w, 0.3 * h, 1.);
74 sdf.union();
75 return sdf.fill(
76 mix(
77 self.color * self.scale,
78 self.color_active,
79 self.active
80 )
81 );
82 }
83 }
84
85 draw_text: {
86 uniform color: (THEME_COLOR_LABEL_INNER)
87 uniform color_active: (THEME_COLOR_LABEL_INNER_ACTIVE)
88
89 fn get_color(self) -> vec4 {
90 return mix(
91 self.color * self.scale,
92 self.color_active,
93 self.active
94 )
95 }
96
97 text_style: <THEME_FONT_REGULAR> {
98 font_size: (THEME_FONT_SIZE_P)
99 }
100 }
101
102 icon_walk: {
103 width: (THEME_DATA_ICON_WIDTH - 2), height: (THEME_DATA_ICON_HEIGHT),
104 margin: { right: (THEME_SPACE_1) }
105 }
106
107 animator: {
108 hover = {
109 default: off
110 off = {
111 from: {all: Forward {duration: 0.2}}
112 apply: {
113 hover: 0.0
114 draw_bg: {hover: 0.0}
115 draw_text: {hover: 0.0}
116 draw_icon: {hover: 0.0}
117 }
118 }
119
120 on = {
121 cursor: Hand
122 from: {all: Snap}
123 apply: {
124 hover: 1.0
125 draw_bg: {hover: 1.0}
126 draw_text: {hover: 1.0}
127 draw_icon: {hover: 1.0}
128 },
129 }
130 }
131
132 focus = {
133 default: on
134 on = {
135 from: {all: Snap}
136 apply: {focussed: 1.0}
137 }
138
139 off = {
140 from: {all: Forward {duration: 0.1}}
141 apply: {focussed: 0.0}
142 }
143 }
144
145 select = {
146 default: off
147 off = {
148 from: {all: Forward {duration: 0.1}}
149 apply: {
150 active: 0.0
151 draw_bg: {active: 0.0}
152 draw_text: {active: 0.0}
153 draw_icon: {active: 0.0}
154 }
155 }
156 on = {
157 from: {all: Snap}
158 apply: {
159 active: 1.0
160 draw_bg: {active: 1.0}
161 draw_text: {active: 1.0}
162 draw_icon: {active: 1.0}
163 }
164 }
165
166 }
167
168 open = {
169 default: off
170 off = {
171 redraw: true
174
175 from: {all: Forward {duration: 0.2}}
176 ease: ExpDecay {d1: 0.80, d2: 0.97}
177
178 apply: {
180 opened: [{time: 0.0, value: 1.0}, {time: 1.0, value: 0.0}]
181 draw_bg: {opened: [{time: 0.0, value: 1.0}, {time: 1.0, value: 0.0}]}
182 draw_text: {opened: [{time: 0.0, value: 1.0}, {time: 1.0, value: 0.0}]}
183 draw_icon: {opened: [{time: 0.0, value: 1.0}, {time: 1.0, value: 0.0}]}
184 }
185 }
186
187 on = {
188 from: {all: Forward {duration: 0.2}}
191 ease: ExpDecay {d1: 0.82, d2: 0.95}
192
193 redraw: true
195 apply: {
196 opened: 1.0
197 draw_bg: {opened: 1.0}
198 draw_text: {opened: 1.0}
199 draw_icon: {opened: 1.0}
200 }
201 }
202 }
203 }
204 }
205
206 pub FileTree = <FileTreeBase> {
207 flow: Down,
208
209 scroll_bars: <ScrollBars> {}
210 scroll_bars: {}
211 node_height: (THEME_DATA_ITEM_HEIGHT),
212 clip_x: true,
213 clip_y: true
214
215 file_node: <FileTreeNode> {
216 is_folder: false,
217 draw_bg: {is_folder: 0.0}
218 draw_text: {is_folder: 0.0}
219 }
220
221 folder_node: <FileTreeNode> {
222 is_folder: true,
223 draw_bg: {is_folder: 1.0}
224 draw_text: {is_folder: 1.0}
225 }
226
227 filler: {
228 fn pixel(self) -> vec4 {
229 return mix(
230 mix(
231 THEME_COLOR_BG_EVEN,
232 THEME_COLOR_BG_ODD,
233 self.is_even
234 ),
235 mix(
236 THEME_COLOR_OUTSET_INACTIVE,
237 THEME_COLOR_OUTSET_ACTIVE,
238 self.focussed
239 ),
240 self.active
241 );
242 }
243 }
244 }
245}
246
247#[derive(Live, LiveHook, LiveRegister)]#[repr(C)]
249struct DrawBgQuad {
250 #[deref] draw_super: DrawQuad,
251 #[live] is_even: f32,
252 #[live] scale: f32,
253 #[live] is_folder: f32,
254 #[live] focussed: f32,
255 #[live] active: f32,
256 #[live] hover: f32,
257 #[live] opened: f32,
258}
259
260#[derive(Live, LiveHook, LiveRegister)]#[repr(C)]
261struct DrawNameText {
262 #[deref] draw_super: DrawText,
263 #[live] is_even: f32,
264 #[live] scale: f32,
265 #[live] is_folder: f32,
266 #[live] focussed: f32,
267 #[live] active: f32,
268 #[live] hover: f32,
269 #[live] opened: f32,
270}
271
272#[derive(Live, LiveHook, LiveRegister)]#[repr(C)]
273struct DrawIconQuad {
274 #[deref] draw_super: DrawQuad,
275 #[live] is_even: f32,
276 #[live] scale: f32,
277 #[live] is_folder: f32,
278 #[live] focussed: f32,
279 #[live] active: f32,
280 #[live] hover: f32,
281 #[live] opened: f32,
282}
283
284#[derive(Live, LiveHook, LiveRegister)]
285pub struct FileTreeNode {
286 #[live] draw_bg: DrawBgQuad,
287 #[live] draw_icon: DrawIconQuad,
288 #[live] draw_text: DrawNameText,
289 #[live] check_box: CheckBox,
290 #[layout] layout: Layout,
291
292 #[animator] animator: Animator,
293
294 #[live] indent_width: f64,
295 #[live] indent_shift: f64,
296
297 #[live] icon_walk: Walk,
298
299 #[live] is_folder: bool,
300 #[live] min_drag_distance: f64,
301
302 #[live] opened: f32,
303 #[live] focussed: f32,
304 #[live] hover: f32,
305 #[live] active: f32,
306}
307
308#[derive(Live, Widget)]
309pub struct FileTree {
310 #[redraw] #[live] scroll_bars: ScrollBars,
311 #[live] file_node: Option<LivePtr>,
312 #[live] folder_node: Option<LivePtr>,
313 #[walk] walk: Walk,
314 #[layout] layout: Layout,
315 #[live] filler: DrawBgQuad,
316
317 #[live] node_height: f64,
318
319 #[live] draw_scroll_shadow: DrawScrollShadow,
320
321 #[rust] draw_state: DrawStateWrap<()>,
322
323 #[rust] dragging_node_id: Option<LiveId>,
324 #[rust] selected_node_id: Option<LiveId>,
325 #[rust] open_nodes: HashSet<LiveId>,
326
327 #[rust] tree_nodes: ComponentMap<LiveId, (FileTreeNode, LiveId)>,
328
329 #[rust] count: usize,
330 #[rust] stack: Vec<f64>,
331}
332
333impl LiveHook for FileTree {
334 fn after_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
335 for (_, (tree_node, id)) in self.tree_nodes.iter_mut() {
336 if let Some(index) = nodes.child_by_name(index, id.as_field()) {
337 tree_node.apply(cx, apply, index, nodes);
338 }
339 }
340 self.scroll_bars.redraw(cx);
341 }
342}
343
344#[derive(Clone, Debug, DefaultNone)]
345pub enum FileTreeAction {
346 None,
347 FileClicked(LiveId),
348 FolderClicked(LiveId),
349 ShouldFileStartDrag(LiveId),
350}
351
352pub enum FileTreeNodeAction {
353 WasClicked,
354 Opening,
355 Closing,
356 ShouldStartDrag
357}
358
359impl FileTreeNode {
360 pub fn set_draw_state(&mut self, is_even: f32, scale: f64) {
361 self.draw_bg.scale = scale as f32;
362 self.draw_bg.is_even = is_even;
363 self.draw_text.scale = scale as f32;
364 self.draw_text.is_even = is_even;
365 self.draw_icon.scale = scale as f32;
366 self.draw_icon.is_even = is_even;
367 self.draw_text.font_scale = scale as f32;
368 }
369
370 pub fn draw_folder(&mut self, cx: &mut Cx2d, name: &str, is_even: f32, node_height: f64, depth: usize, scale: f64) {
371 self.set_draw_state(is_even, scale);
372
373 self.draw_bg.begin(cx, Walk::size(Size::Fill, Size::Fixed(scale * node_height)), self.layout);
374
375 cx.walk_turtle(self.indent_walk(depth));
376
377 self.draw_icon.draw_walk(cx, self.icon_walk);
378
379 self.draw_text.draw_walk(cx, Walk::fit(), Align::default(), name);
380 self.draw_bg.end(cx);
381 }
382
383 pub fn draw_file(&mut self, cx: &mut Cx2d, name: &str, is_even: f32, node_height: f64, depth: usize, scale: f64) {
384 self.set_draw_state(is_even, scale);
385
386 self.draw_bg.begin(cx, Walk::size(Size::Fill, Size::Fixed(scale * node_height)), self.layout);
387
388 cx.walk_turtle(self.indent_walk(depth));
389
390 self.draw_text.draw_walk(cx, Walk::fit(), Align::default(), name);
391 self.draw_bg.end(cx);
392 }
393
394 fn indent_walk(&self, depth: usize) -> Walk {
395 Walk {
396 abs_pos: None,
397 width: Size::Fixed(depth as f64 * self.indent_width + self.indent_shift),
398 height: Size::Fixed(0.0),
399 margin: Margin {
400 left: depth as f64 * 1.0,
401 top: 0.0,
402 right: depth as f64 * 4.0,
403 bottom: 0.0,
404 },
405 }
406 }
407
408 fn set_is_selected(&mut self, cx: &mut Cx, is: bool, animate: Animate) {
409 self.animator_toggle(cx, is, animate, id!(select.on), id!(select.off))
410 }
411
412 fn set_is_focussed(&mut self, cx: &mut Cx, is: bool, animate: Animate) {
413 self.animator_toggle(cx, is, animate, id!(focus.on), id!(focus.off))
414 }
415
416 pub fn set_folder_is_open(&mut self, cx: &mut Cx, is: bool, animate: Animate) {
417 self.animator_toggle(cx, is, animate, id!(open.on), id!(open.off));
418 }
419
420 pub fn handle_event(
421 &mut self,
422 cx: &mut Cx,
423 event: &Event,
424 node_id: LiveId,
425 _scope: &mut Scope,
426 actions: &mut Vec<(LiveId, FileTreeNodeAction)>,
427 ) {
428 if self.animator_handle_event(cx, event).must_redraw() {
429 self.draw_bg.redraw(cx);
430 }
431 match event.hits(cx, self.draw_bg.area()) {
432 Hit::FingerHoverIn(_) => {
433 self.animator_play(cx, id!(hover.on));
434 }
435 Hit::FingerHoverOut(_) => {
436 self.animator_play(cx, id!(hover.off));
437 }
438 Hit::FingerMove(f) => {
439 if f.abs.distance(&f.abs_start) >= self.min_drag_distance {
440 actions.push((node_id, FileTreeNodeAction::ShouldStartDrag));
441 }
442 }
443 Hit::FingerDown(_) => {
444 self.animator_play(cx, id!(select.on));
445 if self.is_folder {
446 if self.animator_in_state(cx, id!(open.on)) {
447 self.animator_play(cx, id!(open.off));
448 actions.push((node_id, FileTreeNodeAction::Closing));
449 }
450 else {
451 self.animator_play(cx, id!(open.on));
452 actions.push((node_id, FileTreeNodeAction::Opening));
453 }
454 }
455 actions.push((node_id, FileTreeNodeAction::WasClicked));
456 }
457 _ => {}
458 }
459 }
460}
461
462impl FileTree {
463
464 pub fn begin(&mut self, cx: &mut Cx2d, walk: Walk) {
465 self.scroll_bars.begin(cx, walk, self.layout);
466 self.count = 0;
467 }
468
469 pub fn end(&mut self, cx: &mut Cx2d) {
470 let height_left = cx.turtle().height_left();
472 let mut walk = 0.0;
473 while walk < height_left {
474 self.count += 1;
475 self.filler.is_even = Self::is_even(self.count);
476 let height = self.node_height.min(height_left - walk);
477 self.filler.draw_walk(cx, Walk::size(Size::Fill, Size::Fixed(height)));
478 walk += height.max(1.0);
479 }
480
481 self.draw_scroll_shadow.draw(cx, dvec2(0., 0.));
482 self.scroll_bars.end(cx);
483
484 let selected_node_id = self.selected_node_id;
485 self.tree_nodes.retain_visible_and( | node_id, _ | Some(*node_id) == selected_node_id);
486 }
487
488 pub fn is_even(count: usize) -> f32 {
489 if count % 2 == 1 {0.0}else {1.0}
490 }
491
492 pub fn should_node_draw(&mut self, cx: &mut Cx2d) -> bool {
493 let scale = self.stack.last().cloned().unwrap_or(1.0);
494 let height = self.node_height * scale;
495 let walk = Walk::size(Size::Fill, Size::Fixed(height));
496 if scale > 0.01 && cx.walk_turtle_would_be_visible(walk) {
497 return true
498 }
499 else {
500 cx.walk_turtle(walk);
501 return false
502 }
503 }
504
505 pub fn begin_folder(
506 &mut self,
507 cx: &mut Cx2d,
508 node_id: LiveId,
509 name: &str,
510 ) -> Result<(), ()> {
511 let scale = self.stack.last().cloned().unwrap_or(1.0);
512
513 if scale > 0.2 {
514 self.count += 1;
515 }
516
517 let is_open = self.open_nodes.contains(&node_id);
518
519 if self.should_node_draw(cx) {
520 let folder_node = self.folder_node;
521 let (tree_node, _) = self.tree_nodes.get_or_insert(cx, node_id, | cx | {
522 let mut tree_node = FileTreeNode::new_from_ptr(cx, folder_node);
523 if is_open {
524 tree_node.set_folder_is_open(cx, true, Animate::No)
525 }
526 (tree_node, live_id!(folder_node))
527 });
528 tree_node.draw_folder(cx, name, Self::is_even(self.count), self.node_height, self.stack.len(), scale);
529 self.stack.push(tree_node.opened as f64 * scale);
530 if tree_node.opened <= 0.001 {
531 self.end_folder();
532 return Err(());
533 }
534 }
535 else {
536 if is_open {
537 self.stack.push(scale * 1.0);
538 }
539 else {
540 return Err(());
541 }
542 }
543 Ok(())
544 }
545
546 pub fn end_folder(&mut self) {
547 self.stack.pop();
548 }
549
550 pub fn file(&mut self, cx: &mut Cx2d, node_id: LiveId, name: &str) {
551 let scale = self.stack.last().cloned().unwrap_or(1.0);
552
553 if scale > 0.2 {
554 self.count += 1;
555 }
556 if self.should_node_draw(cx) {
557 let file_node = self.file_node;
558 let (tree_node, _) = self.tree_nodes.get_or_insert(cx, node_id, | cx | {
559 (FileTreeNode::new_from_ptr(cx, file_node), live_id!(file_node))
560 });
561 tree_node.draw_file(cx, name, Self::is_even(self.count), self.node_height, self.stack.len(), scale);
562 }
563 }
564
565 pub fn forget(&mut self) {
566 self.tree_nodes.clear();
567 }
568
569 pub fn forget_node(&mut self, file_node_id: LiveId) {
570 self.tree_nodes.remove(&file_node_id);
571 }
572
573 pub fn is_folder(&mut self, file_node_id: LiveId)->bool {
574 if let Some((node,_)) = self.tree_nodes.get(&file_node_id){
575 node.is_folder
576 }
577 else{
578 false
579 }
580 }
581
582 pub fn set_folder_is_open(
583 &mut self,
584 cx: &mut Cx,
585 node_id: LiveId,
586 is_open: bool,
587 animate: Animate,
588 ) {
589 if is_open {
590 self.open_nodes.insert(node_id);
591 }
592 else {
593 self.open_nodes.remove(&node_id);
594 }
595 if let Some((tree_node, _)) = self.tree_nodes.get_mut(&node_id) {
596 tree_node.set_folder_is_open(cx, is_open, animate);
597 }
598 }
599
600 pub fn start_dragging_file_node(
601 &mut self,
602 cx: &mut Cx,
603 node_id: LiveId,
604 items: Vec<DragItem>,
605 ) {
606 self.dragging_node_id = Some(node_id);
607
608 log!("makepad: start_dragging_file_node");
609
610 cx.start_dragging(items);
611 }
612}
613
614impl Widget for FileTree {
619
620 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
621 let uid = self.widget_uid();
622
623 self.scroll_bars.handle_event(cx, event, scope);
624
625 match event {
626 Event::DragEnd => self.dragging_node_id = None,
627 _ => ()
628 }
629
630 let mut node_actions = Vec::new();
631
632 for (node_id, (node, _)) in self.tree_nodes.iter_mut() {
633 node.handle_event(cx, event, *node_id, scope, &mut node_actions);
634 }
635
636 for (node_id, node_action) in node_actions {
637 match node_action {
638 FileTreeNodeAction::Opening => {
639 self.open_nodes.insert(node_id);
640 }
641 FileTreeNodeAction::Closing => {
642 self.open_nodes.remove(&node_id);
643 }
644 FileTreeNodeAction::WasClicked => {
645 cx.set_key_focus(self.scroll_bars.area());
646 if let Some(last_selected) = self.selected_node_id {
647 if last_selected != node_id {
648 self.tree_nodes.get_mut(&last_selected).unwrap().0.set_is_selected(cx, false, Animate::Yes);
649 }
650 }
651 self.selected_node_id = Some(node_id);
652 if self.is_folder(node_id){
653 cx.widget_action(uid, &scope.path, FileTreeAction::FolderClicked(node_id));
654 }
655 else{
656 cx.widget_action(uid, &scope.path, FileTreeAction::FileClicked(node_id));
657 }
658 }
659 FileTreeNodeAction::ShouldStartDrag => {
660 if self.dragging_node_id.is_none() {
661 cx.widget_action(uid, &scope.path, FileTreeAction::ShouldFileStartDrag(node_id));
662 }
663 }
664 }
665 }
666
667 match event.hits(cx, self.scroll_bars.area()) {
668 Hit::KeyFocus(_) => {
669 if let Some(node_id) = self.selected_node_id {
670 self.tree_nodes.get_mut(&node_id).unwrap().0.set_is_focussed(cx, true, Animate::Yes);
671 }
672 }
673 Hit::KeyFocusLost(_) => {
674 if let Some(node_id) = self.selected_node_id {
675 self.tree_nodes.get_mut(&node_id).unwrap().0.set_is_focussed(cx, false, Animate::Yes);
676 }
677 }
678 _ => ()
679 }
680 }
681
682 fn draw_walk(&mut self, cx: &mut Cx2d, _scope:&mut Scope,walk: Walk) -> DrawStep {
683 if self.draw_state.begin(cx, ()) {
684 self.begin(cx, walk);
685 return DrawStep::make_step()
686 }
687 if let Some(()) = self.draw_state.get() {
688 self.end(cx);
689 self.draw_state.end();
690 }
691 DrawStep::done()
692 }
693}
694
695impl FileTreeRef{
696 pub fn should_file_start_drag(&self, actions: &Actions) -> Option<LiveId> {
697 if let Some(item) = actions.find_widget_action(self.widget_uid()) {
698 if let FileTreeAction::ShouldFileStartDrag(file_id) = item.cast() {
699 return Some(file_id)
700 }
701 }
702 None
703 }
704
705 pub fn file_clicked(&self, actions: &Actions) -> Option<LiveId> {
706 if let Some(item) = actions.find_widget_action(self.widget_uid()) {
707 if let FileTreeAction::FileClicked(file_id) = item.cast() {
708 return Some(file_id)
709 }
710 }
711 None
712 }
713
714 pub fn folder_clicked(&self, actions: &Actions) -> Option<LiveId> {
715 if let Some(item) = actions.find_widget_action(self.widget_uid()) {
716 if let FileTreeAction::FolderClicked(file_id) = item.cast() {
717 return Some(file_id)
718 }
719 }
720 None
721 }
722
723
724 pub fn set_folder_is_open(
725 &self,
726 cx: &mut Cx,
727 node_id: LiveId,
728 is_open: bool,
729 animate: Animate,
730 ) {
731 if let Some(mut inner) = self.borrow_mut(){
732 inner.set_folder_is_open(cx, node_id, is_open, animate);
733 }
734 }
735
736 pub fn file_start_drag(&self, cx: &mut Cx, _file_id: LiveId, item: DragItem) {
737 cx.start_dragging(vec![item]);
738 }
739}