diff --git a/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs b/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs new file mode 100644 index 0000000..a0d239f --- /dev/null +++ b/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs @@ -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() == 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(); + } + } +} \ No newline at end of file diff --git a/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs.meta b/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs.meta new file mode 100644 index 0000000..8e21fc5 --- /dev/null +++ b/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3d6666b713d84a83b89c84fb2e4fb8ec +timeCreated: 1638700710 \ No newline at end of file diff --git a/Editor/Extensions/SerializedPropertyExtension.cs b/Editor/Extensions/SerializedPropertyExtension.cs index 8049573..c97bbdb 100644 --- a/Editor/Extensions/SerializedPropertyExtension.cs +++ b/Editor/Extensions/SerializedPropertyExtension.cs @@ -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; + } } } \ No newline at end of file diff --git a/Editor/Utilities/EditorTypeUtility.cs b/Editor/Utilities/EditorTypeUtility.cs index 0020342..c78ffbf 100644 --- a/Editor/Utilities/EditorTypeUtility.cs +++ b/Editor/Utilities/EditorTypeUtility.cs @@ -14,7 +14,9 @@ namespace Module.Inspector.Editor private static readonly Dictionary DICT_AS_TYPES = new Dictionary(); private static readonly Dictionary DICT_AS_STRS = new Dictionary(); private static readonly Dictionary DICT_AS_DESCS = new Dictionary(); - + private static readonly Dictionary DICT_AS_GUI = new Dictionary(); + private static readonly Dictionary DICT_AS_GUI_DESC = new Dictionary(); + private static readonly Dictionary DICT_AS_FIELDS = new Dictionary(); private static readonly Dictionary DICT_FIELDS_AS_STRS = new Dictionary(); private static readonly Dictionary DICT_FIELDS_AS_DESCS = new Dictionary(); @@ -96,6 +98,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) { @@ -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} ({fullnames[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) diff --git a/README.md b/README.md index 06fbaff..4a25df5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/Runtime/Drawers/SerializableReferenceToAttribute.cs b/Runtime/Drawers/SerializableReferenceToAttribute.cs new file mode 100644 index 0000000..58fa73f --- /dev/null +++ b/Runtime/Drawers/SerializableReferenceToAttribute.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Runtime/Drawers/SerializableReferenceToAttribute.cs.meta b/Runtime/Drawers/SerializableReferenceToAttribute.cs.meta new file mode 100644 index 0000000..0b70463 --- /dev/null +++ b/Runtime/Drawers/SerializableReferenceToAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9480dc4440ae45a0bd31ccbd9ae23977 +timeCreated: 1638700753 \ No newline at end of file diff --git a/package.json b/package.json index 7bf7670..aced385 100644 --- a/package.json +++ b/package.json @@ -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",