forked from Shardstone/trail-into-darkness
330 lines
10 KiB
C#
330 lines
10 KiB
C#
// ----------------------------------------------------------------------------
|
|
// Author: Dimitry, PixeyeHQ
|
|
// Project : UNITY FOLDOUT
|
|
// https://github.com/PixeyeHQ/InspectorFoldoutGroup
|
|
// Contacts : Pix - ask@pixeye.games
|
|
// Website : http://www.pixeye.games
|
|
// ----------------------------------------------------------------------------
|
|
|
|
using UnityEngine;
|
|
|
|
namespace InspectorToolkit
|
|
{
|
|
public class FoldoutAttribute : PropertyAttribute
|
|
{
|
|
public readonly string Name;
|
|
public readonly bool FoldEverything;
|
|
|
|
/// <summary>Adds the property to the specified foldout group.</summary>
|
|
/// <param name="name">Name of the foldout group.</param>
|
|
/// <param name="foldEverything">Toggle to put all properties to the specified group</param>
|
|
public FoldoutAttribute(string name, bool foldEverything = false)
|
|
{
|
|
FoldEverything = foldEverything;
|
|
Name = name;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
namespace InspectorToolkit.Internal
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using UnityEditor;
|
|
|
|
public class FoldoutAttributeHandler
|
|
{
|
|
|
|
|
|
private readonly Dictionary<string, CacheFoldProp> _cacheFoldouts = new Dictionary<string, CacheFoldProp>();
|
|
private readonly List<SerializedProperty> _props = new List<SerializedProperty>();
|
|
private bool _initialized;
|
|
|
|
private readonly IGUILayoutPropertyField guiLayoutPropertyField;
|
|
|
|
private readonly UnityEngine.Object _target;
|
|
private readonly SerializedObject _serializedObject;
|
|
|
|
|
|
public bool OverrideInspector => _props.Count > 0;
|
|
|
|
public FoldoutAttributeHandler(UnityEngine.Object target, SerializedObject serializedObject, IGUILayoutPropertyField guiLayoutPropertyField)
|
|
{
|
|
_target = target;
|
|
_serializedObject = serializedObject;
|
|
this.guiLayoutPropertyField = guiLayoutPropertyField;
|
|
}
|
|
|
|
public void OnDisable()
|
|
{
|
|
if (_target == null) return;
|
|
|
|
foreach (var c in _cacheFoldouts)
|
|
{
|
|
EditorPrefs.SetBool(string.Format($"{c.Value.Attribute.Name}{c.Value.Properties[0].name}{_target.name}"), c.Value.Expanded);
|
|
c.Value.Dispose();
|
|
}
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
_serializedObject.Update();
|
|
Setup();
|
|
}
|
|
|
|
public void OnInspectorGUI()
|
|
{
|
|
Header();
|
|
Body();
|
|
|
|
_serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
private void Header()
|
|
{
|
|
using (new EditorGUI.DisabledScope("m_Script" == _props[0].propertyPath))
|
|
{
|
|
//EditorGUILayout.Space();
|
|
EditorGUILayout.PropertyField(_props[0], true);
|
|
//EditorGUILayout.Space();
|
|
}
|
|
}
|
|
|
|
private void Body()
|
|
{
|
|
foreach (var pair in _cacheFoldouts)
|
|
{
|
|
EditorGUILayout.BeginVertical(StyleFramework.Box);
|
|
Foldout(pair.Value);
|
|
EditorGUILayout.EndVertical();
|
|
|
|
EditorGUI.indentLevel = 0;
|
|
}
|
|
|
|
for (var i = 1; i < _props.Count; i++) {
|
|
SerializedProperty prop = _props[i];
|
|
|
|
guiLayoutPropertyField.GUILayoutPropertyField(prop);
|
|
}
|
|
}
|
|
|
|
private void Foldout(CacheFoldProp cache)
|
|
{
|
|
cache.Expanded = EditorGUILayout.Foldout(cache.Expanded, cache.Attribute.Name, true, StyleFramework.FoldoutHeader);
|
|
var rect = GUILayoutUtility.GetLastRect();
|
|
rect.x -= 18;
|
|
rect.y -= 4;
|
|
rect.height += 8;
|
|
rect.width += 18;
|
|
EditorGUI.LabelField(rect, GUIContent.none, EditorStyles.helpBox);
|
|
|
|
if (cache.Expanded)
|
|
{
|
|
EditorGUILayout.Space(2);
|
|
|
|
foreach (SerializedProperty property in cache.Properties)
|
|
{
|
|
EditorGUILayout.BeginVertical(StyleFramework.BoxChild);
|
|
guiLayoutPropertyField.GUILayoutPropertyField(
|
|
property,
|
|
new GUIContent(ObjectNames.NicifyVariableName(property.name), property.tooltip),
|
|
true);
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Setup()
|
|
{
|
|
if (_initialized) return;
|
|
|
|
FoldoutAttribute prevFold = default;
|
|
|
|
var length = EditorTypes.GetFields(_target, out var objectFields);
|
|
|
|
for (var i = 0; i < length; i++)
|
|
{
|
|
#region FOLDERS
|
|
|
|
var fold = Attribute.GetCustomAttribute(objectFields[i], typeof(FoldoutAttribute)) as FoldoutAttribute;
|
|
CacheFoldProp c;
|
|
if (fold == null)
|
|
{
|
|
if (prevFold != null && prevFold.FoldEverything)
|
|
{
|
|
if (!_cacheFoldouts.TryGetValue(prevFold.Name, out c))
|
|
{
|
|
_cacheFoldouts.Add(prevFold.Name,
|
|
new CacheFoldProp {Attribute = prevFold, Types = new HashSet<string> {objectFields[i].Name}});
|
|
}
|
|
else
|
|
{
|
|
c.Types.Add(objectFields[i].Name);
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
prevFold = fold;
|
|
|
|
if (!_cacheFoldouts.TryGetValue(fold.Name, out c))
|
|
{
|
|
var expanded = EditorPrefs.GetBool(string.Format($"{fold.Name}{objectFields[i].Name}{_target.name}"), false);
|
|
_cacheFoldouts.Add(fold.Name,
|
|
new CacheFoldProp {Attribute = fold, Types = new HashSet<string> {objectFields[i].Name}, Expanded = expanded});
|
|
}
|
|
else c.Types.Add(objectFields[i].Name);
|
|
|
|
#endregion
|
|
}
|
|
|
|
var property = _serializedObject.GetIterator();
|
|
var next = property.NextVisible(true);
|
|
if (next)
|
|
{
|
|
do
|
|
{
|
|
HandleFoldProp(property);
|
|
} while (property.NextVisible(false));
|
|
}
|
|
|
|
_initialized = true;
|
|
}
|
|
|
|
private void HandleFoldProp(SerializedProperty prop)
|
|
{
|
|
bool shouldBeFolded = false;
|
|
|
|
foreach (var pair in _cacheFoldouts)
|
|
{
|
|
if (pair.Value.Types.Contains(prop.name))
|
|
{
|
|
var pr = prop.Copy();
|
|
shouldBeFolded = true;
|
|
pair.Value.Properties.Add(pr);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shouldBeFolded == false)
|
|
{
|
|
var pr = prop.Copy();
|
|
_props.Add(pr);
|
|
}
|
|
}
|
|
|
|
private class CacheFoldProp
|
|
{
|
|
public HashSet<string> Types = new HashSet<string>();
|
|
public readonly List<SerializedProperty> Properties = new List<SerializedProperty>();
|
|
public FoldoutAttribute Attribute;
|
|
public bool Expanded;
|
|
|
|
public void Dispose()
|
|
{
|
|
Properties.Clear();
|
|
Types.Clear();
|
|
Attribute = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static class StyleFramework
|
|
{
|
|
public static readonly GUIStyle Box;
|
|
public static readonly GUIStyle BoxChild;
|
|
public static readonly GUIStyle FoldoutHeader;
|
|
|
|
static StyleFramework()
|
|
{
|
|
FoldoutHeader = new GUIStyle(EditorStyles.foldout);
|
|
FoldoutHeader.overflow = new RectOffset(-10, 0, 3, 0);
|
|
FoldoutHeader.padding = new RectOffset(20, 0, 0, 0);
|
|
FoldoutHeader.margin = new RectOffset(0, 0, 0, 2);
|
|
FoldoutHeader.border = new RectOffset(2, 2, 2, 2);
|
|
|
|
Box = new GUIStyle(GUI.skin.box);
|
|
Box.padding = new RectOffset(18, 0, 4, 0);
|
|
|
|
BoxChild = new GUIStyle(GUI.skin.box);
|
|
BoxChild.padding = new RectOffset(0, 0, 0, 0);
|
|
BoxChild.margin = new RectOffset(0, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
static class EditorTypes
|
|
{
|
|
private static readonly Dictionary<int, List<FieldInfo>> Fields = new Dictionary<int, List<FieldInfo>>(FastComparable.Default);
|
|
private static readonly Dictionary<int, List<PropertyInfo>> Properties = new Dictionary<int, List<PropertyInfo>>(FastComparable.Default);
|
|
|
|
public static int GetFields(Object target, out List<FieldInfo> objectFields)
|
|
{
|
|
var t = target.GetType();
|
|
var hash = t.GetHashCode();
|
|
|
|
if (!Fields.TryGetValue(hash, out objectFields))
|
|
{
|
|
var typeTree = GetTypeTree(t);
|
|
objectFields = target.GetType()
|
|
.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic)
|
|
.OrderByDescending(x => typeTree.IndexOf(x.DeclaringType))
|
|
.ToList();
|
|
Fields.Add(hash, objectFields);
|
|
}
|
|
|
|
return objectFields.Count;
|
|
}
|
|
|
|
public static int GetProperties(Object target, out List<PropertyInfo> objectProperties) {
|
|
var t = target.GetType();
|
|
var hash = t.GetHashCode();
|
|
|
|
if(!Properties.TryGetValue(hash, out objectProperties)) {
|
|
var typeTree = GetTypeTree(t);
|
|
objectProperties = target.GetType()
|
|
.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic)
|
|
.OrderByDescending(x => typeTree.IndexOf(x.DeclaringType))
|
|
.ToList();
|
|
Properties.Add(hash, objectProperties);
|
|
}
|
|
|
|
return objectProperties.Count;
|
|
}
|
|
|
|
static IList<Type> GetTypeTree(Type t)
|
|
{
|
|
var types = new List<Type>();
|
|
while (t.BaseType != null)
|
|
{
|
|
types.Add(t);
|
|
t = t.BaseType;
|
|
}
|
|
|
|
return types;
|
|
}
|
|
}
|
|
|
|
|
|
internal class FastComparable : IEqualityComparer<int>
|
|
{
|
|
public static readonly FastComparable Default = new FastComparable();
|
|
|
|
public bool Equals(int x, int y)
|
|
{
|
|
return x == y;
|
|
}
|
|
|
|
public int GetHashCode(int obj)
|
|
{
|
|
return obj.GetHashCode();
|
|
}
|
|
}
|
|
}
|
|
#endif
|