1.3.0: Added SerializeReferenceTo as a helper for assigning managed type to fields with SerializeReference
This commit is contained in:
parent
ea849a715d
commit
e1d0e0e90b
132
Editor/Drawers/SerializableReferenceToAttributeDrawer.cs
Normal file
132
Editor/Drawers/SerializableReferenceToAttributeDrawer.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using Module.Inspector.Editor.Utilities;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
namespace Module.Inspector.Editor
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(SerializableReferenceToAttribute))]
|
||||||
|
internal sealed class SerializableReferenceToAttributeDrawer : DrawerPropertyDrawer
|
||||||
|
{
|
||||||
|
private enum ESupportType
|
||||||
|
{
|
||||||
|
Supported,
|
||||||
|
SpecializeUnityEngineObject,
|
||||||
|
NonSerialized,
|
||||||
|
GenericType,
|
||||||
|
MissingSerializeReference,
|
||||||
|
NotManagedReference
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Draw(Rect position, DrawerPropertyAttribute attribute, SerializedProperty property, GUIContent label, EditorPropertyUtility.Result result)
|
||||||
|
{
|
||||||
|
if (IsSupported(property) != ESupportType.Supported)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var att = (SerializableReferenceToAttribute)attribute;
|
||||||
|
Type type = att.useType ? att.type : property.GetValueType();
|
||||||
|
string[] arrStrings = EditorTypeUtility.GetAssignableFromAsStrings(type);
|
||||||
|
GUIContent[] arrGui = EditorTypeUtility.GetAssignableFromAsGUI(type);
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
EditorGUI.BeginProperty(position, label, property);
|
||||||
|
{
|
||||||
|
var rect = new Rect(position)
|
||||||
|
{
|
||||||
|
height = EditorGUIUtility.singleLineHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
int index = Array.IndexOf(arrStrings, ConvertUnityToCSharp(property.managedReferenceFullTypename));
|
||||||
|
int newIndex = EditorGUI.Popup(rect, label, index, arrGui);
|
||||||
|
|
||||||
|
if (newIndex != -1 && index != newIndex)
|
||||||
|
{
|
||||||
|
Type newType = EditorTypeUtility.GetType(arrStrings[newIndex]);
|
||||||
|
|
||||||
|
if (newType != null && IsSupported(newType))
|
||||||
|
property.managedReferenceValue = FormatterServices.GetUninitializedObject(newType);
|
||||||
|
else
|
||||||
|
EditorUtility.DisplayDialog("Error", "Failed to set managed reference to selected type.\n\nType must be:\nSerializable\nNot abstract\nNon-generic\nNot a specialization of UnityEngine.Object", "Ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.PropertyField(position, property, true);
|
||||||
|
}
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
bool hasChanged = EditorGUI.EndChangeCheck();
|
||||||
|
|
||||||
|
if (hasChanged)
|
||||||
|
property.serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetErrorMessage(SerializedProperty property)
|
||||||
|
{
|
||||||
|
ESupportType supportType = IsSupported(property);
|
||||||
|
|
||||||
|
switch (supportType)
|
||||||
|
{
|
||||||
|
case ESupportType.SpecializeUnityEngineObject:
|
||||||
|
return "Must not be a specialization of UnityEngine.Object";
|
||||||
|
case ESupportType.NonSerialized:
|
||||||
|
return "Must be serializable";
|
||||||
|
case ESupportType.GenericType:
|
||||||
|
return "Must not be a generic type";
|
||||||
|
case ESupportType.MissingSerializeReference:
|
||||||
|
return "Missing [SerializeReference]";
|
||||||
|
case ESupportType.NotManagedReference:
|
||||||
|
return "Must be a managed reference type";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown error";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ESupportType IsSupported(SerializedProperty property)
|
||||||
|
{
|
||||||
|
if (property.propertyType != SerializedPropertyType.ManagedReference)
|
||||||
|
return ESupportType.NotManagedReference;
|
||||||
|
|
||||||
|
Type type = property.GetValueType();
|
||||||
|
|
||||||
|
if (!type.IsSerializable && !(type.IsInterface || type.IsAbstract))
|
||||||
|
return ESupportType.NonSerialized;
|
||||||
|
if (typeof(Object).IsAssignableFrom(type))
|
||||||
|
return ESupportType.SpecializeUnityEngineObject;
|
||||||
|
if (type.IsGenericType)
|
||||||
|
return ESupportType.GenericType;
|
||||||
|
|
||||||
|
FieldInfo fi = property.GetFieldInfo();
|
||||||
|
|
||||||
|
if (fi != null && fi.GetCustomAttribute<SerializeReference>() == null)
|
||||||
|
return ESupportType.MissingSerializeReference;
|
||||||
|
|
||||||
|
return ESupportType.Supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSupported(Type type)
|
||||||
|
{
|
||||||
|
if (!type.IsSerializable)
|
||||||
|
return false;
|
||||||
|
if (typeof(Object).IsAssignableFrom(type))
|
||||||
|
return false;
|
||||||
|
if (type.IsGenericType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ConvertUnityToCSharp(string unityNaming)
|
||||||
|
{
|
||||||
|
int index = unityNaming.LastIndexOf(' ');
|
||||||
|
|
||||||
|
if (index != -1)
|
||||||
|
unityNaming = unityNaming.Substring(index);
|
||||||
|
|
||||||
|
unityNaming = unityNaming.Replace('/', '+');
|
||||||
|
return unityNaming.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3d6666b713d84a83b89c84fb2e4fb8ec
|
||||||
|
timeCreated: 1638700710
|
||||||
|
|
@ -421,12 +421,18 @@ namespace Module.Inspector.Editor
|
||||||
|
|
||||||
foreach (string path in property.propertyPath.Split('.'))
|
foreach (string path in property.propertyPath.Split('.'))
|
||||||
{
|
{
|
||||||
type = obj.GetType();
|
FieldInfo field = obj.GetType().GetField(path);
|
||||||
FieldInfo field = type.GetField(path);
|
|
||||||
|
if (field == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
obj = field.GetValue(obj);
|
obj = field.GetValue(obj);
|
||||||
type = field.FieldType;
|
type = field.FieldType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type != null && type.IsArray)
|
||||||
|
type = type.GetElementType();
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,14 +506,14 @@ namespace Module.Inspector.Editor
|
||||||
return "Unable to draw value as string";
|
return "Unable to draw value as string";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int IndexOfProperty(this SerializedProperty sp, SerializedProperty element)
|
public static int IndexOfProperty(this SerializedProperty property, SerializedProperty element)
|
||||||
{
|
{
|
||||||
if (!sp.isArray)
|
if (!property.isArray)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
for (var i = 0; i < sp.arraySize; i++)
|
for (var i = 0; i < property.arraySize; i++)
|
||||||
{
|
{
|
||||||
SerializedProperty e = sp.GetArrayElementAtIndex(i);
|
SerializedProperty e = property.GetArrayElementAtIndex(i);
|
||||||
|
|
||||||
if (e.propertyPath.Equals(element.propertyPath))
|
if (e.propertyPath.Equals(element.propertyPath))
|
||||||
return i;
|
return i;
|
||||||
|
|
@ -515,5 +521,21 @@ namespace Module.Inspector.Editor
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FieldInfo GetFieldInfo(this SerializedProperty property)
|
||||||
|
{
|
||||||
|
object obj = property.serializedObject.targetObject;
|
||||||
|
FieldInfo field = null;
|
||||||
|
|
||||||
|
foreach (string path in property.propertyPath.Split('.'))
|
||||||
|
{
|
||||||
|
field = obj.GetType().GetField(path);
|
||||||
|
|
||||||
|
if (field != null)
|
||||||
|
obj = field.GetValue(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return field;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,9 @@ namespace Module.Inspector.Editor
|
||||||
private static readonly Dictionary<Type, Type[]> DICT_AS_TYPES = new Dictionary<Type, Type[]>();
|
private static readonly Dictionary<Type, Type[]> DICT_AS_TYPES = new Dictionary<Type, Type[]>();
|
||||||
private static readonly Dictionary<Type, string[]> DICT_AS_STRS = new Dictionary<Type, string[]>();
|
private static readonly Dictionary<Type, string[]> DICT_AS_STRS = new Dictionary<Type, string[]>();
|
||||||
private static readonly Dictionary<Type, string[]> DICT_AS_DESCS = new Dictionary<Type, string[]>();
|
private static readonly Dictionary<Type, string[]> DICT_AS_DESCS = new Dictionary<Type, string[]>();
|
||||||
|
private static readonly Dictionary<Type, GUIContent[]> DICT_AS_GUI = new Dictionary<Type, GUIContent[]>();
|
||||||
|
private static readonly Dictionary<Type, GUIContent[]> DICT_AS_GUI_DESC = new Dictionary<Type, GUIContent[]>();
|
||||||
|
|
||||||
private static readonly Dictionary<Type, FieldInfo[]> DICT_AS_FIELDS = new Dictionary<Type, FieldInfo[]>();
|
private static readonly Dictionary<Type, FieldInfo[]> DICT_AS_FIELDS = new Dictionary<Type, FieldInfo[]>();
|
||||||
private static readonly Dictionary<Type, string[]> DICT_FIELDS_AS_STRS = new Dictionary<Type, string[]>();
|
private static readonly Dictionary<Type, string[]> DICT_FIELDS_AS_STRS = new Dictionary<Type, string[]>();
|
||||||
private static readonly Dictionary<Type, string[]> DICT_FIELDS_AS_DESCS = new Dictionary<Type, string[]>();
|
private static readonly Dictionary<Type, string[]> DICT_FIELDS_AS_DESCS = new Dictionary<Type, string[]>();
|
||||||
|
|
@ -96,6 +98,14 @@ namespace Module.Inspector.Editor
|
||||||
|
|
||||||
return DICT_AS_STRS[type];
|
return DICT_AS_STRS[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static GUIContent[] GetAssignableFromAsGUI(Type type)
|
||||||
|
{
|
||||||
|
if (!DICT_AS_GUI.ContainsKey(type))
|
||||||
|
InternalFetch(type);
|
||||||
|
|
||||||
|
return DICT_AS_GUI[type];
|
||||||
|
}
|
||||||
|
|
||||||
internal static string[] GetAssignableFromAsDescriptions(Type type)
|
internal static string[] GetAssignableFromAsDescriptions(Type type)
|
||||||
{
|
{
|
||||||
|
|
@ -105,6 +115,14 @@ namespace Module.Inspector.Editor
|
||||||
return DICT_AS_DESCS[type];
|
return DICT_AS_DESCS[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static GUIContent[] GetAssignableFromAsGUIDescriptions(Type type)
|
||||||
|
{
|
||||||
|
if (!DICT_AS_GUI_DESC.ContainsKey(type))
|
||||||
|
InternalFetch(type);
|
||||||
|
|
||||||
|
return DICT_AS_GUI_DESC[type];
|
||||||
|
}
|
||||||
|
|
||||||
internal static FieldInfo[] GetFields(Type type)
|
internal static FieldInfo[] GetFields(Type type)
|
||||||
{
|
{
|
||||||
if (!DICT_AS_FIELDS.ContainsKey(type))
|
if (!DICT_AS_FIELDS.ContainsKey(type))
|
||||||
|
|
@ -164,16 +182,22 @@ namespace Module.Inspector.Editor
|
||||||
listTypes.Sort((t0, t1) => string.Compare(t0.Name, t1.Name, StringComparison.Ordinal));
|
listTypes.Sort((t0, t1) => string.Compare(t0.Name, t1.Name, StringComparison.Ordinal));
|
||||||
var fullnames = new string[listTypes.Count];
|
var fullnames = new string[listTypes.Count];
|
||||||
var descs = new string[listTypes.Count];
|
var descs = new string[listTypes.Count];
|
||||||
|
var guiFullNames = new GUIContent[listTypes.Count];
|
||||||
|
var guiDescs = new GUIContent[listTypes.Count];
|
||||||
|
|
||||||
for (var i = 0; i < fullnames.Length; i++)
|
for (var i = 0; i < fullnames.Length; i++)
|
||||||
{
|
{
|
||||||
fullnames[i] = listTypes[i].FullName;
|
fullnames[i] = listTypes[i].FullName;
|
||||||
descs[i] = $"{listTypes[i].Name} (<i>{fullnames[i]}</i>)";
|
descs[i] = $"{listTypes[i].Name} (<i>{fullnames[i]}</i>)";
|
||||||
|
guiFullNames[i] = new GUIContent(fullnames[i]);
|
||||||
|
guiDescs[i] = new GUIContent(descs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
DICT_AS_TYPES.Add(assignableFrom, listTypes.ToArray());
|
DICT_AS_TYPES.Add(assignableFrom, listTypes.ToArray());
|
||||||
DICT_AS_STRS.Add(assignableFrom, fullnames);
|
DICT_AS_STRS.Add(assignableFrom, fullnames);
|
||||||
DICT_AS_DESCS.Add(assignableFrom, descs);
|
DICT_AS_DESCS.Add(assignableFrom, descs);
|
||||||
|
DICT_AS_GUI.Add(assignableFrom, guiFullNames);
|
||||||
|
DICT_AS_GUI_DESC.Add(assignableFrom, guiDescs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InternalFetchFields(Type type)
|
private static void InternalFetchFields(Type type)
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ List of all available drawer attributes:
|
||||||
* Adds popup with all tag values for field of type string
|
* Adds popup with all tag values for field of type string
|
||||||
* `UrlGoTo`
|
* `UrlGoTo`
|
||||||
* Adds a button to the field that calls Application.OpenUrl with string value
|
* Adds a button to the field that calls Application.OpenUrl with string value
|
||||||
|
* `SerializeReferenceTo`
|
||||||
|
* Adds a popup for `SerializeReference` fields with types inheriting from assigned type or field type
|
||||||
|
|
||||||
|
|
||||||
## Value
|
## Value
|
||||||
|
|
|
||||||
22
Runtime/Drawers/SerializableReferenceToAttribute.cs
Normal file
22
Runtime/Drawers/SerializableReferenceToAttribute.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Module.Inspector
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
||||||
|
public sealed class SerializableReferenceToAttribute : DrawerPropertyAttribute
|
||||||
|
{
|
||||||
|
public readonly bool useType;
|
||||||
|
public readonly Type type;
|
||||||
|
|
||||||
|
public SerializableReferenceToAttribute()
|
||||||
|
{
|
||||||
|
useType = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SerializableReferenceToAttribute(Type type)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
useType = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Drawers/SerializableReferenceToAttribute.cs.meta
Normal file
3
Runtime/Drawers/SerializableReferenceToAttribute.cs.meta
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9480dc4440ae45a0bd31ccbd9ae23977
|
||||||
|
timeCreated: 1638700753
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "com.module.inspector",
|
"name": "com.module.inspector",
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"displayName": "Module.Inspector",
|
"displayName": "Module.Inspector",
|
||||||
"description": "Custom inspector with various useful property drawers",
|
"description": "Custom inspector with various useful property drawers",
|
||||||
"unity": "2019.2",
|
"unity": "2019.2",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue