Skip to content

Commit 1bbaad8

Browse files
authored
Merge pull request #134 from brunomikoski/feat/browser
2 parents 6b7289a + 76cc659 commit 1bbaad8

13 files changed

Lines changed: 598 additions & 1 deletion

CHANGELOG.MD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
10+
## [2.2.0]
11+
### Added
12+
- Scriptable Object Browser window
13+
14+
915
## [2.1.3]
1016
### Fixed
1117
- Fixed scripts folder validation warning showing incorrectly when mirroring folder

Scripts/Editor/Browser.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.
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using System;
2+
using UnityEditor;
3+
using UnityEditor.IMGUI.Controls;
4+
using UnityEngine;
5+
6+
namespace BrunoMikoski.ScriptableObjectCollections.Browser
7+
{
8+
public class BrowserEditorWindow : EditorWindow
9+
{
10+
[MenuItem("Window/Scriptable Object Collection Browser")]
11+
private static void Init()
12+
{
13+
BrowserEditorWindow wnd = GetWindow<BrowserEditorWindow>();
14+
wnd.titleContent = new GUIContent("Browser");
15+
wnd.Show();
16+
}
17+
18+
private const float DIVIDER_MINIMUM = 0.2f;
19+
private const float DIVIDER_MAXIMUM = 0.8f;
20+
21+
private BrowserTreeView treeView;
22+
private TreeViewState treeViewState;
23+
private Editor itemEditor;
24+
private Vector2 scrollPosition;
25+
private int separatorPosition = 250;
26+
private bool isDragging;
27+
28+
private bool viewSettings;
29+
30+
private void OnEnable()
31+
{
32+
treeViewState ??= new TreeViewState();
33+
treeView = new BrowserTreeView(treeViewState);
34+
treeView.ItemClicked += OnItemClicked;
35+
36+
BrowserSettings.Instance.SettingsChanged += OnSettingsChanged;
37+
}
38+
39+
private void OnDisable()
40+
{
41+
BrowserSettings.Instance.SettingsChanged -= OnSettingsChanged;
42+
}
43+
44+
private void OnItemClicked(BrowserTreeViewItem item)
45+
{
46+
itemEditor = Editor.CreateEditor(item.ScriptableObject);
47+
}
48+
49+
private void OnSettingsChanged()
50+
{
51+
treeView?.Reload();
52+
}
53+
54+
private void OnGUI()
55+
{
56+
DrawToolbar();
57+
DrawTreeView();
58+
DrawItemEditor();
59+
DrawSeparator();
60+
HandleSeparatorMouse();
61+
}
62+
63+
private void DrawToolbar()
64+
{
65+
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
66+
67+
if (GUILayout.Button("Refresh", EditorStyles.toolbarButton))
68+
{
69+
treeView.Reload();
70+
}
71+
72+
GUILayout.FlexibleSpace();
73+
74+
if (GUILayout.Button("Settings", EditorStyles.toolbarButton))
75+
{
76+
SettingsService.OpenProjectSettings("Project/Scriptable Object Collection/Browser");
77+
}
78+
79+
EditorGUILayout.EndHorizontal();
80+
}
81+
82+
private void DrawTreeView()
83+
{
84+
Rect treeViewRect = new(0,
85+
EditorStyles.toolbar.fixedHeight,
86+
separatorPosition,
87+
position.height - EditorStyles.toolbar.fixedHeight);
88+
89+
treeView.OnGUI(treeViewRect);
90+
}
91+
92+
private void DrawItemEditor()
93+
{
94+
if (itemEditor == null)
95+
return;
96+
97+
Rect editorRect = new(separatorPosition,
98+
EditorStyles.toolbar.fixedHeight,
99+
position.width - separatorPosition,
100+
position.height - EditorStyles.toolbar.fixedHeight);
101+
102+
GUILayout.BeginArea(editorRect);
103+
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
104+
itemEditor.OnInspectorGUI();
105+
EditorGUILayout.EndScrollView();
106+
GUILayout.EndArea();
107+
}
108+
109+
private void DrawSeparator()
110+
{
111+
Rect rect = new(separatorPosition, EditorStyles.toolbar.fixedHeight, 1, position.yMax);
112+
EditorGUI.DrawRect(rect, EditorGUIUtility.isProSkin ? Color.black : Color.gray);
113+
}
114+
115+
private void HandleSeparatorMouse()
116+
{
117+
Rect cursorRect = new(separatorPosition - 4, EditorStyles.toolbar.fixedHeight, 8, position.yMax);
118+
EditorGUIUtility.AddCursorRect(cursorRect, MouseCursor.ResizeHorizontal);
119+
120+
Event evt = Event.current;
121+
if (!evt.isMouse)
122+
return;
123+
124+
if (!cursorRect.Contains(evt.mousePosition) && !isDragging)
125+
return;
126+
127+
if (evt.type == EventType.MouseDown)
128+
{
129+
isDragging = true;
130+
}
131+
132+
if (evt.type == EventType.MouseUp)
133+
{
134+
isDragging = false;
135+
}
136+
137+
if (evt.type == EventType.MouseDrag)
138+
{
139+
separatorPosition += Mathf.RoundToInt(evt.delta.x);
140+
}
141+
142+
separatorPosition = Mathf.RoundToInt(Mathf.Clamp(separatorPosition,
143+
position.width * DIVIDER_MINIMUM,
144+
position.width * DIVIDER_MAXIMUM));
145+
146+
evt.Use();
147+
}
148+
}
149+
}

Scripts/Editor/Browser/BrowserEditorWindow.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.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using UnityEditor;
6+
using UnityEditorInternal;
7+
using UnityEngine;
8+
9+
namespace BrunoMikoski.ScriptableObjectCollections.Browser
10+
{
11+
[Serializable]
12+
public class BrowserSettings
13+
{
14+
private const string PATH = "UserSettings/ScriptableObjectCollectionBrowser.json";
15+
16+
[SettingsProvider]
17+
public static SettingsProvider CreateSettingsProvider()
18+
{
19+
return new SettingsProvider("Project/Scriptable Object Collection/Browser", SettingsScope.Project)
20+
{
21+
label = "Browser",
22+
guiHandler = Instance.OnGUI,
23+
keywords = new string[] { "SOC", "Scriptable Objects", "Scriptable Objects Collection", "Browser" }
24+
};
25+
}
26+
27+
private static BrowserSettings instance;
28+
29+
public static BrowserSettings Instance
30+
{
31+
get
32+
{
33+
if (instance != null)
34+
return instance;
35+
36+
if (File.Exists(PATH))
37+
{
38+
string json = File.ReadAllText(PATH);
39+
instance = JsonUtility.FromJson<BrowserSettings>(json);
40+
}
41+
else
42+
{
43+
instance = new BrowserSettings();
44+
}
45+
46+
return instance;
47+
}
48+
}
49+
50+
[SerializeField] private List<string> serializedTypesToIgnore = new();
51+
[SerializeField] private bool showHiddenCollections;
52+
[NonSerialized] private List<Type> uiTypesToIgnore = new();
53+
[NonSerialized] private TypeCache.TypeCollection cachedDerivedTypes;
54+
[NonSerialized] private bool hasCachedDerivedTypes;
55+
56+
private ReorderableList reorderableList;
57+
58+
public TypeCache.TypeCollection DerivedTypes
59+
{
60+
get
61+
{
62+
if (hasCachedDerivedTypes)
63+
return cachedDerivedTypes;
64+
65+
cachedDerivedTypes = TypeCache.GetTypesDerivedFrom<ScriptableObjectCollection>();
66+
hasCachedDerivedTypes = true;
67+
return cachedDerivedTypes;
68+
}
69+
}
70+
71+
public bool ShowHiddenCollections
72+
{
73+
get => showHiddenCollections;
74+
set
75+
{
76+
showHiddenCollections = value;
77+
Save();
78+
}
79+
}
80+
81+
public event Action SettingsChanged;
82+
83+
public bool IsHiddenCollection(Type type)
84+
{
85+
return serializedTypesToIgnore.Contains(type.AssemblyQualifiedName);
86+
}
87+
88+
private void OnGUI(string searchContext)
89+
{
90+
if (reorderableList == null)
91+
{
92+
InitializeList();
93+
}
94+
95+
EditorGUI.BeginChangeCheck();
96+
showHiddenCollections = EditorGUILayout.Toggle("Show Hidden Collections", showHiddenCollections);
97+
if (EditorGUI.EndChangeCheck())
98+
{
99+
Save();
100+
}
101+
102+
EditorGUILayout.LabelField("Collections to hide", EditorStyles.boldLabel);
103+
reorderableList.DoLayoutList();
104+
}
105+
106+
private void InitializeList()
107+
{
108+
uiTypesToIgnore = serializedTypesToIgnore.Select(Type.GetType).ToList();
109+
110+
reorderableList = new ReorderableList(uiTypesToIgnore, typeof(Type), true, false, true, true);
111+
reorderableList.drawElementCallback += OnDrawElement;
112+
reorderableList.onAddCallback += OnAddElement;
113+
reorderableList.onRemoveCallback += OnRemoveElement;
114+
}
115+
116+
private void OnDrawElement(Rect rect, int index, bool isActive, bool isFocused)
117+
{
118+
GUI.Label(rect, uiTypesToIgnore[index].Name);
119+
}
120+
121+
private void OnAddElement(ReorderableList list)
122+
{
123+
List<Type> items = GetFilteredTypes();
124+
GenericMenu genericMenu = new();
125+
126+
foreach (Type type in items)
127+
{
128+
// We don't want to allow the user to remove the last collection, that breaks the tree view
129+
if (items.Count == 1)
130+
{
131+
genericMenu.AddDisabledItem(new GUIContent(type.Name));
132+
}
133+
else
134+
{
135+
genericMenu.AddItem(new GUIContent(type.Name),
136+
false,
137+
() =>
138+
{
139+
uiTypesToIgnore.Add(type);
140+
serializedTypesToIgnore.Add(type.AssemblyQualifiedName);
141+
Save();
142+
});
143+
}
144+
}
145+
146+
genericMenu.ShowAsContext();
147+
}
148+
149+
private void OnRemoveElement(ReorderableList list)
150+
{
151+
uiTypesToIgnore.RemoveAt(list.index);
152+
serializedTypesToIgnore.RemoveAt(list.index);
153+
Save();
154+
}
155+
156+
private void Save()
157+
{
158+
string json = EditorJsonUtility.ToJson(this, prettyPrint: true);
159+
File.WriteAllText(PATH, json);
160+
SettingsChanged?.Invoke();
161+
}
162+
163+
public void ToggleCollection(ScriptableObjectCollection collection)
164+
{
165+
Type type = collection.GetType();
166+
167+
if (serializedTypesToIgnore.Contains(type.AssemblyQualifiedName))
168+
{
169+
uiTypesToIgnore.Remove(type);
170+
serializedTypesToIgnore.Remove(type.AssemblyQualifiedName);
171+
}
172+
else
173+
{
174+
uiTypesToIgnore.Add(type);
175+
serializedTypesToIgnore.Add(type.AssemblyQualifiedName);
176+
}
177+
178+
Save();
179+
}
180+
181+
public bool CanHide(ScriptableObjectCollection collection)
182+
{
183+
if (serializedTypesToIgnore.Contains(collection.GetType().AssemblyQualifiedName))
184+
return true;
185+
186+
List<Type> items = GetFilteredTypes();
187+
return items.Count > 1;
188+
}
189+
190+
private List<Type> GetFilteredTypes()
191+
{
192+
List<Type> items = new();
193+
TypeCache.TypeCollection typesDerivedFrom = DerivedTypes;
194+
foreach (Type type in typesDerivedFrom)
195+
{
196+
if (type.IsAbstract)
197+
continue;
198+
199+
if (type.ContainsGenericParameters)
200+
continue;
201+
202+
if (uiTypesToIgnore.Contains(type))
203+
continue;
204+
205+
items.Add(type);
206+
}
207+
208+
return items;
209+
}
210+
}
211+
}

Scripts/Editor/Browser/BrowserSettings.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)