diff --git a/Scripts/Editor/Core/CollectionCustomEditor.cs b/Scripts/Editor/Core/CollectionCustomEditor.cs index efd109d..f3cc1a3 100644 --- a/Scripts/Editor/Core/CollectionCustomEditor.cs +++ b/Scripts/Editor/Core/CollectionCustomEditor.cs @@ -831,9 +831,12 @@ private void CheckGeneratedCodeLocation() } } - public static void SetLastAddedEnum(ScriptableObjectCollectionItem collectionItem) + public static ScriptableObjectCollectionItem AddNewItem(ScriptableObjectCollection collection, Type itemType) { + ScriptableObjectCollectionItem collectionItem = collection.AddNew(itemType); + Selection.objects = new Object[] {collection}; LAST_ADDED_COLLECTION_ITEM = collectionItem; + return collectionItem; } } } diff --git a/Scripts/Editor/Core/CollectionItemDropdown.cs b/Scripts/Editor/Core/CollectionItemDropdown.cs index 55f5429..217a886 100644 --- a/Scripts/Editor/Core/CollectionItemDropdown.cs +++ b/Scripts/Editor/Core/CollectionItemDropdown.cs @@ -86,10 +86,8 @@ protected override void ItemSelected(AdvancedDropdownItem item) if (item.name.Equals(CREATE_NEW_TEXT, StringComparison.OrdinalIgnoreCase)) { ScriptableObjectCollection collection = collections.First(); - ScriptableObjectCollectionItem collectionItem = collection.AddNew(itemType); - callback.Invoke(collectionItem); - Selection.objects = new Object[] {collection}; - CollectionCustomEditor.SetLastAddedEnum(collectionItem); + ScriptableObjectCollectionItem newItem = CollectionCustomEditor.AddNewItem(collection, itemType); + callback.Invoke(newItem); return; } diff --git a/Scripts/Editor/Core/CollectionItemPickerPropertyDrawer.cs b/Scripts/Editor/Core/CollectionItemPickerPropertyDrawer.cs new file mode 100644 index 0000000..a407282 --- /dev/null +++ b/Scripts/Editor/Core/CollectionItemPickerPropertyDrawer.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace BrunoMikoski.ScriptableObjectCollections +{ + /// + /// Lets you pick one or more items from a collection, similar to how an enum field would work if the enum had the + /// [Flags] attribute applied to it. + /// + [CustomPropertyDrawer(typeof(CollectionItemPicker<>))] + public class CollectionItemPickerPropertyDrawer : PropertyDrawer + { + private readonly List + tempMaskItems = new List(); + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return EditorGUIUtility.singleLineHeight; + } + + private static bool Contains(SerializedProperty itemsProperty, ScriptableObjectCollectionItem item) + { + for (int i = 0; i < itemsProperty.arraySize; i++) + { + if (itemsProperty.GetArrayElementAtIndex(i).objectReferenceValue == item) + return true; + } + + return false; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + // Figure out the collection item type. + Type[] genericArguments = fieldInfo.FieldType.GetGenericArguments(); + Type collectionItemType = genericArguments[0]; + + // Now figure out the collection type. + ScriptableObjectCollection collection; + CollectionsRegistry.Instance.TryGetCollectionFromItemType(collectionItemType, out collection); + + // TODO: Should this support multiple collections? I'm not sure how that use case works. I just saw it + // being used elsewhere. + + if (collection == null) + { + EditorGUI.LabelField(position, label, new GUIContent("Could not determine collection.")); + return; + } + + // Get the list of items. + SerializedProperty itemsProperty = property.FindPropertyRelative("items"); + + // Reserve space for the buttons. We can't use AdvancedDropdown for Flags kind of behaviour, so we have + // to use a real MaskField to get it done. I still want to support adding a new entry from the inspector + // though. For that reason I am adding an Add button next to the dropdown. + position.width -= CollectionItemPropertyDrawer.BUTTON_WIDTH * 2; + Rect goToButtonRect = new Rect( + position.xMax, position.y, CollectionItemPropertyDrawer.BUTTON_WIDTH, position.height); + Rect addButtonRect = new Rect( + goToButtonRect.xMax, position.y, CollectionItemPropertyDrawer.BUTTON_WIDTH, position.height); + + // If the collection is empty, we cannot use MaskField with an empty array because that throws exceptions. + // Because of that we treat it as a special case where we draw a disabled PopUp field. You can then use + // the Go To and Add buttons to add an item to the collection and then you can begin picking. + if (collection.Count == 0) + { + // Calculate the rects for the label and the add button and such. + Rect labelRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth, position.height); + Rect valueRect = new Rect( + position.x + EditorGUIUtility.labelWidth, position.y, position.width - EditorGUIUtility.labelWidth, + position.height); + + // Draw the label. + EditorGUI.LabelField(labelRect, label); + + // Draw the inactive dropdown. + bool wasGuiEnabled = GUI.enabled; + GUI.enabled = false; + EditorGUI.Popup(valueRect, GUIContent.none, 0, new[] {new GUIContent("")}); + GUI.enabled = wasGuiEnabled; + } + else + { + // Create an array of displayed options. + string[] displayedOptions = new string[collection.Count]; + int mask = 0; + for (int i = 0; i < collection.Count; i++) + { + displayedOptions[i] = collection[i].name; + if (Contains(itemsProperty, collection[i])) + mask |= 1 << i; + } + + int maskNew = EditorGUI.MaskField(position, label, mask, displayedOptions); + if (mask != maskNew) + { + // First convert the newly selected mask to a list of items. + tempMaskItems.Clear(); + for (int i = 0; i < collection.Count; i++) + { + int flag = 1 << i; + if ((maskNew & flag) == flag) + tempMaskItems.Add(collection[i]); + } + + // Now update the property to have the values in that list... + itemsProperty.arraySize = tempMaskItems.Count; + for (int i = 0; i < tempMaskItems.Count; i++) + { + itemsProperty.GetArrayElementAtIndex(i).objectReferenceValue = tempMaskItems[i]; + } + } + } + + // Draw the Go To button. + bool shouldGoToCollection = GUI.Button(goToButtonRect, CollectionEditorGUI.ARROW_RIGHT_CHAR); + if (shouldGoToCollection) + Selection.activeObject = collection; + + // Draw the add button. + bool shouldCreateNewItem = GUI.Button(addButtonRect, "+"); + if (shouldCreateNewItem) + CollectionCustomEditor.AddNewItem(collection, collectionItemType); + } + } +} diff --git a/Scripts/Editor/Core/CollectionItemPickerPropertyDrawer.cs.meta b/Scripts/Editor/Core/CollectionItemPickerPropertyDrawer.cs.meta new file mode 100644 index 0000000..4faa4ac --- /dev/null +++ b/Scripts/Editor/Core/CollectionItemPickerPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 264d8d94cfdf3a346bf47076fd933599 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/Core/CollectionItemPropertyDrawer.cs b/Scripts/Editor/Core/CollectionItemPropertyDrawer.cs index 5678d96..8521561 100644 --- a/Scripts/Editor/Core/CollectionItemPropertyDrawer.cs +++ b/Scripts/Editor/Core/CollectionItemPropertyDrawer.cs @@ -10,6 +10,8 @@ namespace BrunoMikoski.ScriptableObjectCollections [CustomPropertyDrawer(typeof(ScriptableObjectCollectionItem), true)] public class CollectionItemPropertyDrawer : PropertyDrawer { + public const float BUTTON_WIDTH = 30; + private static readonly CollectionItemEditorOptionsAttribute DefaultAttribute = new CollectionItemEditorOptionsAttribute(); @@ -210,7 +212,7 @@ private void DrawGotoButton(ref Rect popupRect, ScriptableObjectCollectionItem c if (!OptionsAttribute.ShouldDrawGotoButton) return; Rect buttonRect = popupRect; - buttonRect.width = 30; + buttonRect.width = BUTTON_WIDTH; buttonRect.height = 18; popupRect.width -= buttonRect.width; buttonRect.x += popupRect.width; @@ -227,7 +229,7 @@ private void DrawEditFoldoutButton(ref Rect popupRect, ScriptableObjectCollectio return; Rect buttonRect = popupRect; - buttonRect.width = 30; + buttonRect.width = BUTTON_WIDTH; buttonRect.height = 18; popupRect.width -= buttonRect.width; buttonRect.x += popupRect.width; diff --git a/Scripts/Runtime/Core/CollectionItemPicker.cs b/Scripts/Runtime/Core/CollectionItemPicker.cs new file mode 100644 index 0000000..0ebfc8e --- /dev/null +++ b/Scripts/Runtime/Core/CollectionItemPicker.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace BrunoMikoski.ScriptableObjectCollections +{ + /// + /// Collection Item Picker lets you pick one or more items from a collection, similar to how an enum field would + /// work if the enum had the [Flags] attribute applied to it. + /// + [Serializable] + public class CollectionItemPicker : IList + where ItemType : ScriptableObjectCollectionItem + { + [SerializeField] private List items = new List(); + public List Items => items; + + // Implement IList and forward its members to items. This way we can conveniently use this thing as a list. + #region IList members implementation + + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)items).GetEnumerator(); + } + + public void Add(ItemType item) + { + items.Add(item); + } + + public void Clear() + { + items.Clear(); + } + + public bool Contains(ItemType item) + { + return items.Contains(item); + } + + public void CopyTo(ItemType[] array, int arrayIndex) + { + items.CopyTo(array, arrayIndex); + } + + public bool Remove(ItemType item) + { + return items.Remove(item); + } + + public int Count => items.Count; + + public bool IsReadOnly => false; + + public int IndexOf(ItemType item) + { + return items.IndexOf(item); + } + + public void Insert(int index, ItemType item) + { + items.Insert(index, item); + } + + public void RemoveAt(int index) + { + items.RemoveAt(index); + } + + public ItemType this[int index] + { + get => items[index]; + set => items[index] = value; + } + #endregion + } +} diff --git a/Scripts/Runtime/Core/CollectionItemPicker.cs.meta b/Scripts/Runtime/Core/CollectionItemPicker.cs.meta new file mode 100644 index 0000000..ca93e84 --- /dev/null +++ b/Scripts/Runtime/Core/CollectionItemPicker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a66a5f731a02d84e8af0aecf1d4aca8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: