Last active
September 27, 2022 19:04
-
-
Save ByronMayne/70a46e73f3af7fb9fec7437174dd4858 to your computer and use it in GitHub Desktop.
Override the default UnityEventDrawer to draw a very small version when there are no callbacks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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!