Skip to content

Commit f71a598

Browse files
Merge pull request #138 from brunomikoski/feature/new-soc-item-editor-options
Feature/new soc item editor options
2 parents 3e81317 + b56f347 commit f71a598

File tree

4 files changed

+158
-25
lines changed

4 files changed

+158
-25
lines changed

Scripts/Editor/Core/CollectionItemDropdown.cs

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5+
using UnityEditor;
56
using UnityEditor.IMGUI.Controls;
67
using UnityEngine;
78
using Object = UnityEngine.Object;
@@ -16,22 +17,38 @@ public sealed class CollectionItemDropdown : AdvancedDropdown
1617

1718
private readonly Type itemType;
1819
private readonly SOCItemEditorOptionsAttribute options;
19-
private readonly Object owner;
20+
private readonly SerializedProperty serializedProperty;
2021
private readonly MethodInfo validationMethod;
22+
23+
private readonly MethodInfo onSelectCallbackMethod;
2124

2225
public CollectionItemDropdown(AdvancedDropdownState state, Type targetItemType,
23-
SOCItemEditorOptionsAttribute options, Object owner) : base(state)
26+
SOCItemEditorOptionsAttribute options, SerializedProperty serializedProperty) : base(state)
2427
{
2528
itemType = targetItemType;
2629
collections = CollectionsRegistry.Instance.GetCollectionsByItemType(itemType);
2730
minimumSize = new Vector2(200, 300);
2831
this.options = options;
29-
this.owner = owner;
32+
this.serializedProperty = serializedProperty;
3033

31-
32-
if (options != null && !string.IsNullOrEmpty(options.ValidateMethod))
34+
if (options != null)
3335
{
34-
validationMethod = owner.GetType().GetMethod(options.ValidateMethod, new[] {itemType});
36+
Object owner = serializedProperty.serializedObject.targetObject;
37+
if (!string.IsNullOrEmpty(options.ValidateMethod))
38+
validationMethod = owner.GetType().GetMethod(options.ValidateMethod, new[] {itemType});
39+
40+
// If it's specified that a callback should be fired when an item is selected, cache that callback.
41+
if (!string.IsNullOrEmpty(options.OnSelectCallbackMethod))
42+
{
43+
onSelectCallbackMethod = owner.GetType().GetMethod(options.OnSelectCallbackMethod,
44+
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
45+
null, new[] {itemType, itemType}, null);
46+
if (onSelectCallbackMethod == null)
47+
{
48+
Debug.LogWarning($"Component '{owner.name}' wants selection callback " +
49+
$"'{options.OnSelectCallbackMethod}' which is not a valid method.");
50+
}
51+
}
3552
}
3653
}
3754

@@ -42,19 +59,49 @@ protected override AdvancedDropdownItem BuildRoot()
4259
root.AddChild(new AdvancedDropdownItem("None"));
4360
root.AddSeparator();
4461

