Skip to content

Commit 85f834a

Browse files
committed
feat: Added ability to move through the list of types using keyboard
1 parent 2b71a2d commit 85f834a

11 files changed

+269
-29
lines changed

Editor/TypeDropdown/DropdownWindow.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,11 @@ private void DrawContent()
183183

184184
private void RepaintIfMouseWasUsed()
185185
{
186-
if (Event.current.isMouse || Event.current.type == EventType.Used)
186+
if (Event.current.isMouse || Event.current.type == EventType.Used || _selectionTree.RepaintRequested)
187+
{
187188
Repaint();
189+
_selectionTree.RepaintRequested = false;
190+
}
188191
}
189192

190193
private readonly struct FixedRect : IDisposable

Editor/TypeDropdown/IRepainter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace TypeReferences.Editor.TypeDropdown
2+
{
3+
public interface IRepainter
4+
{
5+
void RequestRepaint();
6+
}
7+
}

Editor/TypeDropdown/IRepainter.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/TypeDropdown/NoneElement.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace TypeReferences.Editor.TypeDropdown
22
{
3+
using System.Linq;
34
using UnityEditor;
45
using UnityEngine;
56

Editor/TypeDropdown/Scrollbar.cs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,23 @@
99
/// </summary>
1010
internal class Scrollbar
1111
{
12+
private readonly IRepainter _repainter;
13+
1214
private bool _visible = true;
1315
private Vector2 _position;
14-
private SelectionNode _nodeToScrollTo;
1516
private Rect _wholeListRect;
1617
private Rect _windowRect;
1718

19+
private SelectionNode _nodeToScrollTo;
20+
private NodePosition _nodePosition;
21+
1822
private static bool ScrollCannotBePerformed => Event.current.type != EventType.Repaint;
1923

24+
public Scrollbar(IRepainter repainter)
25+
{
26+
_repainter = repainter;
27+
}
28+
2029
/// <summary>Draws elements with scrollbar if the list is large enough.</summary>
2130
/// <returns>
2231
/// A scrollbar scope that automatically lays out elements inside the list and places a thumb in the correct position.
@@ -28,20 +37,21 @@ internal class Scrollbar
2837

2938
/// <summary>Ask scrollbar to start moving to a node. The process can take several frames.</summary>
3039
/// <param name="node">The node to scroll to.</param>
31-
public void RequestScrollToNode(SelectionNode node)
40+
public void RequestScrollToNode(SelectionNode node, NodePosition nodePosition)
3241
{
3342
if (node == null)
3443
return;
3544

3645
_nodeToScrollTo = node;
46+
_nodePosition = nodePosition;
3747

3848
foreach (SelectionNode parentNode in node.GetParentNodesRecursive(false))
3949
parentNode.Expanded = true;
4050

4151
if (ScrollCannotBePerformed)
4252
return;
4353

44-
ScrollToNode(node.Rect);
54+
ScrollToNode(node.Rect, nodePosition);
4555
_nodeToScrollTo = null;
4656
}
4757

@@ -50,14 +60,27 @@ private void ScrollToNodeIfNeeded()
5060
if (_nodeToScrollTo == null || ScrollCannotBePerformed)
5161
return;
5262

53-
ScrollToNode(_nodeToScrollTo.Rect);
63+
ScrollToNode(_nodeToScrollTo.Rect, _nodePosition);
5464
_nodeToScrollTo = null;
5565
}
5666

57-
private void ScrollToNode(Rect nodeRect)
67+
private void ScrollToNode(Rect nodeRect, NodePosition nodePosition)
5868
{
5969
float windowHalfHeight = _windowRect.height * 0.5f; // This is needed to center the item vertically.
60-
_position.y = nodeRect.y - windowHalfHeight; // This scrolls to the node but places it in the center of the window.
70+
71+
// scroll to the node but also calculate where it needs to be inside the visible part of the dropdown.
72+
float shift = nodePosition switch
73+
{
74+
NodePosition.Top => 0f,
75+
NodePosition.Center => - windowHalfHeight,
76+
NodePosition.Bottom => - windowHalfHeight * 2 + DropdownStyle.NodeHeight,
77+
_ => throw new NotImplementedException()
78+
};
79+
80+
var position = nodeRect.y + shift;
81+
_position.y = position > 0 ? position : 0;
82+
83+
_repainter.RequestRepaint();
6184
}
6285

6386
/// <summary>Draws elements with scrollbar if the list is large enough.</summary>
@@ -98,5 +121,7 @@ public void Dispose()
98121
_scrollbar.ScrollToNodeIfNeeded();
99122
}
100123
}
124+
125+
public enum NodePosition { Top, Center, Bottom }
101126
}
102127
}

Editor/TypeDropdown/SelectionNode.cs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ internal class SelectionNode
1919
public readonly Type Type;
2020

2121
private readonly string _name;
22-
private readonly SelectionTree _parentTree;
23-
private readonly SelectionNode _parentNode;
22+
protected readonly SelectionTree _parentTree;
23+
public readonly SelectionNode ParentNode;
2424

2525
private bool _expanded;
2626
private Rect _rect;
@@ -45,7 +45,7 @@ protected SelectionNode(
4545
Assert.IsNotNull(name);
4646

4747
_name = name;
48-
_parentNode = parentNode;
48+
ParentNode = parentNode;
4949
_parentTree = parentTree;
5050
Type = type;
5151
FullTypeName = fullTypeName;
@@ -55,7 +55,7 @@ protected SelectionNode(
5555
/// <param name="parentTree">The tree this node belongs to.</param>
5656
private SelectionNode(SelectionTree parentTree)
5757
{
58-
_parentNode = null;
58+
ParentNode = null;
5959
_parentTree = parentTree;
6060
_name = string.Empty;
6161
Type = null;
@@ -76,15 +76,13 @@ public bool Expanded
7676
set => _expanded = value;
7777
}
7878

79-
private bool IsSelected => _parentTree.SelectedNode == this;
79+
public bool IsFolder => ChildNodes.Count != 0;
8080

81-
private bool IsFolder => ChildNodes.Count != 0;
81+
public bool IsRoot => ParentNode == null;
8282

83-
private bool IsHoveredOver => _rect.Contains(Event.current.mousePosition);
84-
85-
private bool IsRoot => _parentNode == null;
83+
public bool IsSelected => _parentTree.SelectedNode == this;
8684

87-
public void Select() => _parentTree.SelectedNode = this;
85+
private bool IsHoveredOver => _rect.Contains(Event.current.mousePosition);
8886

8987
/// <summary>Creates a root node that does not have a parent and does not show up in the popup.</summary>
9088
/// <param name="parentTree">The tree this node belongs to.</param>
@@ -133,7 +131,7 @@ public IEnumerable<SelectionNode> GetParentNodesRecursive(
133131
if (IsRoot)
134132
yield break;
135133

136-
foreach (SelectionNode node in _parentNode.GetParentNodesRecursive(true))
134+
foreach (SelectionNode node in ParentNode.GetParentNodesRecursive(true))
137135
yield return node;
138136
}
139137

@@ -219,13 +217,44 @@ protected void HandleMouseEvents()
219217
return;
220218

221219
if (IsFolder)
220+
{
222221
Expanded = !Expanded;
222+
}
223223
else
224-
Select();
224+
{
225+
_parentTree.SelectedNode = this;
226+
_parentTree.FinalizeSelection();
227+
}
225228

226229
Event.current.Use();
227230
}
228231

232+
public SelectionNode GetNextChild(SelectionNode currentChild)
233+
{
234+
int currentIndex = ChildNodes.IndexOf(currentChild);
235+
236+
if (currentIndex < 0)
237+
return currentChild;
238+
239+
if (currentIndex == ChildNodes.Count - 1)
240+
return ParentNode?.GetNextChild(this) ?? currentChild;
241+
242+
return ChildNodes[currentIndex + 1];
243+
}
244+
245+
public SelectionNode GetPreviousChild(SelectionNode currentChild)
246+
{
247+
int currentIndex = ChildNodes.IndexOf(currentChild);
248+
249+
if (currentIndex < 0)
250+
return currentChild;
251+
252+
if (currentIndex == 0)
253+
return this;
254+
255+
return ChildNodes[currentIndex - 1];
256+
}
257+
229258
private void Draw(int indentLevel, Rect visibleRect)
230259
{
231260
if (ReserveSpaceAndStop())
File renamed without changes.

Editor/TypeDropdown/SelectionTreeItemsCreation.cs.meta renamed to Editor/TypeDropdown/SelectionTree.ItemsCreation.cs.meta

File renamed without changes.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
namespace TypeReferences.Editor.TypeDropdown
2+
{
3+
using System.Linq;
4+
using SolidUtilities.Editor.Extensions;
5+
using UnityEngine;
6+
7+
internal partial class SelectionTree
8+
{
9+
#region KeyboardEvents
10+
11+
protected void HandleKeyboardEvents()
12+
{
13+
// KeyDown is always used somehow, so we use KeyUp here
14+
if (Event.current.type != EventType.KeyDown)
15+
return;
16+
17+
switch (Event.current.keyCode)
18+
{
19+
case KeyCode.RightArrow:
20+
OnArrowRight();
21+
break;
22+
case KeyCode.LeftArrow:
23+
OnArrowLeft();
24+
break;
25+
case KeyCode.KeypadEnter:
26+
case KeyCode.Return:
27+
OnEnter();
28+
break;
29+
case KeyCode.DownArrow:
30+
if (_noneElement is { IsSelected: true })
31+
{
32+
OnArrowDownNone();
33+
}
34+
else
35+
{
36+
OnArrowDown();
37+
}
38+
break;
39+
case KeyCode.UpArrow:
40+
OnArrowUp();
41+
break;
42+
}
43+
}
44+
45+
private void OnArrowRight()
46+
{
47+
if (!SelectedNode.IsFolder || SelectedNode.Expanded)
48+
return;
49+
50+
SelectedNode.Expanded = true;
51+
Event.current.Use();
52+
}
53+
54+
private void OnArrowLeft()
55+
{
56+
if (!SelectedNode.IsFolder || !SelectedNode.Expanded)
57+
return;
58+
59+
SelectedNode.Expanded = false;
60+
Event.current.Use();
61+
}
62+
63+
private void OnEnter()
64+
{
65+
if (SelectedNode.IsFolder)
66+
{
67+
SelectedNode.Expanded = ! SelectedNode.Expanded;
68+
RequestRepaint();
69+
}
70+
else
71+
{
72+
FinalizeSelection();
73+
}
74+
}
75+
76+
private void OnArrowDown()
77+
{
78+
if (SelectedNode.IsFolder && SelectedNode.Expanded)
79+
{
80+
SelectedNode = SelectedNode.ChildNodes[0];
81+
}
82+
else
83+
{
84+
if (SelectedNode.IsRoot)
85+
return;
86+
87+
SelectedNode = SelectedNode.ParentNode.GetNextChild(SelectedNode);
88+
89+
if (!_visibleRect.Contains(SelectedNode.Rect))
90+
{
91+
_scrollbar.RequestScrollToNode(SelectedNode, Scrollbar.NodePosition.Bottom);
92+
}
93+
}
94+
}
95+
96+
private void OnArrowDownNone()
97+
{
98+
var firstItem = _root.ChildNodes.FirstOrDefault();
99+
100+
if (firstItem != null)
101+
SelectedNode = firstItem;
102+
}
103+
104+
private void OnArrowUp()
105+
{
106+
if (SelectedNode.IsRoot)
107+
return;
108+
109+
if (SelectedNode.ParentNode.IsRoot)
110+
{
111+
bool isFirst = SelectedNode.ParentNode.ChildNodes.IndexOf(SelectedNode) == 0;
112+
113+
if (isFirst && _noneElement != null)
114+
{
115+
SelectedNode = _noneElement;
116+
return;
117+
}
118+
}
119+
120+
var previousNode = SelectedNode.ParentNode.GetPreviousChild(SelectedNode);
121+
122+
if (IsExpandedFolder(previousNode))
123+
{
124+
// choose last item of the previous folder instead.
125+
previousNode = previousNode.ChildNodes[previousNode.ChildNodes.Count - 1];
126+
}
127+
128+
SelectedNode = previousNode;
129+
130+
if (!_visibleRect.Contains(SelectedNode.Rect))
131+
{
132+
_scrollbar.RequestScrollToNode(SelectedNode, Scrollbar.NodePosition.Top);
133+
}
134+
}
135+
136+
private bool IsExpandedFolder(SelectionNode previousNode)
137+
{
138+
return SelectedNode.IsFolder && previousNode.IsFolder && previousNode.Expanded && previousNode.ChildNodes.Count != 0;
139+
}
140+
141+
#endregion
142+
}
143+
}

Editor/TypeDropdown/SelectionTree.KeyboardInput.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)