1.3.0: Added SerializeReferenceTo as a helper for assigning managed type to fields with SerializeReference

This commit is contained in:
Anders Ejlersen 2021-12-05 13:19:42 +01:00
parent ea849a715d
commit e1d0e0e90b
8 changed files with 216 additions and 8 deletions

View 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();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3d6666b713d84a83b89c84fb2e4fb8ec
timeCreated: 1638700710

View file

@ -421,12 +421,18 @@ namespace Module.Inspector.Editor
foreach (string path in property.propertyPath.Split('.'))
{
type = obj.GetType();
FieldInfo field = type.GetField(path);
FieldInfo field = obj.GetType().GetField(path);
if (field == null)
continue;
obj = field.GetValue(obj);
type = field.FieldType;
}
if (type != null && type.IsArray)
type = type.GetElementType();
return type;
}
@ -500,14 +506,14 @@ namespace Module.Inspector.Editor
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;
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))
return i;
@ -515,5 +521,21 @@ namespace Module.Inspector.Editor
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;
}
}
}

View file

@ -14,6 +14,8 @@ namespace Module.Inspector.Editor
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_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, string[]> DICT_FIELDS_AS_STRS = new Dictionary<Type, string[]>();
@ -97,6 +99,14 @@ namespace Module.Inspector.Editor
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)
{
if (!DICT_AS_DESCS.ContainsKey(type))
@ -105,6 +115,14 @@ namespace Module.Inspector.Editor
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)
{
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));
var fullnames = 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++)
{
fullnames[i] = listTypes[i].FullName;
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_STRS.Add(assignableFrom, fullnames);
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)

View file

@ -105,6 +105,8 @@ List of all available drawer attributes:
* Adds popup with all tag values for field of type string
* `UrlGoTo`
* 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

View 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;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9480dc4440ae45a0bd31ccbd9ae23977
timeCreated: 1638700753

View file

@ -1,6 +1,6 @@
{
"name": "com.module.inspector",
"version": "1.2.0",
"version": "1.3.0",
"displayName": "Module.Inspector",
"description": "Custom inspector with various useful property drawers",
"unity": "2019.2",