initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
|
@ -0,0 +1,46 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("UltEvents")]
|
||||
[assembly: AssemblyDescription("An serializable event system for Unity with superior features to the inbuilt UnityEvents.")]
|
||||
[assembly: AssemblyCompany("Kybernetik")]
|
||||
[assembly: AssemblyProduct("UltEvents")]
|
||||
[assembly: AssemblyCopyright("Copyright © Kybernetik 2021")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
|
||||
[assembly: SuppressMessage("Style", "IDE0016:Use 'throw' expression",
|
||||
Justification = "Not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0018:Inline variable declaration",
|
||||
Justification = "Not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0019:Use pattern matching",
|
||||
Justification = "Not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0031:Use null propagation",
|
||||
Justification = "Not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0041:Use 'is null' check",
|
||||
Justification = "Not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0044:Make field readonly",
|
||||
Justification = "Using the [SerializeField] attribute on a private field means Unity will set it from serialized data.")]
|
||||
[assembly: SuppressMessage("Code Quality", "IDE0051:Remove unused private members",
|
||||
Justification = "Unity messages can be private, but the IDE will not know that Unity can still call them.")]
|
||||
[assembly: SuppressMessage("Code Quality", "IDE0052:Remove unread private members",
|
||||
Justification = "Unity messages can be private and don't need to be called manually.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0059:Value assigned to symbol is never used",
|
||||
Justification = "Inline variable declarations are not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter",
|
||||
Justification = "Unity messages sometimes need specific signatures, even if you don't use all the parameters.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0063:Use simple 'using' statement",
|
||||
Justification = "Not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression",
|
||||
Justification = "Not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE1005:Delegate invocation can be simplified.",
|
||||
Justification = "Not supported by older Unity versions.")]
|
||||
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles",
|
||||
Justification = "Don't give code style advice in publically released code.")]
|
||||
|
||||
// This suppression doesn't seem to actually work so we need to put #pragma warning disable in every file :(
|
||||
//[assembly: SuppressMessage("Code Quality", "CS0649:Field is never assigned to, and will always have its default value",
|
||||
// Justification = "Using the [SerializeField] attribute on a private field means Unity will set it from serialized data.")]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 238d757ae4fd9d748adb89c3e5ce1590
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bde51928ec736738738f4f0970b09edf
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents.Benchmarks
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A simple performance test that loads and instantiates a prefab to test how long it takes.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]// Don't show in the Add Component menu. You need to drag this script onto a prefab manually.
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/Behchmarks/EventBenchmark")]
|
||||
public sealed class EventBenchmark : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private string _PrefabPath;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Wait a bit to avoid mixing this performance test in with the engine startup processes.
|
||||
if (Time.timeSinceLevelLoad < 1)
|
||||
return;
|
||||
|
||||
// Sleep to make this frame show up easily in the Unity Profiler.
|
||||
System.Threading.Thread.Sleep(100);
|
||||
|
||||
var start = EditorApplication.timeSinceStartup;
|
||||
|
||||
// Include the costs of loading and instantiating the prefab as well as the actual event invocation.
|
||||
var prefab = Resources.Load<GameObject>(_PrefabPath);
|
||||
Instantiate(prefab);
|
||||
|
||||
Debug.Log(EditorApplication.timeSinceStartup - start);
|
||||
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 497712a3d29c46645b6633712f5975da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents.Benchmarks
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A simple performance test which calls <see cref="Test"/> in <see cref="Awake"/> and logs the amount of time it
|
||||
/// takes.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]// Don't show in the Add Component menu. You need to drag this script onto a prefab manually.
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/Behchmarks/EventBenchmarkBase")]
|
||||
public abstract class EventBenchmarkBase : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var start = EditorApplication.timeSinceStartup;
|
||||
|
||||
Test();
|
||||
|
||||
Debug.Log(EditorApplication.timeSinceStartup - start);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called by <see cref="Awake"/>.</summary>
|
||||
protected abstract void Test();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2ecd8d6ecfcb0d1409a227c2b117a74e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents.Benchmarks
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A simple performance test which invokes a <see cref="UltEvent"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]// Don't show in the Add Component menu. You need to drag this script onto a prefab manually.
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/Behchmarks/UltEventBenchmark")]
|
||||
public sealed class UltEventBenchmark : EventBenchmarkBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _Event;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called by <see cref="Awake"/>.</summary>
|
||||
protected override void Test()
|
||||
{
|
||||
_Event.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: da9e6f3d7b0a35d48bbb43b5b52529e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UltEvents.Benchmarks
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A simple performance test which invokes a <see cref="UnityEvent"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]// Don't show in the Add Component menu. You need to drag this script onto a prefab manually.
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/Behchmarks/UnityEventBenchmark")]
|
||||
public sealed class UnityEventBenchmark : EventBenchmarkBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UnityEvent _Event;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called by <see cref="Awake"/>.</summary>
|
||||
protected override void Test()
|
||||
{
|
||||
_Event.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 84601744a18dc8d48a9e3068c3ec5aa5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dfdd566daeabed6e1fd9a3abe2f43817
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// A simple wrapper around <see cref="EditorPrefs"/> to get and set a bool.
|
||||
/// <para></para>
|
||||
/// If you are interested in a more comprehensive pref wrapper that supports more types, you should check out
|
||||
/// <see href="https://kybernetik.com.au/inspector-gadgets">Inspector Gadgets</see>.
|
||||
/// </summary>
|
||||
public sealed class BoolPref
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The text that will be displayed for this item in a context menu.</summary>
|
||||
public readonly string Label;
|
||||
|
||||
/// <summary>The identifier with which this pref will be saved.</summary>
|
||||
public readonly string Key;
|
||||
|
||||
/// <summary>The starting value to use for this pref if none was previously saved.</summary>
|
||||
public readonly bool DefaultValue;
|
||||
|
||||
/// <summary>Called when the value is changed.</summary>
|
||||
public readonly Action OnChanged;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private bool _HasValue;
|
||||
private bool _Value;
|
||||
|
||||
/// <summary>The current value of this pref.</summary>
|
||||
public bool Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_HasValue)
|
||||
{
|
||||
_HasValue = true;
|
||||
_Value = EditorPrefs.GetBool(Key, DefaultValue);
|
||||
}
|
||||
|
||||
return _Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_Value = value;
|
||||
EditorPrefs.SetBool(Key, value);
|
||||
_HasValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns the current value of the `pref`.</summary>
|
||||
public static implicit operator bool(BoolPref pref)
|
||||
{
|
||||
return pref.Value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Constructs a new <see cref="BoolPref"/>.</summary>
|
||||
public BoolPref(string label, bool defaultValue, Action onChanged = null)
|
||||
{
|
||||
Label = label;
|
||||
Key = Names.Namespace + "." + label;
|
||||
DefaultValue = defaultValue;
|
||||
OnChanged = onChanged;
|
||||
_Value = EditorPrefs.GetBool(Key, defaultValue);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds a menu item to toggle this pref.</summary>
|
||||
public void AddToMenu(GenericMenu menu)
|
||||
{
|
||||
menu.AddItem(new GUIContent("Display Options ->/" + Label), _Value, () =>
|
||||
{
|
||||
_Value = !_Value;
|
||||
EditorPrefs.SetBool(Key, _Value);
|
||||
if (OnChanged != null)
|
||||
OnChanged();
|
||||
});
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
UseIndentation = new BoolPref("Use Indentation", true);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
AutoOpenMenu = new BoolPref("Auto Open Menu", true);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
AutoHideFooter = new BoolPref("Auto Hide Footer", true);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
ShowNonPublicMethods = new BoolPref("Show Non-Public Methods", true);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
GroupNonPublicMethods = new BoolPref("Group Non-Public Methods", true);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
ShowStaticMethods = new BoolPref("Show Static Methods", true);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
ShowFullTypeNames = new BoolPref("Use Full Type Names", false);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
GroupMethodOverloads = new BoolPref("Sub-Menu for Method Overloads", true);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
SubMenuForEachBaseType = new BoolPref("Base Types ->/Individual Sub-Menus", true, MethodSelectionMenu.ClearMemberCache);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
SubMenuForBaseTypes = new BoolPref("Base Types ->/Group Sub-Menu", true);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Various settings.</summary>
|
||||
public static readonly BoolPref
|
||||
SubMenuForRootBaseType = new BoolPref("Base Types ->/Group Root Type", false);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds menu items to toggle all prefs.</summary>
|
||||
public static void AddDisplayOptions(GenericMenu menu)
|
||||
{
|
||||
UseIndentation.AddToMenu(menu);
|
||||
AutoOpenMenu.AddToMenu(menu);
|
||||
AutoHideFooter.AddToMenu(menu);
|
||||
ShowNonPublicMethods.AddToMenu(menu);
|
||||
GroupNonPublicMethods.AddToMenu(menu);
|
||||
ShowStaticMethods.AddToMenu(menu);
|
||||
ShowFullTypeNames.AddToMenu(menu);
|
||||
GroupMethodOverloads.AddToMenu(menu);
|
||||
SubMenuForEachBaseType.AddToMenu(menu);
|
||||
SubMenuForBaseTypes.AddToMenu(menu);
|
||||
SubMenuForRootBaseType.AddToMenu(menu);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 87441e384370a30428ebd73357e740d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// Manages the copying and pasting of events and persistent calls.
|
||||
/// </summary>
|
||||
public static class Clipboard
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static UltEventBase _Event;
|
||||
|
||||
/// <summary>Indicates whether an event has been copied.</summary>
|
||||
public static bool HasEvent { get { return _Event != null; } }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Stores the details of the specified event.</summary>
|
||||
public static void CopyEvent(UltEventBase e)
|
||||
{
|
||||
var eventType = e.GetType();
|
||||
|
||||
if (_Event == null || _Event.GetType() != eventType)
|
||||
_Event = (UltEventBase)Activator.CreateInstance(eventType);
|
||||
|
||||
_Event.CopyFrom(e);
|
||||
}
|
||||
|
||||
/// <summary>Stores the details of the event contained in the specified property.</summary>
|
||||
public static void CopyEvent(Serialization.PropertyAccessor accessor, Object target)
|
||||
{
|
||||
var e = (UltEventBase)accessor.GetValue(target);
|
||||
CopyEvent(e);
|
||||
}
|
||||
|
||||
/// <summary>Stores the details of the event contained in the specified property.</summary>
|
||||
public static void CopyEvent(SerializedProperty property)
|
||||
{
|
||||
var accessor = property.GetAccessor();
|
||||
if (accessor == null)
|
||||
return;
|
||||
|
||||
CopyEvent(accessor, property.serializedObject.targetObject);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Overwrites the specified event with the previously copied details.</summary>
|
||||
public static void Paste(UltEventBase e)
|
||||
{
|
||||
e.CopyFrom(_Event);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static PersistentCall _Call;
|
||||
|
||||
/// <summary>Indicates whether a persistent call has been copied.</summary>
|
||||
public static bool HasCall { get { return _Call != null; } }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Stores the details of the specified call.</summary>
|
||||
public static void CopyCall(PersistentCall call)
|
||||
{
|
||||
if (_Call == null)
|
||||
_Call = new PersistentCall();
|
||||
|
||||
_Call.CopyFrom(call);
|
||||
}
|
||||
|
||||
/// <summary>Stores the details of the call contained in the specified property.</summary>
|
||||
public static void CopyCall(Serialization.PropertyAccessor accessor, Object target)
|
||||
{
|
||||
var call = (PersistentCall)accessor.GetValue(target);
|
||||
CopyCall(call);
|
||||
}
|
||||
|
||||
/// <summary>Stores the details of the call contained in the specified property.</summary>
|
||||
public static void CopyCall(SerializedProperty property)
|
||||
{
|
||||
var accessor = property.GetAccessor();
|
||||
if (accessor == null)
|
||||
return;
|
||||
|
||||
CopyCall(accessor, property.serializedObject.targetObject);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Overwrites the specified call with the previously copied details.</summary>
|
||||
public static void PasteCall(PersistentCall call)
|
||||
{
|
||||
call.CopyFrom(_Call);
|
||||
}
|
||||
|
||||
/// <summary>Overwrites the call contained in the specified property with the copied details.</summary>
|
||||
public static void PasteCall(Serialization.PropertyAccessor accessor, Object target)
|
||||
{
|
||||
var call = (PersistentCall)accessor.GetValue(target);
|
||||
PasteCall(call);
|
||||
}
|
||||
|
||||
/// <summary>Overwrites the call contained in the specified property with the copied details.</summary>
|
||||
public static void PasteCall(SerializedProperty property)
|
||||
{
|
||||
property.ModifyValues<PersistentCall>((call) =>
|
||||
{
|
||||
PasteCall(call);
|
||||
}, "Paste PersistentCall");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 430d5eba154fa6c4a8b4511c67734546
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UltEvents.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// Manages the GUI state used when drawing events.
|
||||
/// </summary>
|
||||
internal sealed class DrawerState
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The currently active state.</summary>
|
||||
public static readonly DrawerState Current = new DrawerState();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="SerializedProperty"/> for the event currently being drawn.</summary>
|
||||
public SerializedProperty EventProperty { get; private set; }
|
||||
|
||||
/// <summary>The event currently being drawn.</summary>
|
||||
public UltEventBase Event { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="SerializedProperty"/> for the call currently being drawn.</summary>
|
||||
public SerializedProperty CallProperty { get; private set; }
|
||||
|
||||
/// <summary>The <see cref="SerializedProperty"/> for the target of the call currently being drawn.</summary>
|
||||
public SerializedProperty TargetProperty { get; private set; }
|
||||
|
||||
/// <summary>The <see cref="SerializedProperty"/> for the method name of the call currently being drawn.</summary>
|
||||
public SerializedProperty MethodNameProperty { get; private set; }
|
||||
|
||||
/// <summary>The <see cref="SerializedProperty"/> for the persistent arguments array of the call currently being drawn.</summary>
|
||||
public SerializedProperty PersistentArgumentsProperty { get; private set; }
|
||||
|
||||
/// <summary>The index of the call currently being drawn.</summary>
|
||||
public int callIndex = -1;
|
||||
|
||||
/// <summary>The call currently being drawn.</summary>
|
||||
public PersistentCall call;
|
||||
|
||||
/// <summary>The parameters of the call currently being drawn.</summary>
|
||||
public ParameterInfo[] callParameters;
|
||||
|
||||
/// <summary>The index of the parameter currently being drawn.</summary>
|
||||
public int parameterIndex;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>If true, each call will be stored so that subsequent calls can link to their return value.</summary>
|
||||
public bool CachePreviousCalls { get; private set; }
|
||||
|
||||
/// <summary>The calls of the current event that come before the current call currently being drawn.</summary>
|
||||
private readonly List<PersistentCall> PreviousCalls = new List<PersistentCall>();
|
||||
|
||||
/// <summary>The methods targeted by the calls of the event currently being drawn.</summary>
|
||||
private readonly List<MethodBase> PersistentMethodCache = new List<MethodBase>();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The parameter currently being drawn.</summary>
|
||||
public ParameterInfo CurrentParameter
|
||||
{
|
||||
get { return callParameters[parameterIndex]; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Caches the event from the specified property and returns true as long as it is not null.</summary>
|
||||
public bool TryBeginEvent(SerializedProperty eventProperty)
|
||||
{
|
||||
Event = eventProperty.GetValue<UltEventBase>();
|
||||
if (Event == null)
|
||||
return false;
|
||||
|
||||
EventProperty = eventProperty;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Cancels out a call to <see cref="TryBeginEvent"/>.</summary>
|
||||
public void EndEvent()
|
||||
{
|
||||
EventProperty = null;
|
||||
Event = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Starts caching calls so that subsequent calls can link to earlier return values.</summary>
|
||||
public void BeginCache()
|
||||
{
|
||||
CacheLinkedArguments();
|
||||
CachePreviousCalls = true;
|
||||
}
|
||||
|
||||
/// <summary>Cancels out a call to <see cref="EndCache"/>.</summary>
|
||||
public void EndCache()
|
||||
{
|
||||
CachePreviousCalls = false;
|
||||
PreviousCalls.Clear();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Caches the call from the specified property.</summary>
|
||||
public void BeginCall(SerializedProperty callProperty)
|
||||
{
|
||||
CallProperty = callProperty;
|
||||
|
||||
TargetProperty = GetTargetProperty(callProperty);
|
||||
MethodNameProperty = GetMethodNameProperty(callProperty);
|
||||
PersistentArgumentsProperty = GetPersistentArgumentsProperty(callProperty);
|
||||
|
||||
call = GetCall(callProperty);
|
||||
}
|
||||
|
||||
/// <summary>Cancels out a call to <see cref="BeginCall"/>.</summary>
|
||||
public void EndCall()
|
||||
{
|
||||
if (CachePreviousCalls)
|
||||
PreviousCalls.Add(call);
|
||||
|
||||
call = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the property encapsulating the <see cref="PersistentCall.Target"/>.</summary>
|
||||
public static SerializedProperty GetTargetProperty(SerializedProperty callProperty)
|
||||
{
|
||||
return callProperty.FindPropertyRelative(Names.PersistentCall.Target);
|
||||
}
|
||||
|
||||
/// <summary>Returns the property encapsulating the <see cref="PersistentCall.MethodName"/>.</summary>
|
||||
public static SerializedProperty GetMethodNameProperty(SerializedProperty callProperty)
|
||||
{
|
||||
return callProperty.FindPropertyRelative(Names.PersistentCall.MethodName);
|
||||
}
|
||||
|
||||
/// <summary>Returns the property encapsulating the <see cref="PersistentCall.PersistentArguments"/>.</summary>
|
||||
public static SerializedProperty GetPersistentArgumentsProperty(SerializedProperty callProperty)
|
||||
{
|
||||
return callProperty.FindPropertyRelative(Names.PersistentCall.PersistentArguments);
|
||||
}
|
||||
|
||||
/// <summary>Returns the call encapsulated by the specified property.</summary>
|
||||
public static PersistentCall GetCall(SerializedProperty callProperty)
|
||||
{
|
||||
return callProperty.GetValue<PersistentCall>();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Linked Argument Cache
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Stores all the persistent methods in the current event.</summary>
|
||||
public void CacheLinkedArguments()
|
||||
{
|
||||
PersistentMethodCache.Clear();
|
||||
|
||||
if (Event == null || Event._PersistentCalls == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Event._PersistentCalls.Count; i++)
|
||||
{
|
||||
var call = Event._PersistentCalls[i];
|
||||
PersistentMethodCache.Add(call != null ? call.GetMethodSafe() : null);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that any linked parameters remain linked to the correct target index.</summary>
|
||||
public void UpdateLinkedArguments()
|
||||
{
|
||||
if (Event == null ||
|
||||
PersistentMethodCache.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Event._PersistentCalls.Count; i++)
|
||||
{
|
||||
var call = Event._PersistentCalls[i];
|
||||
if (call == null)
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < call._PersistentArguments.Length; j++)
|
||||
{
|
||||
var argument = call._PersistentArguments[j];
|
||||
if (argument == null || argument._Type != PersistentArgumentType.ReturnValue)
|
||||
continue;
|
||||
|
||||
var linkedMethod = PersistentMethodCache[argument.ReturnedValueIndex];
|
||||
|
||||
if (argument.ReturnedValueIndex < Event._PersistentCalls.Count)
|
||||
{
|
||||
var linkedCall = Event._PersistentCalls[argument.ReturnedValueIndex];
|
||||
if (linkedMethod == (linkedCall != null ? linkedCall.GetMethodSafe() : null))
|
||||
continue;
|
||||
}
|
||||
|
||||
var index = IndexOfMethod(linkedMethod);
|
||||
if (index >= 0)
|
||||
argument.ReturnedValueIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
PersistentMethodCache.Clear();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the index of the persistent call that targets the specified `method` or -1 if there is none.</summary>
|
||||
public int IndexOfMethod(MethodBase method)
|
||||
{
|
||||
for (int i = 0; i < Event._PersistentCalls.Count; i++)
|
||||
{
|
||||
var call = Event._PersistentCalls[i];
|
||||
if ((call != null ? call.GetMethodSafe() : null) == method)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the method cached from the persistent call at the specified `index`.</summary>
|
||||
public MethodBase GetLinkedMethod(int index)
|
||||
{
|
||||
if (index >= 0 && index < PersistentMethodCache.Count)
|
||||
return PersistentMethodCache[index];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Previous Call Cache
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to get the details of the a parameter or return value of the specified `type`.</summary>
|
||||
public bool TryGetLinkable(Type type, out int linkIndex, out PersistentArgumentType linkType)
|
||||
{
|
||||
if (Event != null)
|
||||
{
|
||||
// Parameters.
|
||||
var parameterTypes = Event.ParameterTypes;
|
||||
for (int i = 0; i < parameterTypes.Length; i++)
|
||||
{
|
||||
if (type.IsAssignableFrom(parameterTypes[i]))
|
||||
{
|
||||
linkIndex = i;
|
||||
linkType = PersistentArgumentType.Parameter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return Values.
|
||||
for (int i = 0; i < PreviousCalls.Count; i++)
|
||||
{
|
||||
var method = PreviousCalls[i].GetMethodSafe();
|
||||
if (method == null)
|
||||
continue;
|
||||
|
||||
if (type.IsAssignableFrom(method.GetReturnType()))
|
||||
{
|
||||
linkIndex = i;
|
||||
linkType = PersistentArgumentType.ReturnValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linkIndex = -1;
|
||||
linkType = PersistentArgumentType.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to get the details of the a parameter or return value of the current parameter type.</summary>
|
||||
public bool TryGetLinkable(out int linkIndex, out PersistentArgumentType linkType)
|
||||
{
|
||||
if (callParameters != null)
|
||||
{
|
||||
return TryGetLinkable(CurrentParameter.ParameterType, out linkIndex, out linkType);
|
||||
}
|
||||
else
|
||||
{
|
||||
linkIndex = -1;
|
||||
linkType = PersistentArgumentType.None;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The number of persistent calls that came earlier in the current event.</summary>
|
||||
public int PreviousCallCount
|
||||
{
|
||||
get { return PreviousCalls.Count; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the persistent call at the specified index in the current event.</summary>
|
||||
public PersistentCall GetPreviousCall(int index)
|
||||
{
|
||||
if (index >= 0 && index < PreviousCalls.Count)
|
||||
return PreviousCalls[index];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Copies the contents of the `other` state to overwrite this one.</summary>
|
||||
public void CopyFrom(DrawerState other)
|
||||
{
|
||||
EventProperty = other.EventProperty;
|
||||
Event = other.Event;
|
||||
|
||||
CallProperty = other.CallProperty;
|
||||
TargetProperty = other.TargetProperty;
|
||||
MethodNameProperty = other.MethodNameProperty;
|
||||
PersistentArgumentsProperty = other.PersistentArgumentsProperty;
|
||||
|
||||
callIndex = other.callIndex;
|
||||
call = other.call;
|
||||
callParameters = other.callParameters;
|
||||
parameterIndex = other.parameterIndex;
|
||||
|
||||
PreviousCalls.Clear();
|
||||
PreviousCalls.AddRange(other.PreviousCalls);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Clears all the details of this state.</summary>
|
||||
public void Clear()
|
||||
{
|
||||
EventProperty = null;
|
||||
Event = null;
|
||||
|
||||
CallProperty = null;
|
||||
TargetProperty = null;
|
||||
MethodNameProperty = null;
|
||||
PersistentArgumentsProperty = null;
|
||||
|
||||
callIndex = -1;
|
||||
call = null;
|
||||
callParameters = null;
|
||||
parameterIndex = 0;
|
||||
|
||||
PreviousCalls.Clear();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 01c9b3f0af8a8b642b1c6578cfdcf416
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2ebc5b36ea6721c428b0c87c8307e068
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,728 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
// Copied from Kybernetik.Core.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents.Editor
|
||||
{
|
||||
/// <summary>[Editor-Only] Allows you to draw GUI fields which can be used to pick an object from a list.</summary>
|
||||
public static class ObjectPicker
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Main Drawing Methods
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a field which lets you pick an object from a list and returns the selected object.</summary>
|
||||
public static T Draw<T>(Rect area, T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
|
||||
GUIStyle style)
|
||||
{
|
||||
var id = CheckCommand(ref selected);
|
||||
|
||||
if (GUI.Button(area, getLabel(selected), style))
|
||||
ObjectPickerWindow.Show(id, selected, getOptions(), suggestions, getLabel);
|
||||
|
||||
CheckDragAndDrop(area, ref selected, getOptions, getDragAndDrop);
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/// <summary>Draws a field which lets you pick an object from a list and returns the selected object.</summary>
|
||||
public static T Draw<T>(Rect area, T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop)
|
||||
{
|
||||
return Draw(area, selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a field (using GUILayout) which lets you pick an object from a list and returns the selected object.</summary>
|
||||
public static T DrawLayout<T>(T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
|
||||
GUIStyle style, params GUILayoutOption[] layoutOptions)
|
||||
{
|
||||
var id = CheckCommand(ref selected);
|
||||
|
||||
if (GUILayout.Button(getLabel(selected), style, layoutOptions))
|
||||
ObjectPickerWindow.Show(id, selected, getOptions(), suggestions, getLabel);
|
||||
|
||||
CheckDragAndDrop(GUILayoutUtility.GetLastRect(), ref selected, getOptions, getDragAndDrop);
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/// <summary>Draws a field (using GUILayout) which lets you pick an object from a list and returns the selected object.</summary>
|
||||
public static T DrawLayout<T>(T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
|
||||
params GUILayoutOption[] layoutOptions)
|
||||
{
|
||||
return DrawLayout(selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle, layoutOptions);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Draws a field (as an inspector field using GUILayout) which lets you pick an object from a list and returns
|
||||
/// the selected object.
|
||||
/// </summary>
|
||||
public static T DrawEditorLayout<T>(GUIContent label, T selected, Func<List<T>> getOptions, int suggestions,
|
||||
Func<T, GUIContent> getLabel, Func<T> getDragAndDrop, GUIStyle style, params GUILayoutOption[] layoutOptions)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Label(label, GUILayout.Width(EditorGUIUtility.labelWidth - 4));
|
||||
selected = DrawLayout(selected, getOptions, suggestions, getLabel, getDragAndDrop, style, layoutOptions);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a field (as an inspector field using GUILayout) which lets you pick an object from a list and returns
|
||||
/// the selected object.
|
||||
/// </summary>
|
||||
public static T DrawEditorLayout<T>(GUIContent label, T selected, Func<List<T>> getOptions, int suggestions,
|
||||
Func<T, GUIContent> getLabel, Func<T> getDragAndDrop, params GUILayoutOption[] options)
|
||||
{
|
||||
return DrawEditorLayout(label, selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle, options);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Type Field
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a field which lets you pick a <see cref="Type"></see> from a list and returns the selected type.</summary>
|
||||
public static Type DrawTypeField(Rect area, Type selected, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
|
||||
{
|
||||
return Draw(area, selected, getOptions, suggestions,
|
||||
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "null"),
|
||||
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
|
||||
style: style);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a field which lets you pick an asset <see cref="Type"></see> from a list and returns the selected type.</summary>
|
||||
public static Type DrawAssetTypeField(Rect area, Type selected, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
|
||||
{
|
||||
return Draw(area, selected, getOptions, suggestions,
|
||||
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "null", AssetPreview.GetMiniTypeThumbnail(type)),
|
||||
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
|
||||
style: style);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws a field which lets you pick a <see cref="Type"></see> from a list and returns the selected <see cref="Type.AssemblyQualifiedName"/>.</summary>
|
||||
public static string DrawTypeField(Rect area, string selectedTypeName, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
|
||||
{
|
||||
var selected = Type.GetType(selectedTypeName);
|
||||
|
||||
selected = Draw(area, selected, getOptions, suggestions,
|
||||
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "No Type Selected"),
|
||||
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
|
||||
style: style);
|
||||
|
||||
return selected != null ? selected.AssemblyQualifiedName : null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Utils
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Removes any duplicates of the first few elements in `options` (from 0 to `suggestions`) from anywhere later
|
||||
/// in the list.
|
||||
/// </summary>
|
||||
public static void RemoveDuplicateSuggestions<T>(List<T> options, int suggestions) where T : class
|
||||
{
|
||||
for (int i = options.Count - 1; i >= suggestions; i--)
|
||||
{
|
||||
var obj = options[i];
|
||||
for (int j = 0; j < suggestions; j++)
|
||||
{
|
||||
if (obj == options[j])
|
||||
{
|
||||
options.RemoveAt(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static int CheckCommand<T>(ref T selected)
|
||||
{
|
||||
var id = GUIUtility.GetControlID(FocusType.Passive);
|
||||
ObjectPickerWindow.TryGetPickedObject(id, ref selected);
|
||||
return id;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void CheckDragAndDrop<T>(Rect area, ref T selected, Func<List<T>> getOptions, Func<T> getDragAndDrop)
|
||||
{
|
||||
var currentEvent = Event.current;
|
||||
if (DragAndDrop.objectReferences.Length == 1 && area.Contains(currentEvent.mousePosition))
|
||||
{
|
||||
var drop = getDragAndDrop();
|
||||
|
||||
// If the dragged object is a valid type, continue.
|
||||
if (!getOptions().Contains(drop))
|
||||
return;
|
||||
|
||||
if (currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.MouseDrag)
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
||||
}
|
||||
else if (currentEvent.type == EventType.DragPerform)
|
||||
{
|
||||
selected = drop;
|
||||
DragAndDrop.AcceptDrag();
|
||||
GUI.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static class InternalGUI
|
||||
{
|
||||
public static readonly GUIStyle
|
||||
TypeButtonStyle;
|
||||
|
||||
static InternalGUI()
|
||||
{
|
||||
TypeButtonStyle = new GUIStyle(EditorStyles.miniButton)
|
||||
{
|
||||
alignment = TextAnchor.MiddleLeft
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal sealed class ObjectPickerWindow : EditorWindow
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static int _FieldID;
|
||||
private static bool _HasPickedObject;
|
||||
private static object _PickedObject;
|
||||
|
||||
private readonly List<GUIContent>
|
||||
Labels = new List<GUIContent>();
|
||||
|
||||
private readonly List<GUIContent>
|
||||
SearchedLabels = new List<GUIContent>();
|
||||
private readonly List<object>
|
||||
SearchedObjects = new List<object>();
|
||||
|
||||
[NonSerialized]
|
||||
private object _SelectedObject;
|
||||
|
||||
[NonSerialized]
|
||||
private IList _Objects;
|
||||
|
||||
[NonSerialized]
|
||||
private int _Suggestions;
|
||||
|
||||
[NonSerialized]
|
||||
private int _LabelWidthCalculationProgress;
|
||||
|
||||
[NonSerialized]
|
||||
private float _MaxLabelWidth;
|
||||
|
||||
[NonSerialized]
|
||||
private string _SearchText = "";
|
||||
|
||||
[NonSerialized]
|
||||
private Vector2 _ScrollPosition;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private bool HasSearchText
|
||||
{
|
||||
get { return !string.IsNullOrEmpty(_SearchText); }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public static void Show<T>(int fieldID, T selected, List<T> objects, int suggestions, Func<T, GUIContent> getLabel)
|
||||
{
|
||||
if (objects == null || objects.Count == 0)
|
||||
{
|
||||
Debug.LogError("'objects' list is null or empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
_FieldID = fieldID;
|
||||
_HasPickedObject = false;
|
||||
|
||||
var window = CreateInstance<ObjectPickerWindow>();
|
||||
window.titleContent = new GUIContent("Pick a " + typeof(T).GetNameCS());
|
||||
window.minSize = new Vector2(112, 100);
|
||||
|
||||
if (window.Labels.Capacity < objects.Count)
|
||||
window.Labels.Capacity = objects.Count;
|
||||
|
||||
for (int i = 0; i < objects.Count; i++)
|
||||
window.Labels.Add(getLabel(objects[i]));
|
||||
|
||||
//Debug.LogTemp("Showing Object Picker Window: " + window._Labels.DeepToString());
|
||||
|
||||
window._SelectedObject = selected;
|
||||
window._Objects = objects;
|
||||
window._Suggestions = suggestions;
|
||||
|
||||
// Auto-Scroll to the selected object.
|
||||
if (selected != null)
|
||||
{
|
||||
object sel = selected;
|
||||
|
||||
for (int i = 0; i < window._Objects.Count; i++)
|
||||
{
|
||||
if (sel == window._Objects[i])
|
||||
{
|
||||
window._ScrollPosition = new Vector2(0, i * InternalGUI.LabelHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//window._Objects.LogErrorIfModified("the '" + nameof(objects) + "' list passed into " + Reflection.GetCallingMethod(2).GetNameCS());
|
||||
|
||||
window.ShowAuxWindow();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public static void TryGetPickedObject<T>(int fieldID, ref T picked)
|
||||
{
|
||||
if (_HasPickedObject && _FieldID == fieldID)
|
||||
{
|
||||
picked = (T)_PickedObject;
|
||||
_PickedObject = null;
|
||||
_HasPickedObject = false;
|
||||
GUI.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void PickAndClose()
|
||||
{
|
||||
_PickedObject = _SelectedObject;
|
||||
_HasPickedObject = true;
|
||||
Close();
|
||||
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
switch (Event.current.type)
|
||||
{
|
||||
case EventType.MouseMove:
|
||||
case EventType.Layout:
|
||||
case EventType.DragUpdated:
|
||||
case EventType.DragPerform:
|
||||
case EventType.DragExited:
|
||||
case EventType.Ignore:
|
||||
case EventType.Used:
|
||||
case EventType.ValidateCommand:
|
||||
case EventType.ExecuteCommand:
|
||||
case EventType.ContextClick:
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (CheckInput())
|
||||
{
|
||||
Event.current.Use();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateLabelWidthCalculation();
|
||||
|
||||
var area = new Rect(0, 0, position.width, position.height);
|
||||
|
||||
DrawSearchBar(ref area);
|
||||
|
||||
area.yMax = position.height;
|
||||
|
||||
var viewRect = CalculateViewRect(area.height);
|
||||
|
||||
// Selection List.
|
||||
_ScrollPosition = GUI.BeginScrollView(area, _ScrollPosition, viewRect);
|
||||
{
|
||||
// Figure out how many fields are actually visible.
|
||||
int firstVisibleField, lastVisibleField;
|
||||
DetermineVisibleRange(out firstVisibleField, out lastVisibleField);
|
||||
|
||||
if (HasSearchText)// Active Search.
|
||||
{
|
||||
DrawSearchedOptions(viewRect, firstVisibleField, lastVisibleField);
|
||||
}
|
||||
else// No Search.
|
||||
{
|
||||
DrawAllOptions(viewRect, firstVisibleField, lastVisibleField);
|
||||
}
|
||||
}
|
||||
GUI.EndScrollView(true);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void UpdateLabelWidthCalculation()
|
||||
{
|
||||
if (_LabelWidthCalculationProgress < Labels.Count)
|
||||
{
|
||||
var calculationCount = 0;
|
||||
do
|
||||
{
|
||||
var label = Labels[_LabelWidthCalculationProgress];
|
||||
|
||||
var width = InternalGUI.ButtonStyle.CalcSize(label).x;
|
||||
if (_MaxLabelWidth < width)
|
||||
_MaxLabelWidth = width;
|
||||
}
|
||||
while (++_LabelWidthCalculationProgress < Labels.Count && calculationCount++ < 100);
|
||||
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private bool CheckInput()
|
||||
{
|
||||
var currentEvent = Event.current;
|
||||
if (currentEvent.type == EventType.KeyUp)
|
||||
{
|
||||
switch (currentEvent.keyCode)
|
||||
{
|
||||
case KeyCode.Return:
|
||||
PickAndClose();
|
||||
return true;
|
||||
|
||||
case KeyCode.Escape:
|
||||
Close();
|
||||
return true;
|
||||
|
||||
case KeyCode.UpArrow:
|
||||
OffsetSelectedIndex(-1);
|
||||
return true;
|
||||
|
||||
case KeyCode.DownArrow:
|
||||
OffsetSelectedIndex(1);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DrawSearchBar(ref Rect area)
|
||||
{
|
||||
area.height = InternalGUI.SearchBarHeight;
|
||||
GUI.BeginGroup(area, EditorStyles.toolbar);
|
||||
{
|
||||
area.x += 2;
|
||||
area.y += 2;
|
||||
area.width -= InternalGUI.SearchBarEndStyle.fixedWidth + 4;
|
||||
|
||||
GUI.SetNextControlName("SearchFilter");
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var searchText = GUI.TextField(area, _SearchText, InternalGUI.SearchBarStyle);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
OnSearchTextChanged(searchText);
|
||||
EditorGUI.FocusTextInControl("SearchFilter");
|
||||
|
||||
area.x = area.xMax;
|
||||
area.width = InternalGUI.SearchBarEndStyle.fixedWidth;
|
||||
if (HasSearchText)
|
||||
{
|
||||
if (GUI.Button(area, "", InternalGUI.SearchBarCancelStyle))
|
||||
{
|
||||
_SearchText = "";
|
||||
}
|
||||
}
|
||||
else GUI.Box(area, "", InternalGUI.SearchBarEndStyle);
|
||||
}
|
||||
GUI.EndGroup();
|
||||
|
||||
area.x = 0;
|
||||
area.width = position.width;
|
||||
area.y += area.height;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void OnSearchTextChanged(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
SearchedLabels.Clear();
|
||||
SearchedObjects.Clear();
|
||||
}
|
||||
// If the search text starts the same as before, it will include only a subset of the previous options.
|
||||
// So we can just remove objects from the previous search list instead of checking the full list again.
|
||||
else if (SearchedLabels.Count > 0 && text.StartsWith(_SearchText))
|
||||
{
|
||||
for (int i = SearchedLabels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!IsVisibleInSearch(text, SearchedLabels[i].text))
|
||||
{
|
||||
SearchedLabels.RemoveAt(i);
|
||||
SearchedObjects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise clear the search list and re-gather any visible objects from the full list.
|
||||
else
|
||||
{
|
||||
SearchedLabels.Clear();
|
||||
SearchedObjects.Clear();
|
||||
|
||||
for (int i = 0; i < Labels.Count; i++)
|
||||
{
|
||||
var label = Labels[i];
|
||||
if (IsVisibleInSearch(text, label.text))
|
||||
{
|
||||
SearchedLabels.Add(label);
|
||||
SearchedObjects.Add(_Objects[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_SearchText = text;
|
||||
|
||||
if (!SearchedObjects.Contains(_SelectedObject))
|
||||
_SelectedObject = SearchedObjects.Count > 0 ? SearchedObjects[0] : null;
|
||||
}
|
||||
|
||||
private static bool IsVisibleInSearch(string search, string text)
|
||||
{
|
||||
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(text, search, CompareOptions.IgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Rect CalculateViewRect(float height)
|
||||
{
|
||||
var area = new Rect();
|
||||
|
||||
if (HasSearchText)
|
||||
{
|
||||
area.height = InternalGUI.LabelHeight * SearchedLabels.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
area.height = InternalGUI.LabelHeight * Labels.Count;
|
||||
|
||||
if (_Suggestions > 0)
|
||||
area.height += InternalGUI.HeaddingStyle.fixedHeight * 2;
|
||||
}
|
||||
|
||||
if (_MaxLabelWidth < position.width)
|
||||
{
|
||||
area.width = position.width;
|
||||
|
||||
if (area.height > height)
|
||||
area.width -= 16;
|
||||
}
|
||||
else area.width = _MaxLabelWidth;
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DetermineVisibleRange(out int firstVisibleField, out int lastVisibleField)
|
||||
{
|
||||
var top = _ScrollPosition.y;
|
||||
var bottom = top + position.height - InternalGUI.SearchBarHeight;
|
||||
if (_Suggestions > 0)
|
||||
{
|
||||
top -= InternalGUI.HeaddingStyle.fixedHeight * 2;
|
||||
bottom += InternalGUI.HeaddingStyle.fixedHeight;
|
||||
}
|
||||
|
||||
firstVisibleField = Mathf.Max(0, (int)(top / InternalGUI.LabelHeight));
|
||||
lastVisibleField = Mathf.Min(Labels.Count, Mathf.CeilToInt(bottom / InternalGUI.LabelHeight));
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DrawAllOptions(Rect area, int firstVisibleField, int lastVisibleField)
|
||||
{
|
||||
if (_Suggestions == 0 || _Suggestions >= Labels.Count)
|
||||
{
|
||||
area.y = firstVisibleField * InternalGUI.LabelHeight;
|
||||
DrawRange(ref area, Labels, _Objects, firstVisibleField, lastVisibleField);
|
||||
}
|
||||
else
|
||||
{
|
||||
area.height = InternalGUI.HeaddingStyle.fixedHeight;
|
||||
GUI.Label(area, "Suggestions", InternalGUI.HeaddingStyle);
|
||||
|
||||
area.y = area.yMax + firstVisibleField * InternalGUI.LabelHeight;
|
||||
DrawRange(ref area, Labels, _Objects, firstVisibleField, Mathf.Min(lastVisibleField, _Suggestions));
|
||||
|
||||
area.height = InternalGUI.HeaddingStyle.fixedHeight;
|
||||
GUI.Label(area, "Other Options", InternalGUI.HeaddingStyle);
|
||||
area.y = area.yMax;
|
||||
|
||||
DrawRange(ref area, Labels, _Objects, Mathf.Max(_Suggestions, firstVisibleField), lastVisibleField);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DrawSearchedOptions(Rect area, int firstVisibleField, int lastVisibleField)
|
||||
{
|
||||
area.y = firstVisibleField * InternalGUI.LabelHeight;
|
||||
DrawRange(ref area, SearchedLabels, SearchedObjects, firstVisibleField, lastVisibleField);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DrawRange(ref Rect area, List<GUIContent> labels, IList objects, int start, int end)
|
||||
{
|
||||
area.height = InternalGUI.LabelHeight;
|
||||
|
||||
if (end > labels.Count)
|
||||
end = labels.Count;
|
||||
|
||||
for (; start < end; start++)
|
||||
{
|
||||
DrawOption(area, labels, objects, start);
|
||||
area.y = area.yMax;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DrawOption(Rect area, List<GUIContent> labels, IList objects, int index)
|
||||
{
|
||||
var obj = objects[index];
|
||||
var wasOn = obj == _SelectedObject;
|
||||
var isOn = GUI.Toggle(area, wasOn, labels[index], wasOn ? InternalGUI.SelectedButtonStyle : InternalGUI.ButtonStyle);
|
||||
if (isOn != wasOn)
|
||||
{
|
||||
if (wasOn)
|
||||
{
|
||||
PickAndClose();
|
||||
}
|
||||
else if (isOn)
|
||||
{
|
||||
_SelectedObject = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (focusedWindow != this)
|
||||
Close();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void OffsetSelectedIndex(int offset)
|
||||
{
|
||||
var objects = HasSearchText ? SearchedObjects : _Objects;
|
||||
|
||||
if (objects.Count == 0)
|
||||
return;
|
||||
|
||||
var index = objects.IndexOf(_SelectedObject);
|
||||
if (index >= 0)
|
||||
_SelectedObject = objects[Mathf.Clamp(index + offset, 0, objects.Count)];
|
||||
else
|
||||
_SelectedObject = objects[0];
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static class InternalGUI
|
||||
{
|
||||
public static readonly GUIStyle
|
||||
SearchBarStyle;
|
||||
public static readonly GUIStyle
|
||||
SearchBarEndStyle;
|
||||
public static readonly GUIStyle
|
||||
SearchBarCancelStyle;
|
||||
public static readonly GUIStyle
|
||||
HeaddingStyle;
|
||||
public static readonly GUIStyle
|
||||
ButtonStyle;
|
||||
public static readonly GUIStyle
|
||||
SelectedButtonStyle;
|
||||
|
||||
public static float SearchBarHeight
|
||||
{
|
||||
get { return EditorStyles.toolbar.fixedHeight; }
|
||||
}
|
||||
|
||||
public static float LabelHeight
|
||||
{
|
||||
get { return ButtonStyle.fixedHeight; }
|
||||
}
|
||||
|
||||
static InternalGUI()
|
||||
{
|
||||
SearchBarStyle = GUI.skin.FindStyle("ToolbarSeachTextField");
|
||||
SearchBarEndStyle = GUI.skin.FindStyle("ToolbarSeachCancelButtonEmpty");
|
||||
SearchBarCancelStyle = GUI.skin.FindStyle("ToolbarSeachCancelButton");
|
||||
|
||||
HeaddingStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 12,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
fixedHeight = 22
|
||||
};
|
||||
|
||||
ButtonStyle = new GUIStyle(EditorStyles.toolbarButton)
|
||||
{
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
fontSize = 12
|
||||
};
|
||||
|
||||
SelectedButtonStyle = new GUIStyle(ButtonStyle)
|
||||
{
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 657c235856cafdd41a65047f9688b0cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,689 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(PersistentArgument), true)]
|
||||
internal sealed class PersistentArgumentDrawer : PropertyDrawer
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private const float
|
||||
SpecialModeToggleWidth = PersistentCallDrawer.RemoveButtonWidth;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override void OnGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
int linkIndex;
|
||||
PersistentArgumentType linkType;
|
||||
if (DrawerState.Current.TryGetLinkable(out linkIndex, out linkType))
|
||||
area.width -= SpecialModeToggleWidth;
|
||||
|
||||
var wideMode = EditorGUIUtility.wideMode;
|
||||
EditorGUIUtility.wideMode = true;
|
||||
|
||||
var typeProperty = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Type);
|
||||
switch ((PersistentArgumentType)typeProperty.enumValueIndex)
|
||||
{
|
||||
case PersistentArgumentType.None: DoErrorMessageGUI(area, argumentProperty, label, "Error: argument type not set"); break;
|
||||
|
||||
case PersistentArgumentType.Bool: DoBoolGUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.String: DoStringGUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Int: DoIntGUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Enum: DoEnumGUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Float: DoFloatGUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Vector2: DoVector2GUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Vector3: DoVector3GUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Vector4: DoVector4GUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Quaternion: DoQuaternionGUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Color: DoColorGUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Color32: DoColor32GUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Rect: DoRectGUI(area, argumentProperty, label); break;
|
||||
case PersistentArgumentType.Object: DoObjectGUI(area, argumentProperty, label); break;
|
||||
|
||||
case PersistentArgumentType.Parameter:
|
||||
case PersistentArgumentType.ReturnValue:
|
||||
DoLinkedValueGUI(area, argumentProperty, label); break;
|
||||
|
||||
default: DoErrorMessageGUI(area, argumentProperty, label, "Error: unexpected argument type " + (PersistentArgumentType)typeProperty.enumValueIndex); break;
|
||||
}
|
||||
|
||||
EditorGUIUtility.wideMode = wideMode;
|
||||
|
||||
DoLinkModeToggleGUI(area, argumentProperty, linkIndex, linkType);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoErrorMessageGUI(Rect area, SerializedProperty argumentProperty, GUIContent label, string message)
|
||||
{
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
|
||||
EditorGUI.PrefixLabel(area, label);
|
||||
|
||||
area.xMin += EditorGUIUtility.labelWidth;
|
||||
|
||||
var color = GUI.color;
|
||||
GUI.color = Color.red;
|
||||
GUI.Label(area, message, EditorStyles.whiteLabel);
|
||||
GUI.color = color;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoBoolGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var i = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, i);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var value = EditorGUI.Toggle(area, label, i.intValue != 0);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
i.intValue = value ? 1 : 0;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoStringGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var s = argumentProperty.FindPropertyRelative(Names.PersistentArgument.String);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, s);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var value = EditorGUI.TextField(area, label, s.stringValue);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
s.stringValue = value;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoIntGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var i = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, i);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var value = EditorGUI.IntField(area, label, i.intValue);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
i.intValue = value;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Enum
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoEnumGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var enumType = argumentProperty.GetValue<PersistentArgument>().SystemType;
|
||||
if (enumType == null)
|
||||
{
|
||||
DoErrorMessageGUI(area, argumentProperty, label, "Error: enum type not set");
|
||||
return;
|
||||
}
|
||||
|
||||
var i = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
int value;
|
||||
if (enumType.IsDefined(typeof(FlagsAttribute), true))
|
||||
{
|
||||
value = DrawEnumMaskField(area, label, i.intValue, enumType);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = Convert.ToInt32(EditorGUI.EnumPopup(area, label, (Enum)Enum.ToObject(enumType, i.intValue)));
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
i.intValue = value;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static Dictionary<Type, string[]> _Names;
|
||||
private static Dictionary<Type, int[]> _Values;
|
||||
|
||||
/// <summary>
|
||||
/// Draw a field with a dropdown box for selecting values in a flags enum.
|
||||
/// <para></para>
|
||||
/// This method works properly for some enum configurations not supported by EditorGUI.EnumMaskField or EditorGUI.EnumMaskPopup.
|
||||
/// <para></para>
|
||||
/// This method only supports enums with int as their underlying type.
|
||||
/// </summary>
|
||||
public static int DrawEnumMaskField(Rect area, GUIContent label, int enumValue, Type enumType)
|
||||
{
|
||||
if (_Names == null)
|
||||
{
|
||||
_Names = new Dictionary<Type, string[]>();
|
||||
_Values = new Dictionary<Type, int[]>();
|
||||
}
|
||||
|
||||
string[] names;
|
||||
if (!_Names.TryGetValue(enumType, out names))
|
||||
{
|
||||
names = Enum.GetNames(enumType);
|
||||
_Names.Add(enumType, names);
|
||||
}
|
||||
|
||||
int[] values;
|
||||
if (!_Values.TryGetValue(enumType, out values))
|
||||
{
|
||||
values = Enum.GetValues(enumType) as int[];
|
||||
_Values.Add(enumType, values);
|
||||
}
|
||||
|
||||
var maskValue = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
var v = values[i];
|
||||
if (v != 0)
|
||||
{
|
||||
if ((enumValue & v) == v)
|
||||
maskValue |= 1 << i;
|
||||
}
|
||||
else if (enumValue == 0)
|
||||
{
|
||||
maskValue |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newMaskVal = EditorGUI.MaskField(area, label, maskValue, names);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
var changes = maskValue ^ newMaskVal;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if ((changes & (1 << i)) != 0)
|
||||
{
|
||||
if ((newMaskVal & (1 << i)) != 0)
|
||||
{
|
||||
var v = values[i];
|
||||
if (v == 0)// special case: if "0" is set, just set the value to 0.
|
||||
{
|
||||
enumValue = 0;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
enumValue |= v;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
enumValue &= ~values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoFloatGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, x);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var value = EditorGUI.FloatField(area, label, x.floatValue);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
x.floatValue = value;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIContent[] _Vector2Labels;
|
||||
|
||||
private static void DoVector2GUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
if (_Vector2Labels == null)
|
||||
{
|
||||
_Vector2Labels = new GUIContent[]
|
||||
{
|
||||
new GUIContent("X"),
|
||||
new GUIContent("Y"),
|
||||
};
|
||||
}
|
||||
|
||||
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
EditorGUI.MultiPropertyField(area, _Vector2Labels, x, label);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIContent[] _Vector3Labels;
|
||||
|
||||
private static void DoVector3GUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
if (_Vector3Labels == null)
|
||||
{
|
||||
_Vector3Labels = new GUIContent[]
|
||||
{
|
||||
new GUIContent("X"),
|
||||
new GUIContent("Y"),
|
||||
new GUIContent("Z"),
|
||||
};
|
||||
}
|
||||
|
||||
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
EditorGUI.MultiPropertyField(area, _Vector3Labels, x, label);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIContent[] _Vector4Labels;
|
||||
|
||||
private static void DoVector4GUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
if (_Vector4Labels == null)
|
||||
{
|
||||
_Vector4Labels = new GUIContent[]
|
||||
{
|
||||
new GUIContent("X"),
|
||||
new GUIContent("Y"),
|
||||
new GUIContent("Z"),
|
||||
new GUIContent("W"),
|
||||
};
|
||||
}
|
||||
|
||||
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
EditorGUI.MultiPropertyField(area, _Vector4Labels, x, label);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoQuaternionGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
DoVector3GUI(area, argumentProperty, label);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
|
||||
var y = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Y);
|
||||
var z = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Z);
|
||||
|
||||
x.floatValue %= 360;
|
||||
y.floatValue %= 360;
|
||||
z.floatValue %= 360;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoColorGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
|
||||
var y = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Y);
|
||||
var z = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Z);
|
||||
var w = argumentProperty.FindPropertyRelative(Names.PersistentArgument.W);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var value = EditorGUI.ColorField(area, label, new Color(x.floatValue, y.floatValue, z.floatValue, w.floatValue));
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
x.floatValue = value.r;
|
||||
y.floatValue = value.g;
|
||||
z.floatValue = value.b;
|
||||
w.floatValue = value.a;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoColor32GUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var i = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var intValue = i.intValue;
|
||||
var value = EditorGUI.ColorField(area, label,
|
||||
new Color32((byte)(intValue), (byte)(intValue >> 8), (byte)(intValue >> 16), (byte)(intValue >> 24)));
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
var value32 = (Color32)value;
|
||||
i.intValue = value32.r | (value32.g << 8) | (value32.b << 16) | (value32.a << 24);
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIContent[] _RectLabels;
|
||||
|
||||
private static void DoRectGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
if (_RectLabels == null)
|
||||
{
|
||||
_RectLabels = new GUIContent[]
|
||||
{
|
||||
new GUIContent("X"),
|
||||
new GUIContent("Y"),
|
||||
new GUIContent("W"),
|
||||
new GUIContent("H"),
|
||||
};
|
||||
}
|
||||
|
||||
var x = argumentProperty.FindPropertyRelative(Names.PersistentArgument.X);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
EditorGUI.MultiPropertyField(area, _RectLabels, x, label);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoObjectGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var o = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Object);
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, o);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var type = argumentProperty.GetValue<PersistentArgument>().SystemType ?? typeof(UnityEngine.Object);
|
||||
|
||||
var value = EditorGUI.ObjectField(area, label, o.objectReferenceValue, type, true);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
o.objectReferenceValue = value;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoLinkedValueGUI(Rect area, SerializedProperty argumentProperty, GUIContent label)
|
||||
{
|
||||
var color = GUI.color;
|
||||
|
||||
label = EditorGUI.BeginProperty(area, label, argumentProperty);
|
||||
|
||||
EditorGUI.PrefixLabel(area, label);
|
||||
|
||||
area.xMin += EditorGUIUtility.labelWidth;
|
||||
|
||||
var argument = argumentProperty.GetValue<PersistentArgument>();
|
||||
var callIndex = argument._Int;
|
||||
var argumentType = argument.SystemType;
|
||||
|
||||
if (argumentType == null)
|
||||
{
|
||||
GUI.color = PersistentCallDrawer.ErrorFieldColor;
|
||||
GUI.Label(area, "Unable to determine argument type");
|
||||
}
|
||||
else if (DrawerState.Current.Event != null)
|
||||
{
|
||||
switch (argument.Type)
|
||||
{
|
||||
case PersistentArgumentType.Parameter:
|
||||
label.text = "Parameter " + callIndex;
|
||||
var parameterTypes = DrawerState.Current.Event.ParameterTypes;
|
||||
var parameters = DrawerState.Current.Event.Parameters;
|
||||
|
||||
if (callIndex < 0 || callIndex >= parameterTypes.Length)
|
||||
{
|
||||
GUI.color = PersistentCallDrawer.ErrorFieldColor;
|
||||
label.tooltip = "Parameter link index out of range";
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameterType = parameterTypes[callIndex];
|
||||
|
||||
label.text += " (" + parameterType.GetNameCS(false);
|
||||
|
||||
if (parameters != null)
|
||||
label.text += " " + parameters[callIndex].Name;
|
||||
|
||||
label.text += ")";
|
||||
|
||||
if (!argumentType.IsAssignableFrom(parameterType))
|
||||
{
|
||||
GUI.color = PersistentCallDrawer.ErrorFieldColor;
|
||||
label.tooltip = "Incompatible parameter type";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PersistentArgumentType.ReturnValue:
|
||||
label.text = "Return Value " + callIndex + ": ";
|
||||
var linkedMethod = DrawerState.Current.GetLinkedMethod(callIndex);
|
||||
|
||||
if (linkedMethod == null)
|
||||
{
|
||||
label.text += "(no method set)";
|
||||
GUI.color = PersistentCallDrawer.ErrorFieldColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
label.text += MethodSelectionMenu.GetMethodSignature(linkedMethod, true);
|
||||
|
||||
if (DrawerState.Current.callIndex >= 0 && DrawerState.Current.callIndex <= callIndex)
|
||||
{
|
||||
GUI.color = PersistentCallDrawer.ErrorFieldColor;
|
||||
label.tooltip = "The linked method must be called before this argument can retrieve its return value";
|
||||
}
|
||||
else if (!argumentType.IsAssignableFrom(linkedMethod.GetReturnType()))
|
||||
{
|
||||
GUI.color = PersistentCallDrawer.ErrorFieldColor;
|
||||
label.tooltip = "Return type is incompatible with argument type";
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (GUI.Button(area, label, PersistentCallDrawer.PopupButtonStyle))
|
||||
{
|
||||
if (Event.current.button == 0)
|
||||
ShowLinkMenu(area, argumentProperty, argumentType, callIndex, argument.Type);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
|
||||
GUI.color = color;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void ShowLinkMenu(Rect area, SerializedProperty argumentProperty, Type systemType, int linkIndex, PersistentArgumentType linkType)
|
||||
{
|
||||
var typeProperty = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Type);
|
||||
var intProperty = argumentProperty.FindPropertyRelative(Names.PersistentArgument.Int);
|
||||
|
||||
var menu = new GenericMenu();
|
||||
menu.AddDisabledItem(new GUIContent("Link to " + systemType.GetNameCS()));
|
||||
|
||||
// Parameters.
|
||||
var parameters = DrawerState.Current.Event.Parameters;
|
||||
|
||||
for (int i = 0; i < DrawerState.Current.Event.ParameterTypes.Length; i++)
|
||||
{
|
||||
var parameterType = DrawerState.Current.Event.ParameterTypes[i];
|
||||
if (!systemType.IsAssignableFrom(parameterType))
|
||||
continue;
|
||||
|
||||
var content = parameters == null ?
|
||||
new GUIContent(string.Concat("Parameter ", i.ToString(), " (", parameterType.GetNameCS(false), ")")) :
|
||||
new GUIContent(string.Concat("Parameter ", i.ToString(), " (", parameterType.GetNameCS(false), " ", parameters[i].Name, ")"));
|
||||
|
||||
var on = i == linkIndex && linkType == PersistentArgumentType.Parameter;
|
||||
|
||||
var index = i;
|
||||
menu.AddItem(content, on, () =>
|
||||
{
|
||||
typeProperty.enumValueIndex = (int)PersistentArgumentType.Parameter;
|
||||
intProperty.intValue = index;
|
||||
argumentProperty.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
}
|
||||
|
||||
// Returned Values.
|
||||
for (int i = 0; i < DrawerState.Current.PreviousCallCount; i++)
|
||||
{
|
||||
var method = DrawerState.Current.GetPreviousCall(i).GetMethodSafe();
|
||||
if (method == null || !systemType.IsAssignableFrom(method.GetReturnType()))
|
||||
continue;
|
||||
|
||||
var content = new GUIContent(string.Concat("Returned Value ", i.ToString(), " (", MethodSelectionMenu.GetMethodSignature(method, true), ")"));
|
||||
|
||||
var on = i == linkIndex && linkType == PersistentArgumentType.ReturnValue;
|
||||
|
||||
var index = i;
|
||||
menu.AddItem(content, on, () =>
|
||||
{
|
||||
typeProperty.enumValueIndex = (int)PersistentArgumentType.ReturnValue;
|
||||
intProperty.intValue = index;
|
||||
argumentProperty.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
}
|
||||
|
||||
menu.DropDown(area);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIContent _LinkToggleContent = new GUIContent("∞", "Link to Parameter or Returned Value");
|
||||
private static GUIStyle _LinkToggleStyle;
|
||||
|
||||
private static void DoLinkModeToggleGUI(Rect area, SerializedProperty argumentProperty, int linkIndex, PersistentArgumentType linkType)
|
||||
{
|
||||
if (linkIndex < 0)
|
||||
return;
|
||||
|
||||
if (_LinkToggleStyle == null)
|
||||
{
|
||||
_LinkToggleStyle = new GUIStyle(EditorStyles.miniButton)
|
||||
{
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
padding = new RectOffset(0, -1, 0, 1),
|
||||
#else
|
||||
padding = new RectOffset(0, 0, 0, 1),
|
||||
#endif
|
||||
fontSize = 12,
|
||||
};
|
||||
}
|
||||
|
||||
area.x += area.width + 2;
|
||||
area.width = SpecialModeToggleWidth - 2;
|
||||
|
||||
var currentArgument = DrawerState.Current.call._PersistentArguments[DrawerState.Current.parameterIndex];
|
||||
|
||||
var wasLink =
|
||||
currentArgument.Type == PersistentArgumentType.Parameter ||
|
||||
currentArgument.Type == PersistentArgumentType.ReturnValue;
|
||||
|
||||
if (wasLink != GUI.Toggle(area, wasLink, _LinkToggleContent, _LinkToggleStyle))
|
||||
{
|
||||
argumentProperty.ModifyValues<PersistentArgument>((argument) =>
|
||||
{
|
||||
if (wasLink)
|
||||
{
|
||||
// Revert to normal mode.
|
||||
|
||||
argument.SystemType = argument.SystemType;
|
||||
|
||||
var parameter = DrawerState.Current.CurrentParameter;
|
||||
if ((parameter.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault)
|
||||
argument.Value = parameter.DefaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link to the specified return value.
|
||||
|
||||
var argumentType = argument.Type;
|
||||
argument.Type = linkType;
|
||||
argument._Int = linkIndex;
|
||||
|
||||
switch (argumentType)
|
||||
{
|
||||
case PersistentArgumentType.Bool:
|
||||
case PersistentArgumentType.String:
|
||||
case PersistentArgumentType.Int:
|
||||
case PersistentArgumentType.Float:
|
||||
case PersistentArgumentType.Vector2:
|
||||
case PersistentArgumentType.Vector3:
|
||||
case PersistentArgumentType.Vector4:
|
||||
case PersistentArgumentType.Quaternion:
|
||||
case PersistentArgumentType.Color:
|
||||
case PersistentArgumentType.Color32:
|
||||
case PersistentArgumentType.Rect:
|
||||
argument._X = (float)argumentType;
|
||||
break;
|
||||
case PersistentArgumentType.Enum:
|
||||
case PersistentArgumentType.Object:
|
||||
argument._String = DrawerState.Current.CurrentParameter.ParameterType.AssemblyQualifiedName;
|
||||
break;
|
||||
case PersistentArgumentType.Parameter:
|
||||
case PersistentArgumentType.ReturnValue:
|
||||
throw new InvalidOperationException(Names.PersistentArgument.Class + " was already linked.");
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid " + Names.PersistentArgument.Full.Type + ": " + argumentType);
|
||||
}
|
||||
}
|
||||
}, wasLink ? "Unlink Argument" : "Link Argument");
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2b2c2ed6b237b7f40b961d3cd4be88ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,600 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(PersistentCall), true)]
|
||||
internal sealed class PersistentCallDrawer : PropertyDrawer
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const float
|
||||
RowHeight = 16;
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const float
|
||||
Padding = 2;
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const float
|
||||
SuggestionButtonWidth = 16;
|
||||
|
||||
public static readonly GUIStyle
|
||||
PopupButtonStyle;
|
||||
|
||||
public static readonly GUIStyle
|
||||
PopupLabelStyle;
|
||||
|
||||
private static readonly GUIContent
|
||||
ArgumentLabel = new GUIContent();
|
||||
|
||||
private static readonly GUIContent
|
||||
MethodNameSuggestionLabel = new GUIContent("?", "Suggest a method name");
|
||||
|
||||
public static readonly Color
|
||||
ErrorFieldColor = new Color(1, 0.65f, 0.65f);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
static PersistentCallDrawer()
|
||||
{
|
||||
PopupButtonStyle = new GUIStyle(EditorStyles.popup)
|
||||
{
|
||||
fixedHeight = RowHeight
|
||||
};
|
||||
|
||||
PopupLabelStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 10,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
padding = new RectOffset(4, 14, 0, 0)
|
||||
};
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty callProperty, GUIContent label)
|
||||
{
|
||||
if (callProperty.hasMultipleDifferentValues)
|
||||
{
|
||||
if (DrawerState.GetPersistentArgumentsProperty(callProperty).hasMultipleDifferentValues)
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
|
||||
if (DrawerState.GetMethodNameProperty(callProperty).hasMultipleDifferentValues)
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
|
||||
if (DrawerState.GetCall(callProperty).GetMethodSafe() == null)
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
|
||||
callProperty = DrawerState.GetPersistentArgumentsProperty(callProperty);
|
||||
|
||||
return (EditorGUIUtility.singleLineHeight + Padding) * (1 + callProperty.arraySize) - Padding;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override void OnGUI(Rect area, SerializedProperty callProperty, GUIContent label)
|
||||
{
|
||||
DrawerState.Current.BeginCall(callProperty);
|
||||
|
||||
var propertyarea = area;
|
||||
|
||||
// If we are in the reorderable list of an event, adjust the property area to cover the list bounds.
|
||||
if (DrawerState.Current.CachePreviousCalls)
|
||||
{
|
||||
propertyarea.xMin -= 20;
|
||||
propertyarea.yMin -= 4;
|
||||
propertyarea.width += 4;
|
||||
}
|
||||
|
||||
label = EditorGUI.BeginProperty(propertyarea, label, callProperty);
|
||||
{
|
||||
const float Space = 2;
|
||||
|
||||
var x = area.x;
|
||||
var xMax = area.xMax;
|
||||
|
||||
area.height = RowHeight;
|
||||
|
||||
// Target Field.
|
||||
area.xMax = EditorGUIUtility.labelWidth + 12;
|
||||
bool autoOpenMethodMenu;
|
||||
DoTargetFieldGUI(area,
|
||||
DrawerState.Current.TargetProperty, DrawerState.Current.MethodNameProperty,
|
||||
out autoOpenMethodMenu);
|
||||
|
||||
EditorGUI.showMixedValue = DrawerState.Current.PersistentArgumentsProperty.hasMultipleDifferentValues || DrawerState.Current.MethodNameProperty.hasMultipleDifferentValues;
|
||||
|
||||
var method = EditorGUI.showMixedValue ? null : DrawerState.Current.call.GetMethodSafe();
|
||||
|
||||
// Method Name Dropdown.
|
||||
area.x += area.width + Space;
|
||||
area.xMax = xMax;
|
||||
DoMethodFieldGUI(area, method, autoOpenMethodMenu);
|
||||
|
||||
// Persistent Arguments.
|
||||
if (method != null)
|
||||
{
|
||||
area.x = x;
|
||||
area.xMax = xMax;
|
||||
|
||||
DrawerState.Current.callParameters = method.GetParameters();
|
||||
if (DrawerState.Current.callParameters.Length == DrawerState.Current.PersistentArgumentsProperty.arraySize)
|
||||
{
|
||||
var labelWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth -= area.x - 14;
|
||||
|
||||
for (int i = 0; i < DrawerState.Current.callParameters.Length; i++)
|
||||
{
|
||||
DrawerState.Current.parameterIndex = i;
|
||||
area.y += area.height + Padding;
|
||||
|
||||
ArgumentLabel.text = DrawerState.Current.callParameters[i].Name;
|
||||
|
||||
var argumentProperty = DrawerState.Current.PersistentArgumentsProperty.GetArrayElementAtIndex(i);
|
||||
|
||||
if (argumentProperty.propertyPath != "")
|
||||
{
|
||||
EditorGUI.PropertyField(area, argumentProperty, ArgumentLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUI.Button(area, new GUIContent(
|
||||
"Reselect these objects to show arguments",
|
||||
"This is the result of a bug in the way Unity updates the SerializedProperty for an array after it is resized while multiple objects are selected")))
|
||||
{
|
||||
var selection = Selection.objects;
|
||||
Selection.objects = new Object[0];
|
||||
EditorApplication.delayCall += () => Selection.objects = selection;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUIUtility.labelWidth = labelWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Method parameter count doesn't match serialized argument count " + DrawerState.Current.callParameters.Length
|
||||
+ " : " + DrawerState.Current.PersistentArgumentsProperty.arraySize);
|
||||
}
|
||||
DrawerState.Current.callParameters = null;
|
||||
}
|
||||
|
||||
EditorGUI.showMixedValue = false;
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
|
||||
DrawerState.Current.EndCall();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Target Field
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoTargetFieldGUI(Rect area, SerializedProperty targetProperty, SerializedProperty methodNameProperty, out bool autoOpenMethodMenu)
|
||||
{
|
||||
autoOpenMethodMenu = false;
|
||||
|
||||
// Type field for a static type.
|
||||
if (targetProperty.objectReferenceValue == null && !targetProperty.hasMultipleDifferentValues)
|
||||
{
|
||||
var methodName = methodNameProperty.stringValue;
|
||||
string typeName;
|
||||
|
||||
var lastDot = methodName.LastIndexOf('.');
|
||||
if (lastDot >= 0)
|
||||
{
|
||||
typeName = methodName.Substring(0, lastDot);
|
||||
lastDot++;
|
||||
methodName = methodName.Substring(lastDot, methodName.Length - lastDot);
|
||||
}
|
||||
else typeName = "";
|
||||
|
||||
var color = GUI.color;
|
||||
if (Type.GetType(typeName) == null)
|
||||
GUI.color = ErrorFieldColor;
|
||||
|
||||
const float
|
||||
ObjectPickerButtonWidth = 35,
|
||||
Padding = 2;
|
||||
|
||||
area.width -= ObjectPickerButtonWidth + Padding;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
typeName = ObjectPicker.DrawTypeField(area, typeName, GetAllTypes, 0, EditorStyles.miniButton);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
methodNameProperty.stringValue = typeName + "." + methodName;
|
||||
}
|
||||
|
||||
HandleTargetFieldDragAndDrop(area, ref autoOpenMethodMenu);
|
||||
|
||||
GUI.color = color;
|
||||
|
||||
area.x += area.width + Padding;
|
||||
area.width = ObjectPickerButtonWidth;
|
||||
}
|
||||
|
||||
// Object field for an object reference.
|
||||
DoTargetObjectFieldGUI(area, targetProperty, ref autoOpenMethodMenu);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoTargetObjectFieldGUI(Rect area, SerializedProperty targetProperty, ref bool autoOpenMethodMenu)
|
||||
{
|
||||
if (targetProperty.hasMultipleDifferentValues)
|
||||
EditorGUI.showMixedValue = true;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var oldTarget = targetProperty.objectReferenceValue;
|
||||
var target = EditorGUI.ObjectField(area, oldTarget, typeof(Object), true);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SetBestTarget(oldTarget, target, out autoOpenMethodMenu);
|
||||
}
|
||||
|
||||
EditorGUI.showMixedValue = false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static List<Type> _AllTypes;
|
||||
|
||||
private static List<Type> GetAllTypes()
|
||||
{
|
||||
if (_AllTypes == null)
|
||||
{
|
||||
// Gather all types in all currently loaded assemblies.
|
||||
_AllTypes = new List<Type>(4192);
|
||||
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
for (int i = 0; i < assemblies.Length; i++)
|
||||
{
|
||||
var types = assemblies[i].GetTypes();
|
||||
for (int j = 0; j < types.Length; j++)
|
||||
{
|
||||
var type = types[j];
|
||||
if (!type.ContainsGenericParameters &&// No Generics (because the type picker field doesn't let you pick generic parameters).
|
||||
!type.IsInterface &&// No Interfaces (because they can't have any static methods).
|
||||
!type.IsDefined(typeof(ObsoleteAttribute), true) &&// No Obsoletes.
|
||||
type.GetMethods(UltEventUtils.StaticBindings).Length > 0)// No types without any static methods.
|
||||
{
|
||||
// The type might still not have any valid methods, but at least we've narrowed down the list a lot.
|
||||
|
||||
_AllTypes.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_AllTypes.Sort((a, b) => a.FullName.CompareTo(b.FullName));
|
||||
|
||||
// We probably just allocated thousands of arrays with all those GetMethods calls, so call for a cleanup imediately.
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
return _AllTypes;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void HandleTargetFieldDragAndDrop(Rect area, ref bool autoOpenMethodMenu)
|
||||
{
|
||||
// Drag and drop objects into the type field.
|
||||
switch (Event.current.type)
|
||||
{
|
||||
case EventType.Repaint:
|
||||
case EventType.DragUpdated:
|
||||
{
|
||||
if (!area.Contains(Event.current.mousePosition))
|
||||
break;
|
||||
|
||||
var dragging = DragAndDrop.objectReferences;
|
||||
if (dragging != null && dragging.Length == 1)
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.DragPerform:
|
||||
{
|
||||
if (!area.Contains(Event.current.mousePosition))
|
||||
break;
|
||||
|
||||
var dragging = DragAndDrop.objectReferences;
|
||||
if (dragging != null && dragging.Length == 1)
|
||||
{
|
||||
SetBestTarget(DrawerState.Current.TargetProperty.objectReferenceValue, dragging[0], out autoOpenMethodMenu);
|
||||
|
||||
DragAndDrop.AcceptDrag();
|
||||
GUI.changed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void SetBestTarget(Object oldTarget, Object newTarget, out bool autoOpenMethodMenu)
|
||||
{
|
||||
// It's more likely that the user intends to target a method on a Component than the GameObject itself so
|
||||
// if a GameObject was dropped in, try to select a component with the same type as the old target,
|
||||
// otherwise select it's first component after the Transform.
|
||||
var gameObject = newTarget as GameObject;
|
||||
if (!(oldTarget is GameObject) && !ReferenceEquals(gameObject, null))
|
||||
{
|
||||
var oldComponent = oldTarget as Component;
|
||||
if (!ReferenceEquals(oldComponent, null))
|
||||
{
|
||||
newTarget = gameObject.GetComponent(oldComponent.GetType());
|
||||
if (newTarget != null)
|
||||
goto FoundTarget;
|
||||
}
|
||||
|
||||
var components = gameObject.GetComponents<Component>();
|
||||
newTarget = components.Length > 1 ? components[1] : components[0];
|
||||
}
|
||||
|
||||
FoundTarget:
|
||||
|
||||
SetTarget(newTarget);
|
||||
|
||||
autoOpenMethodMenu = BoolPref.AutoOpenMenu && newTarget != null && DrawerState.Current.call.GetMethodSafe() == null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoMethodFieldGUI(Rect area, MethodBase method, bool autoOpenMethodMenu)
|
||||
{
|
||||
EditorGUI.BeginProperty(area, null, DrawerState.Current.MethodNameProperty);
|
||||
{
|
||||
if (includeRemoveButton)
|
||||
area.width -= RemoveButtonWidth;
|
||||
|
||||
var color = GUI.color;
|
||||
|
||||
string label;
|
||||
if (EditorGUI.showMixedValue)
|
||||
{
|
||||
label = "Mixed Values";
|
||||
}
|
||||
else if (method != null)
|
||||
{
|
||||
label = MethodSelectionMenu.GetMethodSignature(method, false);
|
||||
|
||||
DoGetSetToggleGUI(ref area, method);
|
||||
}
|
||||
else
|
||||
{
|
||||
var methodName = DrawerState.Current.MethodNameProperty.stringValue;
|
||||
Type declaringType;
|
||||
PersistentCall.GetMethodDetails(methodName,
|
||||
DrawerState.Current.TargetProperty.objectReferenceValue,
|
||||
out declaringType, out label);
|
||||
DoMethodNameSuggestionGUI(ref area, declaringType, methodName);
|
||||
GUI.color = ErrorFieldColor;
|
||||
}
|
||||
|
||||
if (autoOpenMethodMenu || (GUI.Button(area, GUIContent.none, PopupButtonStyle) && Event.current.button == 0))
|
||||
{
|
||||
MethodSelectionMenu.ShowMenu(area);
|
||||
}
|
||||
|
||||
GUI.color = color;
|
||||
|
||||
PopupLabelStyle.fontStyle = DrawerState.Current.MethodNameProperty.prefabOverride ? FontStyle.Bold : FontStyle.Normal;
|
||||
GUI.Label(area, label, PopupLabelStyle);
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static float _GetSetWidth;
|
||||
|
||||
private static float GetSetWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_GetSetWidth <= 0)
|
||||
{
|
||||
float _, width;
|
||||
ArgumentLabel.text = "Get";
|
||||
GUI.skin.button.CalcMinMaxWidth(ArgumentLabel, out _, out width);
|
||||
|
||||
ArgumentLabel.text = "Set";
|
||||
GUI.skin.button.CalcMinMaxWidth(ArgumentLabel, out _, out _GetSetWidth);
|
||||
|
||||
if (_GetSetWidth < width)
|
||||
_GetSetWidth = width;
|
||||
}
|
||||
|
||||
return _GetSetWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoGetSetToggleGUI(ref Rect area, MethodBase method)
|
||||
{
|
||||
// Check if the method name starts with "get_" or "set_".
|
||||
// Check the underscore first since it's hopefully the rarest so it can break out early.
|
||||
|
||||
var name = method.Name;
|
||||
if (name.Length <= 4 || name[3] != '_' || name[2] != 't' || name[1] != 'e')
|
||||
return;
|
||||
|
||||
var first = name[0];
|
||||
var isGet = first == 'g';
|
||||
var isSet = first == 's';
|
||||
if (!isGet && !isSet)
|
||||
return;
|
||||
|
||||
var methodName = (isGet ? "set_" : "get_") + name.Substring(4, name.Length - 4);
|
||||
var oppositePropertyMethod = method.DeclaringType.GetMethod(methodName, UltEventUtils.AnyAccessBindings);
|
||||
if (oppositePropertyMethod == null ||
|
||||
(isGet && !MethodSelectionMenu.IsSupported(method.GetReturnType())))
|
||||
return;
|
||||
|
||||
area.width -= GetSetWidth + Padding;
|
||||
|
||||
var buttonArea = new Rect(
|
||||
area.x + area.width + Padding,
|
||||
area.y,
|
||||
GetSetWidth,
|
||||
area.height);
|
||||
|
||||
if (GUI.Button(buttonArea, isGet ? "Get" : "Set"))
|
||||
{
|
||||
var cachedState = new DrawerState();
|
||||
cachedState.CopyFrom(DrawerState.Current);
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
DrawerState.Current.CopyFrom(cachedState);
|
||||
|
||||
SetMethod(oppositePropertyMethod);
|
||||
|
||||
DrawerState.Current.Clear();
|
||||
|
||||
InternalEditorUtility.RepaintAllViews();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoMethodNameSuggestionGUI(ref Rect area, Type declaringType, string methodName)
|
||||
{
|
||||
if (declaringType == null ||
|
||||
string.IsNullOrEmpty(methodName))
|
||||
return;
|
||||
|
||||
var lastDot = methodName.LastIndexOf('.');
|
||||
if (lastDot >= 0)
|
||||
{
|
||||
lastDot++;
|
||||
if (lastDot >= methodName.Length)
|
||||
return;
|
||||
|
||||
methodName = methodName.Substring(lastDot);
|
||||
}
|
||||
|
||||
var methods = declaringType.GetMethods(UltEventUtils.AnyAccessBindings);
|
||||
if (methods.Length == 0)
|
||||
return;
|
||||
|
||||
area.width -= SuggestionButtonWidth + Padding;
|
||||
|
||||
var buttonArea = new Rect(
|
||||
area.x + area.width + Padding,
|
||||
area.y,
|
||||
SuggestionButtonWidth,
|
||||
area.height);
|
||||
|
||||
if (GUI.Button(buttonArea, MethodNameSuggestionLabel))
|
||||
{
|
||||
var cachedState = new DrawerState();
|
||||
cachedState.CopyFrom(DrawerState.Current);
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
DrawerState.Current.CopyFrom(cachedState);
|
||||
|
||||
var bestMethod = methods[0];
|
||||
var bestDistance = UltEventUtils.CalculateLevenshteinDistance(methodName, bestMethod.Name);
|
||||
|
||||
var i = 1;
|
||||
for (; i < methods.Length; i++)
|
||||
{
|
||||
var method = methods[i];
|
||||
var distance = UltEventUtils.CalculateLevenshteinDistance(methodName, method.Name);
|
||||
|
||||
if (bestDistance > distance)
|
||||
{
|
||||
bestDistance = distance;
|
||||
bestMethod = method;
|
||||
}
|
||||
}
|
||||
|
||||
SetMethod(bestMethod);
|
||||
|
||||
DrawerState.Current.Clear();
|
||||
|
||||
InternalEditorUtility.RepaintAllViews();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public static void SetTarget(Object target)
|
||||
{
|
||||
DrawerState.Current.TargetProperty.objectReferenceValue = target;
|
||||
DrawerState.Current.TargetProperty.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (target == null ||
|
||||
DrawerState.Current.call.GetMethodSafe() == null)
|
||||
{
|
||||
SetMethod(null);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public static void SetMethod(MethodInfo methodInfo)
|
||||
{
|
||||
DrawerState.Current.CallProperty.ModifyValues<PersistentCall>((call) =>
|
||||
{
|
||||
if (call != null)
|
||||
call.SetMethod(methodInfo, DrawerState.Current.TargetProperty.objectReferenceValue);
|
||||
}, "Set Method");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Remove Button
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const float RemoveButtonWidth = 18;
|
||||
|
||||
public static bool includeRemoveButton;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public static bool DoRemoveButtonGUI(Rect rowArea)
|
||||
{
|
||||
includeRemoveButton = false;
|
||||
|
||||
rowArea.xMin = rowArea.xMax - RemoveButtonWidth + 2;
|
||||
rowArea.height = EditorGUIUtility.singleLineHeight + 2;
|
||||
|
||||
return GUI.Button(rowArea, ReorderableList.defaultBehaviours.iconToolbarMinus, ReorderableList.defaultBehaviours.preButton);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e04da5e866e49944f9c15d85a6ef2c68
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2d916606b97e4cb4596e7a5a6c505f54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents.Editor
|
||||
{
|
||||
internal static class SerializedPropertyContextMenu
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void OnPropertyContextMenu()
|
||||
{
|
||||
EditorApplication.contextualPropertyMenu += (menu, property) =>
|
||||
{
|
||||
if (property.propertyType != SerializedPropertyType.Generic)
|
||||
return;
|
||||
|
||||
var accessor = property.GetAccessor();
|
||||
if (accessor == null)
|
||||
return;
|
||||
|
||||
var type = accessor.GetFieldElementType(property);
|
||||
if (typeof(UltEventBase).IsAssignableFrom(type))
|
||||
{
|
||||
AddEventFunctions(menu, property, accessor);
|
||||
BoolPref.AddDisplayOptions(menu);
|
||||
}
|
||||
else if (type == typeof(PersistentCall))
|
||||
{
|
||||
AddCallClipboardItems(menu, property, accessor);
|
||||
BoolPref.AddDisplayOptions(menu);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public static void AddEventFunctions(GenericMenu menu, SerializedProperty property,
|
||||
Serialization.PropertyAccessor accessor)
|
||||
{
|
||||
property = property.Copy();
|
||||
|
||||
var type = accessor.GetFieldElementType(property);
|
||||
if (type == typeof(UltEvent))
|
||||
{
|
||||
menu.AddItem(new GUIContent("Invoke Event"), false, () =>
|
||||
{
|
||||
var events = property.GetValues<UltEvent>();
|
||||
for (int i = 0; i < events.Length; i++)
|
||||
{
|
||||
var e = events[i] as UltEvent;
|
||||
if (e != null)
|
||||
e.Invoke();
|
||||
}
|
||||
property.OnPropertyChanged();
|
||||
});
|
||||
}
|
||||
|
||||
AddEventClipboardItems(menu, property, accessor);
|
||||
|
||||
menu.AddItem(new GUIContent("Clear Event"), false, () =>
|
||||
{
|
||||
property.ModifyValues<UltEventBase>((e) =>
|
||||
{
|
||||
if (e != null)
|
||||
e.Clear();
|
||||
}, "Clear Event");
|
||||
});
|
||||
|
||||
menu.AddItem(new GUIContent("Log Description"), false, () =>
|
||||
{
|
||||
var targets = property.serializedObject.targetObjects;
|
||||
var events = property.GetValues<UltEventBase>();
|
||||
|
||||
for (int i = 0; i < events.Length; i++)
|
||||
Debug.Log(events[i], targets[i]);
|
||||
});
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void AddEventClipboardItems(GenericMenu menu, SerializedProperty property,
|
||||
Serialization.PropertyAccessor accessor)
|
||||
{
|
||||
// Copy Event.
|
||||
menu.AddItem(new GUIContent("Copy Event"), false, () =>
|
||||
{
|
||||
Clipboard.CopyEvent(property);
|
||||
});
|
||||
|
||||
// Paste Event.
|
||||
AddMenuItem(menu, "Paste Event (Overwrite)", Clipboard.HasEvent, () =>
|
||||
{
|
||||
property.ModifyValues<UltEventBase>((e) =>
|
||||
{
|
||||
Clipboard.Paste(e);
|
||||
}, "Paste Event");
|
||||
});
|
||||
|
||||
// Paste Listener.
|
||||
AddMenuItem(menu, "Paste Listener (New) %#V", Clipboard.HasCall, () =>
|
||||
{
|
||||
property.ModifyValues<UltEventBase>((e) =>
|
||||
{
|
||||
var call = new PersistentCall();
|
||||
Clipboard.PasteCall(call);
|
||||
|
||||
if (e._PersistentCalls == null)
|
||||
e._PersistentCalls = new List<PersistentCall>();
|
||||
e._PersistentCalls.Add(call);
|
||||
}, "Paste PersistentCall");
|
||||
});
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void AddCallClipboardItems(GenericMenu menu, SerializedProperty property,
|
||||
Serialization.PropertyAccessor accessor)
|
||||
{
|
||||
menu.AddItem(new GUIContent("Copy Listener %C"), false, () =>
|
||||
{
|
||||
Clipboard.CopyCall(property);
|
||||
});
|
||||
|
||||
AddMenuItem(menu, "Paste Listener (Overwrite) %V", Clipboard.HasCall, () => Clipboard.PasteCall(property));
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public static void AddMenuItem(GenericMenu menu, string label, bool enabled, GenericMenu.MenuFunction function)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
menu.AddItem(new GUIContent(label), false, function);
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddDisabledItem(new GUIContent(label));
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public static void AddPropertyModifierFunction(GenericMenu menu, SerializedProperty property, string label, Action function)
|
||||
{
|
||||
menu.AddItem(new GUIContent(label), false, () =>
|
||||
{
|
||||
function();
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddPropertyModifierFunction(GenericMenu menu, SerializedProperty property, string label,
|
||||
Action<SerializedProperty> function)
|
||||
{
|
||||
menu.AddItem(new GUIContent(label), false, () =>
|
||||
{
|
||||
ForEachTarget(property, function);
|
||||
});
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void ForEachTarget(SerializedProperty property, Action<SerializedProperty> function)
|
||||
{
|
||||
var targets = property.serializedObject.targetObjects;
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
function(property);
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
else
|
||||
{
|
||||
var path = property.propertyPath;
|
||||
for (int i = 0; i < targets.Length; i++)
|
||||
{
|
||||
using (var serializedObject = new SerializedObject(targets[i]))
|
||||
{
|
||||
property = serializedObject.FindProperty(path);
|
||||
function(property);
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 47e623a49203d0e4fa67df0c9f6db408
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,636 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(UltEventBase), true)]
|
||||
internal sealed class UltEventDrawer : PropertyDrawer
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const float
|
||||
Border = 1;
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const float
|
||||
Padding = 5;
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const float
|
||||
IndentSize = 15;
|
||||
|
||||
private static readonly GUIContent
|
||||
EventLabel = new GUIContent();
|
||||
|
||||
private static readonly GUIContent
|
||||
CountLabel = new GUIContent();
|
||||
|
||||
private static readonly GUIContent
|
||||
PlusLabel = EditorGUIUtility.IconContent("Toolbar Plus", "Add to list");
|
||||
|
||||
private static readonly GUIStyle
|
||||
HeaderBackground = new GUIStyle("RL Header");
|
||||
|
||||
private static readonly GUIStyle
|
||||
PlusButton = "RL FooterButton";
|
||||
|
||||
private static ReorderableList _CurrentCallList;
|
||||
private static int _CurrentCallCount;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
static UltEventDrawer()
|
||||
{
|
||||
HeaderBackground.fixedHeight -= 1;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
if (!property.isExpanded)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!DrawerState.Current.TryBeginEvent(property))
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
|
||||
CachePersistentCallList(property);
|
||||
DrawerState.Current.EndEvent();
|
||||
|
||||
return _CurrentCallList.GetHeight() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private float CalculateCallHeight(int index)
|
||||
{
|
||||
if (index >= 0 && index < _CurrentCallCount)
|
||||
{
|
||||
var height = EditorGUI.GetPropertyHeight(_CurrentCallList.serializedProperty.GetArrayElementAtIndex(index));
|
||||
height += Border * 2 + Padding;
|
||||
|
||||
if (index == _CurrentCallCount - 1)
|
||||
height -= Padding - 1;
|
||||
|
||||
return height;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
if (!DrawerState.Current.TryBeginEvent(property))
|
||||
return;
|
||||
|
||||
EventLabel.text = label.text + DrawerState.Current.Event.ParameterString;
|
||||
EventLabel.tooltip = label.tooltip;
|
||||
|
||||
if (BoolPref.UseIndentation)
|
||||
area = EditorGUI.IndentedRect(area);
|
||||
area.y -= 1;
|
||||
|
||||
var indentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
CachePersistentCallList(property);
|
||||
|
||||
if (property.isExpanded)
|
||||
{
|
||||
DrawerState.Current.BeginCache();
|
||||
|
||||
_CurrentCallList.DoList(area);
|
||||
|
||||
DrawerState.Current.EndCache();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
HeaderBackground.Draw(area, false, false, false, false);
|
||||
|
||||
DoHeaderGUI(new Rect(area.x + 6, area.y + 1, area.width - 12, area.height));
|
||||
}
|
||||
|
||||
area.y += 1;
|
||||
area.height = HeaderBackground.fixedHeight;
|
||||
property.isExpanded = EditorGUI.Foldout(area, property.isExpanded, "", true);
|
||||
CheckDragDrop(area);
|
||||
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
|
||||
DrawerState.Current.EndEvent();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private readonly Dictionary<string, ReorderableList>
|
||||
PropertyPathToList = new Dictionary<string, ReorderableList>();
|
||||
|
||||
private void CachePersistentCallList(SerializedProperty eventProperty)
|
||||
{
|
||||
var path = eventProperty.propertyPath;
|
||||
if (!PropertyPathToList.TryGetValue(path, out _CurrentCallList))
|
||||
{
|
||||
eventProperty = eventProperty.FindPropertyRelative(Names.UltEvent.PersistentCalls);
|
||||
|
||||
_CurrentCallList = new ReorderableList(eventProperty.serializedObject, eventProperty, true, true, true, true)
|
||||
{
|
||||
drawHeaderCallback = DoHeaderGUI,
|
||||
drawElementCallback = DoPersistentCallGUI,
|
||||
drawFooterCallback = DoFooterGUI,
|
||||
onAddCallback = AddNewCall,
|
||||
onReorderCallback = OnReorder,
|
||||
elementHeight = 19,// Used when the list is empty.
|
||||
elementHeightCallback = CalculateCallHeight,
|
||||
drawElementBackgroundCallback = DoElementBackgroundGUI,
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
drawNoneElementCallback = DoNoneElementGUI,
|
||||
#endif
|
||||
};
|
||||
|
||||
PropertyPathToList.Add(path, _CurrentCallList);
|
||||
}
|
||||
|
||||
_CurrentCallCount = _CurrentCallList.count;
|
||||
RecalculateFooter();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static float _DefaultFooterHeight;
|
||||
|
||||
private void RecalculateFooter()
|
||||
{
|
||||
if (_DefaultFooterHeight == 0)
|
||||
_DefaultFooterHeight = _CurrentCallList.footerHeight;
|
||||
|
||||
if (BoolPref.AutoHideFooter && !DrawerState.Current.Event.HasAnyDynamicCalls())
|
||||
{
|
||||
_CurrentCallList.footerHeight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_CurrentCallList.footerHeight = _DefaultFooterHeight;
|
||||
|
||||
if (DrawerState.Current.EventProperty.isExpanded &&
|
||||
DrawerState.Current.EventProperty.serializedObject.targetObjects.Length == 1)
|
||||
{
|
||||
if (DrawerState.Current.Event.HasAnyDynamicCalls())
|
||||
_CurrentCallList.footerHeight +=
|
||||
DrawerState.Current.Event.GetDynamicCallInvocationListCount() * EditorGUIUtility.singleLineHeight + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoHeaderGUI(Rect area)
|
||||
{
|
||||
EditorGUI.BeginProperty(area, GUIContent.none, DrawerState.Current.EventProperty);
|
||||
|
||||
const float
|
||||
AddButtonWidth = 16,
|
||||
AddButtonPadding = 2;
|
||||
|
||||
var labelStyle = DrawerState.Current.EventProperty.prefabOverride ? EditorStyles.boldLabel : GUI.skin.label;
|
||||
|
||||
CountLabel.text = _CurrentCallCount.ToString();
|
||||
var countLabelWidth = labelStyle.CalcSize(CountLabel).x;
|
||||
|
||||
area.width -= AddButtonWidth + AddButtonPadding + countLabelWidth;
|
||||
GUI.Label(area, EventLabel, labelStyle);
|
||||
|
||||
area.x += area.width;
|
||||
area.width = countLabelWidth;
|
||||
GUI.Label(area, CountLabel, labelStyle);
|
||||
|
||||
area.x += area.width + AddButtonPadding + 1;
|
||||
area.width = AddButtonWidth;
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
area.y += 1;
|
||||
#else
|
||||
area.y -= 1;
|
||||
#endif
|
||||
|
||||
if (GUI.Button(area, PlusLabel, PlusButton))
|
||||
{
|
||||
DrawerState.Current.EventProperty.isExpanded = true;
|
||||
AddNewCall(_CurrentCallList);
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public void DoElementBackgroundGUI(Rect area, int index, bool selected, bool focused)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
area.y -= 2;
|
||||
area.height = CalculateCallHeight(index) + 2;
|
||||
|
||||
if (index == _CurrentCallCount - 1)
|
||||
area.height += 2;
|
||||
|
||||
ReorderableList.defaultBehaviours.elementBackground.Draw(area, false, selected, selected, focused);
|
||||
|
||||
if (index >= 0 && index < _CurrentCallCount - 1)
|
||||
{
|
||||
area.xMin += 1;
|
||||
area.xMax -= 1;
|
||||
area.y += area.height - 3;
|
||||
area.height = 1;
|
||||
|
||||
DoSeparatorLineGUI(area);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoNoneElementGUI(Rect area)
|
||||
{
|
||||
EditorGUI.BeginProperty(area, GUIContent.none, DrawerState.Current.EventProperty);
|
||||
|
||||
if (GUI.Button(area, "Click to add a listener", GUI.skin.label) &&
|
||||
Event.current.button == 0)
|
||||
{
|
||||
AddNewCall(_CurrentCallList);
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIStyle _SeparatorLineStyle;
|
||||
private static readonly Color SeparatorLineColor =
|
||||
EditorGUIUtility.isProSkin ? new Color(0.157f, 0.157f, 0.157f) : new Color(0.5f, 0.5f, 0.5f);
|
||||
|
||||
private static void DoSeparatorLineGUI(Rect area)
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
if (_SeparatorLineStyle == null)
|
||||
{
|
||||
_SeparatorLineStyle = new GUIStyle();
|
||||
_SeparatorLineStyle.normal.background = EditorGUIUtility.whiteTexture;
|
||||
_SeparatorLineStyle.stretchWidth = true;
|
||||
}
|
||||
|
||||
var oldColor = GUI.color;
|
||||
GUI.color = SeparatorLineColor;
|
||||
_SeparatorLineStyle.Draw(area, false, false, false, false);
|
||||
GUI.color = oldColor;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoPersistentCallGUI(Rect area, int index, bool isActive, bool isFocused)
|
||||
{
|
||||
DrawerState.Current.callIndex = index;
|
||||
var callProperty = _CurrentCallList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
|
||||
area.x += Border;
|
||||
area.y += Border;
|
||||
area.height -= Border * 2;
|
||||
|
||||
PersistentCallDrawer.includeRemoveButton = true;
|
||||
|
||||
EditorGUI.PropertyField(area, callProperty);
|
||||
|
||||
if (PersistentCallDrawer.DoRemoveButtonGUI(area))
|
||||
DelayedRemoveCall(index);
|
||||
|
||||
if (isFocused)
|
||||
CheckInput(index);
|
||||
|
||||
DrawerState.Current.callIndex = -1;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIStyle _FooterBackground;
|
||||
|
||||
public void DoFooterGUI(Rect area)
|
||||
{
|
||||
if (area.height == 0)
|
||||
return;
|
||||
|
||||
const float
|
||||
InvokePadding = 2,
|
||||
AddRemoveWidth = 16,
|
||||
RightSideOffset = 5;
|
||||
|
||||
var width = area.width;
|
||||
area.xMin -= 1;
|
||||
|
||||
// Background.
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
if (_FooterBackground == null)
|
||||
{
|
||||
_FooterBackground = new GUIStyle(ReorderableList.defaultBehaviours.footerBackground)
|
||||
{
|
||||
fixedHeight = 0
|
||||
};
|
||||
}
|
||||
|
||||
_FooterBackground.Draw(area, false, false, false, false);
|
||||
}
|
||||
|
||||
area.y -= 3;
|
||||
area.width -= InvokePadding + AddRemoveWidth * 2 + RightSideOffset;
|
||||
area.height = EditorGUIUtility.singleLineHeight;
|
||||
|
||||
if (DrawerState.Current.EventProperty.serializedObject.targetObjects.Length > 1)
|
||||
{
|
||||
// Multiple Objects Selected.
|
||||
area.xMin += 2;
|
||||
GUI.Label(area, "Can't show Dynamic Listeners for multiple objects");
|
||||
}
|
||||
else if (DrawerState.Current.Event != null)
|
||||
{
|
||||
area.xMin += 16;
|
||||
var labelWidth = area.width;
|
||||
area.xMax = EditorGUIUtility.labelWidth + IndentSize;
|
||||
|
||||
GUI.Label(area, "Dynamic Listeners");
|
||||
|
||||
// Dynamic Listener Foldout.
|
||||
|
||||
var dynamicListenerCount = DrawerState.Current.Event.GetDynamicCallInvocationListCount();
|
||||
if (dynamicListenerCount > 0)
|
||||
{
|
||||
var isExpanded = EditorGUI.Foldout(area, _CurrentCallList.serializedProperty.isExpanded, GUIContent.none, true);
|
||||
_CurrentCallList.serializedProperty.isExpanded = isExpanded;
|
||||
if (isExpanded && DrawerState.Current.Event.HasAnyDynamicCalls())
|
||||
{
|
||||
DoDynamicListenerGUI(area.x, area.y + EditorGUIUtility.singleLineHeight - 1, width, DrawerState.Current.Event);
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic Listener Count.
|
||||
area.x += area.width;
|
||||
area.width = labelWidth - area.width;
|
||||
GUI.Label(area, dynamicListenerCount.ToString());
|
||||
}
|
||||
|
||||
// Add.
|
||||
area.x += area.width + InvokePadding;
|
||||
area.y -= 1;
|
||||
area.width = AddRemoveWidth;
|
||||
area.height = _DefaultFooterHeight;
|
||||
if (GUI.Button(area, ReorderableList.defaultBehaviours.iconToolbarPlus, ReorderableList.defaultBehaviours.preButton))
|
||||
{
|
||||
AddNewCall(_CurrentCallList);
|
||||
}
|
||||
|
||||
// Remove.
|
||||
area.x += area.width;
|
||||
using (new EditorGUI.DisabledScope(_CurrentCallList.index < 0 || _CurrentCallList.index >= _CurrentCallCount))
|
||||
{
|
||||
if (GUI.Button(area, ReorderableList.defaultBehaviours.iconToolbarMinus, ReorderableList.defaultBehaviours.preButton))
|
||||
{
|
||||
DelayedRemoveCall(_CurrentCallList.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoDynamicListenerGUI(float x, float y, float width, UltEventBase targetEvent)
|
||||
{
|
||||
x += IndentSize;
|
||||
width -= IndentSize * 2;
|
||||
|
||||
var area = new Rect(x, y, width, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
var calls = targetEvent.GetDynamicCallInvocationList();
|
||||
for (int i = 0; i < calls.Length; i++)
|
||||
{
|
||||
var call = calls[i];
|
||||
DoDelegateGUI(area, call);
|
||||
area.y += area.height;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only]
|
||||
/// Draw the target and name of the specified <see cref="Delegate"/>.
|
||||
/// </summary>
|
||||
public static void DoDelegateGUI(Rect area, Delegate del)
|
||||
{
|
||||
var width = area.width;
|
||||
|
||||
area.xMax = EditorGUIUtility.labelWidth + 15;
|
||||
var obj = del.Target as Object;
|
||||
if (!ReferenceEquals(obj, null))
|
||||
{
|
||||
// If the target is a Unity Object, draw it in an Object Field so the user can click to ping the object.
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUI.ObjectField(area, obj, typeof(Object), true);
|
||||
}
|
||||
}
|
||||
else if (del.Method.DeclaringType.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true))
|
||||
{
|
||||
// Anonymous Methods draw only their method name.
|
||||
|
||||
area.width = width;
|
||||
|
||||
GUI.Label(area, del.Method.GetNameCS());
|
||||
|
||||
return;
|
||||
}
|
||||
else if (del.Target == null)
|
||||
{
|
||||
GUI.Label(area, del.Method.DeclaringType.GetNameCS());
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.Label(area, del.Target.ToString());
|
||||
}
|
||||
|
||||
area.x += area.width;
|
||||
area.width = width - area.width;
|
||||
|
||||
GUI.Label(area, del.Method.GetNameCS(false));
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void AddNewCall(ReorderableList list)
|
||||
{
|
||||
AddNewCall(list, list.serializedProperty.serializedObject.targetObject);
|
||||
}
|
||||
|
||||
private void AddNewCall(ReorderableList list, Object target)
|
||||
{
|
||||
var index = list.index;
|
||||
if (index >= 0 && index < _CurrentCallCount)
|
||||
{
|
||||
index++;
|
||||
list.index = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = _CurrentCallCount;
|
||||
}
|
||||
|
||||
list.serializedProperty.InsertArrayElementAtIndex(index);
|
||||
|
||||
list.serializedProperty.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
var callProperty = list.serializedProperty.GetArrayElementAtIndex(index);
|
||||
DrawerState.Current.BeginCall(callProperty);
|
||||
PersistentCallDrawer.SetTarget(target);
|
||||
DrawerState.Current.EndCall();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void RemoveCall(ReorderableList list, int index)
|
||||
{
|
||||
var property = list.serializedProperty;
|
||||
property.DeleteArrayElementAtIndex(index);
|
||||
|
||||
if (list.index >= property.arraySize - 1)
|
||||
list.index = property.arraySize - 1;
|
||||
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DelayedRemoveCall(int index)
|
||||
{
|
||||
var list = _CurrentCallList;
|
||||
var state = new DrawerState();
|
||||
state.CopyFrom(DrawerState.Current);
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
DrawerState.Current.CopyFrom(state);
|
||||
|
||||
RemoveCall(list, index);
|
||||
|
||||
DrawerState.Current.UpdateLinkedArguments();
|
||||
DrawerState.Current.Clear();
|
||||
|
||||
InternalEditorUtility.RepaintAllViews();
|
||||
};
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void OnReorder(ReorderableList list)
|
||||
{
|
||||
DrawerState.Current.UpdateLinkedArguments();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void CheckInput(int index)
|
||||
{
|
||||
var currentEvent = Event.current;
|
||||
if (currentEvent.type == EventType.KeyUp)
|
||||
{
|
||||
switch (currentEvent.keyCode)
|
||||
{
|
||||
case KeyCode.Backspace:
|
||||
case KeyCode.Delete:
|
||||
RemoveCall(_CurrentCallList, index);
|
||||
currentEvent.Use();
|
||||
break;
|
||||
|
||||
case KeyCode.Plus:
|
||||
case KeyCode.KeypadPlus:
|
||||
case KeyCode.Equals:
|
||||
AddNewCall(_CurrentCallList);
|
||||
currentEvent.Use();
|
||||
break;
|
||||
|
||||
case KeyCode.C:
|
||||
if (currentEvent.control)
|
||||
{
|
||||
var property = _CurrentCallList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
Clipboard.CopyCall(property);
|
||||
currentEvent.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyCode.V:
|
||||
if (currentEvent.control)
|
||||
{
|
||||
var property = _CurrentCallList.serializedProperty;
|
||||
if (currentEvent.shift)
|
||||
{
|
||||
index++;
|
||||
property.InsertArrayElementAtIndex(index);
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
property = property.GetArrayElementAtIndex(index);
|
||||
Clipboard.PasteCall(property);
|
||||
}
|
||||
else
|
||||
{
|
||||
property = property.GetArrayElementAtIndex(index);
|
||||
Clipboard.PasteCall(property);
|
||||
}
|
||||
currentEvent.Use();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void CheckDragDrop(Rect area)
|
||||
{
|
||||
if (!area.Contains(Event.current.mousePosition) ||
|
||||
DragAndDrop.objectReferences.Length == 0)
|
||||
return;
|
||||
|
||||
switch (Event.current.type)
|
||||
{
|
||||
case EventType.Repaint:
|
||||
case EventType.DragUpdated:
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
break;
|
||||
|
||||
case EventType.DragPerform:
|
||||
foreach (var drop in DragAndDrop.objectReferences)
|
||||
{
|
||||
AddNewCall(_CurrentCallList, drop);
|
||||
}
|
||||
DrawerState.Current.EventProperty.isExpanded = true;
|
||||
DragAndDrop.AcceptDrag();
|
||||
GUI.changed = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 73265d0725df35a4089138d5c7068b2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "UltEvents"
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 57a6d82589ae03c4cabdc058f719f231
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a58292df9558ed736ce8b3bd7e2c8c1f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores arrays of various sizes so they can be reused without garbage collection.
|
||||
/// </summary>
|
||||
public static class ArrayCache<T>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[ThreadStatic]
|
||||
private static T[][] _Arrays;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Get a cached array of the specified size for temporary use. The array must be used and discarded
|
||||
/// immediately as it may be reused by anything else that calls this method with the same `length`.
|
||||
/// </summary>
|
||||
public static T[] GetTempArray(int length)
|
||||
{
|
||||
if (_Arrays == null || _Arrays.Length <= length + 1)
|
||||
{
|
||||
var newSize = length < 16 ? 16 : Mathf.NextPowerOfTwo(length + 1);
|
||||
Array.Resize(ref _Arrays, newSize);
|
||||
}
|
||||
|
||||
var array = _Arrays[length];
|
||||
if (array == null)
|
||||
{
|
||||
array = new T[length];
|
||||
_Arrays[length] = array;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d7ec91f3e7c6b044798306a3f16441f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that takes a single <see cref="Collision2D"/> parameter.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public sealed class CollisionEvent2D : UltEvent<Collision2D> { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> 2D collision events:
|
||||
/// <see cref="OnCollisionEnter2D"/>, <see cref="OnCollisionStay2D"/>, and <see cref="OnCollisionExit2D"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Collision Events 2D")]
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/CollisionEvents2D")]
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class CollisionEvents2D : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private CollisionEvent2D _CollisionEnterEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnCollisionEnter2D"/>.</summary>
|
||||
public CollisionEvent2D CollisionEnterEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_CollisionEnterEvent == null)
|
||||
_CollisionEnterEvent = new CollisionEvent2D();
|
||||
return _CollisionEnterEvent;
|
||||
}
|
||||
set { _CollisionEnterEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="CollisionEnterEvent"/>.</summary>
|
||||
public virtual void OnCollisionEnter2D(Collision2D collision)
|
||||
{
|
||||
if (_CollisionEnterEvent != null)
|
||||
_CollisionEnterEvent.Invoke(collision);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private CollisionEvent2D _CollisionStayEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnCollisionStay2D"/>.</summary>
|
||||
public CollisionEvent2D CollisionStayEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_CollisionStayEvent == null)
|
||||
_CollisionStayEvent = new CollisionEvent2D();
|
||||
return _CollisionStayEvent;
|
||||
}
|
||||
set { _CollisionStayEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="CollisionStayEvent"/>.</summary>
|
||||
public virtual void OnCollisionStay2D(Collision2D collision)
|
||||
{
|
||||
if (_CollisionStayEvent != null)
|
||||
_CollisionStayEvent.Invoke(collision);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private CollisionEvent2D _CollisionExitEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnCollisionExit2D"/>.</summary>
|
||||
public CollisionEvent2D CollisionExitEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_CollisionExitEvent == null)
|
||||
_CollisionExitEvent = new CollisionEvent2D();
|
||||
return _CollisionExitEvent;
|
||||
}
|
||||
set { _CollisionExitEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="CollisionExitEvent"/>.</summary>
|
||||
public virtual void OnCollisionExit2D(Collision2D collision)
|
||||
{
|
||||
if (_CollisionExitEvent != null)
|
||||
_CollisionExitEvent.Invoke(collision);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3d9d795461ac3c041bf1635804658d32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that takes a single <see cref="Collision"/> parameter.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public sealed class CollisionEvent3D : UltEvent<Collision> { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> collision events:
|
||||
/// <see cref="OnCollisionEnter"/>, <see cref="OnCollisionStay"/>, and <see cref="OnCollisionExit"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Collision Events 3D")]
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/CollisionEvents3D")]
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class CollisionEvents3D : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private CollisionEvent3D _CollisionEnterEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnCollisionEnter"/>.</summary>
|
||||
public CollisionEvent3D CollisionEnterEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_CollisionEnterEvent == null)
|
||||
_CollisionEnterEvent = new CollisionEvent3D();
|
||||
return _CollisionEnterEvent;
|
||||
}
|
||||
set { _CollisionEnterEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="CollisionEnterEvent"/>.</summary>
|
||||
public virtual void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (_CollisionEnterEvent != null)
|
||||
_CollisionEnterEvent.Invoke(collision);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private CollisionEvent3D _CollisionStayEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnCollisionStay"/>.</summary>
|
||||
public CollisionEvent3D CollisionStayEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_CollisionStayEvent == null)
|
||||
_CollisionStayEvent = new CollisionEvent3D();
|
||||
return _CollisionStayEvent;
|
||||
}
|
||||
set { _CollisionStayEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="CollisionStayEvent"/>.</summary>
|
||||
public virtual void OnCollisionStay(Collision collision)
|
||||
{
|
||||
if (_CollisionStayEvent != null)
|
||||
_CollisionStayEvent.Invoke(collision);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private CollisionEvent3D _CollisionExitEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnCollisionExit"/>.</summary>
|
||||
public CollisionEvent3D CollisionExitEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_CollisionExitEvent == null)
|
||||
_CollisionExitEvent = new CollisionEvent3D();
|
||||
return _CollisionExitEvent;
|
||||
}
|
||||
set { _CollisionExitEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="CollisionExitEvent"/>.</summary>
|
||||
public virtual void OnCollisionExit(Collision collision)
|
||||
{
|
||||
if (_CollisionExitEvent != null)
|
||||
_CollisionExitEvent.Invoke(collision);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d8e0dc99dbc534d4e8bb37159f2d7429
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// A component which encapsulates a single <see cref="UltEventBase"/> with a delay before its invocation.
|
||||
/// </summary>
|
||||
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Delayed Ult Event Holder")]
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/DelayedUltEventHolder")]
|
||||
public class DelayedUltEventHolder : UltEventHolder
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private float _Delay;
|
||||
|
||||
private WaitForSeconds _Wait;
|
||||
|
||||
/// <summary>
|
||||
/// The number of seconds that will pass between calling <see cref="Invoke"/> and the event actually being invoked.
|
||||
/// </summary>
|
||||
public float Delay
|
||||
{
|
||||
get { return _Delay; }
|
||||
set
|
||||
{
|
||||
_Delay = value;
|
||||
_Wait = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Waits for <see cref="Delay"/> seconds then calls Event.Invoke().</summary>
|
||||
public override void Invoke()
|
||||
{
|
||||
if (_Delay < 0)
|
||||
base.Invoke();
|
||||
else
|
||||
StartCoroutine(DelayedInvoke());
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private IEnumerator DelayedInvoke()
|
||||
{
|
||||
if (_Wait == null)
|
||||
_Wait = new WaitForSeconds(_Delay);
|
||||
|
||||
yield return _Wait;
|
||||
|
||||
base.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3f574d8b2153a0d4fa15f5de878f680c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> lifecycle events:
|
||||
/// <see cref="Awake"/>, <see cref="Start"/>, <see cref="OnEnable"/>, <see cref="OnDisable"/>, and
|
||||
/// <see cref="OnDestroy"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Life Cycle Events")]
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/LifeCycleEvents")]
|
||||
[DisallowMultipleComponent]
|
||||
public class LifeCycleEvents : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _AwakeEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="Awake"/>.</summary>
|
||||
public UltEvent AwakeEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_AwakeEvent == null)
|
||||
_AwakeEvent = new UltEvent();
|
||||
return _AwakeEvent;
|
||||
}
|
||||
set { _AwakeEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="AwakeEvent"/>.</summary>
|
||||
public virtual void Awake()
|
||||
{
|
||||
if (_AwakeEvent != null)
|
||||
_AwakeEvent.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _StartEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="Start"/>.</summary>
|
||||
public UltEvent StartEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_StartEvent == null)
|
||||
_StartEvent = new UltEvent();
|
||||
return _StartEvent;
|
||||
}
|
||||
set { _StartEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="StartEvent"/>.</summary>
|
||||
public virtual void Start()
|
||||
{
|
||||
if (_StartEvent != null)
|
||||
_StartEvent.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _EnableEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnEnable"/>.</summary>
|
||||
public UltEvent EnableEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_EnableEvent == null)
|
||||
_EnableEvent = new UltEvent();
|
||||
return _EnableEvent;
|
||||
}
|
||||
set { _EnableEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="EnableEvent"/>.</summary>
|
||||
public virtual void OnEnable()
|
||||
{
|
||||
if (_EnableEvent != null)
|
||||
_EnableEvent.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _DisableEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnDisable"/>.</summary>
|
||||
public UltEvent DisableEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_DisableEvent == null)
|
||||
_DisableEvent = new UltEvent();
|
||||
return _DisableEvent;
|
||||
}
|
||||
set { _DisableEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="DisableEvent"/>.</summary>
|
||||
public virtual void OnDisable()
|
||||
{
|
||||
if (_DisableEvent != null)
|
||||
_DisableEvent.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _DestroyEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnDestroy"/>.</summary>
|
||||
public UltEvent DestroyEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_DestroyEvent == null)
|
||||
_DestroyEvent = new UltEvent();
|
||||
return _DestroyEvent;
|
||||
}
|
||||
set { _DestroyEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="DestroyEvent"/>.</summary>
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
if (_DestroyEvent != null)
|
||||
_DestroyEvent.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 003c25630808c7243847bd9149749434
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>[Editor-Only] The names of various types and members in <see cref="UltEvents"/>.</summary>
|
||||
internal static class Names
|
||||
{
|
||||
public const string
|
||||
Namespace = "UltEvents";
|
||||
public const string
|
||||
PersistentArgumentType = "PersistentArgumentType";
|
||||
|
||||
/// <summary>[Editor-Only] The names of various members in <see cref="UltEvents.PersistentArgument"/>.</summary>
|
||||
internal static class PersistentArgument
|
||||
{
|
||||
public const string
|
||||
Class = "PersistentArgument";
|
||||
public const string
|
||||
|
||||
Type = "_Type";
|
||||
public const string
|
||||
Int = "_Int";
|
||||
public const string
|
||||
String = "_String";
|
||||
public const string
|
||||
X = "_X";
|
||||
public const string
|
||||
Y = "_Y";
|
||||
public const string
|
||||
Z = "_Z";
|
||||
public const string
|
||||
W = "_W";
|
||||
public const string
|
||||
Object = "_Object";
|
||||
|
||||
/// <summary>[Editor-Only] The full names of various members in <see cref="UltEvents.PersistentArgument"/>.</summary>
|
||||
internal static class Full
|
||||
{
|
||||
public const string
|
||||
Type = "PersistentArgument.Type";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>[Editor-Only] The names of various members in <see cref="UltEvents.PersistentCall"/>.</summary>
|
||||
internal static class PersistentCall
|
||||
{
|
||||
public const string
|
||||
Target = "_Target";
|
||||
public const string
|
||||
MethodName = "_MethodName";
|
||||
public const string
|
||||
PersistentArguments = "_PersistentArguments";
|
||||
}
|
||||
|
||||
/// <summary>[Editor-Only] The names of various members in <see cref="UltEvents.UltEvent"/>.</summary>
|
||||
internal static class UltEvent
|
||||
{
|
||||
public const string
|
||||
Class = "UltEvent";
|
||||
public const string
|
||||
PersistentCalls = "_PersistentCalls";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2a322e55daf5c3e458a39587bd3ec3c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,749 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>The type identifier of a <see cref="PersistentArgument"/>.</summary>
|
||||
public enum PersistentArgumentType
|
||||
{
|
||||
/// <summary>Type not set.</summary>
|
||||
None,
|
||||
|
||||
// Uses _Int 0 or 1 as bool.
|
||||
/// <summary><see cref="bool"/>.</summary>
|
||||
Bool,
|
||||
|
||||
// Uses _String.
|
||||
/// <summary><see cref="string"/>.</summary>
|
||||
String,
|
||||
|
||||
// Uses _Int.
|
||||
/// <summary><see cref="int"/>.</summary>
|
||||
Int,
|
||||
|
||||
// Uses _Int for the value and _String for the assembly qualified name of the type.
|
||||
/// <summary>Any kind of <see cref="System.Enum"/>.</summary>
|
||||
Enum,
|
||||
|
||||
// Uses _X.
|
||||
/// <summary><see cref="float"/>.</summary>
|
||||
Float,
|
||||
|
||||
// Uses _X and _Y.
|
||||
/// <summary><see cref="UnityEngine.Vector2"/>.</summary>
|
||||
Vector2,
|
||||
|
||||
// Uses _X, _Y, and _Z.
|
||||
/// <summary><see cref="UnityEngine.Vector3"/>.</summary>
|
||||
Vector3,
|
||||
|
||||
// Uses _X, _Y, _Z, and _W.
|
||||
/// <summary><see cref="UnityEngine.Vector4"/>.</summary>
|
||||
Vector4,
|
||||
|
||||
// Uses _X, _Y, and _Z to store the euler angles.
|
||||
/// <summary><see cref="UnityEngine.Quaternion"/>.</summary>
|
||||
Quaternion,
|
||||
|
||||
// Uses _X, _Y, _Z, and _W as RGBA.
|
||||
/// <summary><see cref="UnityEngine.Color"/>.</summary>
|
||||
Color,
|
||||
|
||||
// Uses _Int to hold the RGBA bytes.
|
||||
/// <summary><see cref="UnityEngine.Color32"/>.</summary>
|
||||
Color32,
|
||||
|
||||
// Uses _X, _Y, _Z, and _W as X, Y, Width, Height.
|
||||
/// <summary><see cref="UnityEngine.Rect"/>.</summary>
|
||||
Rect,
|
||||
|
||||
// Uses _Object for the value and _String for the assembly qualified name of the type.
|
||||
/// <summary><see cref="UnityEngine.Object"/>.</summary>
|
||||
Object,
|
||||
|
||||
// Uses _Int for the index of the target parameter.
|
||||
// If the type is a simple PersistentArgumentType (not Object or Enum), it is casted to a float and stored in _X.
|
||||
// Otherwise the assembly qualified name of the type is stored in _String.
|
||||
/// <summary>The value of a parameter passed to the event.</summary>
|
||||
Parameter,
|
||||
|
||||
// Uses _Int for the index of the target call.
|
||||
// If the type is a simple PersistentArgumentType (not Object or Enum), it is casted to a float and stored in _X.
|
||||
// Otherwise the assembly qualified name of the type is stored in _String.
|
||||
/// <summary>The return value by a previous <see cref="PersistentCall"/>.</summary>
|
||||
ReturnValue,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates a variable so it can be serialized for <see cref="UltEventBase"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class PersistentArgument
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
internal PersistentArgumentType _Type;
|
||||
|
||||
[SerializeField]
|
||||
internal int _Int;
|
||||
|
||||
[SerializeField]
|
||||
internal string _String;
|
||||
|
||||
[SerializeField]
|
||||
internal float _X;
|
||||
|
||||
[SerializeField]
|
||||
internal float _Y;
|
||||
|
||||
[SerializeField]
|
||||
internal float _Z;
|
||||
|
||||
[SerializeField]
|
||||
internal float _W;
|
||||
|
||||
[SerializeField]
|
||||
internal Object _Object;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized]
|
||||
private Type _SystemType;
|
||||
|
||||
[NonSerialized]
|
||||
internal bool _HasSystemType;
|
||||
|
||||
[NonSerialized]
|
||||
private object _Value;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Constructs a new <see cref="PersistentArgument"/> with default values.</summary>
|
||||
public PersistentArgument() { }
|
||||
|
||||
/// <summary>Constructs a new <see cref="PersistentArgument"/> with the specified `type`.</summary>
|
||||
public PersistentArgument(Type type)
|
||||
{
|
||||
_Type = GetArgumentType(type, out _String, out _Int);
|
||||
_SystemType = type;
|
||||
_HasSystemType = true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The type identifier of this argument.</summary>
|
||||
public PersistentArgumentType Type
|
||||
{
|
||||
get { return _Type; }
|
||||
internal set
|
||||
{
|
||||
_Int = 0;
|
||||
_X = _Y = _Z = _W = 0;
|
||||
_String = "";
|
||||
_Object = null;
|
||||
_Type = value;
|
||||
_HasSystemType = false;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="System.Type"/> of this argument.
|
||||
/// </summary>
|
||||
public Type SystemType
|
||||
{
|
||||
get
|
||||
{
|
||||
#if !UNITY_EDITOR// Ignore cache in the editor since it complicates the inspector GUI code.
|
||||
if (!_HasSystemType)
|
||||
#endif
|
||||
{
|
||||
_SystemType = GetArgumentType(_Type, _X, _String);
|
||||
_HasSystemType = true;
|
||||
}
|
||||
|
||||
return _SystemType;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
// Can't pass _String and _Int in directly because setting the Type clears them.
|
||||
string assemblyQualifiedName;
|
||||
int linkIndex;
|
||||
Type = GetArgumentType(value, out assemblyQualifiedName, out linkIndex);
|
||||
_String = assemblyQualifiedName;
|
||||
_Int = linkIndex;
|
||||
_HasSystemType = true;
|
||||
_SystemType = value;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="bool"/> value of this argument.</summary>
|
||||
public bool Bool
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Bool);
|
||||
return _Int != 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Bool);
|
||||
_Int = value ? 1 : 0;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="string"/> value of this argument.</summary>
|
||||
public string String
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.String);
|
||||
return _String;
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.String);
|
||||
_String = value ?? "";
|
||||
_Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="int"/> value of this argument.</summary>
|
||||
public int Int
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Int);
|
||||
return _Int;
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Int);
|
||||
_Int = value;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="System.Enum"/> value of this argument.</summary>
|
||||
public object Enum
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Enum);
|
||||
return System.Enum.ToObject(SystemType, _Int);
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Enum);
|
||||
_Int = (int)value;
|
||||
_Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="float"/> value of this argument.</summary>
|
||||
public float Float
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Float);
|
||||
return _X;
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Float);
|
||||
_X = value;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Vector2"/> value of this argument.</summary>
|
||||
public Vector2 Vector2
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Vector2);
|
||||
return new Vector2(_X, _Y);
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Vector2);
|
||||
_X = value.x;
|
||||
_Y = value.y;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Vector3"/> value of this argument.</summary>
|
||||
public Vector3 Vector3
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Vector3);
|
||||
return new Vector3(_X, _Y, _Z);
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Vector3);
|
||||
_X = value.x;
|
||||
_Y = value.y;
|
||||
_Z = value.z;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Vector4"/> value of this argument.</summary>
|
||||
public Vector4 Vector4
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Vector4);
|
||||
return new Vector4(_X, _Y, _Z, _W);
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Vector4);
|
||||
_X = value.x;
|
||||
_Y = value.y;
|
||||
_Z = value.z;
|
||||
_W = value.w;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Quaternion"/> value of this argument.</summary>
|
||||
public Quaternion Quaternion
|
||||
{
|
||||
// It would be better to store the components of the Quaternion directly instead of the euler angles,
|
||||
// but floating point imprecision when converting between them to show the euler angles in the inspector
|
||||
// means that changes to any one axis will have a small effect on the other axes as well.
|
||||
|
||||
// This could be handled like the [Euler] attribute, but that still leads to small inaccuracies in the
|
||||
// displayed values when they are deserialized so storing the euler angles directly is more user-friendly.
|
||||
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Quaternion);
|
||||
return Quaternion.Euler(_X, _Y, _Z);
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Quaternion);
|
||||
var euler = value.eulerAngles;
|
||||
_X = euler.x;
|
||||
_Y = euler.y;
|
||||
_Z = euler.z;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Color"/> value of this argument.</summary>
|
||||
public Color Color
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Color);
|
||||
return new Color(_X, _Y, _Z, _W);
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Color);
|
||||
_X = value.r;
|
||||
_Y = value.g;
|
||||
_Z = value.b;
|
||||
_W = value.a;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Color32"/> value of this argument.</summary>
|
||||
public Color32 Color32
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Color32);
|
||||
return new Color32((byte)(_Int), (byte)(_Int >> 8), (byte)(_Int >> 16), (byte)(_Int >> 24));
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Color32);
|
||||
_Int = value.r | (value.g << 8) | (value.b << 16) | (value.a << 24);
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Rect"/> value of this argument.</summary>
|
||||
public Rect Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Rect);
|
||||
return new Rect(_X, _Y, _Z, _W);
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Rect);
|
||||
_X = value.x;
|
||||
_Y = value.y;
|
||||
_Z = value.width;
|
||||
_W = value.height;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="UnityEngine.Object"/> value of this argument.</summary>
|
||||
public Object Object
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Object);
|
||||
|
||||
// Unity's fake nulls cause problems if the argument is a child type of UnityEngine.Object.
|
||||
// For example, when invoking a MonoBehaviour parameter a fake null Object would fail to convert to MonoBehaviour.
|
||||
// So we make sure to return actual null instead of any fake value.
|
||||
|
||||
if (_Object == null)
|
||||
return null;
|
||||
else
|
||||
return _Object;
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Object);
|
||||
_Object = value;
|
||||
_String = value != null ? value.GetType().AssemblyQualifiedName : "";
|
||||
_Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The value of a parameter passed into the <see cref="PersistentCall"/> (see <see cref="ParameterIndex"/>.</summary>
|
||||
public object Parameter
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Parameter);
|
||||
return UltEventBase.GetParameterValue(_Int);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The index of the parameter passed into the <see cref="PersistentCall"/>.</summary>
|
||||
public int ParameterIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.Parameter);
|
||||
return _Int;
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.Parameter);
|
||||
_Int = value;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The value returned by a previous <see cref="PersistentCall"/> (see <see cref="ReturnedValueIndex"/>.</summary>
|
||||
public object ReturnedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.ReturnValue);
|
||||
return UltEventBase.GetReturnedValue(_Int);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The index of the <see cref="PersistentCall"/> which returns the value for this argument.</summary>
|
||||
public int ReturnedValueIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertType(PersistentArgumentType.ReturnValue);
|
||||
return _Int;
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertType(PersistentArgumentType.ReturnValue);
|
||||
_Int = value;
|
||||
_Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The value of this argument.</summary>
|
||||
public object Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Value == null)
|
||||
{
|
||||
switch (_Type)
|
||||
{
|
||||
case PersistentArgumentType.Bool: _Value = Bool; break;
|
||||
case PersistentArgumentType.String: _Value = String; break;
|
||||
case PersistentArgumentType.Int: _Value = Int; break;
|
||||
case PersistentArgumentType.Enum: _Value = Enum; break;
|
||||
case PersistentArgumentType.Float: _Value = Float; break;
|
||||
case PersistentArgumentType.Vector2: _Value = Vector2; break;
|
||||
case PersistentArgumentType.Vector3: _Value = Vector3; break;
|
||||
case PersistentArgumentType.Vector4: _Value = Vector4; break;
|
||||
case PersistentArgumentType.Quaternion: _Value = Quaternion; break;
|
||||
case PersistentArgumentType.Color: _Value = Color; break;
|
||||
case PersistentArgumentType.Color32: _Value = Color32; break;
|
||||
case PersistentArgumentType.Rect: _Value = Rect; break;
|
||||
case PersistentArgumentType.Object: _Value = Object; break;
|
||||
|
||||
// Don't cache parameters or returned values.
|
||||
case PersistentArgumentType.Parameter: return Parameter;
|
||||
case PersistentArgumentType.ReturnValue: return ReturnedValue;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
"Invalid " + Names.PersistentArgument.Full.Type + ": " + _Type);
|
||||
}
|
||||
}
|
||||
|
||||
return _Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
switch (_Type)
|
||||
{
|
||||
case PersistentArgumentType.Bool: Bool = (bool)value; break;
|
||||
case PersistentArgumentType.String: String = (string)value; break;
|
||||
case PersistentArgumentType.Int: Int = (int)value; break;
|
||||
case PersistentArgumentType.Enum: Enum = value; break;
|
||||
case PersistentArgumentType.Float: Float = (float)value; break;
|
||||
case PersistentArgumentType.Vector2: Vector2 = (Vector2)value; break;
|
||||
case PersistentArgumentType.Vector3: Vector3 = (Vector3)value; break;
|
||||
case PersistentArgumentType.Vector4: Vector4 = (Vector4)value; break;
|
||||
case PersistentArgumentType.Quaternion: Quaternion = (Quaternion)value; break;
|
||||
case PersistentArgumentType.Color: Color = (Color)value; break;
|
||||
case PersistentArgumentType.Color32: Color32 = (Color32)value; break;
|
||||
case PersistentArgumentType.Rect: Rect = (Rect)value; break;
|
||||
case PersistentArgumentType.Object: Object = (Object)value; break;
|
||||
|
||||
// Don't cache parameters or returned values.
|
||||
case PersistentArgumentType.Parameter: ParameterIndex = (int)value; return;
|
||||
case PersistentArgumentType.ReturnValue: ReturnedValueIndex = (int)value; return;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
"Invalid " + Names.PersistentArgument.Full.Type + ": " + _Type);
|
||||
}
|
||||
|
||||
_Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Methods
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private void AssertType(PersistentArgumentType type)
|
||||
{
|
||||
if (_Type != type)
|
||||
throw new InvalidOperationException(Names.PersistentArgument.Full.Type + " is " + _Type + " but should be " + type);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal void ClearCache()
|
||||
{
|
||||
_Value = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="System.Type"/> associated with the specified <see cref="PersistentArgumentType"/>.
|
||||
/// <para></para>
|
||||
/// If the `type` can be inherited (such as an Enum or Object), the `assemblyQualifiedName` will be used to get the type.
|
||||
/// </summary>
|
||||
public static Type GetArgumentType(PersistentArgumentType type, float secondaryType, string assemblyQualifiedName)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PersistentArgumentType.Bool: return typeof(bool);
|
||||
case PersistentArgumentType.String: return typeof(string);
|
||||
case PersistentArgumentType.Int: return typeof(int);
|
||||
case PersistentArgumentType.Float: return typeof(float);
|
||||
case PersistentArgumentType.Vector2: return typeof(Vector2);
|
||||
case PersistentArgumentType.Vector3: return typeof(Vector3);
|
||||
case PersistentArgumentType.Vector4: return typeof(Vector4);
|
||||
case PersistentArgumentType.Quaternion: return typeof(Quaternion);
|
||||
case PersistentArgumentType.Color: return typeof(Color);
|
||||
case PersistentArgumentType.Color32: return typeof(Color32);
|
||||
case PersistentArgumentType.Rect: return typeof(Rect);
|
||||
|
||||
case PersistentArgumentType.Enum:
|
||||
case PersistentArgumentType.Object:
|
||||
default:
|
||||
if (!string.IsNullOrEmpty(assemblyQualifiedName))
|
||||
return System.Type.GetType(assemblyQualifiedName);
|
||||
else
|
||||
return null;
|
||||
|
||||
case PersistentArgumentType.Parameter:
|
||||
case PersistentArgumentType.ReturnValue:
|
||||
if (!string.IsNullOrEmpty(assemblyQualifiedName))
|
||||
return System.Type.GetType(assemblyQualifiedName);
|
||||
else
|
||||
return GetArgumentType((PersistentArgumentType)secondaryType, -1, null);
|
||||
|
||||
case PersistentArgumentType.None:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="PersistentArgumentType"/> associated with the specified <see cref="System.Type"/>.
|
||||
/// <para></para>
|
||||
/// If the `type` can be inherited (such as an Enum or Object), the `assemblyQualifiedName` will be assigned as well (otherwise null).
|
||||
/// </summary>
|
||||
public static PersistentArgumentType GetArgumentType(Type type, out string assemblyQualifiedName, out int linkIndex)
|
||||
{
|
||||
linkIndex = 0;
|
||||
assemblyQualifiedName = null;
|
||||
|
||||
if (type == typeof(bool)) return PersistentArgumentType.Bool;
|
||||
else if (type == typeof(string)) return PersistentArgumentType.String;
|
||||
else if (type == typeof(int)) return PersistentArgumentType.Int;
|
||||
else if (type == typeof(float)) return PersistentArgumentType.Float;
|
||||
else if (type == typeof(Vector2)) return PersistentArgumentType.Vector2;
|
||||
else if (type == typeof(Vector3)) return PersistentArgumentType.Vector3;
|
||||
else if (type == typeof(Vector4)) return PersistentArgumentType.Vector4;
|
||||
else if (type == typeof(Quaternion)) return PersistentArgumentType.Quaternion;
|
||||
else if (type == typeof(Color)) return PersistentArgumentType.Color;
|
||||
else if (type == typeof(Color32)) return PersistentArgumentType.Color32;
|
||||
else if (type == typeof(Rect)) return PersistentArgumentType.Rect;
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
if (System.Enum.GetUnderlyingType(type) == typeof(int))
|
||||
{
|
||||
assemblyQualifiedName = type.AssemblyQualifiedName;
|
||||
return PersistentArgumentType.Enum;
|
||||
}
|
||||
else return PersistentArgumentType.None;
|
||||
}
|
||||
else if (type == typeof(Object) || type.IsSubclassOf(typeof(Object)))
|
||||
{
|
||||
assemblyQualifiedName = type.AssemblyQualifiedName;
|
||||
return PersistentArgumentType.Object;
|
||||
}
|
||||
else
|
||||
{
|
||||
assemblyQualifiedName = type.AssemblyQualifiedName;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
PersistentArgumentType linkType;
|
||||
if (Editor.DrawerState.Current.TryGetLinkable(type, out linkIndex, out linkType))
|
||||
return linkType;
|
||||
#endif
|
||||
|
||||
return PersistentArgumentType.ReturnValue;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates an exact copy of this argument.</summary>
|
||||
public PersistentArgument Clone()
|
||||
{
|
||||
#pragma warning disable IDE0017 // Simplify object initialization
|
||||
var clone = new PersistentArgument();
|
||||
#pragma warning restore IDE0017 // Simplify object initialization
|
||||
|
||||
clone._Type = _Type;
|
||||
clone._Int = _Int;
|
||||
clone._String = _String;
|
||||
clone._X = _X;
|
||||
clone._Y = _Y;
|
||||
clone._Z = _Z;
|
||||
clone._W = _W;
|
||||
clone._Object = _Object;
|
||||
clone._SystemType = _SystemType;
|
||||
clone._HasSystemType = _HasSystemType;
|
||||
clone._Value = _Value;
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a string which describes this argument.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
switch (_Type)
|
||||
{
|
||||
case PersistentArgumentType.None:
|
||||
return Names.PersistentArgument.Class + ": Type=None";
|
||||
case PersistentArgumentType.Bool:
|
||||
case PersistentArgumentType.String:
|
||||
case PersistentArgumentType.Int:
|
||||
case PersistentArgumentType.Enum:
|
||||
case PersistentArgumentType.Float:
|
||||
case PersistentArgumentType.Vector2:
|
||||
case PersistentArgumentType.Vector3:
|
||||
case PersistentArgumentType.Vector4:
|
||||
case PersistentArgumentType.Quaternion:
|
||||
case PersistentArgumentType.Color:
|
||||
case PersistentArgumentType.Color32:
|
||||
case PersistentArgumentType.Rect:
|
||||
case PersistentArgumentType.Object:
|
||||
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=" + Value;
|
||||
case PersistentArgumentType.Parameter:
|
||||
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=Parameter" + ParameterIndex;
|
||||
case PersistentArgumentType.ReturnValue:
|
||||
return Names.PersistentArgument.Class + ": SystemType=" + SystemType + ", Value=ReturnValue" + ReturnedValueIndex;
|
||||
default:
|
||||
Debug.LogWarning("Unhandled " + Names.PersistentArgumentType);
|
||||
return base.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f40fbbd86dbf0d145ae23236ac2778b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,493 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates a delegate so it can be serialized for <see cref="UltEventBase"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class PersistentCall
|
||||
#if UNITY_EDITOR
|
||||
: ISerializationCallbackReceiver
|
||||
#endif
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private Object _Target;
|
||||
|
||||
/// <summary>The object on which the persistent method is called.</summary>
|
||||
public Object Target
|
||||
{
|
||||
get { return _Target; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private string _MethodName;
|
||||
|
||||
/// <summary>The name of the persistent method.</summary>
|
||||
public string MethodName
|
||||
{
|
||||
get { return _MethodName; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
internal PersistentArgument[] _PersistentArguments = NoArguments;
|
||||
|
||||
/// <summary>The arguments which are passed to the method when it is invoked.</summary>
|
||||
public PersistentArgument[] PersistentArguments
|
||||
{
|
||||
get { return _PersistentArguments; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized]
|
||||
internal MethodBase _Method;
|
||||
|
||||
/// <summary>The method which this call encapsulates.</summary>
|
||||
public MethodBase Method
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Method == null)
|
||||
{
|
||||
Type declaringType;
|
||||
string methodName;
|
||||
GetMethodDetails(out declaringType, out methodName);
|
||||
if (declaringType == null || string.IsNullOrEmpty(methodName))
|
||||
return null;
|
||||
|
||||
var argumentCount = _PersistentArguments.Length;
|
||||
var parameters = ArrayCache<Type>.GetTempArray(argumentCount);
|
||||
for (int i = 0; i < argumentCount; i++)
|
||||
{
|
||||
parameters[i] = _PersistentArguments[i].SystemType;
|
||||
}
|
||||
|
||||
if (methodName == "ctor")
|
||||
_Method = declaringType.GetConstructor(UltEventUtils.AnyAccessBindings, null, parameters, null);
|
||||
else
|
||||
_Method = declaringType.GetMethod(methodName, UltEventUtils.AnyAccessBindings, null, parameters, null);
|
||||
}
|
||||
|
||||
return _Method;
|
||||
}
|
||||
}
|
||||
|
||||
internal MethodBase GetMethodSafe()
|
||||
{
|
||||
try { return Method; }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
// Always clear the cached method in the editor in case the fields have been directly modified by Inspector or Undo operations.
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize() { ClearCache(); }
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize() { ClearCache(); }
|
||||
|
||||
private void ClearCache()
|
||||
{
|
||||
_Method = null;
|
||||
|
||||
for (int i = 0; i < _PersistentArguments.Length; i++)
|
||||
{
|
||||
_PersistentArguments[i].ClearCache();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Constructs a new <see cref="PersistentCall"/> with default values.</summary>
|
||||
public PersistentCall() { }
|
||||
|
||||
/// <summary>Constructs a new <see cref="PersistentCall"/> to serialize the specified `method`.</summary>
|
||||
public PersistentCall(MethodInfo method, Object target)
|
||||
{
|
||||
SetMethod(method, target);
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new <see cref="PersistentCall"/> to serialize the specified `method`.</summary>
|
||||
public PersistentCall(Delegate method)
|
||||
{
|
||||
SetMethod(method);
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new <see cref="PersistentCall"/> to serialize the specified `method`.</summary>
|
||||
public PersistentCall(Action method)
|
||||
{
|
||||
SetMethod(method);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sets the method which this call encapsulates.</summary>
|
||||
public void SetMethod(MethodBase method, Object target)
|
||||
{
|
||||
_Method = method;
|
||||
_Target = target;
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
if (method.IsStatic || method.IsConstructor)
|
||||
{
|
||||
_MethodName = UltEventUtils.GetFullyQualifiedName(method);
|
||||
_Target = null;
|
||||
}
|
||||
else _MethodName = method.Name;
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
if (_PersistentArguments == null || _PersistentArguments.Length != parameters.Length)
|
||||
{
|
||||
_PersistentArguments = NewArgumentArray(parameters.Length);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _PersistentArguments.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
var persistentArgument = _PersistentArguments[i];
|
||||
|
||||
persistentArgument.SystemType = parameter.ParameterType;
|
||||
|
||||
switch (persistentArgument.Type)
|
||||
{
|
||||
case PersistentArgumentType.Parameter:
|
||||
case PersistentArgumentType.ReturnValue:
|
||||
break;
|
||||
default:
|
||||
if ((parameter.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault)
|
||||
{
|
||||
persistentArgument.Value = parameter.DefaultValue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_MethodName = null;
|
||||
_PersistentArguments = NoArguments;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets the delegate which this call encapsulates.</summary>
|
||||
public void SetMethod(Delegate method)
|
||||
{
|
||||
if (method.Target == null)
|
||||
{
|
||||
SetMethod(method.Method, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var target = method.Target as Object;
|
||||
if (target != null)
|
||||
SetMethod(method.Method, target);
|
||||
else
|
||||
throw new InvalidOperationException("SetMethod failed because action.Target is not a UnityEngine.Object.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets the delegate which this call encapsulates.</summary>
|
||||
public void SetMethod(Action method)
|
||||
{
|
||||
SetMethod((Delegate)method);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly PersistentArgument[] NoArguments = new PersistentArgument[0];
|
||||
|
||||
private static PersistentArgument[] NewArgumentArray(int length)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return NoArguments;
|
||||
}
|
||||
else
|
||||
{
|
||||
var array = new PersistentArgument[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
array[i] = new PersistentArgument();
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
private static string[] allTypes = null;
|
||||
private static string[] allNameSpaces = null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Acquire a delegate based on the <see cref="Target"/> and <see cref="MethodName"/> and invoke it.
|
||||
/// </summary>
|
||||
public object Invoke()
|
||||
{
|
||||
if (Method == null)
|
||||
{
|
||||
Debug.LogWarning("Attempted to Invoke a PersistentCall which couldn't find it's method: " + MethodName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (allTypes == null)
|
||||
{
|
||||
// these are the spicy types that should never get called
|
||||
// naming it AllTypes to attempt to be more sneaky
|
||||
allTypes = new string[]
|
||||
{
|
||||
"UnityEngine.Application",
|
||||
"UnityEngine.Device.Application",
|
||||
"UnityEngine.Microphone",
|
||||
"UnityEngine.WebCamTexture",
|
||||
"UnityEngine.WWW",
|
||||
};
|
||||
}
|
||||
|
||||
if (allNameSpaces == null)
|
||||
{
|
||||
// these are the spicy namespaces that should never get called
|
||||
// naming it AllNamespaces to attempt to be more sneaky
|
||||
allNameSpaces = new string[]
|
||||
{
|
||||
"Oculus",
|
||||
"Steamworks",
|
||||
"System.Diagnostics",
|
||||
"System.IO",
|
||||
"System.Net",
|
||||
"System.Runtime",
|
||||
"System.Security",
|
||||
"System.Xml",
|
||||
"UnityEngine.Networking",
|
||||
"UnityEngine.Windows",
|
||||
"WebSocketSharp",
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var spicyNamespace in allNameSpaces)
|
||||
{
|
||||
if (Method.DeclaringType != null && Method.DeclaringType.Namespace != null
|
||||
&& Method.DeclaringType.Namespace.ToLower().Contains(spicyNamespace.ToLower()))
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogError($"UltEvents: Spicy Method attempted invoke, ignoring: {spicyNamespace}: {Method.ToString()}");
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var spicyType in allTypes)
|
||||
{
|
||||
if (Method.DeclaringType != null && Method.DeclaringType.FullName == spicyType)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogError($"UltEvents: Spicy Method attempted invoke, ignoring: {spicyType}: {Method.ToString()}");
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
object[] parameters;
|
||||
if (_PersistentArguments != null && _PersistentArguments.Length > 0)
|
||||
{
|
||||
parameters = ArrayCache<object>.GetTempArray(_PersistentArguments.Length);
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
parameters[i] = _PersistentArguments[i].Value;
|
||||
}
|
||||
}
|
||||
else parameters = null;
|
||||
|
||||
UltEventBase.UpdateLinkedValueOffsets();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Somehow Unity ends up getting a UnityEngine.Object which pretends to be null.
|
||||
// But only in the Editor. At runtime it properly deserialized the target as null.
|
||||
|
||||
// When calling a static method it just gets ignored. But when calling a constructor with a target, it
|
||||
// attempts to apply it to the existing object, which won't work because it's the wrong type.
|
||||
|
||||
if (_Method.IsConstructor)
|
||||
return _Method.Invoke(null, parameters);
|
||||
#endif
|
||||
|
||||
return _Method.Invoke(_Target, parameters);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sets the value of the first persistent argument.</summary>
|
||||
public void SetArguments(object argument0)
|
||||
{
|
||||
PersistentArguments[0].Value = argument0;
|
||||
}
|
||||
|
||||
/// <summary>Sets the value of the first and second persistent arguments.</summary>
|
||||
public void SetArguments(object argument0, object argument1)
|
||||
{
|
||||
PersistentArguments[0].Value = argument0;
|
||||
PersistentArguments[1].Value = argument1;
|
||||
}
|
||||
|
||||
/// <summary>Sets the value of the first, second, and third persistent arguments.</summary>
|
||||
public void SetArguments(object argument0, object argument1, object argument2)
|
||||
{
|
||||
PersistentArguments[0].Value = argument0;
|
||||
PersistentArguments[1].Value = argument1;
|
||||
PersistentArguments[2].Value = argument2;
|
||||
}
|
||||
|
||||
/// <summary>Sets the value of the first, second, third, and fourth persistent arguments.</summary>
|
||||
public void SetArguments(object argument0, object argument1, object argument2, object argument3)
|
||||
{
|
||||
PersistentArguments[0].Value = argument0;
|
||||
PersistentArguments[1].Value = argument1;
|
||||
PersistentArguments[2].Value = argument2;
|
||||
PersistentArguments[3].Value = argument3;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal void GetMethodDetails(out Type declaringType, out string methodName)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// If you think this looks retarded, that's because it is.
|
||||
|
||||
// Sometimes Unity ends up with an old reference to an object where the reference thinks it has been
|
||||
// destroyed even though it hasn't and it still has a value Instance ID. So we just get a new reference.
|
||||
|
||||
if (_Target == null && !ReferenceEquals(_Target, null))
|
||||
_Target = UnityEditor.EditorUtility.InstanceIDToObject(_Target.GetInstanceID());
|
||||
#endif
|
||||
|
||||
GetMethodDetails(_MethodName, _Target, out declaringType, out methodName);
|
||||
}
|
||||
|
||||
internal static void GetMethodDetails(string serializedMethodName, Object target, out Type declaringType, out string methodName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(serializedMethodName))
|
||||
{
|
||||
declaringType = null;
|
||||
methodName = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
var lastDot = serializedMethodName.LastIndexOf('.');
|
||||
if (lastDot < 0)
|
||||
{
|
||||
declaringType = null;
|
||||
methodName = serializedMethodName;
|
||||
}
|
||||
else
|
||||
{
|
||||
declaringType = Type.GetType(serializedMethodName.Substring(0, lastDot));
|
||||
lastDot++;
|
||||
methodName = serializedMethodName.Substring(lastDot, serializedMethodName.Length - lastDot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
declaringType = target.GetType();
|
||||
methodName = serializedMethodName;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified `type` can be represented by a non-linked <see cref="PersistentArgument"/>.
|
||||
/// </summary>
|
||||
public static bool IsSupportedNative(Type type)
|
||||
{
|
||||
return
|
||||
type == typeof(bool) ||
|
||||
type == typeof(string) ||
|
||||
type == typeof(int) ||
|
||||
(type.IsEnum && Enum.GetUnderlyingType(type) == typeof(int)) ||
|
||||
type == typeof(float) ||
|
||||
type == typeof(Vector2) ||
|
||||
type == typeof(Vector3) ||
|
||||
type == typeof(Vector4) ||
|
||||
type == typeof(Quaternion) ||
|
||||
type == typeof(Color) ||
|
||||
type == typeof(Color32) ||
|
||||
type == typeof(Rect) ||
|
||||
type == typeof(Object) || type.IsSubclassOf(typeof(Object));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type of each of the `parameters` can be represented by a non-linked <see cref="PersistentArgument"/>.
|
||||
/// </summary>
|
||||
public static bool IsSupportedNative(ParameterInfo[] parameters)
|
||||
{
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (!IsSupportedNative(parameters[i].ParameterType))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Copies the contents of the `target` call to this call.</summary>
|
||||
public void CopyFrom(PersistentCall target)
|
||||
{
|
||||
_Target = target._Target;
|
||||
_MethodName = target._MethodName;
|
||||
_Method = target._Method;
|
||||
|
||||
_PersistentArguments = new PersistentArgument[target._PersistentArguments.Length];
|
||||
for (int i = 0; i < _PersistentArguments.Length; i++)
|
||||
{
|
||||
_PersistentArguments[i] = target._PersistentArguments[i].Clone();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a description of this call.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
ToString(text);
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Appends a description of this call.</summary>
|
||||
public void ToString(StringBuilder text)
|
||||
{
|
||||
text.Append("PersistentCall: MethodName=");
|
||||
text.Append(_MethodName);
|
||||
text.Append(", Target=");
|
||||
text.Append(_Target != null ? _Target.ToString() : "null");
|
||||
text.Append(", PersistentArguments=");
|
||||
UltEventUtils.AppendDeepToString(text, _PersistentArguments.GetEnumerator(), "\n ");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 53ff8c27454b3e640a33652a4b3a5223
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that takes a single <see cref="Collider2D"/> parameter.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public sealed class TriggerEvent2D : UltEvent<Collider2D> { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> 2D trigger events:
|
||||
/// <see cref="OnTriggerEnter2D"/>, <see cref="OnTriggerStay2D"/>, and <see cref="OnTriggerExit2D"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Trigger Events 2D")]
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/TriggerEvents2D")]
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class TriggerEvents2D : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TriggerEvent2D _TriggerEnterEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnTriggerEnter2D"/>.</summary>
|
||||
public TriggerEvent2D TriggerEnterEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_TriggerEnterEvent == null)
|
||||
_TriggerEnterEvent = new TriggerEvent2D();
|
||||
return _TriggerEnterEvent;
|
||||
}
|
||||
set { _TriggerEnterEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="TriggerEnterEvent"/>.</summary>
|
||||
public virtual void OnTriggerEnter2D(Collider2D collider)
|
||||
{
|
||||
if (_TriggerEnterEvent != null)
|
||||
_TriggerEnterEvent.Invoke(collider);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TriggerEvent2D _TriggerStayEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnTriggerStay2D"/>.</summary>
|
||||
public TriggerEvent2D TriggerStayEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_TriggerStayEvent == null)
|
||||
_TriggerStayEvent = new TriggerEvent2D();
|
||||
return _TriggerStayEvent;
|
||||
}
|
||||
set { _TriggerStayEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="TriggerStayEvent"/>.</summary>
|
||||
public virtual void OnTriggerStay2D(Collider2D collider)
|
||||
{
|
||||
if (_TriggerStayEvent != null)
|
||||
_TriggerStayEvent.Invoke(collider);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TriggerEvent2D _TriggerExitEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnTriggerExit2D"/>.</summary>
|
||||
public TriggerEvent2D TriggerExitEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_TriggerExitEvent == null)
|
||||
_TriggerExitEvent = new TriggerEvent2D();
|
||||
return _TriggerExitEvent;
|
||||
}
|
||||
set { _TriggerExitEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="TriggerExitEvent"/>.</summary>
|
||||
public virtual void OnTriggerExit2D(Collider2D collider)
|
||||
{
|
||||
if (_TriggerExitEvent != null)
|
||||
_TriggerExitEvent.Invoke(collider);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 02d58947ad6b87a4fa5b09899b6bdf85
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that takes a single <see cref="Collider"/> parameter.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public sealed class TriggerEvent3D : UltEvent<Collider> { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> trigger events:
|
||||
/// <see cref="OnTriggerEnter"/>, <see cref="OnTriggerStay"/>, and <see cref="OnTriggerExit"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Trigger Events 3D")]
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/TriggerEvents3D")]
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class TriggerEvents3D : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TriggerEvent3D _TriggerEnterEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnTriggerEnter"/>.</summary>
|
||||
public TriggerEvent3D TriggerEnterEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_TriggerEnterEvent == null)
|
||||
_TriggerEnterEvent = new TriggerEvent3D();
|
||||
return _TriggerEnterEvent;
|
||||
}
|
||||
set { _TriggerEnterEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="TriggerEnterEvent"/>.</summary>
|
||||
public virtual void OnTriggerEnter(Collider collider)
|
||||
{
|
||||
if (_TriggerEnterEvent != null)
|
||||
_TriggerEnterEvent.Invoke(collider);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TriggerEvent3D _TriggerStayEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnTriggerStay"/>.</summary>
|
||||
public TriggerEvent3D TriggerStayEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_TriggerStayEvent == null)
|
||||
_TriggerStayEvent = new TriggerEvent3D();
|
||||
return _TriggerStayEvent;
|
||||
}
|
||||
set { _TriggerStayEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="TriggerStayEvent"/>.</summary>
|
||||
public virtual void OnTriggerStay(Collider collider)
|
||||
{
|
||||
if (_TriggerStayEvent != null)
|
||||
_TriggerStayEvent.Invoke(collider);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TriggerEvent3D _TriggerExitEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="OnTriggerExit"/>.</summary>
|
||||
public TriggerEvent3D TriggerExitEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_TriggerExitEvent == null)
|
||||
_TriggerExitEvent = new TriggerEvent3D();
|
||||
return _TriggerExitEvent;
|
||||
}
|
||||
set { _TriggerExitEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="TriggerExitEvent"/>.</summary>
|
||||
public virtual void OnTriggerExit(Collider collider)
|
||||
{
|
||||
if (_TriggerExitEvent != null)
|
||||
_TriggerExitEvent.Invoke(collider);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f8e0c45775f1b8f4b9a24196038d1b93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to expose the add and remove methods of an <see cref="UltEvent"/> without exposing the rest of its
|
||||
/// members such as the ability to invoke it.
|
||||
/// </summary>
|
||||
public interface IUltEvent : IUltEventBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="UltEvent.Invoke"/> after all
|
||||
/// <see cref="UltEvent.PersistentCalls"/>.
|
||||
/// </summary>
|
||||
event Action DynamicCalls;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A serializable event with no parameters which can be viewed and configured in the inspector.
|
||||
/// <para></para>
|
||||
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class UltEvent : UltEventBase, IUltEvent
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override int ParameterCount { get { return 0; } }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
|
||||
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
|
||||
/// </summary>
|
||||
public event Action PersistentCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
AddPersistentCall(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
RemovePersistentCall(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Action _DynamicCalls;
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
public event Action DynamicCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
_DynamicCalls += value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
remove
|
||||
{
|
||||
_DynamicCalls -= value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The non-serialized method and parameter details of this event.
|
||||
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
protected override Delegate DynamicCallsBase
|
||||
{
|
||||
get { return _DynamicCalls; }
|
||||
set { _DynamicCalls = value as Action; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Operators and Call Registration
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent operator +(UltEvent e, Action method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls += method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls += method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent operator -(UltEvent e, Action method)
|
||||
{
|
||||
if (e == null)
|
||||
return null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls -= method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls -= method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
|
||||
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static implicit operator UltEvent(Action method)
|
||||
{
|
||||
if (method != null)
|
||||
{
|
||||
var e = new UltEvent();
|
||||
e += method;
|
||||
return e;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void AddDynamicCall(ref UltEvent e, Action method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent();
|
||||
|
||||
e.DynamicCalls += method;
|
||||
}
|
||||
|
||||
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void RemoveDynamicCall(ref UltEvent e, Action method)
|
||||
{
|
||||
if (e != null)
|
||||
e.DynamicCalls -= method;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
|
||||
public override Type[] ParameterTypes { get { return Type.EmptyTypes; } }
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX(UltEvent)"/>.
|
||||
/// </summary>
|
||||
public void Invoke()
|
||||
{
|
||||
InvokePersistentCalls();
|
||||
if (_DynamicCalls != null)
|
||||
_DynamicCalls();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
|
||||
/// which logs any exceptions that are thrown.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX(UltEvent)"/>.
|
||||
/// </summary>
|
||||
public void InvokeSafe()
|
||||
{
|
||||
try
|
||||
{
|
||||
Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e557f67c477e23749895b48416ac09b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to expose the add and remove methods of an <see cref="UltEvent{T0}"/> without exposing the rest of its
|
||||
/// members such as the ability to invoke it.
|
||||
/// </summary>
|
||||
public interface IUltEvent<T0> : IUltEventBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="UltEvent{T0}.Invoke"/> after all
|
||||
/// <see cref="UltEvent{T0}.PersistentCalls"/>.
|
||||
/// </summary>
|
||||
event Action<T0> DynamicCalls;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A serializable event with 1 parameter which can be viewed and configured in the inspector.
|
||||
/// <para></para>
|
||||
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent{T0}"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class UltEvent<T0> : UltEventBase, IUltEvent<T0>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override int ParameterCount { get { return 1; } }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
|
||||
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
|
||||
/// </summary>
|
||||
public event Action<T0> PersistentCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
AddPersistentCall(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
RemovePersistentCall(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Action<T0> _DynamicCalls;
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
public event Action<T0> DynamicCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
_DynamicCalls += value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
remove
|
||||
{
|
||||
_DynamicCalls -= value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The non-serialized method and parameter details of this event.
|
||||
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
protected override Delegate DynamicCallsBase
|
||||
{
|
||||
get { return _DynamicCalls; }
|
||||
set { _DynamicCalls = value as Action<T0>; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Operators and Call Registration
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent<T0> operator +(UltEvent<T0> e, Action<T0> method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent<T0>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls += method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls += method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent<T0> operator -(UltEvent<T0> e, Action<T0> method)
|
||||
{
|
||||
if (e == null)
|
||||
return null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls -= method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls -= method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
|
||||
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static implicit operator UltEvent<T0>(Action<T0> method)
|
||||
{
|
||||
if (method != null)
|
||||
{
|
||||
var e = new UltEvent<T0>();
|
||||
e += method;
|
||||
return e;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void AddDynamicCall(ref UltEvent<T0> e, Action<T0> method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent<T0>();
|
||||
|
||||
e.DynamicCalls += method;
|
||||
}
|
||||
|
||||
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void RemoveDynamicCall(ref UltEvent<T0> e, Action<T0> method)
|
||||
{
|
||||
if (e != null)
|
||||
e.DynamicCalls -= method;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
|
||||
public override Type[] ParameterTypes { get { return _ParameterTypes; } }
|
||||
private static Type[] _ParameterTypes = new Type[] { typeof(T0), };
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX{T0}(UltEvent{T0}, T0)"/>.
|
||||
/// </summary>
|
||||
public virtual void Invoke(T0 parameter0)
|
||||
{
|
||||
CacheParameter(parameter0);
|
||||
InvokePersistentCalls();
|
||||
if (_DynamicCalls != null)
|
||||
_DynamicCalls(parameter0);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
|
||||
/// which logs any exceptions that are thrown.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX{T0}(UltEvent{T0}, T0)"/>.
|
||||
/// </summary>
|
||||
public virtual void InvokeSafe(T0 parameter0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Invoke(parameter0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eea129fa20e124a4fa9c0c54b16260c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to expose the add and remove methods of an <see cref="UltEvent{T0, T1}"/> without exposing the rest of its
|
||||
/// members such as the ability to invoke it.
|
||||
/// </summary>
|
||||
public interface IUltEvent<T0, T1> : IUltEventBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="UltEvent{T0, T1}.Invoke"/> after all
|
||||
/// <see cref="UltEvent{T0, T1}.PersistentCalls"/>.
|
||||
/// </summary>
|
||||
event Action<T0, T1> DynamicCalls;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A serializable event with 2 parameters which can be viewed and configured in the inspector.
|
||||
/// <para></para>
|
||||
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent{T0, T1}"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class UltEvent<T0, T1> : UltEventBase, IUltEvent<T0, T1>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override int ParameterCount { get { return 2; } }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
|
||||
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
|
||||
/// </summary>
|
||||
public event Action<T0, T1> PersistentCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
AddPersistentCall(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
RemovePersistentCall(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Action<T0, T1> _DynamicCalls;
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
public event Action<T0, T1> DynamicCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
_DynamicCalls += value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
remove
|
||||
{
|
||||
_DynamicCalls -= value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The non-serialized method and parameter details of this event.
|
||||
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
protected override Delegate DynamicCallsBase
|
||||
{
|
||||
get { return _DynamicCalls; }
|
||||
set { _DynamicCalls = value as Action<T0, T1>; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Operators and Call Registration
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent<T0, T1> operator +(UltEvent<T0, T1> e, Action<T0, T1> method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent<T0, T1>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls += method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls += method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent<T0, T1> operator -(UltEvent<T0, T1> e, Action<T0, T1> method)
|
||||
{
|
||||
if (e == null)
|
||||
return null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls -= method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls -= method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
|
||||
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static implicit operator UltEvent<T0, T1>(Action<T0, T1> method)
|
||||
{
|
||||
if (method != null)
|
||||
{
|
||||
var e = new UltEvent<T0, T1>();
|
||||
e += method;
|
||||
return e;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void AddDynamicCall(ref UltEvent<T0, T1> e, Action<T0, T1> method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent<T0, T1>();
|
||||
|
||||
e.DynamicCalls += method;
|
||||
}
|
||||
|
||||
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void RemoveDynamicCall(ref UltEvent<T0, T1> e, Action<T0, T1> method)
|
||||
{
|
||||
if (e != null)
|
||||
e.DynamicCalls -= method;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
|
||||
public override Type[] ParameterTypes { get { return _ParameterTypes; } }
|
||||
private static Type[] _ParameterTypes = new Type[] { typeof(T0), typeof(T1) };
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1}(UltEvent{T0, T1}, T0, T1)"/>.
|
||||
/// </summary>
|
||||
public virtual void Invoke(T0 parameter0, T1 parameter1)
|
||||
{
|
||||
CacheParameter(parameter0);
|
||||
CacheParameter(parameter1);
|
||||
InvokePersistentCalls();
|
||||
if (_DynamicCalls != null)
|
||||
_DynamicCalls(parameter0, parameter1);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
|
||||
/// which logs any exceptions that are thrown.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1}(UltEvent{T0, T1}, T0, T1)"/>.
|
||||
/// </summary>
|
||||
public virtual void InvokeSafe(T0 parameter0, T1 parameter1)
|
||||
{
|
||||
try
|
||||
{
|
||||
Invoke(parameter0, parameter1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a65b41d87f4dd7d4499a89facfbaa21e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to expose the add and remove methods of an <see cref="UltEvent{T0, T1, T2}"/> without exposing the rest of its
|
||||
/// members such as the ability to invoke it.
|
||||
/// </summary>
|
||||
public interface IUltEvent<T0, T1, T2> : IUltEventBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="UltEvent{T0, T1, T2}.Invoke"/> after all
|
||||
/// <see cref="UltEvent{T0, T1, T2}.PersistentCalls"/>.
|
||||
/// </summary>
|
||||
event Action<T0, T1, T2> DynamicCalls;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A serializable event with 3 parameters which can be viewed and configured in the inspector.
|
||||
/// <para></para>
|
||||
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent{T0, T1, T2}"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class UltEvent<T0, T1, T2> : UltEventBase, IUltEvent<T0, T1, T2>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override int ParameterCount { get { return 3; } }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
|
||||
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
|
||||
/// </summary>
|
||||
public event Action<T0, T1, T2> PersistentCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
AddPersistentCall(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
RemovePersistentCall(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Action<T0, T1, T2> _DynamicCalls;
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
public event Action<T0, T1, T2> DynamicCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
_DynamicCalls += value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
remove
|
||||
{
|
||||
_DynamicCalls -= value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The non-serialized method and parameter details of this event.
|
||||
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
protected override Delegate DynamicCallsBase
|
||||
{
|
||||
get { return _DynamicCalls; }
|
||||
set { _DynamicCalls = value as Action<T0, T1, T2>; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Operators and Call Registration
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent<T0, T1, T2> operator +(UltEvent<T0, T1, T2> e, Action<T0, T1, T2> method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent<T0, T1, T2>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls += method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls += method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent<T0, T1, T2> operator -(UltEvent<T0, T1, T2> e, Action<T0, T1, T2> method)
|
||||
{
|
||||
if (e == null)
|
||||
return null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls -= method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls -= method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
|
||||
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static implicit operator UltEvent<T0, T1, T2>(Action<T0, T1, T2> method)
|
||||
{
|
||||
if (method != null)
|
||||
{
|
||||
var e = new UltEvent<T0, T1, T2>();
|
||||
e += method;
|
||||
return e;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void AddDynamicCall(ref UltEvent<T0, T1, T2> e, Action<T0, T1, T2> method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent<T0, T1, T2>();
|
||||
|
||||
e.DynamicCalls += method;
|
||||
}
|
||||
|
||||
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void RemoveDynamicCall(ref UltEvent<T0, T1, T2> e, Action<T0, T1, T2> method)
|
||||
{
|
||||
if (e != null)
|
||||
e.DynamicCalls -= method;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
|
||||
public override Type[] ParameterTypes { get { return _ParameterTypes; } }
|
||||
private static Type[] _ParameterTypes = new Type[] { typeof(T0), typeof(T1), typeof(T2) };
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1, T2}(UltEvent{T0, T1, T2}, T0, T1, T2)"/>.
|
||||
/// </summary>
|
||||
public virtual void Invoke(T0 parameter0, T1 parameter1, T2 parameter2)
|
||||
{
|
||||
CacheParameter(parameter0);
|
||||
CacheParameter(parameter1);
|
||||
CacheParameter(parameter2);
|
||||
InvokePersistentCalls();
|
||||
if (_DynamicCalls != null)
|
||||
_DynamicCalls(parameter0, parameter1, parameter2);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
|
||||
/// which logs any exceptions that are thrown.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1, T2}(UltEvent{T0, T1, T2}, T0, T1, T2)"/>.
|
||||
/// </summary>
|
||||
public virtual void InvokeSafe(T0 parameter0, T1 parameter1, T2 parameter2)
|
||||
{
|
||||
try
|
||||
{
|
||||
Invoke(parameter0, parameter1, parameter2);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 85d0d72b73e22a9408cd2c5926d55bb9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to expose the add and remove methods of an <see cref="UltEvent{T0, T1, T2, T3}"/> without exposing the rest of its
|
||||
/// members such as the ability to invoke it.
|
||||
/// </summary>
|
||||
public interface IUltEvent<T0, T1, T2, T3> : IUltEventBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="UltEvent{T0, T1, T2, T3}.Invoke"/> after all
|
||||
/// <see cref="UltEvent{T0, T1, T2, T3}.PersistentCalls"/>.
|
||||
/// </summary>
|
||||
event Action<T0, T1, T2, T3> DynamicCalls;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A serializable event with 4 parameters which can be viewed and configured in the inspector.
|
||||
/// <para></para>
|
||||
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent{T0, T1, T2, T3}"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class UltEvent<T0, T1, T2, T3> : UltEventBase, IUltEvent<T0, T1, T2, T3>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override int ParameterCount { get { return 4; } }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered to this event are serialized as <see cref="PersistentCall"/>s and are invoked by
|
||||
/// <see cref="Invoke"/> before all <see cref="DynamicCalls"/>.
|
||||
/// </summary>
|
||||
public event Action<T0, T1, T2, T3> PersistentCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
AddPersistentCall(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
RemovePersistentCall(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Action<T0, T1, T2, T3> _DynamicCalls;
|
||||
|
||||
/// <summary>
|
||||
/// Delegates registered here are invoked by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
public event Action<T0, T1, T2, T3> DynamicCalls
|
||||
{
|
||||
add
|
||||
{
|
||||
_DynamicCalls += value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
remove
|
||||
{
|
||||
_DynamicCalls -= value;
|
||||
OnDynamicCallsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The non-serialized method and parameter details of this event.
|
||||
/// Delegates registered here are called by <see cref="Invoke"/> after all <see cref="PersistentCalls"/>.
|
||||
/// </summary>
|
||||
protected override Delegate DynamicCallsBase
|
||||
{
|
||||
get { return _DynamicCalls; }
|
||||
set { _DynamicCalls = value as Action<T0, T1, T2, T3>; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Operators and Call Registration
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent<T0, T1, T2, T3> operator +(UltEvent<T0, T1, T2, T3> e, Action<T0, T1, T2, T3> method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent<T0, T1, T2, T3>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls += method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls += method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If `e` isn't null, this method removes `method` from its <see cref="PersistentCalls"/> (if in Edit Mode) or
|
||||
/// <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static UltEvent<T0, T1, T2, T3> operator -(UltEvent<T0, T1, T2, T3> e, Action<T0, T1, T2, T3> method)
|
||||
{
|
||||
if (e == null)
|
||||
return null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && method.Target is Object)
|
||||
{
|
||||
e.PersistentCalls -= method;
|
||||
return e;
|
||||
}
|
||||
#endif
|
||||
|
||||
e.DynamicCalls -= method;
|
||||
return e;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="UltEventBase"/> and adds `method` to its <see cref="PersistentCalls"/> (if in edit
|
||||
/// mode), or <see cref="DynamicCalls"/> (in Play Mode and at runtime).
|
||||
/// </summary>
|
||||
public static implicit operator UltEvent<T0, T1, T2, T3>(Action<T0, T1, T2, T3> method)
|
||||
{
|
||||
if (method != null)
|
||||
{
|
||||
var e = new UltEvent<T0, T1, T2, T3>();
|
||||
e += method;
|
||||
return e;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void AddDynamicCall(ref UltEvent<T0, T1, T2, T3> e, Action<T0, T1, T2, T3> method)
|
||||
{
|
||||
if (e == null)
|
||||
e = new UltEvent<T0, T1, T2, T3>();
|
||||
|
||||
e.DynamicCalls += method;
|
||||
}
|
||||
|
||||
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="DynamicCalls"/>.</summary>
|
||||
public static void RemoveDynamicCall(ref UltEvent<T0, T1, T2, T3> e, Action<T0, T1, T2, T3> method)
|
||||
{
|
||||
if (e != null)
|
||||
e.DynamicCalls -= method;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] The types of each of this event's parameters.</summary>
|
||||
public override Type[] ParameterTypes { get { return _ParameterTypes; } }
|
||||
private static Type[] _ParameterTypes = new Type[] { typeof(T0), typeof(T1), typeof(T2), typeof(T3) };
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="InvokeSafe"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1, T2, T3}(UltEvent{T0, T1, T2, T3}, T0, T1, T2, T3)"/>.
|
||||
/// </summary>
|
||||
public virtual void Invoke(T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3)
|
||||
{
|
||||
CacheParameter(parameter0);
|
||||
CacheParameter(parameter1);
|
||||
CacheParameter(parameter2);
|
||||
CacheParameter(parameter3);
|
||||
InvokePersistentCalls();
|
||||
if (_DynamicCalls != null)
|
||||
_DynamicCalls(parameter0, parameter1, parameter2, parameter3);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/> inside a try/catch block
|
||||
/// which logs any exceptions that are thrown.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="Invoke"/> and <seealso cref="UltEventUtils.InvokeX{T0, T1, T2, T3}(UltEvent{T0, T1, T2, T3}, T0, T1, T2, T3)"/>.
|
||||
/// </summary>
|
||||
public virtual void InvokeSafe(T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3)
|
||||
{
|
||||
try
|
||||
{
|
||||
Invoke(parameter0, parameter1, parameter2, parameter3);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2d08afe36d8c1d443a70d8c9eb14f424
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,466 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to expose the add and remove methods of an <see cref="UltEvent"/> without exposing the rest of its
|
||||
/// members such as the ability to invoke it.
|
||||
/// </summary>
|
||||
public interface IUltEventBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds the specified 'method to the persistent call list.</summary>
|
||||
PersistentCall AddPersistentCall(Delegate method);
|
||||
|
||||
/// <summary>Removes the specified 'method from the persistent call list.</summary>
|
||||
void RemovePersistentCall(Delegate method);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A serializable event which can be viewed and configured in the inspector.
|
||||
/// <para></para>
|
||||
/// This is a more versatile and user friendly implementation than <see cref="UnityEvent"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class UltEventBase : IUltEventBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public abstract int ParameterCount { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
internal List<PersistentCall> _PersistentCalls;
|
||||
|
||||
/// <summary>
|
||||
/// The serialized method and parameter details of this event.
|
||||
/// </summary>
|
||||
public List<PersistentCall> PersistentCallsList
|
||||
{
|
||||
get { return _PersistentCalls; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The non-serialized method and parameter details of this event.
|
||||
/// </summary>
|
||||
protected abstract Delegate DynamicCallsBase { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears the cached invocation list of <see cref="DynamicCallsBase"/>.
|
||||
/// </summary>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
protected void OnDynamicCallsChanged()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
_DynamicCallInvocationList = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal bool HasAnyDynamicCalls()
|
||||
{
|
||||
return DynamicCallsBase != null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Delegate[] _DynamicCallInvocationList;
|
||||
|
||||
internal Delegate[] GetDynamicCallInvocationList()
|
||||
{
|
||||
if (_DynamicCallInvocationList == null && DynamicCallsBase != null)
|
||||
_DynamicCallInvocationList = DynamicCallsBase.GetInvocationList();
|
||||
|
||||
return _DynamicCallInvocationList;
|
||||
}
|
||||
|
||||
internal int GetDynamicCallInvocationListCount()
|
||||
{
|
||||
if (DynamicCallsBase == null)
|
||||
return 0;
|
||||
else
|
||||
return GetDynamicCallInvocationList().Length;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Operators and Call Registration
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCallsList"/>.</summary>
|
||||
public static PersistentCall AddPersistentCall<T>(ref T e, Delegate method) where T : UltEventBase, new()
|
||||
{
|
||||
if (e == null)
|
||||
e = new T();
|
||||
|
||||
return e.AddPersistentCall(method);
|
||||
}
|
||||
|
||||
/// <summary>Ensures that `e` isn't null and adds `method` to its <see cref="PersistentCallsList"/>.</summary>
|
||||
public static PersistentCall AddPersistentCall<T>(ref T e, Action method) where T : UltEventBase, new()
|
||||
{
|
||||
if (e == null)
|
||||
e = new T();
|
||||
|
||||
return e.AddPersistentCall(method);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="PersistentCallsList"/>.</summary>
|
||||
public static void RemovePersistentCall(ref UltEventBase e, Delegate method)
|
||||
{
|
||||
if (e != null)
|
||||
e.RemovePersistentCall(method);
|
||||
}
|
||||
|
||||
/// <summary>If `e` isn't null, this method removes `method` from its <see cref="PersistentCallsList"/>.</summary>
|
||||
public static void RemovePersistentCall(ref UltEventBase e, Action method)
|
||||
{
|
||||
if (e != null)
|
||||
e.RemovePersistentCall(method);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Add the specified 'method to the persistent call list.
|
||||
/// </summary>
|
||||
public PersistentCall AddPersistentCall(Delegate method)
|
||||
{
|
||||
if (_PersistentCalls == null)
|
||||
_PersistentCalls = new List<PersistentCall>(4);
|
||||
|
||||
var call = new PersistentCall(method);
|
||||
_PersistentCalls.Add(call);
|
||||
return call;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the specified 'method from the persistent call list.
|
||||
/// </summary>
|
||||
public void RemovePersistentCall(Delegate method)
|
||||
{
|
||||
if (_PersistentCalls == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _PersistentCalls.Count; i++)
|
||||
{
|
||||
var call = _PersistentCalls[i];
|
||||
if (call.GetMethodSafe() == method.Method && ReferenceEquals(call.Target, method.Target))
|
||||
{
|
||||
_PersistentCalls.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all <see cref="PersistentCalls"/> then all <see cref="DynamicCalls"/>.
|
||||
/// </summary>
|
||||
public void DynamicInvoke(params object[] parameters)
|
||||
{
|
||||
// A larger array would actually work fine, but it probably still means something is wrong.
|
||||
if (parameters.Length != ParameterCount)
|
||||
throw new ArgumentException("Invalid parameter count " +
|
||||
parameters.Length + " should be " + ParameterCount);
|
||||
|
||||
CacheParameters(parameters);
|
||||
InvokePersistentCalls();
|
||||
var dynamicCalls = DynamicCallsBase;
|
||||
if (dynamicCalls != null)
|
||||
dynamicCalls.DynamicInvoke(parameters);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Invokes all <see cref="PersistentCall"/>s registered to this event.</summary>
|
||||
protected void InvokePersistentCalls()
|
||||
{
|
||||
var originalParameterOffset = _ParameterOffset;
|
||||
var originalReturnValueOffset = _ReturnValueOffset;
|
||||
|
||||
try
|
||||
{
|
||||
if (_PersistentCalls != null)
|
||||
{
|
||||
for (int i = 0; i < _PersistentCalls.Count; i++)
|
||||
{
|
||||
var result = _PersistentCalls[i].Invoke();
|
||||
LinkedValueCache.Add(result);
|
||||
_ParameterOffset = originalParameterOffset;
|
||||
_ReturnValueOffset = originalReturnValueOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LinkedValueCache.RemoveRange(originalParameterOffset, LinkedValueCache.Count - originalParameterOffset);
|
||||
_ParameterOffset = _ReturnValueOffset = originalParameterOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Linked Value Cache (Parameters and Returned Values)
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly List<object> LinkedValueCache = new List<object>();
|
||||
private static int _ParameterOffset;
|
||||
private static int _ReturnValueOffset;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal static void UpdateLinkedValueOffsets()
|
||||
{
|
||||
_ParameterOffset = _ReturnValueOffset = LinkedValueCache.Count;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Stores the `parameter` so it can be accessed by <see cref="PersistentCall"/>s.
|
||||
/// </summary>
|
||||
protected static void CacheParameter(object parameter)
|
||||
{
|
||||
LinkedValueCache.Add(parameter);
|
||||
_ReturnValueOffset = LinkedValueCache.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the `parameters` so they can be accessed by <see cref="PersistentCall"/>s.
|
||||
/// </summary>
|
||||
protected static void CacheParameters(object[] parameters)
|
||||
{
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
LinkedValueCache.Add(parameters[i]);
|
||||
|
||||
_ReturnValueOffset = LinkedValueCache.Count;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal static int ReturnedValueCount
|
||||
{
|
||||
get { return LinkedValueCache.Count - _ReturnValueOffset; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal static object GetParameterValue(int index)
|
||||
{
|
||||
return LinkedValueCache[_ParameterOffset + index];
|
||||
}
|
||||
|
||||
internal static object GetReturnedValue(int index)
|
||||
{
|
||||
return LinkedValueCache[_ReturnValueOffset + index];
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Parameter Display
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The type of each of this event's parameters.</summary>
|
||||
public abstract Type[] ParameterTypes { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly Dictionary<Type, ParameterInfo[]>
|
||||
EventTypeToParameters = new Dictionary<Type, ParameterInfo[]>();
|
||||
|
||||
internal ParameterInfo[] Parameters
|
||||
{
|
||||
get
|
||||
{
|
||||
var type = GetType();
|
||||
|
||||
ParameterInfo[] parameters;
|
||||
if (!EventTypeToParameters.TryGetValue(type, out parameters))
|
||||
{
|
||||
var invokeMethod = type.GetMethod("Invoke", ParameterTypes);
|
||||
if (invokeMethod == null || invokeMethod.DeclaringType == typeof(UltEvent) ||
|
||||
invokeMethod.DeclaringType.Name.StartsWith(Names.UltEvent.Class + "`"))
|
||||
{
|
||||
parameters = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters = invokeMethod.GetParameters();
|
||||
}
|
||||
|
||||
EventTypeToParameters.Add(type, parameters);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly Dictionary<Type, string>
|
||||
EventTypeToParameterString = new Dictionary<Type, string>();
|
||||
|
||||
internal string ParameterString
|
||||
{
|
||||
get
|
||||
{
|
||||
var type = GetType();
|
||||
|
||||
string parameters;
|
||||
if (!EventTypeToParameterString.TryGetValue(type, out parameters))
|
||||
{
|
||||
if (ParameterTypes.Length == 0)
|
||||
{
|
||||
parameters = " ()";
|
||||
}
|
||||
else
|
||||
{
|
||||
var invokeMethodParameters = Parameters;
|
||||
|
||||
var text = new StringBuilder();
|
||||
|
||||
text.Append(" (");
|
||||
for (int i = 0; i < ParameterTypes.Length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
text.Append(", ");
|
||||
|
||||
text.Append(ParameterTypes[i].GetNameCS(false));
|
||||
|
||||
if (invokeMethodParameters != null)
|
||||
{
|
||||
text.Append(" ");
|
||||
text.Append(invokeMethodParameters[i].Name);
|
||||
}
|
||||
}
|
||||
text.Append(")");
|
||||
|
||||
parameters = text.ToString();
|
||||
}
|
||||
|
||||
EventTypeToParameterString.Add(type, parameters);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Clears all <see cref="PersistentCallsList"/> and <see cref="DynamicCallsBase"/> registered to this event.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
if (_PersistentCalls != null)
|
||||
_PersistentCalls.Clear();
|
||||
|
||||
DynamicCallsBase = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this event has any <see cref="PersistentCallsList"/> or <see cref="DynamicCallsBase"/> registered.
|
||||
/// </summary>
|
||||
public bool HasCalls
|
||||
{
|
||||
get
|
||||
{
|
||||
return (_PersistentCalls != null && _PersistentCalls.Count > 0) || DynamicCallsBase != null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Copies the contents of this the `target` event to this event.</summary>
|
||||
public virtual void CopyFrom(UltEventBase target)
|
||||
{
|
||||
if (target._PersistentCalls == null)
|
||||
{
|
||||
_PersistentCalls = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_PersistentCalls == null)
|
||||
_PersistentCalls = new List<PersistentCall>();
|
||||
else
|
||||
_PersistentCalls.Clear();
|
||||
|
||||
for (int i = 0; i < target._PersistentCalls.Count; i++)
|
||||
{
|
||||
var call = new PersistentCall();
|
||||
call.CopyFrom(target._PersistentCalls[i]);
|
||||
_PersistentCalls.Add(call);
|
||||
}
|
||||
}
|
||||
|
||||
DynamicCallsBase = target.DynamicCallsBase;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
_DynamicCallInvocationList = target._DynamicCallInvocationList;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a description of this event.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
ToString(text);
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Appends a description of this event.</summary>
|
||||
public void ToString(StringBuilder text)
|
||||
{
|
||||
text.Append(GetType().GetNameCS());
|
||||
|
||||
text.Append(": PersistentCalls=");
|
||||
UltEventUtils.AppendDeepToString(text, _PersistentCalls.GetEnumerator(), "\n ");
|
||||
|
||||
text.Append("\n DynamicCalls=");
|
||||
#if UNITY_EDITOR
|
||||
var invocationList = GetDynamicCallInvocationList();
|
||||
#else
|
||||
var invocationList = DynamicCallsBase != null ? DynamicCallsBase.GetInvocationList() : null;
|
||||
#endif
|
||||
var enumerator = invocationList != null ? invocationList.GetEnumerator() : null;
|
||||
UltEventUtils.AppendDeepToString(text, enumerator, "\n ");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ea0687e0f93957745809295b71ff5394
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// A component which encapsulates a single <see cref="UltEvent"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Ult Event Holder")]
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/UltEventHolder")]
|
||||
public class UltEventHolder : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _Event;
|
||||
|
||||
/// <summary>The encapsulated event.</summary>
|
||||
public UltEvent Event
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Event == null)
|
||||
_Event = new UltEvent();
|
||||
return _Event;
|
||||
}
|
||||
set { _Event = value; }
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calls Event.Invoke().</summary>
|
||||
public virtual void Invoke()
|
||||
{
|
||||
if (_Event != null)
|
||||
_Event.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 289ff940279d54e4e92f632d7d3823dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,548 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>Various utility methods used by <see cref="UltEvents"/>.</summary>
|
||||
public static class UltEventUtils
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The sub-menu which all <see cref="UltEvents"/> components are listed in.</summary>
|
||||
public const string ComponentMenuPrefix = "UltEvents/";
|
||||
|
||||
/// <summary>The address of the online documentation.</summary>
|
||||
public const string DocumentationURL = "https://kybernetik.com.au/ultevents";
|
||||
|
||||
/// <summary>The address of the API documentation.</summary>
|
||||
public const string APIDocumentationURL = DocumentationURL + "/api/UltEvents";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Event Extensions
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls e.Invoke if it isn't null.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="UltEvent.Invoke"/> and <seealso cref="UltEvent.InvokeSafe"/>.
|
||||
/// </summary>
|
||||
public static void InvokeX(this UltEvent e)
|
||||
{
|
||||
if (e != null)
|
||||
e.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls e.Invoke if it isn't null.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="UltEvent{T0}.Invoke"/> and <seealso cref="UltEvent{T0}.InvokeSafe"/>.
|
||||
/// </summary>
|
||||
public static void InvokeX<T0>(this UltEvent<T0> e, T0 parameter0)
|
||||
{
|
||||
if (e != null)
|
||||
e.Invoke(parameter0);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls e.Invoke if it isn't null.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="UltEvent{T0, T1}.Invoke"/> and <seealso cref="UltEvent{T0, T1}.InvokeSafe"/>.
|
||||
/// </summary>
|
||||
public static void InvokeX<T0, T1>(this UltEvent<T0, T1> e, T0 parameter0, T1 parameter1)
|
||||
{
|
||||
if (e != null)
|
||||
e.Invoke(parameter0, parameter1);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls e.Invoke if it isn't null.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="UltEvent{T0, T1, T2}.Invoke"/> and <seealso cref="UltEvent{T0, T1, T2}.InvokeSafe"/>.
|
||||
/// </summary>
|
||||
public static void InvokeX<T0, T1, T2>(this UltEvent<T0, T1, T2> e, T0 parameter0, T1 parameter1, T2 parameter2)
|
||||
{
|
||||
if (e != null)
|
||||
e.Invoke(parameter0, parameter1, parameter2);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls e.Invoke if it isn't null.
|
||||
/// <para></para>
|
||||
/// See also: <seealso cref="UltEvent{T0, T1, T2, T3}.Invoke"/> and <seealso cref="UltEvent{T0, T1, T2, T3}.InvokeSafe"/>.
|
||||
/// </summary>
|
||||
public static void InvokeX<T0, T1, T2, T3>(this UltEvent<T0, T1, T2, T3> e, T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3)
|
||||
{
|
||||
if (e != null)
|
||||
e.Invoke(parameter0, parameter1, parameter2, parameter3);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Type Names
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly Dictionary<Type, string>
|
||||
TypeNames = new Dictionary<Type, string>
|
||||
{
|
||||
{ typeof(object), "object" },
|
||||
{ typeof(void), "void" },
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(string), "string" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(long), "long" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(ulong), "ulong" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<Type, string>
|
||||
FullTypeNames = new Dictionary<Type, string>(TypeNames);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of a `type` as it would appear in C# code.
|
||||
/// <para></para>
|
||||
/// For example, typeof(List<float>).FullName would give you:
|
||||
/// System.Collections.Generic.List`1[[System.Single, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
|
||||
/// <para></para>
|
||||
/// This method would instead return System.Collections.Generic.List<float> if `fullName` is true, or
|
||||
/// just List<float> if it is false.
|
||||
/// <para></para>
|
||||
/// Note that all returned values are stored in a dictionary to speed up repeated use.
|
||||
/// </summary>
|
||||
public static string GetNameCS(this Type type, bool fullName = true)
|
||||
{
|
||||
if (type == null)
|
||||
return "";
|
||||
|
||||
// Check if we have already got the name for that type.
|
||||
var names = fullName ? FullTypeNames : TypeNames;
|
||||
string name;
|
||||
if (names.TryGetValue(type, out name))
|
||||
return name;
|
||||
|
||||
var text = new StringBuilder();
|
||||
|
||||
if (type.IsArray)// Array = TypeName[].
|
||||
{
|
||||
text.Append(type.GetElementType().GetNameCS(fullName));
|
||||
|
||||
text.Append('[');
|
||||
var dimensions = type.GetArrayRank();
|
||||
while (dimensions-- > 1)
|
||||
text.Append(",");
|
||||
text.Append(']');
|
||||
|
||||
goto Return;
|
||||
}
|
||||
|
||||
if (type.IsPointer)// Pointer = TypeName*.
|
||||
{
|
||||
text.Append(type.GetElementType().GetNameCS(fullName));
|
||||
text.Append('*');
|
||||
|
||||
goto Return;
|
||||
}
|
||||
|
||||
if (type.IsGenericParameter)// Generic Parameter = TypeName (for unspecified generic parameters).
|
||||
{
|
||||
text.Append(type.Name);
|
||||
goto Return;
|
||||
}
|
||||
|
||||
var underlyingType = Nullable.GetUnderlyingType(type);
|
||||
if (underlyingType != null)// Nullable = TypeName?.
|
||||
{
|
||||
text.Append(underlyingType.GetNameCS(fullName));
|
||||
text.Append('?');
|
||||
|
||||
goto Return;
|
||||
}
|
||||
|
||||
// Other Type = Namespace.NestedTypes.TypeName<GenericArguments>.
|
||||
|
||||
if (fullName && type.Namespace != null)// Namespace.
|
||||
{
|
||||
text.Append(type.Namespace);
|
||||
text.Append('.');
|
||||
}
|
||||
|
||||
var genericArguments = 0;
|
||||
|
||||
if (type.DeclaringType != null)// Account for Nested Types.
|
||||
{
|
||||
// Count the nesting level.
|
||||
var nesting = 1;
|
||||
var declaringType = type.DeclaringType;
|
||||
while (declaringType.DeclaringType != null)
|
||||
{
|
||||
declaringType = declaringType.DeclaringType;
|
||||
nesting++;
|
||||
}
|
||||
|
||||
// Append the name of each outer type, starting from the outside.
|
||||
while (nesting-- > 0)
|
||||
{
|
||||
// Walk out to the current nesting level.
|
||||
// This avoids the need to make a list of types in the nest or to insert type names instead of appending them.
|
||||
declaringType = type;
|
||||
for (int i = nesting; i >= 0; i--)
|
||||
declaringType = declaringType.DeclaringType;
|
||||
|
||||
// Nested Type Name.
|
||||
genericArguments = AppendNameAndGenericArguments(text, declaringType, fullName, genericArguments);
|
||||
text.Append('.');
|
||||
}
|
||||
}
|
||||
|
||||
// Type Name.
|
||||
AppendNameAndGenericArguments(text, type, fullName, genericArguments);
|
||||
|
||||
Return:// Remember and return the name.
|
||||
name = text.ToString();
|
||||
names.Add(type, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Appends the generic arguments of `type` (after skipping the specified number).
|
||||
/// </summary>
|
||||
public static int AppendNameAndGenericArguments(StringBuilder text, Type type, bool fullName = true, int skipGenericArguments = 0)
|
||||
{
|
||||
text.Append(type.Name);
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var backQuote = type.Name.IndexOf('`');
|
||||
if (backQuote >= 0)
|
||||
{
|
||||
text.Length -= type.Name.Length - backQuote;
|
||||
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
if (skipGenericArguments < genericArguments.Length)
|
||||
{
|
||||
text.Append('<');
|
||||
|
||||
var firstArgument = genericArguments[skipGenericArguments];
|
||||
skipGenericArguments++;
|
||||
|
||||
if (firstArgument.IsGenericParameter)
|
||||
{
|
||||
while (skipGenericArguments < genericArguments.Length)
|
||||
{
|
||||
text.Append(',');
|
||||
skipGenericArguments++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Append(firstArgument.GetNameCS(fullName));
|
||||
|
||||
while (skipGenericArguments < genericArguments.Length)
|
||||
{
|
||||
text.Append(", ");
|
||||
text.Append(genericArguments[skipGenericArguments].GetNameCS(fullName));
|
||||
skipGenericArguments++;
|
||||
}
|
||||
}
|
||||
|
||||
text.Append('>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return skipGenericArguments;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Member Names
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full name of a `member` as it would appear in C# code.
|
||||
/// <para></para>
|
||||
/// For example, passing this method info in as its own parameter would return "<see cref="UltEventUtils"/>.GetNameCS".
|
||||
/// <para></para>
|
||||
/// Note that when `member` is a <see cref="Type"/>, this method calls <see cref="GetNameCS(Type, bool)"/> instead.
|
||||
/// </summary>
|
||||
public static string GetNameCS(this MemberInfo member, bool fullName = true)
|
||||
{
|
||||
if (member == null)
|
||||
return "null";
|
||||
|
||||
var type = member as Type;
|
||||
if (type != null)
|
||||
return type.GetNameCS(fullName);
|
||||
|
||||
var text = new StringBuilder();
|
||||
|
||||
if (member.DeclaringType != null)
|
||||
{
|
||||
text.Append(member.DeclaringType.GetNameCS(fullName));
|
||||
text.Append('.');
|
||||
}
|
||||
|
||||
text.Append(member.Name);
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Appends the full name of a `member` as it would appear in C# code.
|
||||
/// <para></para>
|
||||
/// For example, passing this method info in as its own parameter would append "<see cref="UltEventUtils"/>.AppendName".
|
||||
/// <para></para>
|
||||
/// Note that when `member` is a <see cref="Type"/>, this method calls <see cref="GetNameCS(Type, bool)"/> instead.
|
||||
/// </summary>
|
||||
public static StringBuilder AppendNameCS(this StringBuilder text, MemberInfo member, bool fullName = true)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
text.Append("null");
|
||||
return text;
|
||||
}
|
||||
|
||||
var type = member as Type;
|
||||
if (type != null)
|
||||
{
|
||||
text.Append(type.GetNameCS(fullName));
|
||||
return text;
|
||||
}
|
||||
|
||||
if (member.DeclaringType != null)
|
||||
{
|
||||
text.Append(member.DeclaringType.GetNameCS(fullName));
|
||||
text.Append('.');
|
||||
}
|
||||
|
||||
text.Append(member.Name);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Deep to String
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a string containing the value of each element in `collection`.</summary>
|
||||
public static string DeepToString(this IEnumerable collection, string separator)
|
||||
{
|
||||
if (collection == null)
|
||||
return "null";
|
||||
else
|
||||
return collection.GetEnumerator().DeepToString(separator);
|
||||
}
|
||||
|
||||
/// <summary>Returns a string containing the value of each element in `collection` (each on a new line).</summary>
|
||||
public static string DeepToString(this IEnumerable collection)
|
||||
{
|
||||
return collection.DeepToString(Environment.NewLine);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Each element returned by `enumerator` is appended to `text`.</summary>
|
||||
public static void AppendDeepToString(StringBuilder text, IEnumerator enumerator, string separator)
|
||||
{
|
||||
text.Append("[]");
|
||||
var countIndex = text.Length - 1;
|
||||
var count = 0;
|
||||
|
||||
if (enumerator != null)
|
||||
{
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
text.Append(separator);
|
||||
text.Append('[');
|
||||
text.Append(count);
|
||||
text.Append("] = ");
|
||||
text.Append(enumerator.Current);
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
text.Insert(countIndex, count);
|
||||
}
|
||||
|
||||
/// <summary>Returns a string containing the value of each element in `enumerator`.</summary>
|
||||
public static string DeepToString(this IEnumerator enumerator, string separator)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
AppendDeepToString(text, enumerator, separator);
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Returns a string containing the value of each element in `enumerator` (each on a new line).</summary>
|
||||
public static string DeepToString(this IEnumerator enumerator)
|
||||
{
|
||||
return enumerator.DeepToString(Environment.NewLine);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Commonly used <see cref="BindingFlags"/>.</summary>
|
||||
public const BindingFlags
|
||||
AnyAccessBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Commonly used <see cref="BindingFlags"/>.</summary>
|
||||
public const BindingFlags
|
||||
InstanceBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Commonly used <see cref="BindingFlags"/>.</summary>
|
||||
public const BindingFlags
|
||||
StaticBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="MemberInfo.DeclaringType"/> for constructors or <see cref="MethodInfo.ReturnType"/>
|
||||
/// for regular methods.
|
||||
/// </summary>
|
||||
public static Type GetReturnType(this MethodBase method)
|
||||
{
|
||||
return method.IsConstructor ? method.DeclaringType : (method as MethodInfo).ReturnType;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns "AssemblyQualifiedName.MethodName".</summary>
|
||||
public static string GetFullyQualifiedName(MethodBase method)
|
||||
{
|
||||
return method.DeclaringType.AssemblyQualifiedName + "." + method.Name;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the number of removals, inserts, and replacements needed to turn `a` into `b`.
|
||||
/// </summary>
|
||||
public static int CalculateLevenshteinDistance(string a, string b)
|
||||
{
|
||||
if (string.IsNullOrEmpty(a))
|
||||
{
|
||||
return string.IsNullOrEmpty(b) ? 0 : b.Length;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(b))
|
||||
return a.Length;
|
||||
|
||||
var n = a.Length;
|
||||
var m = b.Length;
|
||||
var d = new int[n + 1, m + 1];
|
||||
|
||||
// initialise the top and right of the table to 0, 1, 2, ...
|
||||
for (int i = 0; i <= n; d[i, 0] = i++)
|
||||
{
|
||||
// Execution is contained in the For statement.
|
||||
}
|
||||
|
||||
for (int j = 0; j <= m; d[0, j] = j++)
|
||||
{
|
||||
// Execution is contained in the For statement.
|
||||
}
|
||||
|
||||
for (int i = 1; i <= n; i++)
|
||||
{
|
||||
for (int j = 1; j <= m; j++)
|
||||
{
|
||||
var cost = (b[j - 1] == a[i - 1]) ? 0 : 1;
|
||||
|
||||
d[i, j] = Mathf.Min(
|
||||
Mathf.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
|
||||
d[i - 1, j - 1] + cost);
|
||||
}
|
||||
}
|
||||
|
||||
return d[n, m];
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Sorts `list`, maintaining the order of any elements with an identical comparison
|
||||
/// (unlike the standard <see cref="List{T}.Sort(Comparison{T})"/> method).
|
||||
/// </summary>
|
||||
public static void StableInsertionSort<T>(IList<T> list, Comparison<T> comparison)
|
||||
{
|
||||
var count = list.Count;
|
||||
for (int j = 1; j < count; j++)
|
||||
{
|
||||
var key = list[j];
|
||||
|
||||
var i = j - 1;
|
||||
for (; i >= 0 && comparison(list[i], key) > 0; i--)
|
||||
{
|
||||
list[i + 1] = list[i];
|
||||
}
|
||||
list[i + 1] = key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts `list`, maintaining the order of any elements with an identical comparison
|
||||
/// (unlike the standard <see cref="List{T}.Sort()"/> method).
|
||||
/// </summary>
|
||||
public static void StableInsertionSort<T>(IList<T> list) where T : IComparable<T>
|
||||
{
|
||||
StableInsertionSort(list, (a, b) => a.CompareTo(b));
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Translates a zero based index to a placement name: 0 = "1st", 1 = "2nd", etc.
|
||||
/// </summary>
|
||||
public static string GetPlacementName(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return "1st";
|
||||
case 1: return "2nd";
|
||||
case 2: return "3rd";
|
||||
default: return index + "th";
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 53baa194aa4fb3a4abdd09b83c4d3b79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// UltEvents // Copyright 2021 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds <see cref="UltEvent"/>s which are called by various <see cref="MonoBehaviour"/> update events:
|
||||
/// <see cref="Update"/>, <see cref="LateUpdate"/>, and <see cref="FixedUpdate"/>.
|
||||
/// </summary>
|
||||
[AddComponentMenu(UltEventUtils.ComponentMenuPrefix + "Update Events")]
|
||||
[HelpURL(UltEventUtils.APIDocumentationURL + "/UpdateEvents")]
|
||||
[DisallowMultipleComponent]
|
||||
public class UpdateEvents : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _UpdateEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="Update"/>.</summary>
|
||||
public UltEvent UpdateEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_UpdateEvent == null)
|
||||
_UpdateEvent = new UltEvent();
|
||||
return _UpdateEvent;
|
||||
}
|
||||
set { _UpdateEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="UpdateEvent"/>.</summary>
|
||||
public virtual void Update()
|
||||
{
|
||||
if (_UpdateEvent != null)
|
||||
_UpdateEvent.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _LateUpdateEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="LateUpdate"/>.</summary>
|
||||
public UltEvent LateUpdateEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_LateUpdateEvent == null)
|
||||
_LateUpdateEvent = new UltEvent();
|
||||
return _LateUpdateEvent;
|
||||
}
|
||||
set { _LateUpdateEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="LateUpdateEvent"/>.</summary>
|
||||
public virtual void LateUpdate()
|
||||
{
|
||||
if (_LateUpdateEvent != null)
|
||||
_LateUpdateEvent.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private UltEvent _FixedUpdateEvent;
|
||||
|
||||
/// <summary>Invoked by <see cref="FixedUpdate"/>.</summary>
|
||||
public UltEvent FixedUpdateEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_FixedUpdateEvent == null)
|
||||
_FixedUpdateEvent = new UltEvent();
|
||||
return _FixedUpdateEvent;
|
||||
}
|
||||
set { _FixedUpdateEvent = value; }
|
||||
}
|
||||
|
||||
/// <summary>Invokes <see cref="FixedUpdateEvent"/>.</summary>
|
||||
public virtual void FixedUpdate()
|
||||
{
|
||||
if (_FixedUpdateEvent != null)
|
||||
_FixedUpdateEvent.Invoke();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: faa4e296e5a9d63458b22f3715d61570
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Add table
Add a link
Reference in a new issue