-
-
Save ByronMayne/70a46e73f3af7fb9fec7437174dd4858 to your computer and use it in GitHub Desktop.
using System.Collections.Generic; | |
using System.Reflection; | |
using UnityEditor; | |
using UnityEditorInternal; | |
using UnityEngine; | |
using UnityEngine.Events; | |
[CustomPropertyDrawer(typeof(UnityEvent))] | |
public class CollapsableEventDrawer : UnityEventDrawer | |
{ | |
private class Styles | |
{ | |
public readonly GUIStyle preButton = "RL FooterButton"; | |
public GUIContent iconToolbarPlus = EditorGUIUtility.IconContent("Toolbar Plus", "|Add to list"); | |
} | |
private const string CALLS_PROPERTY_PATH = "m_PersistentCalls.m_Calls"; | |
private const string GET_STATE_METHOD_NAME = "GetState"; | |
private const string REORDERABLE_LIST_FIELD_NAME = "m_ReorderableList"; | |
private const float BUTTON_WIDTH = 30; | |
private const float BUTTON_SPACING = 30; | |
private static readonly BindingFlags NON_PULIC_INSTANCE_FLAGS = BindingFlags.NonPublic | BindingFlags.Instance; | |
// Points the field inside Sate which has the reorderable list. | |
private static FieldInfo _stateReorderableListFieldInfo; | |
// Points to the GetState method defined in UnityEventDrawer. | |
private static MethodInfo _getStateMethod; | |
// Cached array for using reflection | |
private static object[] _getStateArgs = new object[1]; | |
// A class that contains all the custom styling we need | |
private static Styles _styles; | |
// True if we have persistent calls and false if we don't. | |
private bool _hasPersistentCalls; | |
// Holds all our reorderable lists that belong to our state | |
private IDictionary<State, ReorderableList> _lists; | |
/// <summary> | |
/// Used to initialize our values. | |
/// </summary> | |
public CollapsableEventDrawer() | |
{ | |
_lists = new Dictionary<State, ReorderableList>(); | |
} | |
/// <summary> | |
/// A wrapper around the GetState function that is private in <see cref="UnityEventDrawer"/> | |
/// </summary> | |
private State GetState(SerializedProperty property) | |
{ | |
if (_getStateMethod == null) | |
{ | |
_getStateMethod = typeof(UnityEventDrawer).GetMethod(GET_STATE_METHOD_NAME, NON_PULIC_INSTANCE_FLAGS); | |
} | |
_getStateArgs[0] = property; | |
return (State)_getStateMethod.Invoke(this, _getStateArgs); | |
} | |
/// <summary> | |
/// Tries to get the cached ReorderableList from the state. | |
/// </summary> | |
private ReorderableList GetList(SerializedProperty property) | |
{ | |
State state = GetState(property); | |
if(_lists.ContainsKey(state)) | |
{ | |
return _lists[state]; | |
} | |
ReorderableList list = GetReorderableListFromState(state); | |
list.draggable = true; | |
_lists[state] = list; | |
return list; | |
} | |
/// <summary> | |
/// Returns back the number of calls that are currently in the Unity Event. | |
/// </summary> | |
private bool HasPersistentCalls(SerializedProperty property) | |
{ | |
return property.FindPropertyRelative(CALLS_PROPERTY_PATH).arraySize > 0; | |
} | |
/// <summary> | |
/// Returns to Unity to tell it how much space we should use to draw. If we | |
/// have one element we only reserve 32 units. 16 for the element itself and | |
/// 16 for spacing from the next element. If we any calls we let Unity | |
/// draw the default. | |
/// </summary> | |
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |
{ | |
_hasPersistentCalls = HasPersistentCalls(property); | |
ReorderableList list = GetList(property); | |
if (!_hasPersistentCalls) | |
{ | |
list.elementHeight = 0; | |
list.displayAdd = false; | |
list.displayRemove = false; | |
return EditorGUIUtility.singleLineHeight * 2f; | |
} | |
list.elementHeight = 43; | |
list.displayAdd = true; | |
list.displayRemove = true; | |
return base.GetPropertyHeight(property, label); | |
} | |
/// <summary> | |
/// Used to draw our element override a bit of the Unity logic in the case | |
/// of having no elements. | |
/// </summary> | |
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |
{ | |
if(_styles == null) | |
{ | |
_styles = new Styles(); | |
} | |
base.OnGUI(position, property, label); | |
if (!_hasPersistentCalls) | |
{ | |
position.x += position.width - BUTTON_SPACING; | |
position.width = BUTTON_WIDTH; | |
if(GUI.Button(position, _styles.iconToolbarPlus, _styles.preButton)) | |
{ | |
State state = GetState(property); | |
ReorderableList list = GetReorderableListFromState(state); | |
list.onAddCallback(list); | |
state.lastSelectedIndex = 0; | |
} | |
} | |
} | |
/// <summary> | |
/// Gets the internal instance of the <see cref="ReorderableList"/> that exists | |
/// in the state. | |
/// </summary> | |
private static ReorderableList GetReorderableListFromState(State state) | |
{ | |
if (_stateReorderableListFieldInfo == null) | |
{ | |
_stateReorderableListFieldInfo = typeof(State).GetField(REORDERABLE_LIST_FIELD_NAME, NON_PULIC_INSTANCE_FLAGS); | |
} | |
return (ReorderableList)_stateReorderableListFieldInfo.GetValue(state); | |
} | |
} |
@ByronMayne great solution to tidying up UnityEvents in the inspector. I was trying to get it to also work with UnityEvent without needing to specify every custom T0
Do you know if this is possible?
I tried typeof(UnityEvent<>), true
but it didn't work. I'm having to specify every actual object like typeof(UnityEvent<MyType>), true
You have to do it per type unfortunately. Unless that has changed. I have not used Unity for about 7 months so I have not been keeping up to date
Saying that you might be able to break into Unity with reflection and do it but that will take some digging
Yeah I think reflection is the only way I can see of doing it. I'll get something working and post the result back here in case anyone comes across this and needs a solution in the future!
Yes, I know I am late to the party, but this one is still useful, and by now you can use [CustomPropertyDrawer(typeof(UnityEventBase))] to have it also work for events with parameters.
I wrote this 4 years ago I am surprised it's still useful that is... unexpected. hahah
Wow! It has rearrange function too! Thanks!