Skip to content

Commit 69ac0cd

Browse files
committed
feat: Add a toolbar search field for searching nodes
1 parent ad181a3 commit 69ac0cd

15 files changed

+434
-96
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Reflection;
3+
using UnityEditor;
4+
using UnityEngine;
5+
using UnityEngine.Assertions;
6+
7+
namespace GBG.PlayableGraphMonitor.Editor
8+
{
9+
public static class EditorGUILayoutHelper
10+
{
11+
#region Reflection
12+
13+
private static Func<string, GUILayoutOption[], string> _toolbarSearchFieldCache;
14+
15+
16+
public static string ToolbarSearchField(string searchText)
17+
{
18+
// string EditorGUILayout.ToolbarSearchField(string);
19+
if (_toolbarSearchFieldCache == null)
20+
{
21+
MethodInfo toolbarSearchFieldMethod = typeof(EditorGUILayout).GetMethod("ToolbarSearchField", BindingFlags.Static | BindingFlags.NonPublic,
22+
null, new Type[] { typeof(string), typeof(GUILayoutOption[]) }, null);
23+
Assert.IsNotNull(toolbarSearchFieldMethod);
24+
_toolbarSearchFieldCache = (Func<string, GUILayoutOption[], string>)Delegate.CreateDelegate(typeof(Func<string, GUILayoutOption[], string>), toolbarSearchFieldMethod);
25+
}
26+
27+
searchText = _toolbarSearchFieldCache(searchText, null);
28+
return searchText;
29+
}
30+
31+
#endregion
32+
}
33+
}

Editor/Scripts/Element/EditorGUILayoutHelper.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/Scripts/Element/SearchablePopupField.cs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public override void OnOpen()
160160

