fyrox_ui/tree.rs
1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Tree widget allows you to create views for hierarchical data. See [`Tree`] docs for more info
22//! and usage examples.
23
24#![warn(missing_docs)]
25
26use crate::{
27 border::BorderBuilder,
28 brush::Brush,
29 check_box::{CheckBoxBuilder, CheckBoxMessage},
30 core::{
31 algebra::Vector2, color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
32 visitor::prelude::*,
33 },
34 decorator::{DecoratorBuilder, DecoratorMessage},
35 grid::{Column, GridBuilder, Row},
36 message::KeyCode,
37 message::{MessageDirection, UiMessage},
38 stack_panel::StackPanelBuilder,
39 style::resource::StyleResourceExt,
40 style::Style,
41 utils::{make_arrow, ArrowDirection},
42 widget::{Widget, WidgetBuilder, WidgetMessage},
43 BuildContext, Control, MouseButton, Thickness, UiNode, UserInterface, VerticalAlignment,
44};
45
46use crate::check_box::CheckBox;
47use crate::message::MessageData;
48use crate::stack_panel::StackPanel;
49use fyrox_core::pool::{HandlesVecExtension, ObjectOrVariant};
50use fyrox_core::uuid_provider;
51use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
52use fyrox_graph::SceneGraph;
53use std::collections::VecDeque;
54
55/// Opaque selection state of a tree.
56#[derive(Debug, Copy, Clone, PartialEq, Eq)]
57pub struct SelectionState(pub(crate) bool);
58
59/// Expansion strategy for a hierarchical structure.
60#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)]
61pub enum TreeExpansionStrategy {
62 /// Expand a single item.
63 Direct,
64 /// Expand an item and its descendants.
65 RecursiveDescendants,
66 /// Expand an item and its ancestors (chain of parent trees).
67 RecursiveAncestors,
68}
69
70/// A set of messages, that could be used to alternate the state of a [`Tree`] widget.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum TreeMessage {
73 /// A message, that is used to expand a tree. Exact expansion behavior depends on the expansion
74 /// strategy (see [`TreeExpansionStrategy`] docs for more info).
75 Expand {
76 /// Expand (`true`) or collapse (`false`) a tree.
77 expand: bool,
78 /// Expansion strategy.
79 expansion_strategy: TreeExpansionStrategy,
80 },
81 /// A message, that is used to add an item to a tree.
82 AddItem(Handle<Tree>),
83 /// A message, that is used to remove an item from a tree.
84 RemoveItem(Handle<Tree>),
85 /// A message, that is used to prevent expander from being hidden when a tree does not have
86 /// any child items.
87 ExpanderVisible(bool),
88 /// A message, that is used to specify a new set of children items of a tree.
89 SetItems {
90 /// A set of handles to new tree items.
91 items: Vec<Handle<Tree>>,
92 /// A flag, that defines whether the previous items should be deleted or not. `false` is
93 /// usually used to reorder existing items.
94 remove_previous: bool,
95 },
96 // Private, do not use. For internal needs only. Use TreeRootMessage::Selected.
97 #[doc(hidden)]
98 Select(SelectionState),
99}
100impl MessageData for TreeMessage {}
101
102/// A set of messages, that could be used to alternate the state of a [`TreeRoot`] widget.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum TreeRootMessage {
105 /// A message, that is used to add a child item to a tree root.
106 AddItem(Handle<Tree>),
107 /// A message, that is used to remove a child item from a tree root.
108 RemoveItem(Handle<Tree>),
109 /// A message, that is used to specify a new set of children items of a tree root.
110 Items(Vec<Handle<Tree>>),
111 /// A message, that it is used to fetch or set current selection of a tree root.
112 Select(Vec<Handle<Tree>>),
113 /// A message, that is used to expand all descendant trees in the hierarchy.
114 ExpandAll,
115 /// A message, that is used to collapse all descendant trees in the hierarchy.
116 CollapseAll,
117 /// A message, that is used as a notification when tree root's items has changed.
118 ItemsChanged,
119}
120impl MessageData for TreeRootMessage {}
121
122/// Tree widget allows you to create views for hierarchical data. It could be used to show file
123/// system entries, graphs, and anything else that could be represented as a tree.
124///
125/// ## Examples
126///
127/// A simple tree with one root and two children items could be created like so:
128///
129/// ```rust
130/// # use fyrox_ui::{
131/// # core::pool::Handle,
132/// # text::TextBuilder,
133/// # tree::{TreeBuilder, TreeRootBuilder},
134/// # widget::WidgetBuilder,
135/// # BuildContext, UiNode,
136/// # };
137/// # use fyrox_ui::tree::TreeRoot;
138/// #
139/// fn create_tree(ctx: &mut BuildContext) -> Handle<TreeRoot> {
140/// // Note, that `TreeRoot` widget is mandatory here. Otherwise, some functionality of
141/// // descendant trees won't work.
142/// TreeRootBuilder::new(WidgetBuilder::new())
143/// .with_items(vec![TreeBuilder::new(WidgetBuilder::new())
144/// .with_content(
145/// TextBuilder::new(WidgetBuilder::new())
146/// .with_text("Root Item 0")
147/// .build(ctx),
148/// )
149/// .with_items(vec![
150/// TreeBuilder::new(WidgetBuilder::new())
151/// .with_content(
152/// TextBuilder::new(WidgetBuilder::new())
153/// .with_text("Child Item 0")
154/// .build(ctx),
155/// )
156/// .build(ctx),
157/// TreeBuilder::new(WidgetBuilder::new())
158/// .with_content(
159/// TextBuilder::new(WidgetBuilder::new())
160/// .with_text("Child Item 1")
161/// .build(ctx),
162/// )
163/// .build(ctx),
164/// ])
165/// .build(ctx)])
166/// .build(ctx)
167/// }
168/// ```
169///
170/// Note, that `TreeRoot` widget is mandatory here. Otherwise, some functionality of descendant trees
171/// won't work (primarily - selection). See [`TreeRoot`] docs for more detailed explanation.
172///
173/// ## Built-in controls
174///
175/// Tree widget is a rich control element, which has its own set of controls:
176///
177/// `Any Mouse Button` - select.
178/// `Ctrl+Click` - enables multi-selection.
179/// `Alt+Click` - prevents selection allowing you to use drag'n'drop.
180/// `Shift+Click` - select a span of items.
181/// `ArrowUp` - navigate up from the topmost selection.
182/// `ArrowDown` - navigate down from the lowermost selection.
183/// `ArrowRight` - expand the selected item (first from the selection) or (if it is expanded), go
184/// down the tree.
185/// `ArrowLeft` - collapse the selected item or (if it is collapsed), go up the tree.
186///
187/// ## Adding Items
188///
189/// An item could be added to a tree using [`TreeMessage::AddItem`] message like so:
190///
191/// ```rust
192/// # use fyrox_ui::{
193/// # core::pool::Handle,
194/// # message::MessageDirection,
195/// # text::TextBuilder,
196/// # tree::{TreeBuilder, TreeMessage},
197/// # widget::WidgetBuilder,
198/// # UiNode, UserInterface,
199/// # };
200/// #
201/// fn add_item(tree: Handle<UiNode>, ui: &mut UserInterface) {
202/// let ctx = &mut ui.build_ctx();
203///
204/// let item = TreeBuilder::new(WidgetBuilder::new())
205/// .with_content(
206/// TextBuilder::new(WidgetBuilder::new())
207/// .with_text("Some New Item")
208/// .build(ctx),
209/// )
210/// .build(ctx);
211///
212/// ui.send(tree, TreeMessage::AddItem(item));
213/// }
214/// ```
215///
216/// ## Removing Items
217///
218/// An item could be removed from a tree using [`TreeMessage::RemoveItem`] message like so:
219///
220/// ```rust
221/// # use fyrox_ui::{
222/// # core::pool::Handle, message::MessageDirection, tree::TreeMessage, UiNode, UserInterface,
223/// # };
224/// # use fyrox_ui::tree::Tree;
225/// #
226/// fn remove_item(tree: Handle<UiNode>, item_to_remove: Handle<Tree>, ui: &UserInterface) {
227/// // Note that the `ui` is borrowed as immutable here, which means that the item will **not**
228/// // be removed immediately, but on the next update call.
229/// ui.send(tree, TreeMessage::RemoveItem(item_to_remove));
230/// }
231/// ```
232///
233/// ## Setting New Items
234///
235/// Tree items could be changed all at once using the [`TreeMessage::SetItems`] message like so:
236///
237/// ```rust
238/// # use fyrox_ui::{
239/// # core::pool::Handle,
240/// # message::MessageDirection,
241/// # text::TextBuilder,
242/// # tree::{TreeBuilder, TreeMessage},
243/// # widget::WidgetBuilder,
244/// # UiNode, UserInterface,
245/// # };
246/// #
247/// fn set_items(tree: Handle<UiNode>, ui: &mut UserInterface) {
248/// let ctx = &mut ui.build_ctx();
249///
250/// let items = vec![
251/// TreeBuilder::new(WidgetBuilder::new())
252/// .with_content(
253/// TextBuilder::new(WidgetBuilder::new())
254/// .with_text("Item 0")
255/// .build(ctx),
256/// )
257/// .build(ctx),
258/// TreeBuilder::new(WidgetBuilder::new())
259/// .with_content(
260/// TextBuilder::new(WidgetBuilder::new())
261/// .with_text("Item 1")
262/// .build(ctx),
263/// )
264/// .build(ctx),
265/// ];
266///
267/// ui.send(tree, TreeMessage::SetItems{
268/// items,
269/// // A flag, that tells that the UI system must destroy previous items first.
270/// remove_previous: true,
271/// });
272/// }
273/// ```
274///
275/// ## Expanding Items
276///
277/// It is possible to expand/collapse trees at runtime using [`TreeMessage::Expand`] message. It provides
278/// different expansion strategies, see docs for [`TreeExpansionStrategy`] for more info. Tree expansion
279/// could useful to highlight something visually.
280///
281/// ```rust
282/// # use fyrox_ui::{
283/// # core::pool::Handle,
284/// # message::MessageDirection,
285/// # tree::{TreeExpansionStrategy, TreeMessage},
286/// # UiNode, UserInterface,
287/// # };
288/// #
289/// fn expand_tree(tree: Handle<UiNode>, ui: &UserInterface) {
290/// ui.send(tree, TreeMessage::Expand{
291/// expand: true,
292/// expansion_strategy: TreeExpansionStrategy::RecursiveAncestors,
293/// });
294/// }
295/// ```
296#[derive(Default, Debug, Clone, Visit, Reflect, ComponentProvider)]
297#[reflect(derived_type = "UiNode")]
298pub struct Tree {
299 /// Base widget of the tree.
300 pub widget: Widget,
301 /// Current expander of the tree. Usually, it is just a handle of CheckBox widget.
302 pub expander: Handle<CheckBox>,
303 /// Current content of the tree.
304 pub content: Handle<UiNode>,
305 /// Current layout panel, that used to arrange children items.
306 pub panel: Handle<StackPanel>,
307 /// A flag, that indicates whether the tree is expanded or not.
308 pub is_expanded: bool,
309 /// Current background widget of the tree.
310 pub background: Handle<UiNode>,
311 /// Current set of items of the tree.
312 pub items: Vec<Handle<Tree>>,
313 /// A flag, that defines whether the tree is selected or not.
314 pub is_selected: bool,
315 /// A flag, that defines whether the tree should always show its expander, even if there's no
316 /// children elements, or not.
317 pub always_show_expander: bool,
318}
319
320impl ConstructorProvider<UiNode, UserInterface> for Tree {
321 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
322 GraphNodeConstructor::new::<Self>()
323 .with_variant("Tree", |ui| {
324 TreeBuilder::new(WidgetBuilder::new().with_name("Tree"))
325 .build(&mut ui.build_ctx())
326 .to_base()
327 .into()
328 })
329 .with_group("Visual")
330 }
331}
332
333crate::define_widget_deref!(Tree);
334
335uuid_provider!(Tree = "e090e913-393a-4192-a220-e1d87e272170");
336
337impl Control for Tree {
338 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
339 let size = self.widget.arrange_override(ui, final_size);
340
341 let expander_visibility = !self.items.is_empty() || self.always_show_expander;
342 ui.send(
343 self.expander,
344 WidgetMessage::Visibility(expander_visibility),
345 );
346
347 size
348 }
349
350 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
351 self.widget.handle_routed_message(ui, message);
352
353 if let Some(CheckBoxMessage::Check(Some(expanded))) = message.data() {
354 if message.destination() == self.expander
355 && message.direction == MessageDirection::FromWidget
356 {
357 ui.send(
358 self.handle(),
359 TreeMessage::Expand {
360 expand: *expanded,
361 expansion_strategy: TreeExpansionStrategy::Direct,
362 },
363 );
364 }
365 } else if let Some(msg) = message.data::<WidgetMessage>() {
366 if !message.handled() {
367 match msg {
368 WidgetMessage::MouseDown { .. } => {
369 let keyboard_modifiers = ui.keyboard_modifiers();
370 // Prevent selection changes by Alt+Click to be able to drag'n'drop tree items.
371 if !keyboard_modifiers.alt {
372 if let Some((tree_root_handle, tree_root)) =
373 ui.find_component_up::<TreeRoot>(self.parent())
374 {
375 let selection = if keyboard_modifiers.control {
376 let mut selection = tree_root.selected.clone();
377 if let Some(existing) =
378 selection.iter().position(|&h| self.handle == h)
379 {
380 selection.remove(existing);
381 } else {
382 selection.push(self.handle.to_variant());
383 }
384 Some(selection)
385 } else if keyboard_modifiers.shift {
386 // Select range.
387 let mut first_position = None;
388 let mut this_position = None;
389 let mut flat_hierarchy = Vec::<Handle<Tree>>::new();
390
391 fn visit_widget(
392 this_tree: &Tree,
393 handle: Handle<UiNode>,
394 ui: &UserInterface,
395 selection: &[Handle<Tree>],
396 hierarchy: &mut Vec<Handle<Tree>>,
397 first_position: &mut Option<usize>,
398 this_position: &mut Option<usize>,
399 ) {
400 let node = if handle == this_tree.handle {
401 *this_position = Some(hierarchy.len());
402
403 hierarchy.push(handle.to_variant());
404
405 &this_tree.widget
406 } else {
407 let node = ui.node(handle);
408
409 if let Some(first) = selection.first() {
410 if handle == *first {
411 *first_position = Some(hierarchy.len());
412 }
413 }
414
415 if node.query_component::<Tree>().is_some() {
416 hierarchy.push(handle.to_variant());
417 }
418
419 node
420 };
421
422 for &child in node.children() {
423 visit_widget(
424 this_tree,
425 child,
426 ui,
427 selection,
428 hierarchy,
429 first_position,
430 this_position,
431 );
432 }
433 }
434
435 visit_widget(
436 self,
437 tree_root_handle,
438 ui,
439 &tree_root.selected,
440 &mut flat_hierarchy,
441 &mut first_position,
442 &mut this_position,
443 );
444
445 if let (Some(this_position), Some(first_position)) =
446 (this_position, first_position)
447 {
448 Some(if first_position < this_position {
449 flat_hierarchy[first_position..=this_position].to_vec()
450 } else {
451 flat_hierarchy[this_position..=first_position].to_vec()
452 })
453 } else {
454 Some(vec![])
455 }
456 } else if !self.is_selected {
457 Some(vec![self.handle().to_variant()])
458 } else {
459 None
460 };
461 if let Some(selection) = selection {
462 ui.send(tree_root_handle, TreeRootMessage::Select(selection));
463 }
464 message.set_handled(true);
465 }
466 }
467 }
468 WidgetMessage::DoubleClick { button } => {
469 if *button == MouseButton::Left {
470 // Mimic click on expander button to have uniform behavior.
471 ui.send(
472 self.expander,
473 CheckBoxMessage::Check(Some(!self.is_expanded)),
474 );
475
476 message.set_handled(true);
477 }
478 }
479 _ => (),
480 }
481 }
482 } else if let Some(msg) = message.data::<TreeMessage>() {
483 if message.destination() == self.handle() {
484 match msg {
485 &TreeMessage::Expand {
486 expand,
487 expansion_strategy,
488 } => {
489 self.is_expanded = expand;
490
491 ui.send(self.panel, WidgetMessage::Visibility(self.is_expanded));
492 ui.send(self.expander, CheckBoxMessage::Check(Some(expand)));
493
494 match expansion_strategy {
495 TreeExpansionStrategy::RecursiveDescendants => {
496 for &item in &self.items {
497 ui.send(
498 item,
499 TreeMessage::Expand {
500 expand,
501 expansion_strategy,
502 },
503 );
504 }
505 }
506 TreeExpansionStrategy::RecursiveAncestors => {
507 // CAVEAT: This may lead to potential false expansions when there are
508 // trees inside trees (this is insane, but possible) because we're searching
509 // up on visual tree and don't care about search bounds, ideally we should
510 // stop search if we're found TreeRoot.
511 let parent_tree =
512 self.find_by_criteria_up(ui, |n| n.cast::<Tree>().is_some());
513 if parent_tree.is_some() {
514 ui.send(
515 parent_tree,
516 TreeMessage::Expand {
517 expand,
518 expansion_strategy,
519 },
520 );
521 }
522 }
523 TreeExpansionStrategy::Direct => {
524 // Handle this variant too instead of using _ => (),
525 // to force compiler to notify if new strategy is added.
526 }
527 }
528 }
529 &TreeMessage::ExpanderVisible(show) => {
530 self.always_show_expander = show;
531 self.invalidate_arrange();
532 }
533 &TreeMessage::AddItem(item) => {
534 ui.send(item, WidgetMessage::link_with(self.panel));
535 self.items.push(item);
536 }
537 &TreeMessage::RemoveItem(item) => {
538 if let Some(pos) = self.items.iter().position(|&i| i == item) {
539 ui.send(item, WidgetMessage::Remove);
540 self.items.remove(pos);
541 }
542 }
543 TreeMessage::SetItems {
544 items,
545 remove_previous,
546 } => {
547 if *remove_previous {
548 for &item in self.items.iter() {
549 ui.send(item, WidgetMessage::Remove);
550 }
551 }
552 for &item in items {
553 ui.send(item, WidgetMessage::link_with(self.panel));
554 }
555 self.items.clone_from(items);
556 }
557 &TreeMessage::Select(state) => {
558 if self.is_selected != state.0 {
559 self.is_selected = state.0;
560 ui.send(self.background, DecoratorMessage::Select(self.is_selected));
561 }
562 }
563 }
564 }
565 }
566 }
567}
568
569impl Tree {
570 /// Adds new item to given tree. This method is meant to be used only on widget build stage,
571 /// any runtime actions should be done via messages.
572 pub fn add_item(tree: Handle<Tree>, item: Handle<Tree>, ctx: &mut BuildContext) {
573 if let Ok(tree) = ctx.inner_mut().try_get_mut(tree) {
574 tree.items.push(item);
575 let panel = tree.panel;
576 ctx.link(item, panel);
577 }
578 }
579}
580
581/// Tree builder creates [`Tree`] widget instances and adds them to the user interface.
582pub struct TreeBuilder {
583 widget_builder: WidgetBuilder,
584 items: Vec<Handle<Tree>>,
585 content: Handle<UiNode>,
586 is_expanded: bool,
587 always_show_expander: bool,
588 back: Option<Handle<UiNode>>,
589}
590
591impl TreeBuilder {
592 /// Creates a new tree builder instance.
593 pub fn new(widget_builder: WidgetBuilder) -> Self {
594 Self {
595 widget_builder,
596 items: Default::default(),
597 content: Default::default(),
598 is_expanded: true,
599 always_show_expander: false,
600 back: None,
601 }
602 }
603
604 /// Sets the desired children items of the tree.
605 pub fn with_items(mut self, items: Vec<Handle<Tree>>) -> Self {
606 self.items = items;
607 self
608 }
609
610 /// Sets the desired content of the tree.
611 pub fn with_content(mut self, content: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
612 self.content = content.to_base();
613 self
614 }
615
616 /// Sets the desired expansion state of the tree.
617 pub fn with_expanded(mut self, expanded: bool) -> Self {
618 self.is_expanded = expanded;
619 self
620 }
621
622 /// Sets whether the tree should always show its expander, no matter if it has children items or
623 /// not.
624 pub fn with_always_show_expander(mut self, state: bool) -> Self {
625 self.always_show_expander = state;
626 self
627 }
628
629 /// Sets the desired background of the tree.
630 pub fn with_back(mut self, back: Handle<UiNode>) -> Self {
631 self.back = Some(back);
632 self
633 }
634
635 /// Builds the tree widget, but does not add it to the user interface.
636 pub fn build_tree(self, ctx: &mut BuildContext) -> Tree {
637 let expander = build_expander(
638 self.always_show_expander,
639 !self.items.is_empty(),
640 self.is_expanded,
641 ctx,
642 );
643
644 if self.content.is_some() {
645 ctx[self.content].set_row(0).set_column(1);
646 };
647
648 let internals = GridBuilder::new(
649 WidgetBuilder::new()
650 .on_column(0)
651 .on_row(0)
652 .with_margin(Thickness {
653 left: 1.0,
654 top: 1.0,
655 right: 0.0,
656 bottom: 1.0,
657 })
658 .with_child(expander)
659 .with_child(self.content),
660 )
661 .add_column(Column::strict(11.0))
662 .add_column(Column::stretch())
663 .add_row(Row::strict(20.0))
664 .build(ctx);
665
666 let item_background = self.back.unwrap_or_else(|| {
667 DecoratorBuilder::new(BorderBuilder::new(
668 WidgetBuilder::new()
669 .with_foreground(Brush::Solid(Color::TRANSPARENT).into())
670 .with_background(Brush::Solid(Color::TRANSPARENT).into()),
671 ))
672 .with_selected_brush(ctx.style.property(Style::BRUSH_DIM_BLUE))
673 .with_hover_brush(ctx.style.property(Style::BRUSH_DARK))
674 .with_normal_brush(Brush::Solid(Color::TRANSPARENT).into())
675 .with_pressed_brush(Brush::Solid(Color::TRANSPARENT).into())
676 .with_pressable(false)
677 .build(ctx)
678 .to_base()
679 });
680
681 ctx.link(internals, item_background);
682
683 let panel;
684 let grid = GridBuilder::new(
685 WidgetBuilder::new()
686 .with_child(item_background)
687 .with_child({
688 panel = StackPanelBuilder::new(
689 WidgetBuilder::new()
690 .on_row(1)
691 .on_column(0)
692 .with_margin(Thickness::left(15.0))
693 .with_visibility(self.is_expanded)
694 .with_children(self.items.clone().to_base()),
695 )
696 .build(ctx);
697 panel
698 }),
699 )
700 .add_column(Column::stretch())
701 .add_row(Row::strict(24.0))
702 .add_row(Row::stretch())
703 .build(ctx);
704
705 Tree {
706 widget: self
707 .widget_builder
708 .with_allow_drag(true)
709 .with_allow_drop(true)
710 .with_child(grid)
711 .build(ctx),
712 content: self.content,
713 panel,
714 is_expanded: self.is_expanded,
715 expander,
716 background: item_background,
717 items: self.items,
718 is_selected: false,
719 always_show_expander: self.always_show_expander,
720 }
721 }
722
723 /// Finishes widget building and adds it to the user interface, returning a handle to the new
724 /// instance.
725 pub fn build(self, ctx: &mut BuildContext) -> Handle<Tree> {
726 let tree = self.build_tree(ctx);
727 ctx.add(tree)
728 }
729}
730
731fn build_expander(
732 always_show_expander: bool,
733 items_populated: bool,
734 is_expanded: bool,
735 ctx: &mut BuildContext,
736) -> Handle<CheckBox> {
737 let down_arrow = make_arrow(ctx, ArrowDirection::Bottom, 8.0);
738 ctx[down_arrow].set_vertical_alignment(VerticalAlignment::Center);
739
740 let right_arrow = make_arrow(ctx, ArrowDirection::Right, 8.0);
741 ctx[right_arrow].set_vertical_alignment(VerticalAlignment::Center);
742
743 CheckBoxBuilder::new(
744 WidgetBuilder::new()
745 .on_row(0)
746 .on_column(0)
747 .with_visibility(always_show_expander || items_populated),
748 )
749 .with_background(
750 BorderBuilder::new(
751 WidgetBuilder::new()
752 .with_background(Brush::Solid(Color::TRANSPARENT).into())
753 .with_min_size(Vector2::new(10.0, 4.0)),
754 )
755 .with_stroke_thickness(Thickness::zero().into())
756 .build(ctx),
757 )
758 .checked(Some(is_expanded))
759 .with_check_mark(down_arrow)
760 .with_uncheck_mark(right_arrow)
761 .build(ctx)
762}
763
764/// Tree root is a special widget that handles the entire hierarchy of descendant [`Tree`] widgets. Its
765/// main purpose is to handle the selection of descendant [`Tree`] widgets. Tree root cannot have a
766/// content and it only could have children tree items. See docs for [`Tree`] for usage examples.
767///
768/// ## Selection
769///
770/// Tree root handles selection in the entire descendant hierarchy, and you can use [`TreeRootMessage::Select`]
771/// message to manipulate (or listen for changes) the current selection.
772///
773/// ### Listening for Changes
774///
775/// All that is needed is to check a UI message that comes from the common message queue like so:
776///
777/// ```rust
778/// # use fyrox_ui::{
779/// # core::pool::Handle,
780/// # message::{MessageDirection, UiMessage},
781/// # tree::TreeRootMessage,
782/// # UiNode,
783/// # };
784/// #
785/// fn listen_for_selection_changes(tree_root: Handle<UiNode>, message: &UiMessage) {
786/// if let Some(TreeRootMessage::Select(new_selection)) = message.data() {
787/// if message.destination() == tree_root
788/// && message.direction() == MessageDirection::FromWidget
789/// {
790/// println!("Selection has changed: {new_selection:?}");
791/// }
792/// }
793/// }
794/// ```
795///
796/// ### Changing Selection
797///
798/// To change a selection of the entire tree use something like this:
799///
800/// ```rust
801/// # use fyrox_ui::{
802/// # core::pool::Handle, message::MessageDirection, tree::TreeRootMessage, UiNode, UserInterface,
803/// # };
804/// # use fyrox_ui::tree::Tree;
805/// #
806/// fn change_selection(
807/// tree: Handle<UiNode>,
808/// new_selection: Vec<Handle<Tree>>,
809/// ui: &UserInterface,
810/// ) {
811/// ui.send(tree, TreeRootMessage::Select(new_selection));
812/// }
813/// ```
814#[derive(Default, Debug, Clone, Visit, Reflect, ComponentProvider)]
815#[reflect(derived_type = "UiNode")]
816pub struct TreeRoot {
817 /// Base widget of the tree root.
818 pub widget: Widget,
819 /// Current layout panel of the tree root, that is used to arrange children trees.
820 pub panel: Handle<StackPanel>,
821 /// Current items of the tree root.
822 pub items: Vec<Handle<Tree>>,
823 /// Selected items of the tree root.
824 pub selected: Vec<Handle<Tree>>,
825}
826
827impl ConstructorProvider<UiNode, UserInterface> for TreeRoot {
828 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
829 GraphNodeConstructor::new::<Self>()
830 .with_variant("Tree Root", |ui| {
831 TreeRootBuilder::new(WidgetBuilder::new().with_name("Tree Root"))
832 .build(&mut ui.build_ctx())
833 .to_base()
834 .into()
835 })
836 .with_group("Visual")
837 }
838}
839
840crate::define_widget_deref!(TreeRoot);
841
842uuid_provider!(TreeRoot = "cf7c0476-f779-4e4b-8b7e-01a23ff51a72");
843
844impl Control for TreeRoot {
845 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
846 self.widget.handle_routed_message(ui, message);
847
848 if let Some(msg) = message.data_for::<TreeRootMessage>(self.handle()) {
849 match msg {
850 &TreeRootMessage::AddItem(item) => {
851 ui.send(item, WidgetMessage::link_with(self.panel));
852 self.items.push(item);
853 ui.post(self.handle, TreeRootMessage::ItemsChanged);
854 }
855 &TreeRootMessage::RemoveItem(item) => {
856 if let Some(pos) = self.items.iter().position(|&i| i == item) {
857 ui.send(item, WidgetMessage::Remove);
858 self.items.remove(pos);
859 ui.post(self.handle, TreeRootMessage::ItemsChanged);
860 }
861 }
862 TreeRootMessage::Items(items) => {
863 for &item in self.items.iter() {
864 ui.send(item, WidgetMessage::Remove);
865 }
866 for &item in items {
867 ui.send(item, WidgetMessage::link_with(self.panel));
868 }
869
870 self.items = items.to_vec();
871 ui.post(self.handle, TreeRootMessage::ItemsChanged);
872 }
873 TreeRootMessage::Select(selected) => {
874 if &self.selected != selected {
875 let mut items = self.items.clone();
876 while let Some(handle) = items.pop() {
877 if let Ok(tree_ref) = ui.try_get(handle) {
878 items.extend_from_slice(&tree_ref.items);
879
880 let new_selection_state = if selected.contains(&handle) {
881 SelectionState(true)
882 } else {
883 SelectionState(false)
884 };
885
886 if tree_ref.is_selected != new_selection_state.0 {
887 ui.send(handle, TreeMessage::Select(new_selection_state));
888 }
889 }
890 }
891
892 self.selected.clone_from(selected);
893 ui.send_message(message.reverse());
894 }
895 }
896 TreeRootMessage::CollapseAll => {
897 self.expand_all(ui, false);
898 }
899 TreeRootMessage::ExpandAll => {
900 self.expand_all(ui, true);
901 }
902 TreeRootMessage::ItemsChanged => {
903 // Do nothing.
904 }
905 }
906 } else if let Some(WidgetMessage::KeyDown(key_code)) = message.data() {
907 if !message.handled() {
908 match *key_code {
909 KeyCode::ArrowRight => {
910 self.move_selection(ui, Direction::Down, true);
911 message.set_handled(true);
912 }
913 KeyCode::ArrowLeft => {
914 if let Some(selection) = self.selected.first() {
915 if let Ok(item) = ui.try_get(*selection) {
916 if item.is_expanded {
917 ui.send(
918 *selection,
919 TreeMessage::Expand {
920 expand: false,
921 expansion_strategy: TreeExpansionStrategy::Direct,
922 },
923 );
924 message.set_handled(true);
925 } else if let Some((parent_handle, _)) =
926 ui.find_component_up::<Tree>(item.parent())
927 {
928 ui.send(
929 self.handle,
930 TreeRootMessage::Select(vec![parent_handle.to_variant()]),
931 );
932 message.set_handled(true);
933 }
934 }
935 }
936 }
937 KeyCode::ArrowUp => {
938 self.move_selection(ui, Direction::Up, false);
939 message.set_handled(true);
940 }
941 KeyCode::ArrowDown => {
942 self.move_selection(ui, Direction::Down, false);
943 message.set_handled(true);
944 }
945 _ => (),
946 }
947 }
948 }
949 }
950}
951
952enum Direction {
953 Up,
954 Down,
955}
956
957impl TreeRoot {
958 fn expand_all(&self, ui: &UserInterface, expand: bool) {
959 for &item in self.items.iter() {
960 ui.send(
961 item,
962 TreeMessage::Expand {
963 expand,
964 expansion_strategy: TreeExpansionStrategy::RecursiveDescendants,
965 },
966 );
967 }
968 }
969
970 fn select(&self, ui: &UserInterface, item: Handle<Tree>) {
971 ui.send(self.handle, TreeRootMessage::Select(vec![item]));
972 }
973
974 fn move_selection(&self, ui: &UserInterface, direction: Direction, expand: bool) {
975 if let Some(selected_item) = self.selected.first() {
976 let Ok(item) = ui.try_get(*selected_item) else {
977 return;
978 };
979
980 if !item.is_expanded && expand {
981 ui.send(
982 *selected_item,
983 TreeMessage::Expand {
984 expand: true,
985 expansion_strategy: TreeExpansionStrategy::Direct,
986 },
987 );
988 return;
989 }
990
991 let (parent_handle, parent_items, parent_ancestor, is_parent_root) = ui
992 .find_component_up::<Tree>(item.parent())
993 .map(|(tree_handle, tree)| {
994 (
995 tree_handle.to_variant::<Tree>(),
996 &tree.items,
997 tree.parent,
998 false,
999 )
1000 })
1001 .unwrap_or_else(|| {
1002 (
1003 self.handle.to_variant::<Tree>(),
1004 &self.items,
1005 self.parent,
1006 true,
1007 )
1008 });
1009
1010 let Some(selected_item_position) =
1011 parent_items.iter().position(|c| *c == *selected_item)
1012 else {
1013 return;
1014 };
1015
1016 match direction {
1017 Direction::Up => {
1018 if let Some(prev) = selected_item_position
1019 .checked_sub(1)
1020 .and_then(|prev| parent_items.get(prev))
1021 {
1022 let mut last_descendant_item = None;
1023 let mut queue = VecDeque::new();
1024 queue.push_back(*prev);
1025 while let Some(item) = queue.pop_front() {
1026 if let Ok(item_ref) = ui.try_get(item) {
1027 if item_ref.is_expanded {
1028 queue.extend(item_ref.items.iter());
1029 }
1030 last_descendant_item = Some(item);
1031 }
1032 }
1033
1034 if let Some(last_descendant_item) = last_descendant_item {
1035 self.select(ui, last_descendant_item);
1036 }
1037 } else if !is_parent_root {
1038 self.select(ui, parent_handle);
1039 }
1040 }
1041 Direction::Down => {
1042 if let (Some(first_item), true) = (item.items.first(), item.is_expanded) {
1043 self.select(ui, *first_item);
1044 } else if let Some(next) =
1045 parent_items.get(selected_item_position.saturating_add(1))
1046 {
1047 self.select(ui, *next);
1048 } else {
1049 let mut current_ancestor = parent_handle;
1050 let mut current_ancestor_parent = parent_ancestor;
1051 while let Some((ancestor_handle, ancestor)) =
1052 ui.find_component_up::<Tree>(current_ancestor_parent)
1053 {
1054 if ancestor.is_expanded {
1055 if let Some(current_ancestor_position) =
1056 ancestor.items.iter().position(|c| *c == current_ancestor)
1057 {
1058 if let Some(next) = ancestor
1059 .items
1060 .get(current_ancestor_position.saturating_add(1))
1061 {
1062 self.select(ui, *next);
1063 break;
1064 }
1065 }
1066 }
1067
1068 current_ancestor_parent = ancestor.parent();
1069 current_ancestor = ancestor_handle.to_variant();
1070 }
1071 }
1072 }
1073 }
1074 } else if let Some(first_item) = self.items.first() {
1075 self.select(ui, *first_item);
1076 }
1077 }
1078}
1079
1080/// Tree root builder creates [`TreeRoot`] instances and adds them to the user interface.
1081pub struct TreeRootBuilder {
1082 widget_builder: WidgetBuilder,
1083 items: Vec<Handle<Tree>>,
1084}
1085
1086impl TreeRootBuilder {
1087 /// Creates new tree root builder.
1088 pub fn new(widget_builder: WidgetBuilder) -> Self {
1089 Self {
1090 widget_builder,
1091 items: Default::default(),
1092 }
1093 }
1094
1095 /// Sets the desired items of the tree root.
1096 pub fn with_items(mut self, items: Vec<Handle<Tree>>) -> Self {
1097 self.items = items;
1098 self
1099 }
1100
1101 /// Finishes widget building and adds the new instance to the user interface, returning its handle.
1102 pub fn build(self, ctx: &mut BuildContext) -> Handle<TreeRoot> {
1103 let panel = StackPanelBuilder::new(
1104 WidgetBuilder::new().with_children(self.items.clone().to_base()),
1105 )
1106 .build(ctx);
1107
1108 let tree = TreeRoot {
1109 widget: self.widget_builder.with_child(panel).build(ctx),
1110 panel,
1111 items: self.items,
1112 selected: Default::default(),
1113 };
1114
1115 ctx.add(tree)
1116 }
1117}
1118
1119#[cfg(test)]
1120mod test {
1121 use crate::tree::{TreeBuilder, TreeRootBuilder};
1122 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
1123
1124 #[test]
1125 fn test_deletion() {
1126 test_widget_deletion(|ctx| TreeRootBuilder::new(WidgetBuilder::new()).build(ctx));
1127 test_widget_deletion(|ctx| TreeBuilder::new(WidgetBuilder::new()).build(ctx));
1128 }
1129}