rg3d_ui/
tree.rs

1//! Tree widget allows you to create views for hierarchical data.
2//!
3//! ## Built-in controls
4//!
5//! Selection works on all mouse buttons, not just left.
6//!
7//! `Ctrl+Click` - enables multi-selection.
8//! `Alt+Click` - prevents selection allowing you to use drag'n'drop.
9
10use crate::{
11    border::BorderBuilder,
12    brush::Brush,
13    button::{Button, ButtonBuilder, ButtonMessage},
14    core::algebra::Vector2,
15    core::{color::Color, pool::Handle},
16    decorator::{DecoratorBuilder, DecoratorMessage},
17    define_constructor,
18    grid::{Column, GridBuilder, Row},
19    message::{MessageDirection, UiMessage},
20    stack_panel::StackPanelBuilder,
21    text::TextMessage,
22    widget::{Widget, WidgetBuilder, WidgetMessage},
23    BuildContext, Control, NodeHandleMapping, Thickness, UiNode, UserInterface, BRUSH_DARK,
24    BRUSH_DARKEST, BRUSH_LIGHT,
25};
26use std::any::{Any, TypeId};
27use std::ops::{Deref, DerefMut};
28
29#[derive(Debug, Copy, Clone, PartialEq)]
30pub struct SelectionState(pub(in crate) bool);
31
32#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)]
33pub enum TreeExpansionStrategy {
34    /// Expand a single item.
35    Direct,
36    /// Expand an item and its descendants.
37    RecursiveDescendants,
38    /// Expand an item and its ancestors (chain of parent trees).
39    RecursiveAncestors,
40}
41
42#[derive(Debug, Clone, PartialEq)]
43pub enum TreeMessage {
44    Expand {
45        expand: bool,
46        expansion_strategy: TreeExpansionStrategy,
47    },
48    AddItem(Handle<UiNode>),
49    RemoveItem(Handle<UiNode>),
50    SetExpanderShown(bool),
51    SetItems(Vec<Handle<UiNode>>),
52    // Private, do not use. For internal needs only. Use TreeRootMessage::Selected.
53    Select(SelectionState),
54}
55
56impl TreeMessage {
57    define_constructor!(TreeMessage:Expand => fn expand(expand: bool, expansion_strategy: TreeExpansionStrategy), layout: false);
58    define_constructor!(TreeMessage:AddItem => fn add_item(Handle<UiNode>), layout: false);
59    define_constructor!(TreeMessage:RemoveItem => fn remove_item(Handle<UiNode>), layout: false);
60    define_constructor!(TreeMessage:SetExpanderShown => fn set_expander_shown(bool), layout: false);
61    define_constructor!(TreeMessage:SetItems => fn set_items(Vec<Handle<UiNode >>), layout: false);
62    define_constructor!(TreeMessage:Select => fn select(SelectionState), layout: false);
63}
64
65#[derive(Debug, Clone, PartialEq)]
66pub enum TreeRootMessage {
67    AddItem(Handle<UiNode>),
68    RemoveItem(Handle<UiNode>),
69    Items(Vec<Handle<UiNode>>),
70    Selected(Vec<Handle<UiNode>>),
71    ExpandAll,
72    CollapseAll,
73}
74
75impl TreeRootMessage {
76    define_constructor!(TreeRootMessage:AddItem => fn add_item(Handle<UiNode>), layout: false);
77    define_constructor!(TreeRootMessage:RemoveItem=> fn remove_item(Handle<UiNode>), layout: false);
78    define_constructor!(TreeRootMessage:Items => fn items(Vec<Handle<UiNode >>), layout: false);
79    define_constructor!(TreeRootMessage:Selected => fn select(Vec<Handle<UiNode >>), layout: false);
80    define_constructor!(TreeRootMessage:ExpandAll => fn expand_all(), layout: false);
81    define_constructor!(TreeRootMessage:CollapseAll => fn collapse_all(), layout: false);
82}
83
84#[derive(Debug, Clone)]
85pub struct Tree {
86    widget: Widget,
87    expander: Handle<UiNode>,
88    content: Handle<UiNode>,
89    panel: Handle<UiNode>,
90    is_expanded: bool,
91    background: Handle<UiNode>,
92    items: Vec<Handle<UiNode>>,
93    is_selected: bool,
94    always_show_expander: bool,
95}
96
97crate::define_widget_deref!(Tree);
98
99impl Control for Tree {
100    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
101        if type_id == TypeId::of::<Self>() {
102            Some(self)
103        } else {
104            None
105        }
106    }
107
108    fn resolve(&mut self, node_map: &NodeHandleMapping) {
109        node_map.resolve(&mut self.content);
110        node_map.resolve(&mut self.expander);
111        node_map.resolve(&mut self.panel);
112        node_map.resolve(&mut self.background);
113    }
114
115    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
116        let size = self.widget.arrange_override(ui, final_size);
117
118        let expander_visibility = !self.items.is_empty() || self.always_show_expander;
119        ui.send_message(WidgetMessage::visibility(
120            self.expander,
121            MessageDirection::ToWidget,
122            expander_visibility,
123        ));
124
125        size
126    }
127
128    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
129        self.widget.handle_routed_message(ui, message);
130
131        if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
132            if message.destination() == self.expander {
133                ui.send_message(TreeMessage::expand(
134                    self.handle(),
135                    MessageDirection::ToWidget,
136                    !self.is_expanded,
137                    TreeExpansionStrategy::Direct,
138                ));
139            }
140        } else if let Some(WidgetMessage::MouseDown { .. }) = message.data::<WidgetMessage>() {
141            if !message.handled() {
142                let keyboard_modifiers = ui.keyboard_modifiers();
143                // Prevent selection changes by Alt+Click to be able to drag'n'drop tree items.
144                if !keyboard_modifiers.alt {
145                    if let Some((tree_root_handle, tree_root)) =
146                        ui.try_borrow_by_type_up::<TreeRoot>(self.parent())
147                    {
148                        let selection = if keyboard_modifiers.control {
149                            let mut selection = tree_root.selected.clone();
150                            if let Some(existing) = selection.iter().position(|&h| h == self.handle)
151                            {
152                                selection.remove(existing);
153                            } else {
154                                selection.push(self.handle);
155                            }
156                            Some(selection)
157                        } else if !self.is_selected {
158                            Some(vec![self.handle()])
159                        } else {
160                            None
161                        };
162                        if let Some(selection) = selection {
163                            ui.send_message(TreeRootMessage::select(
164                                tree_root_handle,
165                                MessageDirection::ToWidget,
166                                selection,
167                            ));
168                        }
169                        message.set_handled(true);
170                    }
171                }
172            }
173        } else if let Some(msg) = message.data::<TreeMessage>() {
174            if message.destination() == self.handle() {
175                match msg {
176                    &TreeMessage::Expand {
177                        expand,
178                        expansion_strategy,
179                    } => {
180                        self.is_expanded = expand;
181                        ui.send_message(WidgetMessage::visibility(
182                            self.panel,
183                            MessageDirection::ToWidget,
184                            self.is_expanded,
185                        ));
186                        if let Some(expander) = ui.node(self.expander).cast::<Button>() {
187                            let content = expander.content();
188                            let text = if expand { "-" } else { "+" };
189                            ui.send_message(TextMessage::text(
190                                content,
191                                MessageDirection::ToWidget,
192                                text.to_owned(),
193                            ));
194                        }
195
196                        match expansion_strategy {
197                            TreeExpansionStrategy::RecursiveDescendants => {
198                                for &item in self.items() {
199                                    ui.send_message(TreeMessage::expand(
200                                        item,
201                                        MessageDirection::ToWidget,
202                                        expand,
203                                        expansion_strategy,
204                                    ));
205                                }
206                            }
207                            TreeExpansionStrategy::RecursiveAncestors => {
208                                // CAVEAT: This may lead to potential false expansions when there are
209                                // trees inside trees (this is insane, but possible) because we're searching
210                                // up on visual tree and don't care about search bounds, ideally we should
211                                // stop search if we're found TreeRoot.
212                                let parent_tree =
213                                    self.find_by_criteria_up(ui, |n| n.cast::<Tree>().is_some());
214                                if parent_tree.is_some() {
215                                    ui.send_message(TreeMessage::expand(
216                                        parent_tree,
217                                        MessageDirection::ToWidget,
218                                        expand,
219                                        expansion_strategy,
220                                    ));
221                                }
222                            }
223                            TreeExpansionStrategy::Direct => {
224                                // Handle this variant too instead of using _ => (),
225                                // to force compiler to notify if new strategy is added.
226                            }
227                        }
228                    }
229                    &TreeMessage::SetExpanderShown(show) => {
230                        self.always_show_expander = show;
231                        self.invalidate_arrange();
232                    }
233                    &TreeMessage::AddItem(item) => {
234                        ui.send_message(WidgetMessage::link(
235                            item,
236                            MessageDirection::ToWidget,
237                            self.panel,
238                        ));
239
240                        self.items.push(item);
241                    }
242                    &TreeMessage::RemoveItem(item) => {
243                        if let Some(pos) = self.items.iter().position(|&i| i == item) {
244                            ui.send_message(WidgetMessage::remove(
245                                item,
246                                MessageDirection::ToWidget,
247                            ));
248                            self.items.remove(pos);
249                        }
250                    }
251                    TreeMessage::SetItems(items) => {
252                        for &item in self.items.iter() {
253                            ui.send_message(WidgetMessage::remove(
254                                item,
255                                MessageDirection::ToWidget,
256                            ));
257                        }
258                        for &item in items {
259                            ui.send_message(WidgetMessage::link(
260                                item,
261                                MessageDirection::ToWidget,
262                                self.panel,
263                            ));
264                        }
265                        self.items = items.clone();
266                    }
267                    &TreeMessage::Select(state) => {
268                        if self.is_selected != state.0 {
269                            self.is_selected = state.0;
270                            ui.send_message(DecoratorMessage::select(
271                                self.background,
272                                MessageDirection::ToWidget,
273                                self.is_selected,
274                            ));
275                        }
276                    }
277                }
278            }
279        }
280    }
281}
282
283impl Tree {
284    pub fn content(&self) -> Handle<UiNode> {
285        self.content
286    }
287
288    pub fn back(&self) -> Handle<UiNode> {
289        self.background
290    }
291
292    pub fn items(&self) -> &[Handle<UiNode>] {
293        &self.items
294    }
295
296    /// Adds new item to given tree. This method is meant to be used only on widget build stage,
297    /// any runtime actions should be done via messages.
298    pub fn add_item(tree: Handle<UiNode>, item: Handle<UiNode>, ctx: &mut BuildContext) {
299        if let Some(tree) = ctx[tree].cast_mut::<Tree>() {
300            tree.items.push(item);
301            let panel = tree.panel;
302            ctx.link(item, panel);
303        }
304    }
305
306    pub fn expanded(&self) -> bool {
307        self.is_expanded
308    }
309
310    pub fn expander_shown(&self) -> bool {
311        self.always_show_expander
312    }
313}
314
315pub struct TreeBuilder {
316    widget_builder: WidgetBuilder,
317    items: Vec<Handle<UiNode>>,
318    content: Handle<UiNode>,
319    is_expanded: bool,
320    always_show_expander: bool,
321    back: Option<Handle<UiNode>>,
322}
323
324impl TreeBuilder {
325    pub fn new(widget_builder: WidgetBuilder) -> Self {
326        Self {
327            widget_builder,
328            items: Default::default(),
329            content: Default::default(),
330            is_expanded: true,
331            always_show_expander: false,
332            back: None,
333        }
334    }
335
336    pub fn with_items(mut self, items: Vec<Handle<UiNode>>) -> Self {
337        self.items = items;
338        self
339    }
340
341    pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
342        self.content = content;
343        self
344    }
345
346    pub fn with_expanded(mut self, expanded: bool) -> Self {
347        self.is_expanded = expanded;
348        self
349    }
350
351    pub fn with_always_show_expander(mut self, state: bool) -> Self {
352        self.always_show_expander = state;
353        self
354    }
355
356    pub fn with_back(mut self, back: Handle<UiNode>) -> Self {
357        self.back = Some(back);
358        self
359    }
360
361    pub fn build_tree(self, ctx: &mut BuildContext) -> Tree {
362        let expander = build_expander_button(
363            self.always_show_expander,
364            !self.items.is_empty(),
365            self.is_expanded,
366            ctx,
367        );
368
369        if self.content.is_some() {
370            ctx[self.content].set_row(0).set_column(1);
371        };
372
373        let internals = GridBuilder::new(
374            WidgetBuilder::new()
375                .on_column(0)
376                .on_row(0)
377                .with_margin(Thickness {
378                    left: 1.0,
379                    top: 1.0,
380                    right: 0.0,
381                    bottom: 1.0,
382                })
383                .with_child(expander)
384                .with_child(self.content),
385        )
386        .add_column(Column::auto())
387        .add_column(Column::stretch())
388        .add_row(Row::strict(20.0))
389        .build(ctx);
390
391        let item_background = self.back.unwrap_or_else(|| {
392            DecoratorBuilder::new(BorderBuilder::new(
393                WidgetBuilder::new()
394                    .with_foreground(BRUSH_LIGHT)
395                    .with_background(Brush::Solid(Color::TRANSPARENT)),
396            ))
397            .with_selected_brush(BRUSH_DARKEST)
398            .with_hover_brush(BRUSH_DARK)
399            .with_normal_brush(Brush::Solid(Color::TRANSPARENT))
400            .with_pressed_brush(Brush::Solid(Color::TRANSPARENT))
401            .with_pressable(false)
402            .build(ctx)
403        });
404
405        ctx.link(internals, item_background);
406
407        let panel;
408        let grid = GridBuilder::new(
409            WidgetBuilder::new()
410                .with_child(item_background)
411                .with_child({
412                    panel = StackPanelBuilder::new(
413                        WidgetBuilder::new()
414                            .on_row(1)
415                            .on_column(0)
416                            .with_margin(Thickness::left(15.0))
417                            .with_visibility(self.is_expanded)
418                            .with_children(self.items.iter().cloned()),
419                    )
420                    .build(ctx);
421                    panel
422                }),
423        )
424        .add_column(Column::auto())
425        .add_row(Row::strict(24.0))
426        .add_row(Row::stretch())
427        .build(ctx);
428
429        Tree {
430            widget: self
431                .widget_builder
432                .with_allow_drag(true)
433                .with_allow_drop(true)
434                .with_child(grid)
435                .build(),
436            content: self.content,
437            panel,
438            is_expanded: self.is_expanded,
439            expander,
440            background: item_background,
441            items: self.items,
442            is_selected: false,
443            always_show_expander: self.always_show_expander,
444        }
445    }
446
447    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
448        let tree = self.build_tree(ctx);
449        ctx.add_node(UiNode::new(tree))
450    }
451}
452
453fn build_expander_button(
454    always_show_expander: bool,
455    items_populated: bool,
456    is_expanded: bool,
457    ctx: &mut BuildContext,
458) -> Handle<UiNode> {
459    ButtonBuilder::new(
460        WidgetBuilder::new()
461            .with_width(20.0)
462            .with_visibility(always_show_expander || items_populated)
463            .on_row(0)
464            .on_column(0),
465    )
466    .with_text(if is_expanded { "-" } else { "+" })
467    .build(ctx)
468}
469
470#[derive(Debug, Clone)]
471pub struct TreeRoot {
472    widget: Widget,
473    panel: Handle<UiNode>,
474    items: Vec<Handle<UiNode>>,
475    selected: Vec<Handle<UiNode>>,
476}
477
478crate::define_widget_deref!(TreeRoot);
479
480impl Control for TreeRoot {
481    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
482        if type_id == TypeId::of::<Self>() {
483            Some(self)
484        } else {
485            None
486        }
487    }
488
489    fn resolve(&mut self, node_map: &NodeHandleMapping) {
490        node_map.resolve(&mut self.panel);
491        node_map.resolve_slice(&mut self.selected);
492    }
493
494    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
495        self.widget.handle_routed_message(ui, message);
496
497        if let Some(msg) = message.data::<TreeRootMessage>() {
498            if message.destination() == self.handle()
499                && message.direction() == MessageDirection::ToWidget
500            {
501                match msg {
502                    &TreeRootMessage::AddItem(item) => {
503                        ui.send_message(WidgetMessage::link(
504                            item,
505                            MessageDirection::ToWidget,
506                            self.panel,
507                        ));
508
509                        self.items.push(item);
510                    }
511                    &TreeRootMessage::RemoveItem(item) => {
512                        if let Some(pos) = self.items.iter().position(|&i| i == item) {
513                            ui.send_message(WidgetMessage::remove(
514                                item,
515                                MessageDirection::ToWidget,
516                            ));
517                            self.items.remove(pos);
518                        }
519                    }
520                    TreeRootMessage::Items(items) => {
521                        for &item in self.items.iter() {
522                            ui.send_message(WidgetMessage::remove(
523                                item,
524                                MessageDirection::ToWidget,
525                            ));
526                        }
527                        for &item in items {
528                            ui.send_message(WidgetMessage::link(
529                                item,
530                                MessageDirection::ToWidget,
531                                self.panel,
532                            ));
533                        }
534                        self.items = items.to_vec();
535                    }
536                    TreeRootMessage::Selected(selected) => {
537                        if &self.selected != selected {
538                            let mut stack = self.children().to_vec();
539                            while let Some(handle) = stack.pop() {
540                                let node = ui.node(handle);
541                                stack.extend_from_slice(node.children());
542
543                                let new_selection_state = if selected.contains(&handle) {
544                                    SelectionState(true)
545                                } else {
546                                    SelectionState(false)
547                                };
548
549                                if let Some(tree_ref) = node
550                                    .query_component(TypeId::of::<Tree>())
551                                    .and_then(|tree_ref| tree_ref.downcast_ref::<Tree>())
552                                {
553                                    if tree_ref.is_selected != new_selection_state.0 {
554                                        ui.send_message(TreeMessage::select(
555                                            handle,
556                                            MessageDirection::ToWidget,
557                                            new_selection_state,
558                                        ));
559                                    }
560                                }
561                            }
562                            self.selected = selected.clone();
563                            ui.send_message(message.reverse());
564                        }
565                    }
566                    TreeRootMessage::CollapseAll => {
567                        self.expand_all(ui, false);
568                    }
569                    TreeRootMessage::ExpandAll => {
570                        self.expand_all(ui, true);
571                    }
572                }
573            }
574        }
575    }
576}
577
578impl TreeRoot {
579    pub fn items(&self) -> &[Handle<UiNode>] {
580        &self.items
581    }
582
583    fn expand_all(&self, ui: &UserInterface, expand: bool) {
584        for &item in self.items.iter() {
585            ui.send_message(TreeMessage::expand(
586                item,
587                MessageDirection::ToWidget,
588                expand,
589                TreeExpansionStrategy::RecursiveDescendants,
590            ));
591        }
592    }
593}
594
595pub struct TreeRootBuilder {
596    widget_builder: WidgetBuilder,
597    items: Vec<Handle<UiNode>>,
598}
599
600impl TreeRootBuilder {
601    pub fn new(widget_builder: WidgetBuilder) -> Self {
602        Self {
603            widget_builder,
604            items: Default::default(),
605        }
606    }
607
608    pub fn with_items(mut self, items: Vec<Handle<UiNode>>) -> Self {
609        self.items = items;
610        self
611    }
612
613    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
614        let panel =
615            StackPanelBuilder::new(WidgetBuilder::new().with_children(self.items.iter().cloned()))
616                .build(ctx);
617
618        let tree = TreeRoot {
619            widget: self.widget_builder.with_child(panel).build(),
620            panel,
621            items: self.items,
622            selected: Default::default(),
623        };
624
625        ctx.add_node(UiNode::new(tree))
626    }
627}