161161
public override void OnGUI(Rect rect)
162162
{
163-
const string SEARCH_CONTROL = "ToolbarSearchField";
163+
const string SEARCH_CONTROL = "SearchablePopupField.PopupWindowContent.ToolbarSearchField";
164164

165165
EditorGUI.BeginChangeCheck();
166166
{
@@ -254,29 +254,4 @@ private void OnMouseUp(ReorderableList list)
254254
}
255255
}
256256
}
257-
258-
public static class EditorGUILayoutHelper
259-
{
260-
#region Reflection
261-
262-
private static Func<string, GUILayoutOption[], string> _toolbarSearchFieldCache;
263-
264-
265-
public static string ToolbarSearchField(string searchText)
266-
{
267-
// string EditorGUILayout.ToolbarSearchField(string);
268-
if (_toolbarSearchFieldCache == null)
269-
{
270-
MethodInfo toolbarSearchFieldMethod = typeof(EditorGUILayout).GetMethod("ToolbarSearchField", BindingFlags.Static | BindingFlags.NonPublic,
271-
null, new Type[] { typeof(string), typeof(GUILayoutOption[]) }, null);
272-
Assert.IsNotNull(toolbarSearchFieldMethod);
273-
_toolbarSearchFieldCache = (Func<string, GUILayoutOption[], string>)Delegate.CreateDelegate(typeof(Func<string, GUILayoutOption[], string>), toolbarSearchFieldMethod);
274-
}
275-
276-
searchText = _toolbarSearchFieldCache(searchText, null);
277-
return searchText;
278-
}
279-
280-
#endregion
281-
}
282257
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using UnityEditor;
4+
using UnityEditorInternal;
5+
using UnityEngine;
6+
using PopupWindow = UnityEditor.PopupWindow;
7+
8+
namespace GBG.PlayableGraphMonitor.Editor
9+
{
10+
public class SearchablePopupWindowContent<T> : PopupWindowContent
11+
{
12+
public static void Show(Rect activatorRect, GetChoices choicesProvider, Action<T> itemSelected,
13+
Func<T, string> formatListItemCallback = null, Func<T, string> formatSelectedValueCallback = null)
14+
{
15+
PopupWindow.Show(activatorRect, new SearchablePopupWindowContent<T>(choicesProvider, itemSelected,
16+
formatListItemCallback, formatSelectedValueCallback));
17+
}
18+
19+
20+
public delegate void GetChoices(out IList<T> choices, out int selectionIndex);
21+
22+
private readonly GetChoices _choicesProvider;
23+
private readonly Action<T> _itemSelected;
24+
private readonly Func<T, string> _formatListItemCallback;
25+
private readonly Func<T, string> _formatSelectedValueCallback;
26+
27+
private string _searchContent;
28+
private Vector2 _scrollPosition;
29+
private List<T> _filteredChoices;
30+
private ReorderableList _list;
31+
32+
public Vector2 minSize { get; set; } = new Vector2(300, 200);
33+
public Vector2 maxSize { get; set; } = new Vector2(900, 600);
34+
35+
36+
public SearchablePopupWindowContent(GetChoices choicesProvider, Action<T> itemSelected,
37+
Func<T, string> formatListItemCallback = null, Func<T, string> formatSelectedValueCallback = null)
38+
{
39+
_choicesProvider = choicesProvider ?? throw new ArgumentNullException(nameof(choicesProvider));
40+
_itemSelected = itemSelected;
41+
_formatListItemCallback = formatListItemCallback;
42+
_formatSelectedValueCallback = formatSelectedValueCallback ?? formatListItemCallback;
43+
}
44+
45+
public override void OnOpen()
46+
{
47+
base.OnOpen();
48+
49+
IList<T> allChoices = null;
50+
int currentSelection = -1;
51+
_choicesProvider?.Invoke(out allChoices, out currentSelection);
52+
53+
_filteredChoices = new List<T>(allChoices ?? Array.Empty<T>());
54+
_list = new ReorderableList(_filteredChoices, typeof(T))
55+
{
56+
index = currentSelection,
57+
displayAdd = false,
58+
displayRemove = false,
59+
headerHeight = 0,
60+
footerHeight = 0,
61+
draggable = false,
62+
onMouseUpCallback = OnMouseUp,
63+
drawElementCallback = DrawElement,
64+
drawElementBackgroundCallback = DrawElementBackground,
65+
};
66+
}
67+
68+
public override void OnGUI(Rect rect)
69+
{
70+
const string SEARCH_CONTROL = "SearchablePopupWindowContent.ToolbarSearchField";
71+
72+
EditorGUI.BeginChangeCheck();
73+
{
74+
GUI.SetNextControlName(SEARCH_CONTROL);
75+
_searchContent = EditorGUILayoutHelper.ToolbarSearchField(_searchContent);
76+
EditorGUI.FocusTextInControl(SEARCH_CONTROL);
77+
}
78+
if (EditorGUI.EndChangeCheck())
79+
{
80+
IList<T> allChoices = null;
81+
int currentSelection = -1;
82+
_choicesProvider?.Invoke(out allChoices, out currentSelection);
83+
allChoices = allChoices ?? Array.Empty<T>();
84+
85+
_filteredChoices.Clear();
86+
for (int i = 0; i < allChoices.Count; i++)
87+
{
88+
string elemDisplayName = GetElementDisplayName(allChoices, i, currentSelection == i);
89+
if (elemDisplayName.IndexOf(_searchContent, StringComparison.OrdinalIgnoreCase) >= 0)
90+
_filteredChoices.Add(allChoices[i]);
91+
}
92+
93+
_list.list = _filteredChoices;
94+
}
95+
96+
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
97+
_list.DoLayoutList();
98+
EditorGUILayout.EndScrollView();
99+
100+
editorWindow.Repaint();
101+
}
102+
103+
public override Vector2 GetWindowSize()
104+
{
105+
IList<T> allChoices = null;
106+
int currentSelection = -1;
107+
_choicesProvider?.Invoke(out allChoices, out currentSelection);
108+
allChoices = allChoices ?? Array.Empty<T>();
109+
T currentValue = currentSelection == -1 ? default : allChoices[currentSelection];
110+
111+
GUIContent tempLabelContent = new GUIContent();
112+
float maxItemWidth = 0;
113+
foreach (T item in allChoices)
114+
{
115+
string label = item?.GetHashCode() == currentValue?.GetHashCode()
116+
? _formatSelectedValueCallback?.Invoke(item) ?? item?.ToString() ?? string.Empty
117+
: _formatListItemCallback?.Invoke(item) ?? item?.ToString() ?? string.Empty;
118+
tempLabelContent.text = label;
119+
float width = EditorStyles.toolbarPopup.CalcSize(tempLabelContent).x + 12;
120+
if (width > maxItemWidth)
121+
maxItemWidth = width;
122+
}
123+
124+
float listHeight = _list.elementHeight * _list.count + 36;
125+
Vector2 size = new Vector2
126+
{
127+
x = Mathf.Clamp(maxItemWidth, minSize.x, maxSize.x),
128+
y = Mathf.Clamp(listHeight, minSize.y, maxSize.y),
129+
};
130+
131+
return size;
132+
}
133+
134+
135+
private string GetElementDisplayName(IList<T> elements, int index, bool isActive)
136+
{
137+
T item = elements[index];
138+
string text;
139+
if (isActive)
140+
text = _formatSelectedValueCallback?.Invoke(item) ?? item.ToString();
141+
else
142+
text = _formatListItemCallback?.Invoke(item) ?? item.ToString();
143+
144+
return text;
145+
}
146+
147+
private void DrawElementBackground(Rect rect, int index, bool isActive, bool isFocused)
148+
{
149+
if (isActive)
150+
{
151+
EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.5f));
152+
}
153+
else if (isFocused)
154+
{
155+
EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.2f));
156+
}
157+
else if (rect.Contains(Event.current.mousePosition))
158+
{
159+
EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.1f));
160+
}
161+
}
162+
163+
private void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
164+
{
165+
string text = GetElementDisplayName(_filteredChoices, index, isActive);
166+
GUI.Label(rect, text);
167+
}
168+
169+
private void OnMouseUp(ReorderableList list)
170+
{
171+
T newSelection = _filteredChoices[list.index];
172+
editorWindow.Close();
173+
_itemSelected?.Invoke(newSelection);
174+
}
175+
}
176+
}

