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.
331 lines
10 KiB
331 lines
10 KiB
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; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|