Files

376 lines
11 KiB
C#

using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using System.Reflection;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Collections.Generic;
using EditorAttributes.Editor.Utility;
using Object = UnityEngine.Object;
namespace EditorAttributes.Editor
{
[CanEditMultipleObjects, CustomEditor(typeof(Object), true)]
public class EditorExtension : UnityEditor.Editor
{
public static readonly Color DEFAULT_GLOBAL_COLOR = new(0.8f, 0.8f, 0.8f, 1.0f);
public static Color GLOBAL_COLOR = DEFAULT_GLOBAL_COLOR;
private string buttonParamsDataFilePath;
private Dictionary<MethodInfo, bool> buttonFoldouts = new();
private Dictionary<MethodInfo, object[]> buttonParameterValues = new();
private MethodInfo[] functions;
protected virtual void OnEnable()
{
var funcList = new List<MethodInfo>();
var targetType = target.GetType();
while (targetType != null)
{
funcList.AddRange(targetType.GetMethods(ReflectionUtility.BINDING_FLAGS));
targetType = targetType.BaseType;
}
functions = funcList.ToArray();
ButtonDrawer.LoadParamsData(functions, target, ref buttonFoldouts, ref buttonParameterValues);
try
{
buttonParamsDataFilePath = Path.Combine(ButtonDrawer.PARAMS_DATA_LOCATION, ButtonDrawer.GetFileName(target));
}
catch (ArgumentException)
{
return;
}
}
protected virtual void OnDisable()
{
if (target == null)
ButtonDrawer.DeleteParamsData(buttonParamsDataFilePath);
EditorHandles.handleProperties.Clear();
EditorHandles.boundsHandleList.Clear();
}
void OnSceneGUI() => EditorHandles.DrawHandles();
public override VisualElement CreateInspectorGUI()
{
// Reset the global color per component GUI so it doesnt leak from other components
GLOBAL_COLOR = DEFAULT_GLOBAL_COLOR;
var root = new VisualElement();
var nonSerializedMembers = DrawNonSerializedMembers();
var defaultInspector = DrawDefaultInspector();
var buttons = DrawButtons();
root.Add(defaultInspector);
root.Add(nonSerializedMembers);
root.Add(buttons);
return root;
}
protected virtual new VisualElement DrawDefaultInspector()
{
var root = new VisualElement();
var propertyList = new Dictionary<string, PropertyField>();
using (var property = serializedObject.GetIterator())
{
if (property.NextVisible(true))
{
IColorAttribute prevColor = null;
do
{
var propertyField = PropertyDrawerBase.CreatePropertyField(property);
if (property.name == "m_Script")
{
propertyField.SetEnabled(false);
root.Add(propertyField);
continue;
}
var field = ReflectionUtility.FindField(property.name, target);
if (field?.GetCustomAttribute<HidePropertyAttribute>() != null)
propertyField.style.display = DisplayStyle.None;
var colorAttribute = field?.GetCustomAttribute<GUIColorAttribute>();
if (colorAttribute != null)
{
GUIColorDrawer.ColorField(propertyField, colorAttribute);
prevColor = colorAttribute;
}
else if (prevColor != null)
{
GUIColorDrawer.ColorField(propertyField, prevColor);
}
propertyList.Add(property.name, propertyField);
}
while (property.NextVisible(false));
}
}
var orderedProperties = propertyList.OrderBy((property) =>
{
var field = ReflectionUtility.FindField(property.Key, target);
var propertyOrderAttribute = field?.GetCustomAttribute<PropertyOrderAttribute>();
if (propertyOrderAttribute != null)
return propertyOrderAttribute.PropertyOrder;
return 0;
});
foreach (var property in orderedProperties)
root.Add(property.Value);
return root;
}
/// <summary>
/// Draws all the members marked with the ShowInInspector attribute
/// </summary>
/// <returns>A visual element containing all non serialized member fields</returns>
protected VisualElement DrawNonSerializedMembers()
{
var root = new VisualElement();
var nonSerializedFields = target.GetType().GetFields(ReflectionUtility.BINDING_FLAGS).Where((field) => field.GetCustomAttribute<ShowInInspectorAttribute>() != null);
foreach (var nonSerializedField in nonSerializedFields)
{
if (HasRestrictedAttributes(nonSerializedField, out string errorMessage))
{
root.Add(new HelpBox(errorMessage, HelpBoxMessageType.Error));
continue;
}
var field = DrawNonSerializedField(nonSerializedField, nonSerializedField.FieldType, nonSerializedField.GetValue(target));
root.Add(field);
}
var nonSerializedProperties = target.GetType().GetProperties(ReflectionUtility.BINDING_FLAGS).Where((field) => field.GetCustomAttribute<ShowInInspectorAttribute>() != null);
foreach (var nonSerializedProperty in nonSerializedProperties)
{
if (HasRestrictedAttributes(nonSerializedProperty, out string errorMessage))
{
root.Add(new HelpBox(errorMessage, HelpBoxMessageType.Error));
continue;
}
var field = DrawNonSerializedField(nonSerializedProperty, nonSerializedProperty.PropertyType, nonSerializedProperty.GetValue(target));
root.Add(field);
}
var nonSerializedMethods = target.GetType().GetMethods(ReflectionUtility.BINDING_FLAGS).Where((field) => field.GetCustomAttribute<ShowInInspectorAttribute>() != null);
foreach (var nonSerializedMethod in nonSerializedMethods)
{
if (HasRestrictedAttributes(nonSerializedMethod, out string errorMessage))
{
root.Add(new HelpBox(errorMessage, HelpBoxMessageType.Error));
continue;
}
if (nonSerializedMethod.GetParameters().Length > 0 || nonSerializedMethod.ContainsGenericParameters)
{
root.Add(new HelpBox($"Method <b>{nonSerializedMethod.Name}</b> cannot be drawn because it has parameters or is generic", HelpBoxMessageType.Error));
continue;
}
var field = DrawNonSerializedField(nonSerializedMethod, nonSerializedMethod.ReturnType, nonSerializedMethod.Invoke(target, null));
root.Add(field);
}
return root;
}
private VisualElement DrawNonSerializedField(MemberInfo memberInfo, Type memberType, object memberValue)
{
var root = new VisualElement();
var header = new Label()
{
style = {
marginTop = 13,
marginLeft = 3,
marginRight = -2,
unityFontStyleAndWeight = FontStyle.Bold,
unityTextAlign = TextAnchor.LowerLeft
}
};
header.AddToClassList("unity-header-drawer__label");
var field = PropertyDrawerBase.CreateFieldForType(memberType, memberInfo.Name, memberValue, AreNonSerializedMemberValuesDifferent(memberInfo, targets));
field.AddToClassList(BaseField<Void>.alignedFieldUssClassName);
if (field is Foldout)
{
field.contentContainer.SetEnabled(false);
field.Q<Label>().SetEnabled(false);
}
else
{
field.SetEnabled(false);
}
PropertyDrawerBase.BindFieldToMember(memberType, field, memberInfo, target);
foreach (var spaceAttribute in memberInfo.GetCustomAttributes<SpaceAttribute>())
{
var space = new VisualElement();
space.style.height = spaceAttribute.height;
space.AddToClassList("unity-space-drawer");
root.Add(space);
}
var headerAttribute = memberInfo.GetCustomAttribute<HeaderAttribute>();
if (headerAttribute != null)
{
header.text = headerAttribute.header;
root.Add(header);
}
root.Add(field);
return root;
}
private bool AreNonSerializedMemberValuesDifferent(MemberInfo memberInfo, Object[] targets)
{
if (targets == null || targets.Length <= 1)
return false;
object firstValue = ReflectionUtility.GetMemberInfoValue(memberInfo, targets[0]);
for (int i = 1; i < targets.Length; i++)
{
object otherValue = ReflectionUtility.GetMemberInfoValue(memberInfo, targets[i]);
if (!Equals(firstValue, otherValue))
return true;
}
return false;
}
private bool HasRestrictedAttributes(MemberInfo memberInfo, out string errorMessage)
{
if (memberInfo.GetCustomAttribute<HideInInspector>() != null || memberInfo.GetCustomAttribute<HidePropertyAttribute>() != null)
{
errorMessage = $"You want to show the member <b>{memberInfo.Name}</b> but you mark it with the HideInInspector or HideProperty Attribute, make up your mind bro";
return true;
}
if (memberInfo.GetCustomAttribute<SerializeField>() != null || memberInfo.GetCustomAttribute<SerializeReference>() != null)
{
errorMessage = $"The member <b>{memberInfo.Name}</b> is already serialized, there is no need to use the ShowInInspector Attribute";
return true;
}
errorMessage = string.Empty;
return false;
}
/// <summary>
/// Draws all the buttons from functions using the Button Attribute
/// </summary>
/// <returns>A visual element containing all drawn buttons</returns>
protected VisualElement DrawButtons()
{
var root = new VisualElement();
var errorBox = new HelpBox();
IColorAttribute prevColor = null;
foreach (var function in functions)
{
var buttonAttribute = function.GetCustomAttribute<ButtonAttribute>();
if (buttonAttribute == null)
continue;
var colorAttribute = function?.GetCustomAttribute<GUIColorAttribute>();
if (colorAttribute != null)
{
GUIColorDrawer.ColorField(root, colorAttribute);
prevColor = colorAttribute;
}
else if (prevColor != null)
{
GUIColorDrawer.ColorField(root, prevColor);
}
var button = ButtonDrawer.DrawButton(function, buttonAttribute, buttonFoldouts, buttonParameterValues, targets);
var conditionalProperty = ReflectionUtility.GetValidMemberInfo(buttonAttribute.ConditionName, target);
button.RegisterCallback<FocusOutEvent>((callback) => ButtonDrawer.SaveParamsData(functions, target, buttonFoldouts, buttonParameterValues));
if (conditionalProperty != null)
{
PropertyDrawerBase.UpdateVisualElement(root, () =>
{
var conditionValue = PropertyDrawerBase.GetConditionValue(conditionalProperty, buttonAttribute, target, errorBox);
if (buttonAttribute.Negate)
conditionValue = !conditionValue;
switch (buttonAttribute.ConditionResult)
{
case ConditionResult.ShowHide:
if (conditionValue)
{
if (!root.Contains(button))
root.Add(button);
}
else
{
PropertyDrawerBase.RemoveElement(root, button);
}
break;
case ConditionResult.EnableDisable:
button.SetEnabled(conditionValue);
break;
}
PropertyDrawerBase.DisplayErrorBox(root, errorBox);
});
root.Add(button);
}
else
{
root.Add(button);
}
}
return root;
}
}
}