Editor/Scripts/Element/SearchablePopupWindowContent.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/Scripts/GraphView/PlayableGraphView.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public class PlayableGraphView : UGraphView
4646

4747
private bool _isViewFocused;
4848

49+
public IReadOnlyList<GraphViewNode> ActiveNodes => _activeNodes;
50+
private List<GraphViewNode> _activeNodes = new List<GraphViewNode>();
51+
4952

5053
#region Properties for connection and layout
5154

@@ -86,6 +89,7 @@ public PlayableGraphView()
8689

8790
public bool Update(PlayableGraphViewUpdateContext context)
8891
{
92+
_activeNodes.Clear();
8993
var success = true;
9094

9195
var playableGraphChanged = !GraphTool.IsEqual(ref _playableGraph, ref context.PlayableGraph);
@@ -205,9 +209,13 @@ private void AllocAndSetupPlayableNodeTree(PlayableGraphViewUpdateContext contex
205209

206210
private void ConnectNodes()
207211
{
212+
_activeNodes.Clear();
213+
208214
// PlayableOutputNodes
209215
foreach (var parentNode in _outputNodePoolFactory.GetActiveNodes())
210216
{
217+
_activeNodes.Add(parentNode);
218+
211219
var childPlayable = parentNode.PlayableOutput.GetSourcePlayable();
212220
if (!childPlayable.IsValid())
213221
{
@@ -226,6 +234,8 @@ private void ConnectNodes()
226234
// PlayableNodes
227235
foreach (var parentNode in _playableNodePoolFactory.GetActiveNodes())
228236
{
237+
_activeNodes.Add(parentNode);
238+
229239
var parentPlayable = parentNode.Playable;
230240
var inputCount = parentPlayable.GetInputCount();
231241
for (int i = 0; i < inputCount; i++)
@@ -429,16 +439,20 @@ public void SetNodesMovability(bool movable)
429439
foreach (var outputNode in _outputNodePoolFactory.GetActiveNodes())
430440
{
431441
var nodeCaps = outputNode.capabilities;
432-
if (movable) nodeCaps |= Capabilities.Movable;
433-
else nodeCaps &= ~Capabilities.Movable;
442+
if (movable)
443+
nodeCaps |= Capabilities.Movable;
444+
else
445+
nodeCaps &= ~Capabilities.Movable;
434446
outputNode.capabilities = nodeCaps;
435447
}
436448

437449
foreach (var playableNode in _playableNodePoolFactory.GetActiveNodes())
438450
{
439451
var nodeCaps = playableNode.capabilities;
440-
if (movable) nodeCaps |= Capabilities.Movable;
441-
else nodeCaps &= ~Capabilities.Movable;
452+
if (movable)
453+
nodeCaps |= Capabilities.Movable;
454+
else
455+
nodeCaps &= ~Capabilities.Movable;
442456
playableNode.capabilities = nodeCaps;
443457
}
444458
}

Editor/Scripts/Node/AnimationClipPlayableNode.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,5 +176,12 @@ protected override void DrawNodeDescriptionInternal()
176176
GUILayout.Label($" #{(i + 1)} {evtPosition:F2}% {evt.functionName}");
177177
}
178178
}
179+
180+
public AnimationClip GetAnimationClip()
181+
{
182+
var clipPlayable = (AnimationClipPlayable)Playable;
183+
var clip = clipPlayable.GetAnimationClip();
184+
return clip;
185+
}
179186
}
180187
}

Editor/Scripts/Node/AnimationPlayableOutputNode.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,12 @@ protected override void DrawNodeDescriptionInternal()
6666
if (EditorGUI.EndChangeCheck())
6767
animationPlayableOutput.SetSortingOrder(sortingOrder);
6868
}
69+
70+
public Animator GetAnimatorTarget()
71+
{
72+
var animationPlayableOutput = (AnimationPlayableOutput)PlayableOutput;
73+
var target = animationPlayableOutput.GetTarget();
74+
return target;
75+
}
6976
}
7077
}

