├── .gitattributes ├── .gitignore ├── Editor.meta ├── Editor ├── AdvanceDropdownEventDrawer.cs ├── AdvanceDropdownEventDrawer.cs.meta ├── GenericAdvancedDropdown.cs ├── GenericAdvancedDropdown.cs.meta ├── com.bennykok.eventdrawer.editor.asmdef └── com.bennykok.eventdrawer.editor.asmdef.meta ├── README.md ├── README.md.meta ├── package.json └── package.json.meta /.gitattributes: -------------------------------------------------------------------------------- 1 | # .gitattributes from https://github.com/github-for-unity/Unity/blob/master/src/GitHub.Api/Resources/.gitattributes 2 | * text=auto 3 | 4 | # Unity files 5 | *.meta -text merge=unityyamlmerge diff 6 | *.unity -text merge=unityyamlmerge diff 7 | *.asset -text merge=unityyamlmerge diff 8 | *.prefab -text merge=unityyamlmerge diff 9 | *.mat -text merge=unityyamlmerge diff 10 | *.anim -text merge=unityyamlmerge diff 11 | *.controller -text merge=unityyamlmerge diff 12 | *.overrideController -text merge=unityyamlmerge diff 13 | *.physicMaterial -text merge=unityyamlmerge diff 14 | *.physicsMaterial2D -text merge=unityyamlmerge diff 15 | *.playable -text merge=unityyamlmerge diff 16 | *.mask -text merge=unityyamlmerge diff 17 | *.brush -text merge=unityyamlmerge diff 18 | *.flare -text merge=unityyamlmerge diff 19 | *.fontsettings -text merge=unityyamlmerge diff 20 | *.guiskin -text merge=unityyamlmerge diff 21 | *.giparams -text merge=unityyamlmerge diff 22 | *.renderTexture -text merge=unityyamlmerge diff 23 | *.spriteatlas -text merge=unityyamlmerge diff 24 | *.terrainlayer -text merge=unityyamlmerge diff 25 | *.mixer -text merge=unityyamlmerge diff 26 | *.shadervariants -text merge=unityyamlmerge diff 27 | 28 | # Image formats 29 | *.psd filter=lfs diff=lfs merge=lfs -text 30 | *.jpg filter=lfs diff=lfs merge=lfs -text 31 | *.png filter=lfs diff=lfs merge=lfs -text 32 | *.gif filter=lfs diff=lfs merge=lfs -text 33 | *.bmp filter=lfs diff=lfs merge=lfs -text 34 | *.tga filter=lfs diff=lfs merge=lfs -text 35 | *.tiff filter=lfs diff=lfs merge=lfs -text 36 | *.tif filter=lfs diff=lfs merge=lfs -text 37 | *.iff filter=lfs diff=lfs merge=lfs -text 38 | *.pict filter=lfs diff=lfs merge=lfs -text 39 | *.dds filter=lfs diff=lfs merge=lfs -text 40 | *.xcf filter=lfs diff=lfs merge=lfs -text 41 | 42 | # Audio formats 43 | *.mp3 filter=lfs diff=lfs merge=lfs -text 44 | *.ogg filter=lfs diff=lfs merge=lfs -text 45 | *.wav filter=lfs diff=lfs merge=lfs -text 46 | *.aiff filter=lfs diff=lfs merge=lfs -text 47 | *.aif filter=lfs diff=lfs merge=lfs -text 48 | *.mod filter=lfs diff=lfs merge=lfs -text 49 | *.it filter=lfs diff=lfs merge=lfs -text 50 | *.s3m filter=lfs diff=lfs merge=lfs -text 51 | *.xm filter=lfs diff=lfs merge=lfs -text 52 | 53 | # Video formats 54 | *.mov filter=lfs diff=lfs merge=lfs -text 55 | *.avi filter=lfs diff=lfs merge=lfs -text 56 | *.asf filter=lfs diff=lfs merge=lfs -text 57 | *.mpg filter=lfs diff=lfs merge=lfs -text 58 | *.mpeg filter=lfs diff=lfs merge=lfs -text 59 | *.mp4 filter=lfs diff=lfs merge=lfs -text 60 | 61 | # 3D formats 62 | *.fbx filter=lfs diff=lfs merge=lfs -text 63 | *.obj filter=lfs diff=lfs merge=lfs -text 64 | *.max filter=lfs diff=lfs merge=lfs -text 65 | *.blend filter=lfs diff=lfs merge=lfs -text 66 | *.dae filter=lfs diff=lfs merge=lfs -text 67 | *.mb filter=lfs diff=lfs merge=lfs -text 68 | *.ma filter=lfs diff=lfs merge=lfs -text 69 | *.3ds filter=lfs diff=lfs merge=lfs -text 70 | *.dfx filter=lfs diff=lfs merge=lfs -text 71 | *.c4d filter=lfs diff=lfs merge=lfs -text 72 | *.lwo filter=lfs diff=lfs merge=lfs -text 73 | *.lwo2 filter=lfs diff=lfs merge=lfs -text 74 | *.abc filter=lfs diff=lfs merge=lfs -text 75 | *.3dm filter=lfs diff=lfs merge=lfs -text 76 | 77 | # Build 78 | *.dll filter=lfs diff=lfs merge=lfs -text 79 | *.pdb filter=lfs diff=lfs merge=lfs -text 80 | *.mdb filter=lfs diff=lfs merge=lfs -text 81 | 82 | # Packaging 83 | *.zip filter=lfs diff=lfs merge=lfs -text 84 | *.7z filter=lfs diff=lfs merge=lfs -text 85 | *.gz filter=lfs diff=lfs merge=lfs -text 86 | *.rar filter=lfs diff=lfs merge=lfs -text 87 | *.tar filter=lfs diff=lfs merge=lfs -text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/unity 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=unity 4 | 5 | ### Unity ### 6 | # This .gitignore file should be placed at the root of your Unity project directory 7 | # 8 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 9 | /[Ll]ibrary/ 10 | /[Tt]emp/ 11 | /[Oo]bj/ 12 | /[Bb]uild/ 13 | /[Bb]uilds/ 14 | /[Ll]ogs/ 15 | /[Uu]ser[Ss]ettings/ 16 | 17 | # MemoryCaptures can get excessive in size. 18 | # They also could contain extremely sensitive data 19 | /[Mm]emoryCaptures/ 20 | 21 | # Asset meta data should only be ignored when the corresponding asset is also ignored 22 | !/[Aa]ssets/**/*.meta 23 | 24 | # Uncomment this line if you wish to ignore the asset store tools plugin 25 | # /[Aa]ssets/AssetStoreTools* 26 | 27 | # Autogenerated Jetbrains Rider plugin 28 | /[Aa]ssets/Plugins/Editor/JetBrains* 29 | 30 | # Visual Studio cache directory 31 | .vs/ 32 | 33 | # Gradle cache directory 34 | .gradle/ 35 | 36 | # Autogenerated VS/MD/Consulo solution and project files 37 | ExportedObj/ 38 | .consulo/ 39 | *.csproj 40 | *.unityproj 41 | *.sln 42 | *.suo 43 | *.tmp 44 | *.user 45 | *.userprefs 46 | *.pidb 47 | *.booproj 48 | *.svd 49 | *.pdb 50 | *.mdb 51 | *.opendb 52 | *.VC.db 53 | 54 | # Unity3D generated meta files 55 | *.pidb.meta 56 | *.pdb.meta 57 | *.mdb.meta 58 | 59 | # Unity3D generated file on crash reports 60 | sysinfo.txt 61 | 62 | # Builds 63 | *.apk 64 | *.unitypackage 65 | 66 | # Crashlytics generated file 67 | crashlytics-build.properties 68 | 69 | # Autogenerated files 70 | InitTestScene*.unity.meta 71 | InitTestScene*.unity 72 | 73 | 74 | # End of https://www.toptal.com/developers/gitignore/api/unity 75 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fda7ca045f7e31f4fbc7ef6fb6f1daaa 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/AdvanceDropdownEventDrawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using UnityEditor; 4 | using UnityEditorInternal; 5 | using UnityEngine; 6 | using UnityEngine.Events; 7 | using Object = UnityEngine.Object; 8 | using System.Collections.Generic; 9 | using System.Reflection; 10 | using UnityEditor.AnimatedValues; 11 | using UnityEngine.EventSystems; 12 | 13 | namespace BennyKok.EventDrawer.Editor 14 | { 15 | //https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/UnityEventDrawer.cs 16 | 17 | [CustomPropertyDrawer(typeof(UnityEventBase), true)] 18 | public class AdvanceDropdownEventDrawer : UnityEventDrawer 19 | { 20 | public static SerializedObject copiedPersistentCalls; 21 | public static SerializedProperty targetPersistentCalls; 22 | public static int copySelectedIndex = -1; 23 | 24 | public static Dictionary stringToTypeCache = new Dictionary(); 25 | 26 | Dictionary states = new Dictionary(); 27 | 28 | public class MState 29 | { 30 | public AnimBool visible; 31 | public int lastSelected; 32 | } 33 | 34 | public MState GetCurrentState(SerializedProperty property, bool defaultValue) 35 | { 36 | if (!states.TryGetValue(property.propertyPath, out var currentTabState)) 37 | { 38 | var visible = new AnimBool(); 39 | visible.speed = DrawerUtil.AnimSpeed; 40 | visible.valueChanged.AddListener(() => 41 | { 42 | DrawerUtil.RepaintInspector(property.serializedObject); 43 | }); 44 | visible.value = defaultValue; 45 | 46 | currentTabState = new MState() 47 | { 48 | visible = visible, 49 | }; 50 | states.Add(property.propertyPath, currentTabState); 51 | } 52 | return currentTabState; 53 | } 54 | 55 | private void CopyPersistentCallProperty(string relative, SerializedProperty item, SerializedProperty copyItem) 56 | { 57 | SerializedProperty serializedProperty = item.FindPropertyRelative(relative); 58 | SerializedProperty serializedProperty1 = copyItem.FindPropertyRelative(relative); 59 | 60 | // p.s. not all serializedProperty type is checked, since some are only used in m_PersistentCalls.m_Calls 61 | switch (serializedProperty.propertyType) 62 | { 63 | case SerializedPropertyType.ObjectReference: 64 | serializedProperty.objectReferenceValue = serializedProperty1.objectReferenceValue; 65 | break; 66 | case SerializedPropertyType.String: 67 | serializedProperty.stringValue = serializedProperty1.stringValue; 68 | break; 69 | case SerializedPropertyType.Enum: 70 | serializedProperty.enumValueIndex = serializedProperty1.enumValueIndex; 71 | break; 72 | case SerializedPropertyType.Integer: 73 | serializedProperty.intValue = serializedProperty1.intValue; 74 | break; 75 | case SerializedPropertyType.Float: 76 | serializedProperty.floatValue = serializedProperty1.floatValue; 77 | break; 78 | case SerializedPropertyType.Boolean: 79 | serializedProperty.boolValue = serializedProperty1.boolValue; 80 | break; 81 | case SerializedPropertyType.Color: 82 | serializedProperty.colorValue = serializedProperty1.colorValue; 83 | break; 84 | case SerializedPropertyType.Vector3: 85 | serializedProperty.vector3Value = serializedProperty1.vector3Value; 86 | break; 87 | } 88 | } 89 | 90 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 91 | { 92 | m_Prop = property; 93 | 94 | var trailingSpaceInHeader = false; 95 | 96 | if (property.serializedObject.targetObject is EventTrigger) 97 | { 98 | trailingSpaceInHeader = true; 99 | } 100 | 101 | // EditorGUI.indentLevel++; 102 | position = EditorGUI.IndentedRect(position); 103 | 104 | position.height = EditorGUIUtility.singleLineHeight; 105 | var temp = new GUIContent(label); 106 | 107 | SerializedProperty persistentCalls = property.FindPropertyRelative("m_PersistentCalls.m_Calls"); 108 | var m_state = GetCurrentState(persistentCalls, property.isExpanded); 109 | var visible = m_state.visible; 110 | if (persistentCalls != null) 111 | temp.text += " (" + persistentCalls.arraySize + ")"; 112 | 113 | EditorGUI.BeginChangeCheck(); 114 | 115 | // visible.target = property.isExpanded; 116 | var tempBool = property.isExpanded; 117 | var headerRect = position; 118 | if (trailingSpaceInHeader) 119 | headerRect.width -= 30; 120 | property.isExpanded = EditorGUI.BeginFoldoutHeaderGroup(headerRect, property.isExpanded, temp, null, (rect) => 121 | { 122 | var menu = new GenericMenu(); 123 | menu.AddItem(new GUIContent("Reset"), false, () => 124 | { 125 | persistentCalls.ClearArray(); 126 | property.serializedObject.ApplyModifiedProperties(); 127 | }); 128 | AddCopySelectOption(property.serializedObject, persistentCalls, menu, "Copy Selected", m_state.lastSelected); 129 | AddCopyAllOption(property, persistentCalls, menu); 130 | 131 | if (copiedPersistentCalls != null && copySelectedIndex == -1) 132 | AddPasteAllOption(property, persistentCalls, menu); 133 | 134 | if (copiedPersistentCalls != null && copySelectedIndex > -1) 135 | AddPasteSelectOption(property.serializedObject, persistentCalls, menu, persistentCalls.arraySize); 136 | 137 | menu.DropDown(rect); 138 | }); 139 | 140 | if (EditorGUI.EndChangeCheck()) 141 | { 142 | visible.target = property.isExpanded; 143 | } 144 | 145 | position.height = base.GetPropertyHeight(property, label) * visible.faded; 146 | position.y += EditorGUIUtility.singleLineHeight; 147 | if (DrawerUtil.BeginFade(visible, ref position)) 148 | { 149 | var text = label.text; 150 | label.text = null; 151 | base.OnGUI(position, property, label); 152 | 153 | m_state.lastSelected = (m_LastSelectedIndexField.GetValue(this) as int?).Value; 154 | 155 | label.text = text; 156 | } 157 | DrawerUtil.EndFade(); 158 | EditorGUI.EndFoldoutHeaderGroup(); 159 | 160 | // EditorGUI.indentLevel--; 161 | } 162 | 163 | private void AddPasteSelectOption(SerializedObject property, SerializedProperty persistentCalls, GenericMenu menu, int targetIndex) 164 | { 165 | menu.AddItem(new GUIContent("Paste"), false, () => 166 | { 167 | copiedPersistentCalls.Update(); 168 | 169 | persistentCalls.InsertArrayElementAtIndex(targetIndex); 170 | CopyEvent(persistentCalls, targetIndex, copySelectedIndex); 171 | 172 | property.ApplyModifiedProperties(); 173 | 174 | CleanUpAfterCopy(); 175 | DrawerUtil.RepaintInspector(property); 176 | }); 177 | } 178 | 179 | private void AddPasteAllOption(SerializedProperty property, SerializedProperty persistentCalls, GenericMenu menu) 180 | { 181 | menu.AddItem(new GUIContent("Paste All"), false, () => 182 | { 183 | copiedPersistentCalls.Update(); 184 | 185 | persistentCalls.ClearArray(); 186 | persistentCalls.arraySize = targetPersistentCalls.arraySize; 187 | for (int i = 0; i < persistentCalls.arraySize; i++) 188 | { 189 | CopyEvent(persistentCalls, i, i); 190 | } 191 | 192 | property.serializedObject.ApplyModifiedProperties(); 193 | 194 | CleanUpAfterCopy(); 195 | }); 196 | } 197 | 198 | public void AddCopySelectOption(SerializedObject property, SerializedProperty persistentCalls, GenericMenu menu, string label, int index) 199 | { 200 | menu.AddItem(new GUIContent(label), false, () => 201 | { 202 | if (copiedPersistentCalls != null) 203 | copiedPersistentCalls.Dispose(); 204 | 205 | copySelectedIndex = index; 206 | copiedPersistentCalls = new SerializedObject(property.targetObject); 207 | targetPersistentCalls = copiedPersistentCalls.FindProperty(persistentCalls.propertyPath); 208 | }); 209 | } 210 | 211 | public void AddCopyAllOption(SerializedProperty property, SerializedProperty persistentCalls, GenericMenu menu) 212 | { 213 | menu.AddItem(new GUIContent("Copy All"), false, () => 214 | { 215 | if (copiedPersistentCalls != null) 216 | copiedPersistentCalls.Dispose(); 217 | 218 | copySelectedIndex = -1; 219 | copiedPersistentCalls = new SerializedObject(property.serializedObject.targetObject); 220 | targetPersistentCalls = copiedPersistentCalls.FindProperty(persistentCalls.propertyPath); 221 | }); 222 | } 223 | 224 | private void CleanUpAfterCopy() 225 | { 226 | copiedPersistentCalls.Dispose(); 227 | copiedPersistentCalls = null; 228 | targetPersistentCalls = null; 229 | copySelectedIndex = -1; 230 | } 231 | 232 | private void CopyEvent(SerializedProperty persistentCalls, int i, int j) 233 | { 234 | var item = persistentCalls.GetArrayElementAtIndex(i); 235 | var copyItem = targetPersistentCalls.GetArrayElementAtIndex(j); 236 | 237 | CopyPersistentCallProperty("m_Target", item, copyItem); 238 | CopyPersistentCallProperty("m_TargetAssemblyTypeName", item, copyItem); 239 | CopyPersistentCallProperty("m_MethodName", item, copyItem); 240 | CopyPersistentCallProperty("m_Mode", item, copyItem); 241 | CopyPersistentCallProperty("m_CallState", item, copyItem); 242 | 243 | CopyPersistentCallProperty("m_Arguments.m_ObjectArgument", item, copyItem); 244 | CopyPersistentCallProperty("m_Arguments.m_ObjectArgumentAssemblyTypeName", item, copyItem); 245 | CopyPersistentCallProperty("m_Arguments.m_IntArgument", item, copyItem); 246 | CopyPersistentCallProperty("m_Arguments.m_FloatArgument", item, copyItem); 247 | CopyPersistentCallProperty("m_Arguments.m_StringArgument", item, copyItem); 248 | CopyPersistentCallProperty("m_Arguments.m_BoolArgument", item, copyItem); 249 | } 250 | 251 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 252 | { 253 | var baseHeight = base.GetPropertyHeight(property, label); 254 | SerializedProperty persistentCalls = property.FindPropertyRelative("m_PersistentCalls.m_Calls"); 255 | var m_state = GetCurrentState(persistentCalls, property.isExpanded); 256 | var visible = m_state.visible; 257 | 258 | if (property.propertyPath.Contains("Array")) 259 | return visible.target ? baseHeight + EditorGUIUtility.singleLineHeight : EditorGUIUtility.singleLineHeight; 260 | 261 | return baseHeight * visible.faded + EditorGUIUtility.singleLineHeight; 262 | } 263 | 264 | private const string kNoFunctionString = "No Function"; 265 | 266 | //Persistent Listener Paths 267 | internal const string kInstancePath = "m_Target"; 268 | internal const string kCallStatePath = "m_CallState"; 269 | internal const string kArgumentsPath = "m_Arguments"; 270 | internal const string kModePath = "m_Mode"; 271 | internal const string kMethodNamePath = "m_MethodName"; 272 | 273 | //ArgumentCache paths 274 | internal const string kFloatArgument = "m_FloatArgument"; 275 | internal const string kIntArgument = "m_IntArgument"; 276 | internal const string kObjectArgument = "m_ObjectArgument"; 277 | internal const string kStringArgument = "m_StringArgument"; 278 | internal const string kBoolArgument = "m_BoolArgument"; 279 | internal const string kObjectArgumentAssemblyTypeName = "m_ObjectArgumentAssemblyTypeName"; 280 | 281 | UnityEventBase m_DummyEvent; 282 | SerializedProperty m_Prop; 283 | SerializedProperty m_ListenersArray; 284 | 285 | static FieldInfo m_ListenersArrayField = typeof(UnityEventDrawer).GetField("m_ListenersArray", 286 | BindingFlags.NonPublic | 287 | BindingFlags.Instance); 288 | 289 | static FieldInfo m_DummyEventField = typeof(UnityEventDrawer).GetField("m_DummyEvent", 290 | BindingFlags.NonPublic | 291 | BindingFlags.Instance); 292 | 293 | static FieldInfo m_LastSelectedIndexField = typeof(UnityEventDrawer).GetField("m_LastSelectedIndex", 294 | BindingFlags.NonPublic | 295 | BindingFlags.Instance); 296 | 297 | static FieldInfo m_ReorderableListField = typeof(UnityEventDrawer).GetField("m_ReorderableList", 298 | BindingFlags.NonPublic | 299 | BindingFlags.Instance); 300 | 301 | static PropertyInfo mixedValueContentProp = typeof(EditorGUI).GetProperty("mixedValueContent", 302 | BindingFlags.NonPublic | 303 | BindingFlags.Static); 304 | 305 | static MethodInfo GetRowRectsMethod = typeof(UnityEventDrawer).GetMethod("GetRowRects", 306 | BindingFlags.NonPublic | 307 | BindingFlags.Instance); 308 | 309 | static MethodInfo BuildPopupListsMethod = typeof(UnityEventDrawer).GetMethod("BuildPopupList", 310 | BindingFlags.NonPublic | 311 | BindingFlags.Static); 312 | 313 | Rect[] GetRowRects(Rect rect) 314 | { 315 | return GetRowRectsMethod.Invoke(this, new object[] { rect }) as Rect[]; 316 | } 317 | 318 | static GenericMenu BuildPopupList(Object target, UnityEventBase dummyEvent, SerializedProperty listener) 319 | { 320 | return BuildPopupListsMethod.Invoke(null, new object[] { target, dummyEvent, listener }) as GenericMenu; 321 | } 322 | 323 | static PersistentListenerMode GetMode(SerializedProperty mode) 324 | { 325 | return (PersistentListenerMode)mode.enumValueIndex; 326 | } 327 | 328 | protected override void SetupReorderableList(ReorderableList list) 329 | { 330 | base.SetupReorderableList(list); 331 | list.draggable = true; 332 | list.drawHeaderCallback = null; 333 | list.headerHeight = 0; 334 | list.elementHeight = EditorGUIUtility.singleLineHeight + 5; 335 | } 336 | 337 | protected override void DrawEvent(Rect rect, int index, bool isActive, bool isFocused) 338 | { 339 | m_ListenersArray = m_ListenersArrayField.GetValue(this) as SerializedProperty; 340 | m_DummyEvent = m_DummyEventField.GetValue(this) as UnityEventBase; 341 | 342 | var pListener = m_ListenersArray.GetArrayElementAtIndex(index); 343 | 344 | var m_state = GetCurrentState(m_ListenersArray, false); 345 | 346 | rect.y++; 347 | Rect[] subRects = GetRowRects(rect); 348 | Rect enabledRect = subRects[0]; 349 | Rect goRect = subRects[1]; 350 | Rect functionRect = subRects[2]; 351 | Rect argRect = subRects[3]; 352 | 353 | enabledRect.width -= 10; 354 | functionRect.x -= 10; 355 | functionRect.width += 10; 356 | 357 | // find the current event target... 358 | var callState = pListener.FindPropertyRelative(kCallStatePath); 359 | var mode = pListener.FindPropertyRelative(kModePath); 360 | var arguments = pListener.FindPropertyRelative(kArgumentsPath); 361 | var listenerTarget = pListener.FindPropertyRelative(kInstancePath); 362 | var methodName = pListener.FindPropertyRelative(kMethodNamePath); 363 | 364 | Color c = GUI.backgroundColor; 365 | GUI.backgroundColor = Color.white; 366 | 367 | Event current = Event.current; 368 | var tempRect = rect; 369 | tempRect.x -= 20; 370 | if (tempRect.Contains(current.mousePosition) && current.type == EventType.ContextClick) 371 | { 372 | DrawerUtil.RepaintInspector(m_ListenersArray.serializedObject); 373 | 374 | GenericMenu menu = new GenericMenu(); 375 | 376 | AddCopySelectOption(m_ListenersArray.serializedObject, m_ListenersArray, menu, "Copy", index); 377 | if (copiedPersistentCalls != null && copySelectedIndex > -1) 378 | AddPasteSelectOption(m_ListenersArray.serializedObject, m_ListenersArray, menu, index + 1); 379 | 380 | menu.AddSeparator(""); 381 | for (int i = 0; i < callState.enumDisplayNames.Length; i++) 382 | { 383 | string state = (string)callState.enumDisplayNames[i]; 384 | int localI = i; 385 | menu.AddItem(new GUIContent(state), callState.enumValueIndex == localI, () => 386 | { 387 | callState.enumValueIndex = localI; 388 | callState.serializedObject.ApplyModifiedProperties(); 389 | }); 390 | } 391 | 392 | menu.ShowAsContext(); 393 | 394 | current.Use(); 395 | }; 396 | 397 | // EditorGUI.PropertyField(enabledRect, callState, GUIContent.none); 398 | 399 | EditorGUI.BeginDisabledGroup(callState.enumValueIndex == 0); 400 | 401 | EditorGUI.BeginChangeCheck(); 402 | { 403 | GUI.Box(enabledRect, GUIContent.none); 404 | EditorGUI.PropertyField(enabledRect, listenerTarget, GUIContent.none); 405 | if (EditorGUI.EndChangeCheck()) 406 | methodName.stringValue = null; 407 | } 408 | 409 | SerializedProperty argument; 410 | var modeEnum = GetMode(mode); 411 | //only allow argument if we have a valid target / method 412 | if (listenerTarget.objectReferenceValue == null || string.IsNullOrEmpty(methodName.stringValue)) 413 | modeEnum = PersistentListenerMode.Void; 414 | 415 | switch (modeEnum) 416 | { 417 | case PersistentListenerMode.Float: 418 | argument = arguments.FindPropertyRelative(kFloatArgument); 419 | break; 420 | case PersistentListenerMode.Int: 421 | argument = arguments.FindPropertyRelative(kIntArgument); 422 | break; 423 | case PersistentListenerMode.Object: 424 | argument = arguments.FindPropertyRelative(kObjectArgument); 425 | break; 426 | case PersistentListenerMode.String: 427 | argument = arguments.FindPropertyRelative(kStringArgument); 428 | break; 429 | case PersistentListenerMode.Bool: 430 | argument = arguments.FindPropertyRelative(kBoolArgument); 431 | break; 432 | default: 433 | argument = arguments.FindPropertyRelative(kIntArgument); 434 | break; 435 | } 436 | 437 | var desiredArgTypeName = arguments.FindPropertyRelative(kObjectArgumentAssemblyTypeName).stringValue; 438 | var desiredType = typeof(Object); 439 | if (!string.IsNullOrEmpty(desiredArgTypeName)) 440 | desiredType = Type.GetType(desiredArgTypeName, false) ?? typeof(Object); 441 | 442 | if (modeEnum == PersistentListenerMode.Object) 443 | { 444 | argRect.y = functionRect.y; 445 | argRect.width = 100; 446 | 447 | functionRect.width -= argRect.width; 448 | argRect.x = 4 + functionRect.x + functionRect.width; 449 | 450 | EditorGUI.BeginChangeCheck(); 451 | var result = EditorGUI.ObjectField(argRect, GUIContent.none, argument.objectReferenceValue, desiredType, true); 452 | if (EditorGUI.EndChangeCheck()) 453 | argument.objectReferenceValue = result; 454 | } 455 | else if (modeEnum != PersistentListenerMode.Void && modeEnum != PersistentListenerMode.EventDefined) 456 | { 457 | if (modeEnum == PersistentListenerMode.Bool) 458 | { 459 | argRect.y = functionRect.y; 460 | argRect.width = EditorStyles.toggle.CalcSize(GUIContent.none).x; 461 | 462 | functionRect.width -= argRect.width; 463 | argRect.x = 4 + functionRect.x + functionRect.width; 464 | } 465 | else 466 | { 467 | argRect.y = functionRect.y; 468 | argRect.width = 100; 469 | 470 | functionRect.width -= argRect.width; 471 | argRect.x = 4 + functionRect.x + functionRect.width; 472 | } 473 | 474 | EditorGUI.PropertyField(argRect, argument, GUIContent.none); 475 | } 476 | 477 | using (new EditorGUI.DisabledScope(listenerTarget.objectReferenceValue == null)) 478 | { 479 | EditorGUI.BeginProperty(functionRect, GUIContent.none, methodName); 480 | { 481 | GUIContent buttonContent; 482 | if (EditorGUI.showMixedValue) 483 | { 484 | buttonContent = mixedValueContentProp.GetValue(null, null) as GUIContent; 485 | } 486 | else 487 | { 488 | var buttonLabel = new StringBuilder(); 489 | if (listenerTarget.objectReferenceValue == null || string.IsNullOrEmpty(methodName.stringValue)) 490 | { 491 | buttonLabel.Append(kNoFunctionString); 492 | } 493 | else if (!IsPersistantListenerValid(m_DummyEvent, methodName.stringValue, listenerTarget.objectReferenceValue, GetMode(mode), desiredType)) 494 | { 495 | var instanceString = "UnknownComponent"; 496 | var instance = listenerTarget.objectReferenceValue; 497 | if (instance != null) 498 | instanceString = instance.GetType().Name; 499 | 500 | buttonLabel.Append(string.Format("", instanceString, methodName.stringValue)); 501 | } 502 | else 503 | { 504 | buttonLabel.Append(listenerTarget.objectReferenceValue.GetType().Name); 505 | 506 | if (!string.IsNullOrEmpty(methodName.stringValue)) 507 | { 508 | buttonLabel.Append("."); 509 | if (methodName.stringValue.StartsWith("set_")) 510 | buttonLabel.Append(methodName.stringValue.Substring(4)); 511 | else 512 | buttonLabel.Append(methodName.stringValue); 513 | } 514 | } 515 | buttonContent = new GUIContent(buttonLabel.ToString()); 516 | } 517 | 518 | if (GUI.Button(functionRect, buttonContent, EditorStyles.popup)) 519 | { 520 | var menu = BuildPopupList(listenerTarget.objectReferenceValue, m_DummyEvent, pListener); 521 | new GenericAdvancedDropdown("Functions", menu, (content) => 522 | { 523 | if (content.text.StartsWith(kNoFunctionString)) return null; 524 | 525 | var type = content.text.Substring(0, content.text.IndexOf("/")); 526 | 527 | if (!stringToTypeCache.TryGetValue(type, out var value)) 528 | { 529 | Type t = null; 530 | var types = TypeCache.GetTypesDerivedFrom(); 531 | for (int n = 0; n < types.Count; n++) 532 | { 533 | if (type == types[n].Name) 534 | { 535 | t = types[n]; 536 | break; 537 | } 538 | } 539 | value = EditorGUIUtility.ObjectContent(null, t); 540 | if (value.image == null) 541 | value = EditorGUIUtility.IconContent("cs Script Icon"); 542 | 543 | stringToTypeCache.Add(type, value); 544 | } 545 | 546 | return value.image as Texture2D; 547 | 548 | }).Dropdown(functionRect, 10); 549 | } 550 | } 551 | EditorGUI.EndProperty(); 552 | } 553 | GUI.backgroundColor = c; 554 | 555 | EditorGUI.EndDisabledGroup(); 556 | } 557 | } 558 | 559 | 560 | public static class DrawerUtil 561 | { 562 | public static float AnimSpeed = 10f; 563 | private static Stack cacheColors = new Stack(); 564 | 565 | public static bool BeginFade(AnimBool anim, ref Rect rect) 566 | { 567 | cacheColors.Push(GUI.color); 568 | GUI.BeginClip(rect); 569 | rect.x = 0; 570 | rect.y = 0; 571 | 572 | if ((double)anim.faded == 0.0) 573 | return false; 574 | if ((double)anim.faded == 1.0) 575 | return true; 576 | 577 | var c = GUI.color; 578 | c.a = anim.faded; 579 | GUI.color = c; 580 | 581 | if ((double)anim.faded != 0.0 && (double)anim.faded != 1.0) 582 | { 583 | if (Event.current.type == EventType.MouseDown) 584 | { 585 | Event.current.Use(); 586 | } 587 | 588 | GUI.FocusControl(null); 589 | } 590 | 591 | return (double)anim.faded != 0.0; 592 | } 593 | 594 | public static void EndFade() 595 | { 596 | GUI.EndClip(); 597 | GUI.color = cacheColors.Pop(); 598 | } 599 | 600 | public static void RepaintInspector(SerializedObject BaseObject) 601 | { 602 | foreach (var item in ActiveEditorTracker.sharedTracker.activeEditors) 603 | if (item.serializedObject == BaseObject) 604 | { 605 | item.Repaint(); 606 | return; 607 | } 608 | } 609 | } 610 | } -------------------------------------------------------------------------------- /Editor/AdvanceDropdownEventDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d75d215fb4143a14582ef2e9375dfa79 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/GenericAdvancedDropdown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using UnityEditor; 7 | using UnityEditor.IMGUI.Controls; 8 | using UnityEngine; 9 | 10 | // Little help from https://qiita.com/shogo281/items/fb24cf7d28f06822527e 11 | 12 | namespace BennyKok.EventDrawer.Editor 13 | { 14 | public class GenericAdvancedDropdown : AdvancedDropdown 15 | { 16 | private Dictionary pathToActionMap = new Dictionary(); 17 | private Dictionary idToActionMap = new Dictionary(); 18 | private string title = "Items"; 19 | 20 | public struct Entry 21 | { 22 | public Action action; 23 | public Texture2D icon; 24 | } 25 | 26 | 27 | // #if UNITY_2021_1_20 28 | // static PropertyInfo menuItemsField = typeof(GenericMenu).GetProperty("menuItems", 29 | // BindingFlags.NonPublic | 30 | // BindingFlags.Instance); 31 | // #elif UNITY_2021_2_OR_NEWER 32 | static PropertyInfo menuItemsField2 = typeof(GenericMenu).GetProperty("menuItems", 33 | BindingFlags.NonPublic | 34 | BindingFlags.Instance); 35 | // #else 36 | static FieldInfo menuItemsField = typeof(GenericMenu).GetField("menuItems", 37 | BindingFlags.NonPublic | 38 | BindingFlags.Instance); 39 | // #endif 40 | 41 | static FieldInfo menuItemContentField = typeof(GenericMenu).GetNestedType("MenuItem", BindingFlags.NonPublic | 42 | BindingFlags.Instance)?.GetField("content", 43 | BindingFlags.Public | 44 | BindingFlags.Instance); 45 | 46 | static FieldInfo menuItemMenuFunctionField = typeof(GenericMenu).GetNestedType("MenuItem", BindingFlags.NonPublic | 47 | BindingFlags.Instance)?.GetField("func", 48 | BindingFlags.Public | 49 | BindingFlags.Instance); 50 | 51 | static FieldInfo menuItemMenuFunction2Field = typeof(GenericMenu).GetNestedType("MenuItem", BindingFlags.NonPublic | 52 | BindingFlags.Instance)?.GetField("func2", 53 | BindingFlags.Public | 54 | BindingFlags.Instance); 55 | 56 | static FieldInfo menuItemUserDataField = typeof(GenericMenu).GetNestedType("MenuItem", BindingFlags.NonPublic | 57 | BindingFlags.Instance)?.GetField("userData", 58 | BindingFlags.Public | 59 | BindingFlags.Instance); 60 | 61 | static FieldInfo menuItemSeparatorField = typeof(GenericMenu).GetNestedType("MenuItem", BindingFlags.NonPublic | 62 | BindingFlags.Instance)?.GetField("separator", 63 | BindingFlags.Public | 64 | BindingFlags.Instance); 65 | 66 | public GenericAdvancedDropdown(AdvancedDropdownState state) : base(state) { } 67 | public GenericAdvancedDropdown() : base(new AdvancedDropdownState()) { } 68 | public GenericAdvancedDropdown(string title) : base(new AdvancedDropdownState()) 69 | { 70 | this.title = title; 71 | } 72 | 73 | public GenericAdvancedDropdown(string title, GenericMenu menu, Func iconCallback = null) : base(new AdvancedDropdownState()) 74 | { 75 | this.title = title; 76 | 77 | // foreach (var item in typeof(GenericMenu).GetProperties(BindingFlags.NonPublic | 78 | // BindingFlags.Instance)) 79 | // { 80 | // Debug.Log(item.Name); 81 | // } 82 | 83 | // Debug.Log("------------------"); 84 | 85 | // foreach (var item in typeof(GenericMenu).GetFields(BindingFlags.NonPublic | 86 | // BindingFlags.Instance)) 87 | // { 88 | // Debug.Log(item.Name); 89 | // } 90 | 91 | IEnumerable allItems = null; 92 | 93 | if (menuItemsField == null) 94 | { 95 | var raw2 = menuItemsField2.GetValue(menu); 96 | allItems = raw2 as IEnumerable; 97 | } 98 | else 99 | { 100 | var raw = menuItemsField.GetValue(menu); 101 | allItems = raw as IEnumerable; 102 | } 103 | 104 | foreach (var item in allItems) 105 | { 106 | var content = menuItemContentField.GetValue(item) as GUIContent; 107 | var function = menuItemMenuFunctionField.GetValue(item) as GenericMenu.MenuFunction; 108 | var function2 = menuItemMenuFunction2Field.GetValue(item) as GenericMenu.MenuFunction2; 109 | var userData = menuItemUserDataField.GetValue(item); 110 | var separator = menuItemSeparatorField.GetValue(item) as bool?; 111 | 112 | // Debug.Log(menuItemMenuFunctionField.GetValue(item).GetType()); 113 | // Debug.Log(menuItemMenuFunction2Field.GetValue(item).GetType()); 114 | 115 | if (string.IsNullOrEmpty(content.text)) continue; 116 | if (separator.HasValue && separator.Value) continue; 117 | if (function == null && function2 == null) continue; 118 | 119 | AddItem(content.text, () => 120 | { 121 | if (function != null) function(); 122 | if (function2 != null) function2(userData); 123 | }, iconCallback?.Invoke(content)); 124 | } 125 | } 126 | 127 | public void Dropdown(Rect rect, int minLineCount = -1) 128 | { 129 | var originalHeight = rect.height; 130 | Vector2 size = new Vector2(rect.width, 100); 131 | 132 | if (minLineCount > 0) 133 | { 134 | size.y = minLineCount * EditorGUIUtility.singleLineHeight; 135 | } 136 | this.minimumSize = size; 137 | 138 | var r = new Rect(rect.position - new Vector2(0, size.y - originalHeight), size); 139 | Show(r); 140 | } 141 | 142 | public void ShowAsContext(int minLineCount = -1) 143 | { 144 | Vector2 size = new Vector2(200, 100); 145 | 146 | if (minLineCount > 0) 147 | { 148 | size.y = minLineCount * EditorGUIUtility.singleLineHeight; 149 | } 150 | this.minimumSize = size; 151 | 152 | var r = new Rect(Event.current.mousePosition - new Vector2(size.x / 2, size.y), size); 153 | Show(r); 154 | } 155 | 156 | public void AddItem(string label, Action action, Texture2D icon = null) 157 | { 158 | var tempLabel = label; 159 | var index = 0; 160 | while (pathToActionMap.ContainsKey(tempLabel)) 161 | { 162 | index++; 163 | tempLabel = label + " " + index.ToString(); 164 | } 165 | pathToActionMap.Add(tempLabel, new Entry() 166 | { 167 | action = action, 168 | icon = icon, 169 | }); 170 | } 171 | 172 | protected override AdvancedDropdownItem BuildRoot() 173 | { 174 | var root = new AdvancedDropdownItem(title); 175 | 176 | foreach (var item in pathToActionMap) 177 | { 178 | var splitStrings = item.Key.Split('/'); 179 | var parent = root; 180 | AdvancedDropdownItem lastItem = null; 181 | 182 | foreach (var str in splitStrings) 183 | { 184 | var foundChildItem = parent.children.FirstOrDefault(children => children.name == str); 185 | 186 | if (foundChildItem != null) 187 | { 188 | parent = foundChildItem; 189 | lastItem = foundChildItem; 190 | continue; 191 | } 192 | 193 | var child = new AdvancedDropdownItem(str); 194 | 195 | parent.AddChild(child); 196 | 197 | parent = child; 198 | lastItem = child; 199 | } 200 | 201 | if (lastItem != null && !idToActionMap.ContainsKey(lastItem.id)) 202 | { 203 | lastItem.icon = item.Value.icon; 204 | idToActionMap.Add(lastItem.id, item.Value.action); 205 | } 206 | } 207 | 208 | return root; 209 | } 210 | 211 | protected override void ItemSelected(AdvancedDropdownItem item) 212 | { 213 | base.ItemSelected(item); 214 | 215 | if (idToActionMap.TryGetValue(item.id, out var action)) 216 | { 217 | action(); 218 | } 219 | } 220 | } 221 | } -------------------------------------------------------------------------------- /Editor/GenericAdvancedDropdown.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b7c1ac1ab3985e44798ce9b799f3f1d8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/com.bennykok.eventdrawer.editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.bennykok.eventdrawer.editor", 3 | "rootNamespace": "BennyKok.EventDrawer.Editor", 4 | "references": [], 5 | "includePlatforms": [ 6 | "Editor" 7 | ], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /Editor/com.bennykok.eventdrawer.editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e5576e3b7506d1a47985e833529c86f9 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Searchable Event Drawer 2 | Searchable and compact drawer for UnityEvent 3 | 4 | Insprired by https://github.com/neon-age/Compact-Unity-Events 5 | but using AdvancedDropdown! 6 | 7 | ## Features 8 | - Single line 9 | - Collpasable with animation 10 | - Copy All/ Selected 11 | - Searchable method 12 | - Reorderable 13 | 14 | https://user-images.githubusercontent.com/18395202/122580103-321fb300-d088-11eb-8625-8d1df6224ced.mp4 15 | 16 | ## Todo 17 | - Active/Disable toggle? 18 | - Context menu for copy paste? 19 | 20 | 21 | ## Explore 22 | Feel free to check me out!! :) 23 | 24 | [Twitter](https://twitter.com/BennyKokMusic) | [Website](https://bennykok.com) | [AssetStore](https://assetstore.unity.com/publishers/28510) 25 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 69a05bd173bf3024ba06fc1046a9e2a8 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.bennykok.unity-searchable-event-drawer", 3 | "version": "1.0.0", 4 | "description": "Searchable and compact drawer for UnityEvent", 5 | "author": "BennyKok", 6 | "license": "MIT", 7 | "type": "tool", 8 | "displayName": "Searchable Event Drawer" 9 | } 10 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 06b4b5037e83dd241a9e151991f001b6 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------