Skip to content

Commit

Permalink
Merge pull request #138 from brunomikoski/feature/new-soc-item-editor…
Browse files Browse the repository at this point in the history
…-options

Feature/new soc item editor options
  • Loading branch information
RoyTheunissen committed Feb 27, 2024
2 parents 3e81317 + b56f347 commit f71a598
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 25 deletions.
101 changes: 92 additions & 9 deletions Scripts/Editor/Core/CollectionItemDropdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Object = UnityEngine.Object;
Expand All @@ -16,22 +17,38 @@ public sealed class CollectionItemDropdown : AdvancedDropdown

private readonly Type itemType;
private readonly SOCItemEditorOptionsAttribute options;
private readonly Object owner;
private readonly SerializedProperty serializedProperty;
private readonly MethodInfo validationMethod;

private readonly MethodInfo onSelectCallbackMethod;

public CollectionItemDropdown(AdvancedDropdownState state, Type targetItemType,
SOCItemEditorOptionsAttribute options, Object owner) : base(state)
SOCItemEditorOptionsAttribute options, SerializedProperty serializedProperty) : base(state)
{
itemType = targetItemType;
collections = CollectionsRegistry.Instance.GetCollectionsByItemType(itemType);
minimumSize = new Vector2(200, 300);
this.options = options;
this.owner = owner;
this.serializedProperty = serializedProperty;


if (options != null && !string.IsNullOrEmpty(options.ValidateMethod))
if (options != null)
{
validationMethod = owner.GetType().GetMethod(options.ValidateMethod, new[] {itemType});
Object owner = serializedProperty.serializedObject.targetObject;
if (!string.IsNullOrEmpty(options.ValidateMethod))
validationMethod = owner.GetType().GetMethod(options.ValidateMethod, new[] {itemType});

// If it's specified that a callback should be fired when an item is selected, cache that callback.
if (!string.IsNullOrEmpty(options.OnSelectCallbackMethod))
{
onSelectCallbackMethod = owner.GetType().GetMethod(options.OnSelectCallbackMethod,
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
null, new[] {itemType, itemType}, null);
if (onSelectCallbackMethod == null)
{
Debug.LogWarning($"Component '{owner.name}' wants selection callback " +
$"'{options.OnSelectCallbackMethod}' which is not a valid method.");
}
}
}
}

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

// If specified, limit the displayed items to those of a collection specified in a certain field.
ScriptableObjectCollection collectionToConstrainTo = null;
if (!string.IsNullOrEmpty(options.ConstrainToCollectionField))
{
SerializedProperty collectionField = serializedProperty.serializedObject.FindProperty(
options.ConstrainToCollectionField);
if (collectionField == null)
{
Debug.LogWarning($"Tried to constrain dropdown to collection specified in field " +
$"'{options.ConstrainToCollectionField}' but no such field existed in " +
$"'{serializedProperty.serializedObject.targetObject}'");
return root;
}

collectionToConstrainTo = collectionField.objectReferenceValue as ScriptableObjectCollection;
if (collectionToConstrainTo == null)
{
Debug.LogWarning($"Tried to constrain dropdown to collection specified in field " +
$"'{options.ConstrainToCollectionField}' but no collection was specified.");
return root;
}
}
bool shouldConstrainToCollection = collectionToConstrainTo != null;

AdvancedDropdownItem targetParent = root;
bool multipleCollections = collections.Count > 1;
for (int i = 0; i < collections.Count; i++)
{
ScriptableObjectCollection collection = collections[i];

// If we're meant to constrain the selection to a specific collection, enforce that now.
if (shouldConstrainToCollection && collectionToConstrainTo != collection)
continue;

if (multipleCollections)
// If there are multiple collections, group them together.
if (multipleCollections && !shouldConstrainToCollection)
{
AdvancedDropdownItem collectionParent = new AdvancedDropdownItem(collection.name);
root.AddChild(collectionParent);
targetParent = collectionParent;
}

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

if (validationMethod != null)
{
bool result = (bool) validationMethod.Invoke(owner, new object[] {collectionItem});
bool result = (bool) validationMethod.Invoke(
serializedProperty.serializedObject.targetObject, new object[] {collectionItem});
if (!result)
continue;
}

targetParent.AddChild(new CollectionItemDropdownItem(collectionItem));
}
}
Expand All @@ -81,22 +129,57 @@ protected override AdvancedDropdownItem BuildRoot()
return root;
}

