From 2546e19d6588238fc959a3f7f77dfca349626678 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 17 Jul 2025 23:07:06 -0500 Subject: [PATCH 1/3] Add shift selection for tree nodes --- Source/Editor/GUI/Tree/Tree.cs | 71 ++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 8df26c211..fdedf0203 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -41,6 +41,7 @@ namespace FlaxEditor.GUI.Tree private Margin _margin; private bool _autoSize = true; private bool _deferLayoutUpdate = false; + private TreeNode _lastSelectedNode; /// /// The TreeNode that is being dragged over. This could have a value when not dragging. @@ -383,20 +384,27 @@ namespace FlaxEditor.GUI.Tree AfterDeferredLayout?.Invoke(); _deferLayoutUpdate = false; } + var window = Root; + bool shiftDown = window.GetKey(KeyboardKeys.Shift); + bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp); + bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown); + + // Use last selection for last selected node if sift is down + if (shiftDown) + _lastSelectedNode ??= Selection[^1]; + + var node = _lastSelectedNode ?? SelectedNode; - var node = SelectedNode; + if (Selection.Count == 0) + _lastSelectedNode = null; // Check if has focus and if any node is focused and it isn't a root if (ContainsFocus && node != null && node.AutoFocus) { - var window = Root; if (window.GetKeyDown(KeyboardKeys.ArrowUp) || window.GetKeyDown(KeyboardKeys.ArrowDown)) _keyUpdateTime = KeyUpdateTimeout; if (_keyUpdateTime >= KeyUpdateTimeout && window is WindowRootControl windowRoot && windowRoot.Window.IsFocused) { - bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp); - bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown); - // Check if arrow flags are different if (keyDownArrow != keyUpArrow) { @@ -406,24 +414,39 @@ namespace FlaxEditor.GUI.Tree Assert.AreNotEqual(-1, myIndex); // Up - TreeNode toSelect = null; + List toSelect = new List(); + if (shiftDown) + { + toSelect.AddRange(Selection); + } + //TreeNode toSelect = null; if (keyUpArrow) { if (myIndex == 0) { // Select parent - toSelect = parentNode; + if (toSelect.Contains(parentNode)) + toSelect.Remove(node); + else + toSelect.Add(parentNode); + _lastSelectedNode = parentNode; } else { // Select previous parent child - toSelect = nodeParent.GetChild(myIndex - 1) as TreeNode; + var select = nodeParent.GetChild(myIndex - 1) as TreeNode; // Select last child if is valid and expanded and has any children - if (toSelect != null && toSelect.IsExpanded && toSelect.HasAnyVisibleChild) + if (select != null && select.IsExpanded && select.HasAnyVisibleChild) { - toSelect = toSelect.GetChild(toSelect.ChildrenCount - 1) as TreeNode; + select = select.GetChild(select.ChildrenCount - 1) as TreeNode; } + + if (toSelect.Contains(select)) + toSelect.Remove(node); + else + toSelect.Add(select); + _lastSelectedNode = select; } } // Down @@ -432,32 +455,48 @@ namespace FlaxEditor.GUI.Tree if (node.IsExpanded && node.HasAnyVisibleChild) { // Select the first child - toSelect = node.GetChild(0) as TreeNode; + var select = node.GetChild(0) as TreeNode; + if (toSelect.Contains(select)) + toSelect.Remove(node); + else + toSelect.Add(select); + _lastSelectedNode = select; } else if (myIndex == nodeParent.ChildrenCount - 1) { // Select next node after parent - while (parentNode != null && toSelect == null) + TreeNode select = null; + while (parentNode != null && select == null) { int parentIndex = parentNode.IndexInParent; if (parentIndex != -1 && parentIndex < parentNode.Parent.ChildrenCount - 1) { - toSelect = parentNode.Parent.GetChild(parentIndex + 1) as TreeNode; + select = parentNode.Parent.GetChild(parentIndex + 1) as TreeNode; } parentNode = parentNode.Parent as TreeNode; } + if (toSelect.Contains(select)) + toSelect.Remove(node); + else + toSelect.Add(select); + _lastSelectedNode = select; } else { // Select next parent child - toSelect = nodeParent.GetChild(myIndex + 1) as TreeNode; + var select = nodeParent.GetChild(myIndex + 1) as TreeNode; + if (toSelect.Contains(select)) + toSelect.Remove(node); + else + toSelect.Add(select); + _lastSelectedNode = select; } } - if (toSelect != null && toSelect.AutoFocus) + if (toSelect.Count > 0) { // Select Select(toSelect); - toSelect.Focus(); + _lastSelectedNode?.Focus(); } // Reset time From fc46219a82d8a5872f716e9fcbd4c1ab9259288b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 17 Jul 2025 23:09:36 -0500 Subject: [PATCH 2/3] Add support for multi-select disable. --- Source/Editor/GUI/Tree/Tree.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index fdedf0203..22483f1ac 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -415,7 +415,7 @@ namespace FlaxEditor.GUI.Tree // Up List toSelect = new List(); - if (shiftDown) + if (shiftDown && _supportMultiSelect) { toSelect.AddRange(Selection); } From 2c1713d300c34be0b13fdd8997871add248ab576 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 19 Aug 2025 23:00:29 +0200 Subject: [PATCH 3/3] Fix rare issues with shift tree selection --- Source/Editor/GUI/Tree/Tree.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 5405a6f24..5530a1738 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -399,14 +399,17 @@ namespace FlaxEditor.GUI.Tree bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown); // Use last selection for last selected node if sift is down - if (shiftDown) + if (Selection.Count < 2) + _lastSelectedNode = null; + else if (shiftDown) _lastSelectedNode ??= Selection[^1]; - var node = _lastSelectedNode ?? SelectedNode; - - if (Selection.Count == 0) + // Skip root to prevent blocking input + if (_lastSelectedNode != null && _lastSelectedNode.IsRoot) _lastSelectedNode = null; + var node = _lastSelectedNode ?? SelectedNode; + // Check if has focus and if any node is focused and it isn't a root if (ContainsFocus && node != null && node.AutoFocus) { @@ -435,7 +438,7 @@ namespace FlaxEditor.GUI.Tree // Select parent if (toSelect.Contains(parentNode)) toSelect.Remove(node); - else + else if (parentNode != null) toSelect.Add(parentNode); _lastSelectedNode = parentNode; } @@ -450,7 +453,7 @@ namespace FlaxEditor.GUI.Tree select = select.GetChild(select.ChildrenCount - 1) as TreeNode; } - if (toSelect.Contains(select)) + if (select == null || toSelect.Contains(select)) toSelect.Remove(node); else toSelect.Add(select); @@ -464,7 +467,7 @@ namespace FlaxEditor.GUI.Tree { // Select the first child var select = node.GetChild(0) as TreeNode; - if (toSelect.Contains(select)) + if (select == null || toSelect.Contains(select)) toSelect.Remove(node); else toSelect.Add(select); @@ -483,7 +486,7 @@ namespace FlaxEditor.GUI.Tree } parentNode = parentNode.Parent as TreeNode; } - if (toSelect.Contains(select)) + if (select == null || toSelect.Contains(select)) toSelect.Remove(node); else toSelect.Add(select); @@ -493,7 +496,7 @@ namespace FlaxEditor.GUI.Tree { // Select next parent child var select = nodeParent.GetChild(myIndex + 1) as TreeNode; - if (toSelect.Contains(select)) + if (select == null || toSelect.Contains(select)) toSelect.Remove(node); else toSelect.Add(select);