editor namespace

This commit is contained in:
max 2022-12-28 14:06:42 +01:00
parent 0d04c50612
commit acec8003c1
6 changed files with 248 additions and 230 deletions

View File

@ -4,27 +4,30 @@ using System.Collections.Generic;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
[CustomPropertyDrawer(typeof(SerializeReferenceButtonAttribute))] namespace Textus.SerializeReferenceUI.Editor
public class SerializeReferenceButtonAttributeDrawer : PropertyDrawer
{ {
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) [CustomPropertyDrawer(typeof(SerializeReferenceButtonAttribute))]
public class SerializeReferenceButtonAttributeDrawer : PropertyDrawer
{ {
return EditorGUI.GetPropertyHeight(property, true); public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
} {
return EditorGUI.GetPropertyHeight(property, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{ {
EditorGUI.BeginProperty(position, label, property); EditorGUI.BeginProperty(position, label, property);
Rect labelPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); Rect labelPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.LabelField(labelPosition, label); EditorGUI.LabelField(labelPosition, label);
IEnumerable<Func<Type, bool>> typeRestrictions = SerializedReferenceUIDefaultTypeRestrictions.GetAllBuiltInTypeRestrictions(fieldInfo); IEnumerable<Func<Type, bool>> typeRestrictions = SerializedReferenceUIDefaultTypeRestrictions.GetAllBuiltInTypeRestrictions(fieldInfo);
property.DrawSelectionButtonForManagedReference(position, typeRestrictions); property.DrawSelectionButtonForManagedReference(position, typeRestrictions);
EditorGUI.PropertyField(position, property, GUIContent.none, true); EditorGUI.PropertyField(position, property, GUIContent.none, true);
EditorGUI.EndProperty(); EditorGUI.EndProperty();
}
} }
} }
#endif #endif

View File