private void InvokeOnSelectCallback(ScriptableObject from, ScriptableObject to)
{
if (onSelectCallbackMethod == null)
return;

object[] arguments = { from, to };

// The method may be static, in which case there is no target.
if (onSelectCallbackMethod.IsStatic)
{
onSelectCallbackMethod.Invoke(null, arguments);
return;
}

// Otherwise, fire the callback on every target.
for (int i = 0; i < serializedProperty.serializedObject.targetObjects.Length; i++)
{
object target = serializedProperty.serializedObject.targetObjects[i];
onSelectCallbackMethod.Invoke(target, arguments);
}
}

protected override void ItemSelected(AdvancedDropdownItem item)
{
base.ItemSelected(item);

ScriptableObject previousValue = null;
if (onSelectCallbackMethod != null)
previousValue = serializedProperty.objectReferenceValue as ScriptableObject;

if (item.name.Equals(CREATE_NEW_TEXT, StringComparison.OrdinalIgnoreCase))
{
ScriptableObjectCollection collection = collections.First();
ScriptableObject newItem = CollectionCustomEditor.AddNewItem(collection, itemType);
callback.Invoke(newItem);

InvokeOnSelectCallback(previousValue, newItem);

return;
}

if (item is CollectionItemDropdownItem dropdownItem)
{
callback.Invoke(dropdownItem.CollectionItem);
InvokeOnSelectCallback(previousValue, dropdownItem.CollectionItem);
}
else
{
callback.Invoke(null);
InvokeOnSelectCallback(previousValue, null);
}
}

public void Show(Rect rect, Action<ScriptableObject> onSelectedCallback)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
SetCollectionItemType();

if (socItemPropertyDrawer == null)
CreateCollectionItemPropertyDrawer(property.serializedObject.targetObject);
CreateCollectionItemPropertyDrawer(property);

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

if (socItemPropertyDrawer.OptionsAttribute.DrawType == DrawType.Dropdown)
{
DrawItemDrawer(position, label, collectionItem);
DrawItemDrawer(position, property, label, collectionItem);
return;
}

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

