// ---------------------------------------------------------------------------- // 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; /// Adds the property to the specified foldout group. /// Name of the foldout group. /// Toggle to put all properties to the specified group 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 _cacheFoldouts = new Dictionary(); private readonly List _props = new List(); 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 {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 {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 Types = new HashSet(); public readonly List Properties = new List(); 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> Fields = new Dictionary>(FastComparable.Default); private static readonly Dictionary> Properties = new Dictionary>(FastComparable.Default); public static int GetFields(Object target, out List 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 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 GetTypeTree(Type t) { var types = new List(); while (t.BaseType != null) { types.Add(t); t = t.BaseType; } return types; } } internal class FastComparable : IEqualityComparer { 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