Files
trail-into-darkness/Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs
2026-03-29 18:59:24 +02:00

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