private void DrawItemDrawer(Rect position, GUIContent label, ScriptableObject collectionItem
)
private void DrawItemDrawer(Rect position, SerializedProperty property, GUIContent label, ScriptableObject collectionItem)
{
socItemPropertyDrawer.DrawCollectionItemDrawer(ref position, collectionItem, label, item =>
socItemPropertyDrawer.DrawCollectionItemDrawer(ref position, property, collectionItem, label, item =>
{
SetSerializedPropertyGUIDs(item);
drawingProperty.serializedObject.ApplyModifiedProperties();
Expand Down Expand Up @@ -133,11 +132,10 @@ private bool TryGetCollectionItem(out ScriptableObject item)
return true;
}

private void CreateCollectionItemPropertyDrawer(Object serializedObjectTargetObject)
private void CreateCollectionItemPropertyDrawer(SerializedProperty serializedProperty)
{
socItemPropertyDrawer = new SOCItemPropertyDrawer();
socItemPropertyDrawer.Initialize(collectionItemType, serializedObjectTargetObject,
GetOptionsAttribute());
socItemPropertyDrawer.Initialize(collectionItemType, serializedProperty, GetOptionsAttribute());
}

private void SetCollectionItemType()
Expand Down
57 changes: 49 additions & 8 deletions Scripts/Editor/PropertyDrawers/SOCItemPropertyDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
item = property.objectReferenceValue as ScriptableObject;

EditorGUI.BeginProperty(position, label, property);
DrawCollectionItemDrawer(ref position, item, label,
DrawCollectionItemDrawer(ref position, property, item, label,
newItem =>
{
property.objectReferenceValue = newItem;
Expand All @@ -81,7 +81,8 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
EditorGUI.EndProperty();
}

internal void DrawCollectionItemDrawer(ref Rect position, ScriptableObject collectionItem, GUIContent label,
internal void DrawCollectionItemDrawer(
ref Rect position, SerializedProperty property, ScriptableObject collectionItem, GUIContent label,
Action<ScriptableObject> callback)
{
float originY = position.y;
Expand All @@ -98,7 +99,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
}

DrawGotoButton(ref prefixPosition, collectionItem);
DrawCollectionItemDropDown(ref prefixPosition, collectionItem, callback);
DrawCollectionItemDropDown(ref prefixPosition, property, collectionItem, callback);
DrawEditorPreview(ref position, collectionItem);
EditorGUI.indentLevel = indent;
totalHeight = position.y - originY;
Expand Down Expand Up @@ -171,15 +172,16 @@ private void Initialize(SerializedProperty property)
itemType = arrayOrListType ?? TargetFieldInfo.FieldType;
}

Initialize(itemType, property.serializedObject.targetObject, GetOptionsAttribute());
Initialize(itemType, property, GetOptionsAttribute());
}

internal void Initialize(Type collectionItemType, SOCItemEditorOptionsAttribute optionsAttribute)
{
Initialize(collectionItemType, null, optionsAttribute ?? GetOptionsAttribute());
}

internal void Initialize(Type collectionItemType, Object obj, SOCItemEditorOptionsAttribute optionsAttribute)
internal void Initialize(
Type collectionItemType, SerializedProperty serializedProperty, SOCItemEditorOptionsAttribute optionsAttribute)
{
if (initialized)
return;
Expand All @@ -193,26 +195,65 @@ internal void Initialize(Type collectionItemType, Object obj, SOCItemEditorOptio
new AdvancedDropdownState(),
collectionItemType,
OptionsAttribute,
obj
serializedProperty
);

currentItemType = collectionItemType;
currentObject = obj;
currentObject = serializedProperty.serializedObject.targetObject;
initialized = true;

}

private void DrawCollectionItemDropDown(ref Rect position, ScriptableObject collectionItem,
private void DrawCollectionItemDropDown(
ref Rect position, SerializedProperty property, ScriptableObject collectionItem,
Action<ScriptableObject> callback)
{
GUIContent displayValue = new GUIContent("None");

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

bool canUseDropDown = true;
bool isDropdownError = false;

// If the options are meant to be constrained to a specific collection, check if the collection specified
// is valid. If not, draw some useful messages so you're aware what's wrong and know how to fix it.
if (!string.IsNullOrEmpty(OptionsAttribute.ConstrainToCollectionField))
{
SerializedProperty collectionField = property.serializedObject.FindProperty(
OptionsAttribute.ConstrainToCollectionField);
if (collectionField == null)
{
displayValue.text = $"Invalid collection constraint '{OptionsAttribute.ConstrainToCollectionField}'";
canUseDropDown = false;
isDropdownError = true;
}
else
{
ScriptableObjectCollection collectionToConstrainTo = collectionField
.objectReferenceValue as ScriptableObjectCollection;
if (collectionToConstrainTo == null)
{
displayValue.text = $"No collection specified.";
canUseDropDown = false;
}
}
}

bool wasGuiEnabled = GUI.enabled;
GUI.enabled = canUseDropDown;

Color originalContentColor = GUI.contentColor;
if (isDropdownError)
GUI.contentColor = Color.red;

if (GUI.Button(position, displayValue, EditorStyles.popup))
{
collectionItemDropdown.Show(position, callback.Invoke);
}

GUI.contentColor = originalContentColor;
GUI.enabled = wasGuiEnabled;
}

private void DrawGotoButton(ref Rect popupRect, ScriptableObject collectionItem)
Expand Down
11 changes: 11 additions & 0 deletions Scripts/Runtime/Attributes/SOCItemEditorOptionsAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ public class SOCItemEditorOptionsAttribute : Attribute
public bool ShouldDrawPreviewButton { get; set; } = true;

public string ValidateMethod { get; set; }

/// <summary>
/// If specified, only show collection items that belong to the collection assigned to the specified field.
/// </summary>
public string ConstrainToCollectionField { get; set; }

/// <summary>
/// If specified, will perform this method whenever the value changes.
/// Parameters of the method should be: ItemType from, ItemType to
/// </summary>
public string OnSelectCallbackMethod { get; set; }
}

//Temporary
Expand Down

0 comments on commit f71a598

Please sign in to comment.