Editor/Scripts/Node/AnimationScriptPlayableNode.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,21 @@ protected override void AppendPlayableTypeDescription()
5858
GUILayout.Label($"Job: {jobType?.Name ?? "?"}");
5959
}
6060

61-
protected override void DrawNodeDescriptionInternal()
61+
protected override void DrawNodeDescriptionInternal()
6262
{
6363
base.DrawNodeDescriptionInternal();
6464

6565
if (!Playable.IsValid())
6666
{
6767
return;
6868
}
69-
69+
7070
var animScriptPlayable = (AnimationScriptPlayable)Playable;
7171
GUILayout.Label(LINE);
7272
GUILayout.Label($"ProcessInputs: {animScriptPlayable.GetProcessInputs()}");
7373
}
7474

75-
private Type GetJobType()
75+
public Type GetJobType()
7676
{
7777
if (_getJobTypeFunc != null)
7878
{

Editor/Scripts/Node/AudioClipPlayableNode.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,13 @@ protected override void DrawNodeDescriptionInternal()
145145
GUILayout.Label($"LoadInBackground: {clip.loadInBackground}");
146146
GUILayout.Label($"PreloadAudioData: {clip.preloadAudioData}");
147147
}
148+
149+
public AudioClip GetAudioClip()
150+
{
151+
var clipPlayable = (AudioClipPlayable)Playable;
152+
GUILayout.Label(LINE);
153+
var clip = clipPlayable.GetClip();
154+
return clip;
155+
}
148156
}
149157
}

0 commit comments

Comments
 (0)