You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
10 KiB
332 lines
10 KiB
|
1 month ago
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Reflection;
|
||
|
|
using UnityEditor;
|
||
|
|
using UnityEngine;
|
||
|
|
|
||
|
|
namespace UDE_HAND_INTERACTION
|
||
|
|
{
|
||
|
|
public class EditorBase
|
||
|
|
{
|
||
|
|
private SerializedObject _serializedObject;
|
||
|
|
|
||
|
|
private HashSet<string> _hiddenProperties = new HashSet<string>();
|
||
|
|
private HashSet<string> _skipProperties = new HashSet<string>();
|
||
|
|
|
||
|
|
private Dictionary<string, Action<SerializedProperty>> _customDrawers =
|
||
|
|
new Dictionary<string, Action<SerializedProperty>>();
|
||
|
|
|
||
|
|
private Dictionary<string, Section> _sections =
|
||
|
|
new Dictionary<string, Section>();
|
||
|
|
private List<string> _orderedSections = new List<string>();
|
||
|
|
|
||
|
|
public class Section
|
||
|
|
{
|
||
|
|
public string title;
|
||
|
|
public bool isFoldout;
|
||
|
|
public bool foldout;
|
||
|
|
public List<string> properties = new List<string>();
|
||
|
|
|
||
|
|
public bool HasSomethingToDraw()
|
||
|
|
{
|
||
|
|
return properties != null && properties.Count > 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public EditorBase(SerializedObject serializedObject)
|
||
|
|
{
|
||
|
|
_serializedObject = serializedObject;
|
||
|
|
}
|
||
|
|
|
||
|
|
#region Sections
|
||
|
|
private Section GetOrCreateSection(string sectionName)
|
||
|
|
{
|
||
|
|
if (_sections.TryGetValue(sectionName, out Section existingSection))
|
||
|
|
{
|
||
|
|
return existingSection;
|
||
|
|
}
|
||
|
|
|
||
|
|
Section section = CreateSection(sectionName, false);
|
||
|
|
return section;
|
||
|
|
}
|
||
|
|
|
||
|
|
public Section CreateSection(string sectionName, bool isFoldout)
|
||
|
|
{
|
||
|
|
if (_sections.TryGetValue(sectionName, out Section existingSection))
|
||
|
|
{
|
||
|
|
Debug.LogError($"Section {sectionName} already exists");
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
Section section = new Section() { title = sectionName, isFoldout = isFoldout };
|
||
|
|
_sections.Add(sectionName, section);
|
||
|
|
_orderedSections.Add(sectionName);
|
||
|
|
return section;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void CreateSections(Dictionary<string, string[]> sections, bool isFoldout)
|
||
|
|
{
|
||
|
|
foreach (var sectionData in sections)
|
||
|
|
{
|
||
|
|
CreateSection(sectionData.Key, isFoldout);
|
||
|
|
AddToSection(sectionData.Key, sectionData.Value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void AddToSection(string sectionName, params string[] properties)
|
||
|
|
{
|
||
|
|
if (properties.Length == 0
|
||
|
|
|| !ValidateProperties(properties))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Section section = GetOrCreateSection(sectionName);
|
||
|
|
foreach (var property in properties)
|
||
|
|
{
|
||
|
|
section.properties.Add(property);
|
||
|
|
_skipProperties.Add(property);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
#region IMPLEMENTATION
|
||
|
|
|
||
|
|
public bool IsInSection(string property)
|
||
|
|
{
|
||
|
|
return _skipProperties.Contains(property);
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool IsHidden(string property)
|
||
|
|
{
|
||
|
|
return _hiddenProperties.Contains(property);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void DrawFullInspector()
|
||
|
|
{
|
||
|
|
SerializedProperty it = _serializedObject.GetIterator();
|
||
|
|
it.NextVisible(enterChildren: true);
|
||
|
|
EditorGUI.BeginDisabledGroup(true);
|
||
|
|
EditorGUILayout.PropertyField(it);
|
||
|
|
EditorGUI.EndDisabledGroup();
|
||
|
|
|
||
|
|
EditorGUI.BeginChangeCheck();
|
||
|
|
|
||
|
|
while (it.NextVisible(enterChildren: false))
|
||
|
|
{
|
||
|
|
if (IsInSection(it.name))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
DrawProperty(it);
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (string sectionKey in _orderedSections)
|
||
|
|
{
|
||
|
|
DrawSection(sectionKey);
|
||
|
|
}
|
||
|
|
|
||
|
|
_serializedObject.ApplyModifiedProperties();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void DrawSection(string sectionName)
|
||
|
|
{
|
||
|
|
Section section = _sections[sectionName];
|
||
|
|
DrawSection(section);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DrawSection(Section section)
|
||
|
|
{
|
||
|
|
if (!section.HasSomethingToDraw())
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (section.isFoldout)
|
||
|
|
{
|
||
|
|
section.foldout = EditorGUILayout.Foldout(section.foldout, section.title);
|
||
|
|
if (!section.foldout)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
EditorGUI.indentLevel++;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (string prop in section.properties)
|
||
|
|
{
|
||
|
|
DrawProperty(_serializedObject.FindProperty(prop));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (section.isFoldout)
|
||
|
|
{
|
||
|
|
EditorGUI.indentLevel--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DrawProperty(SerializedProperty property)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
if (IsHidden(property.name))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Action<SerializedProperty> customDrawer;
|
||
|
|
if (_customDrawers.TryGetValue(property.name, out customDrawer))
|
||
|
|
{
|
||
|
|
customDrawer(property);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
EditorGUILayout.PropertyField(property, includeChildren: true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (Exception e)
|
||
|
|
{
|
||
|
|
Debug.LogError($"Error drawing property {e.Message}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool ValidateProperties(params string[] properties)
|
||
|
|
{
|
||
|
|
foreach (var property in properties)
|
||
|
|
{
|
||
|
|
if (_serializedObject.FindProperty(property) == null)
|
||
|
|
{
|
||
|
|
Debug.LogWarning(
|
||
|
|
$"Could not find property {property}, maybe it was deleted or renamed?");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
}
|
||
|
|
public class OptionalAttribute : PropertyAttribute
|
||
|
|
{
|
||
|
|
[System.Flags]
|
||
|
|
public enum Flag
|
||
|
|
{
|
||
|
|
None = 0,
|
||
|
|
AutoGenerated = 1 << 0,
|
||
|
|
DontHide = 1 << 1
|
||
|
|
}
|
||
|
|
|
||
|
|
public Flag Flags { get; private set; } = Flag.None;
|
||
|
|
|
||
|
|
public OptionalAttribute() { }
|
||
|
|
|
||
|
|
public OptionalAttribute(Flag flags)
|
||
|
|
{
|
||
|
|
Flags = flags;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
public class SectionAttribute : PropertyAttribute
|
||
|
|
{
|
||
|
|
public string SectionName { get; private set; } = string.Empty;
|
||
|
|
|
||
|
|
public SectionAttribute(string sectionName)
|
||
|
|
{
|
||
|
|
SectionName = sectionName;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public class EditorUtils : Editor
|
||
|
|
{
|
||
|
|
protected EditorBase _editorDrawer;
|
||
|
|
private const string OptionalSection = "Optionals";
|
||
|
|
|
||
|
|
protected virtual void OnEnable()
|
||
|
|
{
|
||
|
|
_editorDrawer = new EditorBase(serializedObject);
|
||
|
|
|
||
|
|
_editorDrawer.CreateSections(FindCustomSections(serializedObject), true);
|
||
|
|
|
||
|
|
_editorDrawer.CreateSection(OptionalSection, true);
|
||
|
|
_editorDrawer.AddToSection(OptionalSection, FindOptionals(serializedObject));
|
||
|
|
}
|
||
|
|
|
||
|
|
public override void OnInspectorGUI()
|
||
|
|
{
|
||
|
|
_editorDrawer.DrawFullInspector();
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string[] FindOptionals(SerializedObject serializedObject)
|
||
|
|
{
|
||
|
|
List<AttributedProperty<OptionalAttribute>> props = new List<AttributedProperty<OptionalAttribute>>();
|
||
|
|
UnityEngine.Object obj = serializedObject.targetObject;
|
||
|
|
if (obj != null)
|
||
|
|
{
|
||
|
|
FindAttributedSerializedFields(obj.GetType(), props);
|
||
|
|
}
|
||
|
|
|
||
|
|
return props.Where(p => (p.attribute.Flags & OptionalAttribute.Flag.DontHide) == 0)
|
||
|
|
.Select(p => p.propertyName)
|
||
|
|
.ToArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Dictionary<string, string[]> FindCustomSections(SerializedObject serializedObject)
|
||
|
|
{
|
||
|
|
List<AttributedProperty<SectionAttribute>> props = new List<AttributedProperty<SectionAttribute>>();
|
||
|
|
UnityEngine.Object obj = serializedObject.targetObject;
|
||
|
|
if (obj != null)
|
||
|
|
{
|
||
|
|
FindAttributedSerializedFields(obj.GetType(), props);
|
||
|
|
}
|
||
|
|
|
||
|
|
Dictionary<string, string[]> sections = new Dictionary<string, string[]>();
|
||
|
|
var namedSections = props.GroupBy(p => p.attribute.SectionName);
|
||
|
|
foreach (var namedSection in namedSections)
|
||
|
|
{
|
||
|
|
string[] values = namedSection.Select(p => p.propertyName).ToArray();
|
||
|
|
sections.Add(namedSection.Key, values);
|
||
|
|
}
|
||
|
|
return sections;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void FindAttributedSerializedFields<TAttribute>(Type type,
|
||
|
|
List<AttributedProperty<TAttribute>> props)
|
||
|
|
where TAttribute : PropertyAttribute
|
||
|
|
{
|
||
|
|
FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
|
||
|
|
foreach (FieldInfo field in fields)
|
||
|
|
{
|
||
|
|
TAttribute attribute = field.GetCustomAttribute<TAttribute>();
|
||
|
|
if (attribute == null)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (field.GetCustomAttribute<SerializeField>() == null)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
props.Add(new AttributedProperty<TAttribute>(field.Name, attribute));
|
||
|
|
}
|
||
|
|
if (typeof(Component).IsAssignableFrom(type.BaseType))
|
||
|
|
{
|
||
|
|
FindAttributedSerializedFields<TAttribute>(type.BaseType, props);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private struct AttributedProperty<TAttribute>
|
||
|
|
where TAttribute : PropertyAttribute
|
||
|
|
{
|
||
|
|
public string propertyName;
|
||
|
|
public TAttribute attribute;
|
||
|
|
|
||
|
|
public AttributedProperty(string propertyName, TAttribute attribute)
|
||
|
|
{
|
||
|
|
this.propertyName = propertyName;
|
||
|
|
this.attribute = attribute;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|