1
+ use std:: ops:: ControlFlow ;
2
+
1
3
use egui:: { Response , Ui } ;
2
4
use smallvec:: SmallVec ;
3
5
@@ -55,6 +57,12 @@ pub struct BlueprintTree {
55
57
/// This is the item we used as a starting point for range selection. It is set and remembered
56
58
/// everytime the user clicks on an item _without_ holding shift.
57
59
range_selection_anchor_item : Option < Item > ,
60
+
61
+ /// Used when the selection is modified using key navigation.
62
+ ///
63
+ /// IMPORTANT: Always make sure that the item will be drawn this or next frame when setting this
64
+ /// to `Some`, so that this flag is immediately consumed.
65
+ scroll_to_me_item : Option < Item > ,
58
66
}
59
67
60
68
impl BlueprintTree {
@@ -186,6 +194,14 @@ impl BlueprintTree {
186
194
) {
187
195
let item = Item :: Container ( container_data. id ) ;
188
196
197
+ // It's possible that the root becomes technically collapsed (e.g. context menu or arrow
198
+ // navigation), even though we don't allow that in the ui. We really don't want that,
199
+ // though, because it breaks the collapse-based tree data visiting. To avoid that, we always
200
+ // force uncollapse this item.
201
+ self . collapse_scope ( )
202
+ . container ( container_data. id )
203
+ . set_open ( ctx. egui_ctx ( ) , true ) ;
204
+
189
205
let item_response = ui
190
206
. list_item ( )
191
207
. render_offscreen ( false )
@@ -218,7 +234,7 @@ impl BlueprintTree {
218
234
viewport_blueprint,
219
235
blueprint_tree_data,
220
236
ui,
221
- item,
237
+ & item,
222
238
& item_response,
223
239
) ;
224
240
@@ -336,7 +352,7 @@ impl BlueprintTree {
336
352
viewport_blueprint,
337
353
blueprint_tree_data,
338
354
ui,
339
- item,
355
+ & item,
340
356
& response,
341
357
) ;
342
358
@@ -448,7 +464,7 @@ impl BlueprintTree {
448
464
viewport_blueprint,
449
465
blueprint_tree_data,
450
466
ui,
451
- item,
467
+ & item,
452
468
& response,
453
469
) ;
454
470
@@ -618,7 +634,7 @@ impl BlueprintTree {
618
634
viewport_blueprint,
619
635
blueprint_tree_data,
620
636
ui,
621
- item,
637
+ & item,
622
638
& response,
623
639
) ;
624
640
}
@@ -632,24 +648,119 @@ impl BlueprintTree {
632
648
viewport_blueprint : & ViewportBlueprint ,
633
649
blueprint_tree_data : & BlueprintTreeData ,
634
650
ui : & egui:: Ui ,
635
- item : Item ,
651
+ item : & Item ,
636
652
response : & Response ,
637
653
) {
638
654
context_menu_ui_for_item_with_context (
639
655
ctx,
640
656
viewport_blueprint,
641
- & item,
657
+ item,
642
658
// expand/collapse context menu actions need this information
643
659
ItemContext :: BlueprintTree {
644
660
filter_session_id : self . filter_state . session_id ( ) ,
645
661
} ,
646
662
response,
647
663
SelectionUpdateBehavior :: UseSelection ,
648
664
) ;
649
- self . scroll_to_me_if_needed ( ui, & item, response) ;
665
+ self . scroll_to_me_if_needed ( ui, item, response) ;
650
666
ctx. handle_select_hover_drag_interactions ( response, item. clone ( ) , true ) ;
651
667
652
- self . handle_range_selection ( ctx, blueprint_tree_data, item, response) ;
668
+ self . handle_range_selection ( ctx, blueprint_tree_data, item. clone ( ) , response) ;
669
+
670
+ self . handle_key_navigation ( ctx, blueprint_tree_data, item) ;
671
+ }
672
+
673
+ fn handle_key_navigation (
674
+ & mut self ,
675
+ ctx : & ViewerContext < ' _ > ,
676
+ blueprint_tree_data : & BlueprintTreeData ,
677
+ item : & Item ,
678
+ ) {
679
+ if ctx. selection_state ( ) . selected_items ( ) . single_item ( ) != Some ( item) {
680
+ return ;
681
+ }
682
+
683
+ if ctx
684
+ . egui_ctx ( )
685
+ . input_mut ( |i| i. consume_key ( egui:: Modifiers :: NONE , egui:: Key :: ArrowRight ) )
686
+ {
687
+ if let Some ( collapse_id) = self . collapse_scope ( ) . item ( item. clone ( ) ) {
688
+ collapse_id. set_open ( ctx. egui_ctx ( ) , true ) ;
689
+ }
690
+ }
691
+
692
+ if ctx
693
+ . egui_ctx ( )
694
+ . input_mut ( |i| i. consume_key ( egui:: Modifiers :: NONE , egui:: Key :: ArrowLeft ) )
695
+ {
696
+ if let Some ( collapse_id) = self . collapse_scope ( ) . item ( item. clone ( ) ) {
697
+ collapse_id. set_open ( ctx. egui_ctx ( ) , false ) ;
698
+ }
699
+ }
700
+
701
+ if ctx
702
+ . egui_ctx ( )
703
+ . input_mut ( |i| i. consume_key ( egui:: Modifiers :: NONE , egui:: Key :: ArrowDown ) )
704
+ {
705
+ let mut found_current = false ;
706
+
707
+ let result = blueprint_tree_data. visit ( |tree_item| {
708
+ let is_item_collapsed = !tree_item. is_open ( ctx. egui_ctx ( ) , self . collapse_scope ( ) ) ;
709
+
710
+ if & tree_item. item ( ) == item {
711
+ found_current = true ;
712
+
713
+ return if is_item_collapsed {
714
+ VisitorControlFlow :: SkipBranch
715
+ } else {
716
+ VisitorControlFlow :: Continue
717
+ } ;
718
+ }
719
+
720
+ if found_current {
721
+ VisitorControlFlow :: Break ( Some ( tree_item. item ( ) ) )
722
+ } else if is_item_collapsed {
723
+ VisitorControlFlow :: SkipBranch
724
+ } else {
725
+ VisitorControlFlow :: Continue
726
+ }
727
+ } ) ;
728
+
729
+ if let ControlFlow :: Break ( Some ( item) ) = result {
730
+ ctx. selection_state ( ) . set_selection ( item. clone ( ) ) ;
731
+ self . scroll_to_me_item = Some ( item. clone ( ) ) ;
732
+ self . range_selection_anchor_item = Some ( item) ;
733
+ }
734
+ }
735
+
736
+ if ctx
737
+ . egui_ctx ( )
738
+ . input_mut ( |i| i. consume_key ( egui:: Modifiers :: NONE , egui:: Key :: ArrowUp ) )
739
+ {
740
+ let mut last_item = None ;
741
+
742
+ let result = blueprint_tree_data. visit ( |tree_item| {
743
+ let is_item_collapsed = !tree_item. is_open ( ctx. egui_ctx ( ) , self . collapse_scope ( ) ) ;
744
+
745
+ if & tree_item. item ( ) == item {
746
+ return VisitorControlFlow :: Break ( last_item. clone ( ) ) ;
747
+ }
748
+
749
+ last_item = Some ( tree_item. item ( ) ) ;
750
+
751
+ if is_item_collapsed {
752
+ VisitorControlFlow :: SkipBranch
753
+ } else {
754
+ VisitorControlFlow :: Continue
755
+ }
756
+ } ) ;
757
+
758
+ if let ControlFlow :: Break ( Some ( item) ) = result {
759
+ ctx. selection_state ( ) . set_selection ( item. clone ( ) ) ;
760
+ self . scroll_to_me_item = Some ( item. clone ( ) ) ;
761
+ self . range_selection_anchor_item = Some ( item) ;
762
+ }
763
+ }
653
764
}
654
765
655
766
/// Handle setting/extending the selection based on shift-clicking.
@@ -738,9 +849,7 @@ impl BlueprintTree {
738
849
return VisitorControlFlow :: Break ( ( ) ) ;
739
850
}
740
851
741
- let is_expanded = blueprint_tree_item
742
- . is_open ( ctx. egui_ctx ( ) , collapse_scope)
743
- . unwrap_or ( false ) ;
852
+ let is_expanded = blueprint_tree_item. is_open ( ctx. egui_ctx ( ) , collapse_scope) ;
744
853
745
854
if is_expanded {
746
855
VisitorControlFlow :: Continue
@@ -757,7 +866,7 @@ impl BlueprintTree {
757
866
}
758
867
759
868
/// Check if the provided item should be scrolled to.
760
- fn scroll_to_me_if_needed ( & self , ui : & egui:: Ui , item : & Item , response : & egui:: Response ) {
869
+ fn scroll_to_me_if_needed ( & mut self , ui : & egui:: Ui , item : & Item , response : & egui:: Response ) {
761
870
if Some ( item) == self . blueprint_tree_scroll_to_item . as_ref ( ) {
762
871
// Scroll only if the entity isn't already visible. This is important because that's what
763
872
// happens when double-clicking an entity _in the blueprint tree_. In such case, it would be
@@ -766,6 +875,13 @@ impl BlueprintTree {
766
875
response. scroll_to_me ( Some ( egui:: Align :: Center ) ) ;
767
876
}
768
877
}
878
+
879
+ if Some ( item) == self . scroll_to_me_item . as_ref ( ) {
880
+ // This is triggered by keyboard navigation, so in this case we just want to scroll
881
+ // minimally for the item to be visible.
882
+ response. scroll_to_me ( None ) ;
883
+ self . scroll_to_me_item = None ;
884
+ }
769
885
}
770
886
771
887
// ----------------------------------------------------------------------------
0 commit comments