62+
// If specified, limit the displayed items to those of a collection specified in a certain field.
63+
ScriptableObjectCollection collectionToConstrainTo = null;
64+
if (!string.IsNullOrEmpty(options.ConstrainToCollectionField))
65+
{
66+
SerializedProperty collectionField = serializedProperty.serializedObject.FindProperty(
67+
options.ConstrainToCollectionField);
68+
if (collectionField == null)
69+
{
70+
Debug.LogWarning($"Tried to constrain dropdown to collection specified in field " +
71+
$"'{options.ConstrainToCollectionField}' but no such field existed in " +
72+
$"'{serializedProperty.serializedObject.targetObject}'");
73+
return root;
74+
}
75+
76+
collectionToConstrainTo = collectionField.objectReferenceValue as ScriptableObjectCollection;
77+
if (collectionToConstrainTo == null)
78+
{
79+
Debug.LogWarning($"Tried to constrain dropdown to collection specified in field " +
80+
$"'{options.ConstrainToCollectionField}' but no collection was specified.");
81+
return root;
82+
}
83+
}
84+
bool shouldConstrainToCollection = collectionToConstrainTo != null;
85+
4586
AdvancedDropdownItem targetParent = root;
4687
bool multipleCollections = collections.Count > 1;
4788
for (int i = 0; i < collections.Count; i++)
4889
{
4990
ScriptableObjectCollection collection = collections[i];
91+
92+
// If we're meant to constrain the selection to a specific collection, enforce that now.
93+
if (shouldConstrainToCollection && collectionToConstrainTo != collection)
94+
continue;
5095

51-
if (multipleCollections)
96+
// If there are multiple collections, group them together.
97+
if (multipleCollections && !shouldConstrainToCollection)
5298
{
5399
AdvancedDropdownItem collectionParent = new AdvancedDropdownItem(collection.name);
54100
root.AddChild(collectionParent);
55101
targetParent = collectionParent;
56102
}
57103

104+
// Add every individual item in the collection.
58105
for (int j = 0; j < collection.Count; j++)
59106
{
60107
ScriptableObject collectionItem = collection[j];
@@ -64,11 +111,12 @@ protected override AdvancedDropdownItem BuildRoot()
64111

65112
if (validationMethod != null)
66113
{
67-
bool result = (bool) validationMethod.Invoke(owner, new object[] {collectionItem});
114+
bool result = (bool) validationMethod.Invoke(
115+
serializedProperty.serializedObject.targetObject, new object[] {collectionItem});
68116
if (!result)
69117
continue;
70118
}
71-
119+
72120
targetParent.AddChild(new CollectionItemDropdownItem(collectionItem));
73121
}
74122
}
@@ -81,22 +129,57 @@ protected override AdvancedDropdownItem BuildRoot()
81129
return root;
82130
}
83131

132+
private void InvokeOnSelectCallback(ScriptableObject from, ScriptableObject to)
133+
{
134+
if (onSelectCallbackMethod == null)
135+
return;
136+
137+
object[] arguments = { from, to };
138+
139+
// The method may be static, in which case there is no target.
140+
if (onSelectCallbackMethod.IsStatic)
141+
{
142+
onSelectCallbackMethod.Invoke(null, arguments);
143+
return;
144+
}
145+
146+
// Otherwise, fire the callback on every target.
147+
for (int i = 0; i < serializedProperty.serializedObject.targetObjects.Length; i++)
148+
{
149+
object target = serializedProperty.serializedObject.targetObjects[i];
150+
onSelectCallbackMethod.Invoke(target, arguments);
151+
}
152+
}
153+
84154
protected override void ItemSelected(AdvancedDropdownItem item)
85155
{
86156
base.ItemSelected(item);
87157

158+
ScriptableObject previousValue = null;
159+
if (onSelectCallbackMethod != null)
160+
previousValue = serializedProperty.objectReferenceValue as ScriptableObject;
161+
88162
if (item.name.Equals(CREATE_NEW_TEXT, StringComparison.OrdinalIgnoreCase))
89163
{
90164
ScriptableObjectCollection collection = collections.First();
91165
ScriptableObject newItem = CollectionCustomEditor.AddNewItem(collection, itemType);
92166
callback.Invoke(newItem);
167+
168+
InvokeOnSelectCallback(previousValue, newItem);
169+
93170
return;
94171
}
95172

96173
if (item is CollectionItemDropdownItem dropdownItem)
174+
{
97175
callback.Invoke(dropdownItem.CollectionItem);
176+
InvokeOnSelectCallback(previousValue, dropdownItem.CollectionItem);
177+
}
98178
else
179+
{
99180
callback.Invoke(null);
181+
InvokeOnSelectCallback(previousValue, null);
182+
}
100183
}
101184

102185
public void Show(Rect rect, Action<ScriptableObject> onSelectedCallback)

Scripts/Editor/PropertyDrawers/CollectionItemIndirectReferencePropertyDrawer.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
4141
SetCollectionItemType();
4242

4343
if (socItemPropertyDrawer == null)
44-
CreateCollectionItemPropertyDrawer(property.serializedObject.targetObject);
44+
CreateCollectionItemPropertyDrawer(property);
4545

4646
drawingProperty = property;
4747
itemGUIDValueASerializedProperty = property.FindPropertyRelative(COLLECTION_ITEM_GUID_VALUE_A_PROPERTY_PATH);
@@ -64,17 +64,16 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
6464

6565
if (socItemPropertyDrawer.OptionsAttribute.DrawType == DrawType.Dropdown)
6666
{
67-
DrawItemDrawer(position, label, collectionItem);
67+
DrawItemDrawer(position, property, label, collectionItem);
6868
return;
6969
}
7070

7171
EditorGUI.PropertyField(position, property, label, true);
7272
}
7373

74-
private void DrawItemDrawer(Rect position, GUIContent label, ScriptableObject collectionItem
75-
)
74+
private void DrawItemDrawer(Rect position, SerializedProperty property, GUIContent label, ScriptableObject collectionItem)
7675
{
77-
socItemPropertyDrawer.DrawCollectionItemDrawer(ref position, collectionItem, label, item =>
76+
socItemPropertyDrawer.DrawCollectionItemDrawer(ref position, property, collectionItem, label, item =>
7877
{
7978
SetSerializedPropertyGUIDs(item);
8079
drawingProperty.serializedObject.ApplyModifiedProperties();
@@ -133,11 +132,10 @@ private bool TryGetCollectionItem(out ScriptableObject item)
133132
return true;
134133
}
135134

136-
private void CreateCollectionItemPropertyDrawer(Object serializedObjectTargetObject)
135+
private void CreateCollectionItemPropertyDrawer(SerializedProperty serializedProperty)
137136
{
138137
socItemPropertyDrawer = new SOCItemPropertyDrawer();
139-
socItemPropertyDrawer.Initialize(collectionItemType, serializedObjectTargetObject,
140-
GetOptionsAttribute());
138+
socItemPropertyDrawer.Initialize(collectionItemType, serializedProperty, GetOptionsAttribute());
141139
}
142140

143141
private void SetCollectionItemType()

Scripts/Editor/PropertyDrawers/SOCItemPropertyDrawer.cs

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
7272
item = property.objectReferenceValue as ScriptableObject;
7373

7474
EditorGUI.BeginProperty(position, label, property);
75-
DrawCollectionItemDrawer(ref position, item, label,
75+
DrawCollectionItemDrawer(ref position, property, item, label,
7676
newItem =>
7777
{
7878
property.objectReferenceValue = newItem;
@@ -81,7 +81,8 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
8181
EditorGUI.EndProperty();
8282
}
8383

84-
internal void DrawCollectionItemDrawer(ref Rect position, ScriptableObject collectionItem, GUIContent label,
84+
internal void DrawCollectionItemDrawer(
85+
ref Rect position, SerializedProperty property, ScriptableObject collectionItem, GUIContent label,
8586
Action<ScriptableObject> callback)
8687
{
8788
float originY = position.y;
@@ -98,7 +99,7 @@ internal void DrawCollectionItemDrawer(ref Rect position, ScriptableObject colle
9899
}
99100

100101
DrawGotoButton(ref prefixPosition, collectionItem);
101-
DrawCollectionItemDropDown(ref prefixPosition, collectionItem, callback);
102+
DrawCollectionItemDropDown(ref prefixPosition, property, collectionItem, callback);
102103
DrawEditorPreview(ref position, collectionItem);
103104
EditorGUI.indentLevel = indent;
104105
totalHeight = position.y - originY;
@@ -171,15 +172,16 @@ private void Initialize(SerializedProperty property)
171172
itemType = arrayOrListType ?? TargetFieldInfo.FieldType;
172173
}
173174

174-
Initialize(itemType, property.serializedObject.targetObject, GetOptionsAttribute());
175+
Initialize(itemType, property, GetOptionsAttribute());
175176
}
176177

177178
internal void Initialize(Type collectionItemType, SOCItemEditorOptionsAttribute optionsAttribute)
178179
{
179180
Initialize(collectionItemType, null, optionsAttribute ?? GetOptionsAttribute());
180181
}
181182

182-
internal void Initialize(Type collectionItemType, Object obj, SOCItemEditorOptionsAttribute optionsAttribute)
183+
internal void Initialize(
184+
Type collectionItemType, SerializedProperty serializedProperty, SOCItemEditorOptionsAttribute optionsAttribute)
183185
{
184186
if (initialized)
185187
return;
@@ -193,26 +195,65 @@ internal void Initialize(Type collectionItemType, Object obj, SOCItemEditorOptio
193195
new AdvancedDropdownState(),
194196
collectionItemType,
195197
OptionsAttribute,
196-
obj
198+
serializedProperty
197199
);
200+
198201
currentItemType = collectionItemType;
199-
currentObject = obj;
202+
currentObject = serializedProperty.serializedObject.targetObject;
200203
initialized = true;
201204

202205
}
203206

204-
private void DrawCollectionItemDropDown(ref Rect position, ScriptableObject collectionItem,
207+
private void DrawCollectionItemDropDown(
208+
ref Rect position, SerializedProperty property, ScriptableObject collectionItem,
205209
Action<ScriptableObject> callback)
206210
{
207211
GUIContent displayValue = new GUIContent("None");
208212

209213
if (collectionItem != null)
210214
displayValue = new GUIContent(collectionItem.name);
211215

216+
bool canUseDropDown = true;
217+
bool isDropdownError = false;
218+
219+
// If the options are meant to be constrained to a specific collection, check if the collection specified
220+
// is valid. If not, draw some useful messages so you're aware what's wrong and know how to fix it.
221+
if (!string.IsNullOrEmpty(OptionsAttribute.ConstrainToCollectionField))
222+
{
223+
SerializedProperty collectionField = property.serializedObject.FindProperty(
224+
OptionsAttribute.ConstrainToCollectionField);
225+
if (collectionField == null)
226+
{
227+
displayValue.text = $"Invalid collection constraint '{OptionsAttribute.ConstrainToCollectionField}'";
228+
canUseDropDown = false;
229+
isDropdownError = true;
230+
}
231+
else
232+
{
233+
ScriptableObjectCollection collectionToConstrainTo = collectionField
234+
.objectReferenceValue as ScriptableObjectCollection;
235+
if (collectionToConstrainTo == null)
236+
{
237+
displayValue.text = $"No collection specified.";
238+
canUseDropDown = false;
239+
}
240+
}
241+
}
242+
243+
bool wasGuiEnabled = GUI.enabled;
244+
GUI.enabled = canUseDropDown;
245+
246+
Color originalContentColor = GUI.contentColor;
247+
if (isDropdownError)
248+
GUI.contentColor = Color.red;
249+
212250
if (GUI.Button(position, displayValue, EditorStyles.popup))
213251
{
214252
collectionItemDropdown.Show(position, callback.Invoke);
215253
}
254+
255+
GUI.contentColor = originalContentColor;
256+
GUI.enabled = wasGuiEnabled;
216257
}
217258

218259
private void DrawGotoButton(ref Rect popupRect, ScriptableObject collectionItem)

Scripts/Runtime/Attributes/SOCItemEditorOptionsAttribute.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ public class SOCItemEditorOptionsAttribute : Attribute
2929
public bool ShouldDrawPreviewButton { get; set; } = true;
3030

3131
public string ValidateMethod { get; set; }
32+
33+
/// <summary>
34+
/// If specified, only show collection items that belong to the collection assigned to the specified field.
35+
/// </summary>
36+
public string ConstrainToCollectionField { get; set; }
37+
38+
/// <summary>
39+
/// If specified, will perform this method whenever the value changes.
40+
/// Parameters of the method should be: ItemType from, ItemType to
41+
/// </summary>
42+
public string OnSelectCallbackMethod { get; set; }
3243
}
3344

3445
//Temporary

0 commit comments

Comments
 (0)