@ -4,23 +4,26 @@ using System.Collections.Generic;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
[CustomPropertyDrawer(typeof(SerializeReferenceMenuAttribute))] namespace Textus.SerializeReferenceUI.Editor
public class SerializeReferenceMenuAttributeDrawer : PropertyDrawer
{ {
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) [CustomPropertyDrawer(typeof(SerializeReferenceMenuAttribute))]
public class SerializeReferenceMenuAttributeDrawer : PropertyDrawer
{ {
return EditorGUI.GetPropertyHeight(property, true); public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
} {
return EditorGUI.GetPropertyHeight(property, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{ {
EditorGUI.BeginProperty(position, label, property); EditorGUI.BeginProperty(position, label, property);
IEnumerable<Func<Type, bool>> typeRestrictions = SerializedReferenceUIDefaultTypeRestrictions.GetAllBuiltInTypeRestrictions(fieldInfo); IEnumerable<Func<Type, bool>> typeRestrictions = SerializedReferenceUIDefaultTypeRestrictions.GetAllBuiltInTypeRestrictions(fieldInfo);
property.ShowContextMenuForManagedReferenceOnMouseMiddleButton(position, typeRestrictions); property.ShowContextMenuForManagedReferenceOnMouseMiddleButton(position, typeRestrictions);
EditorGUI.PropertyField(position, property, true); EditorGUI.PropertyField(position, property, true);
EditorGUI.EndProperty(); EditorGUI.EndProperty();
}
} }
} }
#endif #endif

View File

@ -6,111 +6,114 @@ using UnityEditor;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
public static class ManagedReferenceUtility namespace Textus.SerializeReferenceUI.Editor
{ {
/// Creates instance of passed type and assigns it to managed reference public static class ManagedReferenceUtility
public static object AssignNewInstanceOfTypeToManagedReference(this SerializedProperty serializedProperty, Type type)
{ {
object instance = Activator.CreateInstance(type); /// Creates instance of passed type and assigns it to managed reference
public static object AssignNewInstanceOfTypeToManagedReference(this SerializedProperty serializedProperty, Type type)
serializedProperty.serializedObject.Update();
serializedProperty.managedReferenceValue = instance;
serializedProperty.serializedObject.ApplyModifiedProperties();
return instance;
}
/// Sets managed reference to null
public static void SetManagedReferenceToNull(this SerializedProperty serializedProperty)
{
serializedProperty.serializedObject.Update();
serializedProperty.managedReferenceValue = null;
serializedProperty.serializedObject.ApplyModifiedProperties();
}
/// Collects appropriate types based on managed reference field type and filters. Filters all derive
public static IEnumerable<Type> GetAppropriateTypesForAssigningToManagedReference(this SerializedProperty property, List<Func<Type, bool>> filters = null)
{
Type fieldType = property.GetManagedReferenceFieldType();
return GetAppropriateTypesForAssigningToManagedReference(fieldType, filters);
}
/// Filters derived types of field typ parameter and finds ones whose are compatible with managed reference and filters.
public static IEnumerable<Type> GetAppropriateTypesForAssigningToManagedReference(Type fieldType, List<Func<Type, bool>> filters = null)
{
List<Type> appropriateTypes = new List<Type>();
// Get and filter all appropriate types
TypeCache.TypeCollection derivedTypes = TypeCache.GetTypesDerivedFrom(fieldType);
foreach (Type type in derivedTypes)
{ {
// Skips unity engine Objects (because they are not serialized by SerializeReference) object instance = Activator.CreateInstance(type);
if (type.IsSubclassOf(typeof(Object)))
{
continue;
}
// Skip abstract classes because they should not be instantiated
if (type.IsAbstract)
{
continue;
}
// Skip generic classes because they can not be instantiated
if (type.ContainsGenericParameters)
{
continue;
}
// Skip types that has no public empty constructors (activator can not create them)
if (type.IsClass && type.GetConstructor(Type.EmptyTypes) == null) // Structs still can be created (strangely)
{
continue;
}
// Filter types by provided filters if there is ones
if (filters != null && filters.All(f => f == null || f.Invoke(type)) == false)
{
continue;
}
appropriateTypes.Add(type); serializedProperty.serializedObject.Update();
serializedProperty.managedReferenceValue = instance;
serializedProperty.serializedObject.ApplyModifiedProperties();
return instance;
} }
return appropriateTypes; /// Sets managed reference to null
} public static void SetManagedReferenceToNull(this SerializedProperty serializedProperty)
/// Gets real type of managed reference
public static Type GetManagedReferenceFieldType(this SerializedProperty property)
{
Type realPropertyType = GetRealTypeFromTypename(property.managedReferenceFieldTypename);
if (realPropertyType != null)
{ {
return realPropertyType; serializedProperty.serializedObject.Update();
serializedProperty.managedReferenceValue = null;
serializedProperty.serializedObject.ApplyModifiedProperties();
} }
Debug.LogError($"Can not get field type of managed reference : {property.managedReferenceFieldTypename}"); /// Collects appropriate types based on managed reference field type and filters. Filters all derive
return null; public static IEnumerable<Type> GetAppropriateTypesForAssigningToManagedReference(this SerializedProperty property, List<Func<Type, bool>> filters = null)
}
/// Gets real type of managed reference's field typeName
public static Type GetRealTypeFromTypename(string stringType)
{
(string AssemblyName, string ClassName) = GetSplitNamesFromTypename(stringType);
Type realType = Type.GetType($"{ClassName}, {AssemblyName}");
return realType;
}
/// Get assembly and class names from typeName
public static (string AssemblyName, string ClassName) GetSplitNamesFromTypename(string typename)
{
if (string.IsNullOrEmpty(typename))
{ {
return ("",""); Type fieldType = property.GetManagedReferenceFieldType();
return GetAppropriateTypesForAssigningToManagedReference(fieldType, filters);
} }
string[] typeSplitString = typename.Split(char.Parse(" ")); /// Filters derived types of field typ parameter and finds ones whose are compatible with managed reference and filters.
string typeClassName = typeSplitString[1]; public static IEnumerable<Type> GetAppropriateTypesForAssigningToManagedReference(Type fieldType, List<Func<Type, bool>> filters = null)
string typeAssemblyName = typeSplitString[0]; {
List<Type> appropriateTypes = new List<Type>();
return (typeAssemblyName, typeClassName); // Get and filter all appropriate types
TypeCache.TypeCollection derivedTypes = TypeCache.GetTypesDerivedFrom(fieldType);
foreach (Type type in derivedTypes)
{
// Skips unity engine Objects (because they are not serialized by SerializeReference)
if (type.IsSubclassOf(typeof(Object)))
{
continue;
}
// Skip abstract classes because they should not be instantiated
if (type.IsAbstract)
{
continue;
}
// Skip generic classes because they can not be instantiated
if (type.ContainsGenericParameters)
{
continue;
}
// Skip types that has no public empty constructors (activator can not create them)
if (type.IsClass && type.GetConstructor(Type.EmptyTypes) == null) // Structs still can be created (strangely)
{
continue;
}
// Filter types by provided filters if there is ones
if (filters != null && filters.All(f => f == null || f.Invoke(type)) == false)
{
continue;
}
appropriateTypes.Add(type);
}
return appropriateTypes;
}
/// Gets real type of managed reference
public static Type GetManagedReferenceFieldType(this SerializedProperty property)
{
Type realPropertyType = GetRealTypeFromTypename(property.managedReferenceFieldTypename);
if (realPropertyType != null)
{
return realPropertyType;
}
Debug.LogError($"Can not get field type of managed reference : {property.managedReferenceFieldTypename}");
return null;
}
/// Gets real type of managed reference's field typeName
public static Type GetRealTypeFromTypename(string stringType)
{
(string AssemblyName, string ClassName) = GetSplitNamesFromTypename(stringType);
Type realType = Type.GetType($"{ClassName}, {AssemblyName}");
return realType;
}
/// Get assembly and class names from typeName
public static (string AssemblyName, string ClassName) GetSplitNamesFromTypename(string typename)
{
if (string.IsNullOrEmpty(typename))
{
return ("", "");
}
string[] typeSplitString = typename.Split(char.Parse(" "));
string typeClassName = typeSplitString[1];
string typeAssemblyName = typeSplitString[0];
return (typeAssemblyName, typeClassName);
}
} }
} }
#endif #endif

View File

@ -5,78 +5,81 @@ using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
public static class SerializeReferenceGenericSelectionMenu namespace Textus.SerializeReferenceUI.Editor
{ {
/// Purpose. public static class SerializeReferenceGenericSelectionMenu
/// This is generic selection menu (will appear at position).
/// Filtering.
/// You can add substring filter here to filter by search string.
/// As well ass type or interface restrictions.
/// As well as any custom restriction that is based on input type.
/// And it will be performed on each Appropriate type found by TypeCache.
public static void ShowContextMenuForManagedReference(this SerializedProperty property, Rect position, IEnumerable<Func<Type,bool>> filters = null)
{ {
GenericMenu context = new GenericMenu(); /// Purpose.
FillContextMenu(filters, context, property); /// This is generic selection menu (will appear at position).
context.DropDown(position); /// Filtering.
} /// You can add substring filter here to filter by search string.
/// As well ass type or interface restrictions.
/// Purpose. /// As well as any custom restriction that is based on input type.
/// This is generic selection menu (will appear at click position). /// And it will be performed on each Appropriate type found by TypeCache.
/// Filtering. public static void ShowContextMenuForManagedReference(this SerializedProperty property, Rect position, IEnumerable<Func<Type, bool>> filters = null)
/// You can add substring filter here to filter by search string.
/// As well ass type or interface restrictions.
/// As well as any custom restriction that is based on input type.
/// And it will be performed on each Appropriate type found by TypeCache.
public static void ShowContextMenuForManagedReference(this SerializedProperty property, IEnumerable<Func<Type, bool>> filters = null)
{
GenericMenu context = new GenericMenu();
FillContextMenu(filters, context, property);
context.ShowAsContext();
}
private static void FillContextMenu(IEnumerable<Func<Type, bool>> enumerableFilters, GenericMenu contextMenu, SerializedProperty property)
{
List<Func<Type, bool>> filters = enumerableFilters.ToList();// Prevents possible multiple enumerations
// Adds "Make Null" menu command
contextMenu.AddItem(new GUIContent("Null"), false, property.SetManagedReferenceToNull);
// Collects appropriate types
IEnumerable<Type> appropriateTypes = property.GetAppropriateTypesForAssigningToManagedReference(filters);
// Adds appropriate types to menu
foreach (Type appropriateType in appropriateTypes)
{ {
AddItemToContextMenu(appropriateType, contextMenu, property); GenericMenu context = new GenericMenu();
} FillContextMenu(filters, context, property);
} context.DropDown(position);
private static void AddItemToContextMenu(Type type, GenericMenu genericMenuContext, SerializedProperty property)
{
string assemblyName = type.Assembly.ToString().Split('(', ',')[0];
string entryName = type + " ( " + assemblyName + " )";
genericMenuContext.AddItem(new GUIContent(entryName), false, AssignNewInstanceCommand, new GenericMenuParameterForAssignInstanceCommand(type, property));
}
private static void AssignNewInstanceCommand(object objectGenericMenuParameter )
{
GenericMenuParameterForAssignInstanceCommand parameter = (GenericMenuParameterForAssignInstanceCommand) objectGenericMenuParameter;
Type type = parameter.Type;
SerializedProperty property = parameter.Property;
property.AssignNewInstanceOfTypeToManagedReference(type);
}
private readonly struct GenericMenuParameterForAssignInstanceCommand
{
public GenericMenuParameterForAssignInstanceCommand(Type type, SerializedProperty property)
{
Type = type;
Property = property;
} }
public readonly SerializedProperty Property; /// Purpose.
public readonly Type Type; /// This is generic selection menu (will appear at click position).
/// Filtering.
/// You can add substring filter here to filter by search string.
/// As well ass type or interface restrictions.
/// As well as any custom restriction that is based on input type.
/// And it will be performed on each Appropriate type found by TypeCache.
public static void ShowContextMenuForManagedReference(this SerializedProperty property, IEnumerable<Func<Type, bool>> filters = null)
{
GenericMenu context = new GenericMenu();
FillContextMenu(filters, context, property);
context.ShowAsContext();
}
private static void FillContextMenu(IEnumerable<Func<Type, bool>> enumerableFilters, GenericMenu contextMenu, SerializedProperty property)
{
List<Func<Type, bool>> filters = enumerableFilters.ToList();// Prevents possible multiple enumerations
// Adds "Make Null" menu command
contextMenu.AddItem(new GUIContent("Null"), false, property.SetManagedReferenceToNull);
// Collects appropriate types
IEnumerable<Type> appropriateTypes = property.GetAppropriateTypesForAssigningToManagedReference(filters);
// Adds appropriate types to menu
foreach (Type appropriateType in appropriateTypes)
{
AddItemToContextMenu(appropriateType, contextMenu, property);
}
}
private static void AddItemToContextMenu(Type type, GenericMenu genericMenuContext, SerializedProperty property)
{
string assemblyName = type.Assembly.ToString().Split('(', ',')[0];
string entryName = type + " ( " + assemblyName + " )";
genericMenuContext.AddItem(new GUIContent(entryName), false, AssignNewInstanceCommand, new GenericMenuParameterForAssignInstanceCommand(type, property));
}
private static void AssignNewInstanceCommand(object objectGenericMenuParameter)
{
GenericMenuParameterForAssignInstanceCommand parameter = (GenericMenuParameterForAssignInstanceCommand)objectGenericMenuParameter;
Type type = parameter.Type;
SerializedProperty property = parameter.Property;
property.AssignNewInstanceOfTypeToManagedReference(type);
}
private readonly struct GenericMenuParameterForAssignInstanceCommand
{
public GenericMenuParameterForAssignInstanceCommand(Type type, SerializedProperty property)
{
Type = type;
Property = property;
}
public readonly SerializedProperty Property;
public readonly Type Type;
}
} }
} }
#endif #endif

View File

@ -4,35 +4,38 @@ using System.Collections.Generic;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
public static class SerializeReferenceInspectorButton namespace Textus.SerializeReferenceUI.Editor
{ {
/// Must be drawn before DefaultProperty in order to receive input public static class SerializeReferenceInspectorButton
public static void DrawSelectionButtonForManagedReference(this SerializedProperty property, Rect position, IEnumerable<Func<Type, bool>> filters = null)
{ {
Rect buttonPosition = position; /// Must be drawn before DefaultProperty in order to receive input
buttonPosition.x += EditorGUIUtility.labelWidth + 1 * EditorGUIUtility.standardVerticalSpacing; public static void DrawSelectionButtonForManagedReference(this SerializedProperty property, Rect position, IEnumerable<Func<Type, bool>> filters = null)
buttonPosition.width = position.width - EditorGUIUtility.labelWidth - 1 * EditorGUIUtility.standardVerticalSpacing;
buttonPosition.height = EditorGUIUtility.singleLineHeight;
Color storedColor = GUI.backgroundColor;
int storedIndent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
(string AssemblyName, string ClassName) = ManagedReferenceUtility.GetSplitNamesFromTypename(property.managedReferenceFullTypename);
bool isNull = string.IsNullOrEmpty(ClassName);
GUI.backgroundColor = isNull ? Color.red : storedColor;
string className = isNull ? "Null (Assign)" : ClassName;
string assemblyName = AssemblyName;
if (GUI.Button(buttonPosition, new GUIContent(className, className + " ( "+ assemblyName +" )")))
{ {
property.ShowContextMenuForManagedReference(buttonPosition, filters); Rect buttonPosition = position;
} buttonPosition.x += EditorGUIUtility.labelWidth + 1 * EditorGUIUtility.standardVerticalSpacing;
buttonPosition.width = position.width - EditorGUIUtility.labelWidth - 1 * EditorGUIUtility.standardVerticalSpacing;
buttonPosition.height = EditorGUIUtility.singleLineHeight;
GUI.backgroundColor = storedColor; Color storedColor = GUI.backgroundColor;
EditorGUI.indentLevel = storedIndent; int storedIndent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
(string AssemblyName, string ClassName) = ManagedReferenceUtility.GetSplitNamesFromTypename(property.managedReferenceFullTypename);
bool isNull = string.IsNullOrEmpty(ClassName);
GUI.backgroundColor = isNull ? Color.red : storedColor;
string className = isNull ? "Null (Assign)" : ClassName;
string assemblyName = AssemblyName;
if (GUI.Button(buttonPosition, new GUIContent(className, className + " ( " + assemblyName + " )")))
{
property.ShowContextMenuForManagedReference(buttonPosition, filters);
}
GUI.backgroundColor = storedColor;
EditorGUI.indentLevel = storedIndent;
}
} }
} }
#endif #endif

View File

@ -4,17 +4,20 @@ using System.Collections.Generic;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
public static class SerializeReferenceInspectorMiddleMouseMenu namespace Textus.SerializeReferenceUI.Editor
{ {
public static void ShowContextMenuForManagedReferenceOnMouseMiddleButton(this SerializedProperty property, Rect position, IEnumerable<Func<Type, bool>> filters = null) public static class SerializeReferenceInspectorMiddleMouseMenu
{ {
Event e = Event.current; public static void ShowContextMenuForManagedReferenceOnMouseMiddleButton(this SerializedProperty property, Rect position, IEnumerable<Func<Type, bool>> filters = null)
if (e.type != EventType.MouseDown || !position.Contains(e.mousePosition) || e.button != 2)
{ {
return; Event e = Event.current;
} if (e.type != EventType.MouseDown || !position.Contains(e.mousePosition) || e.button != 2)
{
return;
}
property.ShowContextMenuForManagedReference(filters); property.ShowContextMenuForManagedReference(filters);
}
} }
} }
#endif #endif