diff --git a/Editor/SerializeReferenceSelector.meta b/Editor/SerializeReferenceSelector.meta
new file mode 100644
index 0000000..e1def37
--- /dev/null
+++ b/Editor/SerializeReferenceSelector.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 070a846b9b875f042b9b4ac4ac4e4cc5
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/SerializeReferenceSelector/AdvancedTypePopup.cs b/Editor/SerializeReferenceSelector/AdvancedTypePopup.cs
new file mode 100644
index 0000000..c509058
--- /dev/null
+++ b/Editor/SerializeReferenceSelector/AdvancedTypePopup.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+using UnityEditor.IMGUI.Controls;
+
+namespace ubco.ovilab.HPUI.Editor
+{
+ ///
+ /// Items used in
+ ///
+ public class AdvancedTypePopupItem : AdvancedDropdownItem
+ {
+ ///
+ /// The representing the item.
+ ///
+ public Type Type { get; }
+
+ public AdvancedTypePopupItem (Type type,string name) : base(name)
+ {
+ Type = type;
+ }
+ }
+
+ ///
+ /// A type popup with a fuzzy finder.
+ ///
+ /// This is taken from https://github.com/mackysoft/Unity-SerializeReferenceExtensions
+ public class AdvancedTypePopup : AdvancedDropdown
+ {
+ private string emptyDisplayName;
+ private Type[] types;
+
+ public event Action OnItemSelected;
+
+ public AdvancedTypePopup (IEnumerable types, int maxLineCount, string emptyDisplayName, AdvancedDropdownState state) : base(state)
+ {
+ this.emptyDisplayName = emptyDisplayName;
+ this.types = types.ToArray();
+ minimumSize = new Vector2(minimumSize.x,EditorGUIUtility.singleLineHeight * maxLineCount + EditorGUIUtility.singleLineHeight * 2f);
+ }
+
+ ///
+ protected override AdvancedDropdownItem BuildRoot ()
+ {
+ AdvancedDropdownItem root = new AdvancedDropdownItem("Select Type");
+ int itemCount = 0;
+
+ // Add null item.
+ AdvancedTypePopupItem nullItem = new AdvancedTypePopupItem(null, emptyDisplayName)
+ {
+ id = itemCount++
+ };
+
+ root.AddChild(nullItem);
+
+ // Add type items.
+ foreach (Type type in types)
+ {
+ AdvancedDropdownItem parent = root;
+
+ string typeDisplayName = ObjectNames.NicifyVariableName(type.Name);
+ if (!string.IsNullOrEmpty(type.Namespace))
+ {
+ typeDisplayName += $" ({type.Namespace})";
+ }
+
+ // Add type item.
+ AdvancedTypePopupItem item = new AdvancedTypePopupItem(type, typeDisplayName)
+ {
+ id = itemCount++
+ };
+ parent.AddChild(item);
+ }
+ return root;
+ }
+
+ ///
+ protected override void ItemSelected (AdvancedDropdownItem item)
+ {
+ base.ItemSelected(item);
+ if (item is AdvancedTypePopupItem typePopupItem)
+ {
+ OnItemSelected?.Invoke(typePopupItem);
+ }
+ }
+ }
+}
diff --git a/Editor/SerializeReferenceSelector/AdvancedTypePopup.cs.meta b/Editor/SerializeReferenceSelector/AdvancedTypePopup.cs.meta
new file mode 100644
index 0000000..e55df1e
--- /dev/null
+++ b/Editor/SerializeReferenceSelector/AdvancedTypePopup.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9be93f9edc00426478e676044a88b8f8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/SerializeReferenceSelector/ManagedReferenceContextualPropertyMenu.cs b/Editor/SerializeReferenceSelector/ManagedReferenceContextualPropertyMenu.cs
new file mode 100644
index 0000000..dc04ccf
--- /dev/null
+++ b/Editor/SerializeReferenceSelector/ManagedReferenceContextualPropertyMenu.cs
@@ -0,0 +1,103 @@
+using System;
+using UnityEditor;
+using UnityEngine;
+
+namespace ubco.ovilab.HPUI.Editor
+{
+ public static class ManagedReferenceContextualPropertyMenu
+ {
+ const string kCopiedPropertyPathKey = "SerializeReferenceExtensions.CopiedPropertyPath";
+ const string kClipboardKey = "SerializeReferenceExtensions.CopyAndPasteProperty";
+
+ static readonly GUIContent kPasteContent = new GUIContent("Paste Property");
+ static readonly GUIContent kNewInstanceContent = new GUIContent("New Instance");
+ static readonly GUIContent kResetAndNewInstanceContent = new GUIContent("Reset and New Instance");
+
+ [InitializeOnLoadMethod]
+ static void Initialize ()
+ {
+ EditorApplication.contextualPropertyMenu += OnContextualPropertyMenu;
+ }
+
+ static void OnContextualPropertyMenu (GenericMenu menu, SerializedProperty property)
+ {
+ if (property.propertyType == SerializedPropertyType.ManagedReference)
+ {
+ // NOTE: When the callback function is called, the SerializedProperty is rewritten to the property that was being moused over at the time,
+ // so a new SerializedProperty instance must be created.
+ SerializedProperty clonedProperty = property.Copy();
+
+ menu.AddItem(new GUIContent($"Copy \"{property.propertyPath}\" property"), false, Copy, clonedProperty);
+
+ string copiedPropertyPath = SessionState.GetString(kCopiedPropertyPathKey, string.Empty);
+ if (!string.IsNullOrEmpty(copiedPropertyPath))
+ {
+ menu.AddItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property"), false, Paste, clonedProperty);
+ }
+ else
+ {
+ menu.AddDisabledItem(kPasteContent);
+ }
+
+ menu.AddSeparator("");
+
+ bool hasInstance = clonedProperty.managedReferenceValue != null;
+ if (hasInstance)
+ {
+ menu.AddItem(kNewInstanceContent, false, NewInstance, clonedProperty);
+ menu.AddItem(kResetAndNewInstanceContent, false, ResetAndNewInstance, clonedProperty);
+ }
+ else
+ {
+ menu.AddDisabledItem(kNewInstanceContent);
+ menu.AddDisabledItem(kResetAndNewInstanceContent);
+ }
+ }
+ }
+
+ static void Copy (object customData)
+ {
+ SerializedProperty property = (SerializedProperty)customData;
+ string json = JsonUtility.ToJson(property.managedReferenceValue);
+ SessionState.SetString(kCopiedPropertyPathKey, property.propertyPath);
+ SessionState.SetString(kClipboardKey, json);
+ }
+
+ static void Paste (object customData)
+ {
+ SerializedProperty property = (SerializedProperty)customData;
+ string json = SessionState.GetString(kClipboardKey, string.Empty);
+ if (string.IsNullOrEmpty(json))
+ {
+ return;
+ }
+
+ Undo.RecordObject(property.serializedObject.targetObject, "Paste Property");
+ JsonUtility.FromJsonOverwrite(json, property.managedReferenceValue);
+ property.serializedObject.ApplyModifiedProperties();
+ }
+
+ static void NewInstance (object customData)
+ {
+ SerializedProperty property = (SerializedProperty)customData;
+ string json = JsonUtility.ToJson(property.managedReferenceValue);
+
+ Undo.RecordObject(property.serializedObject.targetObject, "New Instance");
+ property.managedReferenceValue = JsonUtility.FromJson(json, property.managedReferenceValue.GetType());
+ property.serializedObject.ApplyModifiedProperties();
+
+ Debug.Log($"Create new instance of \"{property.propertyPath}\".");
+ }
+
+ static void ResetAndNewInstance (object customData)
+ {
+ SerializedProperty property = (SerializedProperty)customData;
+
+ Undo.RecordObject(property.serializedObject.targetObject, "Reset and New Instance");
+ property.managedReferenceValue = Activator.CreateInstance(property.managedReferenceValue.GetType());
+ property.serializedObject.ApplyModifiedProperties();
+
+ Debug.Log($"Reset property and created new instance of \"{property.propertyPath}\".");
+ }
+ }
+}
diff --git a/Editor/SerializeReferenceSelector/ManagedReferenceContextualPropertyMenu.cs.meta b/Editor/SerializeReferenceSelector/ManagedReferenceContextualPropertyMenu.cs.meta
new file mode 100644
index 0000000..2c16ab8
--- /dev/null
+++ b/Editor/SerializeReferenceSelector/ManagedReferenceContextualPropertyMenu.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: df77d1f7db91da049be82b46b15cb07c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/SerializeReferenceSelector/SubclassSelectorDrawer.cs b/Editor/SerializeReferenceSelector/SubclassSelectorDrawer.cs
new file mode 100644
index 0000000..10112f5
--- /dev/null
+++ b/Editor/SerializeReferenceSelector/SubclassSelectorDrawer.cs
@@ -0,0 +1,350 @@
+using System;
+using System.Reflection;
+using System.Linq;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+using UnityEditor.IMGUI.Controls;
+using ubco.ovilab.HPUI.utils;
+
+namespace ubco.ovilab.HPUI.Editor
+{
+ /// This is taken from https://github.com/mackysoft/Unity-SerializeReferenceExtensions
+ [CustomPropertyDrawer(typeof(SubclassSelectorAttribute))]
+ public class SubclassSelectorDrawer : PropertyDrawer
+ {
+ static readonly int maxTypePopupLineCount = 13;
+ static readonly Type unityObjectType = typeof(UnityEngine.Object);
+ static readonly Dictionary drawerCaches = new Dictionary();
+ static readonly string nullDisplayName = "";
+ static readonly GUIContent contentNullDisplayName = new GUIContent("");
+ static readonly GUIContent contentIsNotManagedReferenceLabel = new GUIContent("The property type is not manage reference.");
+
+ readonly Dictionary typePopups = new Dictionary();
+ readonly Dictionary typeNameCaches = new Dictionary();
+
+ SerializedProperty m_TargetProperty;
+
+ public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
+ {
+ EditorGUI.BeginProperty(position, label, property);
+
+ if (property.propertyType == SerializedPropertyType.ManagedReference)
+ {
+ // Render label first to avoid label overlap for lists
+ Rect foldoutLabelRect = new Rect(position);
+ foldoutLabelRect.height = EditorGUIUtility.singleLineHeight;
+ foldoutLabelRect = EditorGUI.IndentedRect(foldoutLabelRect);
+ Rect popupPosition = EditorGUI.PrefixLabel(foldoutLabelRect, label);
+
+ // Draw the subclass selector popup.
+ if (EditorGUI.DropdownButton(popupPosition, GetTypeName(property), FocusType.Keyboard))
+ {
+ AdvancedTypePopup popup = GetTypePopup(property);
+ m_TargetProperty = property;
+ popup.Show(popupPosition);
+ }
+
+ // Draw the foldout.
+ if (!string.IsNullOrEmpty(property.managedReferenceFullTypename))
+ {
+ Rect foldoutRect = new Rect(position);
+ foldoutRect.height = EditorGUIUtility.singleLineHeight;
+
+ // // NOTE: Position x must be adjusted.
+ // // FIXME: Is there a more essential solution...?
+ // foldoutRect.x -= 12;
+
+ property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, GUIContent.none, true);
+ }
+
+ // Draw property if expanded.
+ if (property.isExpanded)
+ {
+ using (new EditorGUI.IndentLevelScope())
+ {
+ // Check if a custom property drawer exists for this type.
+ PropertyDrawer customDrawer = GetCustomPropertyDrawer(property);
+ if (customDrawer != null)
+ {
+ // Draw the property with custom property drawer.
+ Rect indentedRect = position;
+ float foldoutDifference = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+ indentedRect.height = customDrawer.GetPropertyHeight(property, label);
+ indentedRect.y += foldoutDifference;
+ customDrawer.OnGUI(indentedRect, property, label);
+ }
+ else
+ {
+ // Draw the properties of the child elements.
+ // NOTE: In the following code, since the foldout layout isn't working properly, I'll iterate through the properties of the child elements myself.
+ // EditorGUI.PropertyField(position, property, GUIContent.none, true);
+ Rect childPosition = position;
+ childPosition.y += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
+
+ GUIStyle style = new GUIStyle(GUI.skin.label);
+ style.fontStyle = FontStyle.BoldAndItalic;
+ GUIContent referenceName = new GUIContent(property.managedReferenceFullTypename);
+ float height = style.CalcSize(referenceName).y;
+ childPosition.height = height;
+ EditorGUI.LabelField(childPosition, referenceName, style);
+
+ childPosition.y += (height + EditorGUIUtility.standardVerticalSpacing);
+ foreach (SerializedProperty childProperty in GetChildProperties(property))
+ {
+ height = EditorGUI.GetPropertyHeight(childProperty, new GUIContent(childProperty.displayName, childProperty.tooltip), true);
+ childPosition.height = height;
+ EditorGUI.PropertyField(childPosition, childProperty, true);
+
+ childPosition.y += height + EditorGUIUtility.standardVerticalSpacing;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ EditorGUI.LabelField(position, label, contentIsNotManagedReferenceLabel);
+ }
+
+ EditorGUI.EndProperty();
+ }
+
+ ///
+ /// Tries to get the custom property drawer if one exists.
+ ///
+ private PropertyDrawer GetCustomPropertyDrawer (SerializedProperty property)
+ {
+ Type propertyType = GetType(property.managedReferenceFullTypename);
+ PropertyDrawer drawer = null;
+
+ if (propertyType != null && !drawerCaches.TryGetValue(propertyType, out drawer))
+ {
+ Type drawerType = GetCustomPropertyDrawerType(propertyType);
+ drawer = (drawerType != null) ? (PropertyDrawer)Activator.CreateInstance(drawerType) : null;
+
+ drawerCaches.Add(propertyType, drawer);
+ }
+
+ if (propertyType != null && drawer != null)
+ {
+ return drawer;
+ }
+ return null;
+ }
+
+ ///
+ /// Gets the searchable popup to set values.
+ ///
+ private AdvancedTypePopup GetTypePopup (SerializedProperty property)
+ {
+ // Cache this string. This property internally call Assembly.GetName, which result in a large allocation.
+ string managedReferenceFieldTypename = property.managedReferenceFieldTypename;
+
+ if (!typePopups.TryGetValue(managedReferenceFieldTypename,out AdvancedTypePopup popup))
+ {
+ var state = new AdvancedDropdownState();
+
+ Type baseType = GetType(managedReferenceFieldTypename);
+ popup = new AdvancedTypePopup(
+ TypeCache.GetTypesDerivedFrom(baseType).Append(baseType).Where(p =>
+ (p.IsPublic || p.IsNestedPublic || p.IsNestedPrivate) &&
+ !p.IsAbstract &&
+ !p.IsGenericType &&
+ !unityObjectType.IsAssignableFrom(p) &&
+ Attribute.IsDefined(p,typeof(SerializableAttribute))
+ ),
+ maxTypePopupLineCount,
+ nullDisplayName,
+ state
+ );
+
+ popup.OnItemSelected += item =>
+ {
+ Type type = item.Type;
+
+ // Apply changes to individual serialized objects.
+ foreach (var targetObject in m_TargetProperty.serializedObject.targetObjects) {
+ SerializedObject individualObject = new SerializedObject(targetObject);
+ SerializedProperty individualProperty = individualObject.FindProperty(m_TargetProperty.propertyPath);
+ object obj = SetManagedReference(individualProperty, type);
+ individualProperty.isExpanded = (obj != null);
+
+ individualObject.ApplyModifiedProperties();
+ individualObject.Update();
+ }
+ };
+
+ typePopups.Add(managedReferenceFieldTypename, popup);
+ }
+ return popup;
+ }
+
+ ///
+ /// Gets the GUIContent of the SerializedProperty that is decorated.
+ ///
+ private GUIContent GetTypeName (SerializedProperty property)
+ {
+ // Cache this string.
+ string managedReferenceFullTypename = property.managedReferenceFullTypename;
+
+ if (string.IsNullOrEmpty(managedReferenceFullTypename))
+ {
+ return contentNullDisplayName;
+ }
+
+ if (typeNameCaches.TryGetValue(managedReferenceFullTypename,out GUIContent cachedTypeName))
+ {
+ return cachedTypeName;
+ }
+
+ Type type = GetType(managedReferenceFullTypename);
+ string typeName = null;
+
+ typeName = ObjectNames.NicifyVariableName(type.Name);
+
+ GUIContent result = new GUIContent(typeName);
+ typeNameCaches.Add(managedReferenceFullTypename,result);
+ return result;
+ }
+
+ ///
+ public override float GetPropertyHeight (SerializedProperty property,GUIContent label) {
+ PropertyDrawer customDrawer = GetCustomPropertyDrawer(property);
+ if (customDrawer != null)
+ {
+ return property.isExpanded ? EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing + customDrawer.GetPropertyHeight(property,label):EditorGUIUtility.singleLineHeight;
+ }
+ else
+ {
+ if (property.isExpanded)
+ {
+ return EditorGUI.GetPropertyHeight(property, true) + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+ }
+ else
+ {
+ return EditorGUIUtility.singleLineHeight;
+ }
+ }
+ }
+
+ ///
+ /// Get from typeName
+ ///
+ internal static Type GetType (string typeName)
+ {
+ if (string.IsNullOrEmpty(typeName))
+ {
+ return null;
+ }
+
+ int splitIndex = typeName.IndexOf(' ');
+ var assembly = Assembly.Load(typeName.Substring(0, splitIndex));
+ return assembly.GetType(typeName.Substring(splitIndex + 1));
+ }
+
+ ///
+ /// Sets and returns the managed reference.
+ ///
+ internal static object SetManagedReference (SerializedProperty property,Type type)
+ {
+ object result = null;
+
+ if ((type != null) && (property.managedReferenceValue != null))
+ {
+ // Restore an previous values from json.
+ string json = JsonUtility.ToJson(property.managedReferenceValue);
+ result = JsonUtility.FromJson(json, type);
+ }
+
+ if (result == null)
+ {
+ result = (type != null) ? Activator.CreateInstance(type) : null;
+ }
+
+ property.managedReferenceValue = result;
+ return result;
+ }
+
+ ///
+ /// Enumerator to iterate on the child properties of a SerializedProperty.
+ ///
+ internal static IEnumerable GetChildProperties(SerializedProperty parent, int depth = 1)
+ {
+ parent = parent.Copy();
+
+ int depthOfParent = parent.depth;
+ var enumerator = parent.GetEnumerator();
+
+ while (enumerator.MoveNext())
+ {
+ if (enumerator.Current is not SerializedProperty childProperty)
+ {
+ continue;
+ }
+ // if (childProperty.depth > (depthOfParent + depth))
+ // {
+ // continue;
+ // }
+ yield return childProperty.Copy();
+ }
+ }
+
+ ///
+ /// Helper to
+ ///
+ private static Type GetCustomPropertyDrawerType (Type type)
+ {
+ Type[] interfaceTypes = type.GetInterfaces();
+
+ var types = TypeCache.GetTypesWithAttribute();
+ foreach (Type drawerType in types)
+ {
+ var customPropertyDrawerAttributes = drawerType.GetCustomAttributes(typeof(CustomPropertyDrawer), true);
+ foreach (CustomPropertyDrawer customPropertyDrawer in customPropertyDrawerAttributes)
+ {
+ var field = customPropertyDrawer.GetType().GetField("m_Type", BindingFlags.NonPublic | BindingFlags.Instance);
+ if (field != null)
+ {
+ var fieldType = field.GetValue(customPropertyDrawer) as Type;
+ if (fieldType != null)
+ {
+ if (fieldType == type)
+ {
+ return drawerType;
+ }
+
+ // If the property drawer also allows for being applied to child classes, check if they match
+ var useForChildrenField = customPropertyDrawer.GetType().GetField("m_UseForChildren", BindingFlags.NonPublic | BindingFlags.Instance);
+ if (useForChildrenField != null)
+ {
+ object useForChildrenValue = useForChildrenField.GetValue(customPropertyDrawer);
+ if (useForChildrenValue is bool && (bool)useForChildrenValue)
+ {
+ // Check interfaces
+ if (Array.Exists(interfaceTypes, interfaceType => interfaceType == fieldType))
+ {
+ return drawerType;
+ }
+
+ // Check derived types
+ Type baseType = type.BaseType;
+ while (baseType != null)
+ {
+ if (baseType == fieldType)
+ {
+ return drawerType;
+ }
+
+ baseType = baseType.BaseType;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/Editor/SerializeReferenceSelector/SubclassSelectorDrawer.cs.meta b/Editor/SerializeReferenceSelector/SubclassSelectorDrawer.cs.meta
new file mode 100644
index 0000000..23fe2f4
--- /dev/null
+++ b/Editor/SerializeReferenceSelector/SubclassSelectorDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 754575bf6febdd34ba06c3e303f376a8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Interaction/HPUIInteractor.cs b/Runtime/Interaction/HPUIInteractor.cs
index ce68fd5..d73122f 100644
--- a/Runtime/Interaction/HPUIInteractor.cs
+++ b/Runtime/Interaction/HPUIInteractor.cs
@@ -9,6 +9,7 @@
using UnityEngine.XR.Hands;
using Unity.XR.CoreUtils;
using UnityEngine.Pool;
+using ubco.ovilab.HPUI.utils;
namespace ubco.ovilab.HPUI.Interaction
{
@@ -256,10 +257,13 @@ public bool ShowSphereVisual
///
public HPUIInteractorFullRangeAngles FullRangeRayAngles { get => fullRangeRayAngles; set => fullRangeRayAngles = value; }
+ [SerializeReference, SubclassSelector]
+ private IHPUIGestureLogic gestureLogic;
+
///
/// The gesture logic used by the interactor
///
- public IHPUIGestureLogic GestureLogic { get; set; }
+ public IHPUIGestureLogic GestureLogic { get => gestureLogic; set => value = gestureLogic; }
private Dictionary validTargets = new Dictionary();
private Vector3 lastInteractionPoint;
diff --git a/Runtime/Utils.meta b/Runtime/Utils.meta
new file mode 100644
index 0000000..cefedb2
--- /dev/null
+++ b/Runtime/Utils.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a6556fd3442c7df45961677dc42ad586
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Utils/SubclassSelectorAttribute.cs b/Runtime/Utils/SubclassSelectorAttribute.cs
new file mode 100644
index 0000000..d8ebcfe
--- /dev/null
+++ b/Runtime/Utils/SubclassSelectorAttribute.cs
@@ -0,0 +1,50 @@
+using System;
+using UnityEngine;
+
+namespace ubco.ovilab.HPUI.utils
+{
+ ///
+ /// Attribute to specify the type of the field serialized by the SerializeReference attribute in the inspector.
+ /// This is taken from https://github.com/mackysoft/Unity-SerializeReferenceExtensions
+ ///
+ [AttributeUsage(AttributeTargets.Field,AllowMultiple = false)]
+ public sealed class SubclassSelectorAttribute : PropertyAttribute {}
+
+ ///
+ /// An attribute that overrides the name of the type displayed in the SubclassSelector popup.
+ /// This is taken from https://github.com/mackysoft/Unity-SerializeReferenceExtensions
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface,AllowMultiple = false,Inherited = false)]
+ public sealed class AddTypeMenuAttribute : Attribute
+ {
+ public string MenuName { get; }
+
+ public int Order { get; }
+
+ public AddTypeMenuAttribute (string menuName,int order = 0)
+ {
+ MenuName = menuName;
+ Order = order;
+ }
+
+ static readonly char[] k_Separeters = new char[] { '/' };
+
+ ///
+ /// Returns the menu name split by the '/' separator.
+ ///
+ public string[] GetSplittedMenuName ()
+ {
+ return !string.IsNullOrWhiteSpace(MenuName) ? MenuName.Split(k_Separeters,StringSplitOptions.RemoveEmptyEntries) : Array.Empty();
+ }
+
+ ///
+ /// Returns the display name without the path.
+ ///
+ public string GetTypeNameWithoutPath ()
+ {
+ string[] splittedDisplayName = GetSplittedMenuName();
+ return (splittedDisplayName.Length != 0) ? splittedDisplayName[splittedDisplayName.Length - 1] : null;
+ }
+
+ }
+}
diff --git a/Runtime/Utils/SubclassSelectorAttribute.cs.meta b/Runtime/Utils/SubclassSelectorAttribute.cs.meta
new file mode 100644
index 0000000..1cd2b1d
--- /dev/null
+++ b/Runtime/Utils/SubclassSelectorAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b79973b7280c841428132b1e39da008b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: