#if UNITY_EDITOR using System.Collections; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.UIElements; using System.Reflection; using UnityEditor; using Type = System.Type; using Attribute = System.Attribute; using static VInspector.VInspectorState; using static VInspector.Libs.VUtils; using static VInspector.Libs.VGUI; namespace VInspector { class VInspectorEditor { public void OnGUI() { var drawingTabPath = ""; var drawingFoldoutPath = ""; var hideField = false; var disableField = false; var noVariablesShown = true; var prevFieldDeclaringType = default(Type); var selectedTabPath = rootTab.GetSelectedTabPath(); void drawMember(MemberInfo memberInfo) { void handleDeclaringTypeChange() { var curFieldDeclaringType = memberInfo.DeclaringType; if (prevFieldDeclaringType == null) { prevFieldDeclaringType = curFieldDeclaringType; return; } if (prevFieldDeclaringType == curFieldDeclaringType) return; drawingTabPath = ""; drawingFoldoutPath = ""; hideField = false; disableField = false; prevFieldDeclaringType = curFieldDeclaringType; } void ifs() { var endIfAttribute = memberInfo.GetCustomAttributeCached(); if (endIfAttribute != null) hideField = disableField = false; var ifAttribute = memberInfo.GetCustomAttributeCached(); if (ifAttribute is HideIfAttribute) hideField = ifAttribute.Evaluate(target); if (ifAttribute is ShowIfAttribute) hideField = !ifAttribute.Evaluate(target); if (ifAttribute is DisableIfAttribute) disableField = ifAttribute.Evaluate(target); if (ifAttribute is EnableIfAttribute) disableField = !ifAttribute.Evaluate(target); } void tabs() { void drawSubtabs(Tab tab) { if (!tab.subtabs.Any()) return; Space(noVariablesShown ? 2 : 6); var selName = TabsMultiRow(tab.selectedSubtab.name, false, 24, tab.subtabs.Select(r => r.name).ToArray()); Space(5); if (selName != tab.selectedSubtab.name) { tab.selectedSubtabIndex = tab.subtabs.IndexOfFirst(r => r.name == selName); selectedTabPath = rootTab.GetSelectedTabPath(); } GUI.backgroundColor = Color.white; tab.subtabsDrawn = true; } void endTab() { if (memberInfo.GetCustomAttributeCached() is not EndTabAttribute endTabAttribute) return; drawingTabPath = ""; drawingFoldoutPath = ""; hideField = false; disableField = false; } void beginTab() { if (memberInfo.GetCustomAttributeCached() is not TabAttribute tabAttribute) return; drawingTabPath = tabAttribute.name; drawingFoldoutPath = ""; hideField = false; disableField = false; } void ensureNeededTabsDrawn() { if (!selectedTabPath.StartsWith(drawingTabPath)) return; var curTab = rootTab; foreach (var name in drawingTabPath.Split('/').Where(r => r != "")) { if (!curTab.subtabsDrawn) drawSubtabs(curTab); curTab = curTab.subtabs.Find(r => r.name == name); } } endTab(); beginTab(); ensureNeededTabsDrawn(); } void foldouts() { bool drawFoldout(string name, bool isExpanded, Foldout foldout) { var controlRect = EditorGUILayout.GetControlRect(); var fullRect = controlRect.AddWidthFromRight(-15 * EditorGUI.indentLevel).AddWidthFromRight(18).AddWidth(3); var controlId = EditorGUIUtility.GetControlID(FocusType.Passive); var isPressed = EditorGUIUtility.hotControl == controlId; void name_() { var labelRect = controlRect.AddWidthFromRight(-15 * EditorGUI.indentLevel); SetLabelBold(); SetLabelFontSize(12); GUI.Label(labelRect, name); ResetLabelStyle(); } void triangle() { // return; // if (!curEvent.isRepaint) return; var unityFoldoutRect = controlRect.MoveX(-.5f); isExpanded = EditorGUI.Foldout(unityFoldoutRect, isExpanded, ""); } void highlight() { var hoveredColor = Greyscale(1, .06f); var pressedColor = Greyscale(1, .04f); fullRect.MarkInteractive(); if (isPressed) fullRect.Draw(pressedColor); else if (fullRect.IsHovered()) fullRect.Draw(hoveredColor); } void mouseDown() { if (!curEvent.isMouseDown) return; if (!fullRect.IsHovered()) return; EditorGUIUtility.hotControl = controlId; curEvent.Use(); } void mouseUp() { if (!curEvent.isMouseUp) return; if (!isPressed) return; if (fullRect.IsHovered()) isExpanded = !isExpanded; EditorGUIUtility.hotControl = 0; EditorGUIUtility.keyboardControl = 0; curEvent.Use(); } // background(); name_(); triangle(); highlight(); mouseDown(); mouseUp(); return isExpanded; } void drawButtons(Foldout foldout) { var noButtonsToShow = true; Space(10); foreach (var button in foldout.buttons) drawButton(button, ref noButtonsToShow); if (noButtonsToShow) Space(-10); else Space(5); } void beginFoldout(string name) { if (!rootFoldout.IsSubfoldoutContentVisible(drawingFoldoutPath)) return; updateIndentLevel(); drawingFoldoutPath = drawingFoldoutPath.CombinePath(name); var foldout = rootFoldout.GetSubfoldout(drawingFoldoutPath); foldout.isExpanded = drawFoldout(foldout.name, foldout.isExpanded, foldout); } void endFoldout() { var foldout = rootFoldout.GetSubfoldout(drawingFoldoutPath); if (foldout.isExpanded) drawButtons(foldout); drawingFoldoutPath = drawingFoldoutPath.HasParentPath() ? drawingFoldoutPath.GetParentPath() : ""; } var newFoldoutPath = drawingFoldoutPath; if (memberInfo.GetCustomAttributeCached() is not null) newFoldoutPath = ""; if (memberInfo.GetCustomAttributeCached() is FoldoutAttribute foldoutAttribute) newFoldoutPath = foldoutAttribute.name; if (newFoldoutPath == drawingFoldoutPath) return; var drawingPathNames = drawingFoldoutPath.Split('/').Where(r => r != "").ToList(); var newPathNames = newFoldoutPath.Split('/').Where(r => r != "").ToList(); var sharedPathNames = new List(); for (int i = 0; i < drawingPathNames.Count && i < newPathNames.Count; i++) if (drawingPathNames[i] == newPathNames[i]) sharedPathNames.Add(drawingPathNames[i]); else break; for (int i = drawingPathNames.Count; i > sharedPathNames.Count; i--) endFoldout(); for (int i = sharedPathNames.Count; i < newPathNames.Count; i++) beginFoldout(newPathNames[i]); } void updateIndentLevel() { var prev = EditorGUI.indentLevel; EditorGUI.indentLevel = baseIndentLevel + drawingFoldoutPath.Split('/').Where(r => r != "").Count(); if (prev > EditorGUI.indentLevel) Space(7); } void field() { var fieldInfo = memberInfo as FieldInfo; var propertyInfo = memberInfo as PropertyInfo; var isSerialized = serializedProperties_byMemberInfos.TryGetValue(memberInfo, out var serializedProeprty); var isNestedEditor = isSerialized && typesUsingVInspector.Contains((fieldInfo.FieldType)); var isResettable = isSerialized && VInspectorResettableVariables.IsResettable(fieldInfo); var isReadOnly = Attribute.IsDefined(memberInfo, typeof(ReadOnlyAttribute)) || memberInfo is PropertyInfo && !propertyInfo.CanWrite; void serialized_default() { if (!isSerialized) return; if (isNestedEditor) return; if (isResettable) return; if (Attribute.IsDefined(memberInfo, typeof(ButtonAttribute))) return; EditorGUILayout.PropertyField(serializedProeprty, true); } void serialized_resettable() { if (!isSerialized) return; if (isNestedEditor) return; if (!isResettable) return; var lastControlId = typeof(EditorGUIUtility).GetFieldValue("s_LastControlID"); if (!curEvent.isRepaint) if (fieldRects_byLastControlId.ContainsKey(lastControlId)) VInspectorResettableVariables.ResetButtonGUI(fieldRects_byLastControlId[lastControlId], serializedProeprty, fieldInfo, targets); EditorGUILayout.PropertyField(serializedProeprty, true); if (curEvent.isRepaint) fieldRects_byLastControlId[lastControlId] = lastRect.AddWidthFromRight(-EditorGUIUtility.labelWidth - 2).SetHeightFromBottom(EditorGUIUtility.singleLineHeight); if (curEvent.isRepaint) VInspectorResettableVariables.ResetButtonGUI(fieldRects_byLastControlId[lastControlId], serializedProeprty, fieldInfo, targets); } void serialized_nestedEditor() { if (!isSerialized) return; if (!isNestedEditor) return; EditorGUILayout.PropertyField(serializedProeprty, false); if (!serializedProeprty.isExpanded) return; if (!nestedEditors_byPropertyPath.TryGetValue(serializedProeprty.propertyPath, out var nestedEditor)) nestedEditor = nestedEditors_byPropertyPath[serializedProeprty.propertyPath] = new VInspectorEditor(rootPropertyGetter: () => serializedProeprty, targetsGetter: () => targets.Select(r => fieldInfo.GetValue(r))); nestedEditor.baseIndentLevel = EditorGUI.indentLevel + 1; nestedEditor.OnGUI(); } void nonSerialized_field() { if (isSerialized) return; if (!Attribute.IsDefined(memberInfo, typeof(ShowInInspectorAttribute))) return; if (memberInfo is not FieldInfo fieldInfo) return; var type = fieldInfo.FieldType; var name = fieldInfo.Name.PrettifyVarName(); var curValue = fieldInfo.GetValue(fieldInfo.IsStatic ? null : target); var newValue = curValue; if (type == typeof(int)) newValue = EditorGUILayout.IntField(name, (int)curValue); else if (type == typeof(float)) newValue = EditorGUILayout.FloatField(name, (float)curValue); else if (type == typeof(double)) newValue = EditorGUILayout.DoubleField(name, (float)curValue); else if (type == typeof(string)) newValue = EditorGUILayout.TextField(name, (string)curValue); else if (type == typeof(bool)) newValue = EditorGUILayout.Toggle(name, (bool)curValue); else if (type == typeof(Vector2)) newValue = EditorGUILayout.Vector2Field(name, (Vector2)curValue); else if (type == typeof(Vector3)) newValue = EditorGUILayout.Vector3Field(name, (Vector3)curValue); else if (type == typeof(Vector4)) newValue = EditorGUILayout.Vector4Field(name, (Vector4)curValue); else if (type == typeof(Color)) newValue = EditorGUILayout.ColorField(name, (Color)curValue); else if (type == typeof(Rect)) newValue = EditorGUILayout.RectField(name, (Rect)curValue); else if (type == typeof(RectInt)) newValue = EditorGUILayout.RectIntField(name, (RectInt)curValue); else if (type == typeof(Bounds)) newValue = EditorGUILayout.BoundsField(name, (Bounds)curValue); else if (type == typeof(BoundsInt)) newValue = EditorGUILayout.BoundsIntField(name, (BoundsInt)curValue); else if (type == typeof(Vector2Int)) newValue = EditorGUILayout.Vector2IntField(name, (Vector2Int)curValue); else if (type == typeof(Vector3Int)) newValue = EditorGUILayout.Vector3IntField(name, (Vector3Int)curValue); else if (type.IsEnum) newValue = EditorGUILayout.EnumPopup(name, (System.Enum)curValue); else if (typeof(Object).IsAssignableFrom(type)) newValue = EditorGUILayout.ObjectField(name, (Object)curValue, type, true); else EditorGUILayout.TextField(name, curValue?.ToString()); fieldInfo.SetValue(fieldInfo.IsStatic ? null : target, newValue); noVariablesShown = false; } void nonSerialized_property() { if (isSerialized) return; if (!Attribute.IsDefined(memberInfo, typeof(ShowInInspectorAttribute))) return; if (memberInfo is not PropertyInfo propertyInfo) return; var type = propertyInfo.PropertyType; var name = propertyInfo.Name.PrettifyVarName(); var curValue = propertyInfo.GetValue(propertyInfo.GetAccessors(true).First().IsStatic ? null : target); var newValue = curValue; if (type == typeof(int)) newValue = EditorGUILayout.IntField(name, (int)curValue); else if (type == typeof(float)) newValue = EditorGUILayout.FloatField(name, (float)curValue); else if (type == typeof(double)) newValue = EditorGUILayout.DoubleField(name, (float)curValue); else if (type == typeof(string)) newValue = EditorGUILayout.TextField(name, (string)curValue); else if (type == typeof(bool)) newValue = EditorGUILayout.Toggle(name, (bool)curValue); else if (type == typeof(Vector2)) newValue = EditorGUILayout.Vector2Field(name, (Vector2)curValue); else if (type == typeof(Vector3)) newValue = EditorGUILayout.Vector3Field(name, (Vector3)curValue); else if (type == typeof(Vector4)) newValue = EditorGUILayout.Vector4Field(name, (Vector4)curValue); else if (type == typeof(Color)) newValue = EditorGUILayout.ColorField(name, (Color)curValue); else if (type == typeof(Rect)) newValue = EditorGUILayout.RectField(name, (Rect)curValue); else if (type == typeof(RectInt)) newValue = EditorGUILayout.RectIntField(name, (RectInt)curValue); else if (type == typeof(Bounds)) newValue = EditorGUILayout.BoundsField(name, (Bounds)curValue); else if (type == typeof(BoundsInt)) newValue = EditorGUILayout.BoundsIntField(name, (BoundsInt)curValue); else if (type == typeof(Vector2Int)) newValue = EditorGUILayout.Vector2IntField(name, (Vector2Int)curValue); else if (type == typeof(Vector3Int)) newValue = EditorGUILayout.Vector3IntField(name, (Vector3Int)curValue); else if (type.IsEnum) newValue = EditorGUILayout.EnumPopup(name, (System.Enum)curValue); else if (typeof(Object).IsAssignableFrom(type)) newValue = EditorGUILayout.ObjectField(name, (Object)curValue, type, true); else EditorGUILayout.TextField(name, curValue?.ToString()); if (propertyInfo.CanWrite && !Attribute.IsDefined(propertyInfo, typeof(ReadOnlyAttribute))) if (!object.Equals(newValue, curValue)) propertyInfo.SetValue(propertyInfo.GetAccessors(true).First().IsStatic ? null : target, newValue); noVariablesShown = false; } void onGuiChanged() { if (memberInfo is not FieldInfo fieldInfo) return; if (!valueChangedCallbacks_byFieldInfos.TryGetValue(fieldInfo, out var methodInfoList)) return; foreach (var target in targets) AbstractEditor.toCallAfterModifyingSO += () => methodInfoList.ForEach(r => r.Invoke(target, null)); } SetGUIEnabled(!isReadOnly && !disableField); EditorGUI.BeginChangeCheck(); serialized_default(); serialized_resettable(); serialized_nestedEditor(); nonSerialized_field(); nonSerialized_property(); if (EditorGUI.EndChangeCheck()) onGuiChanged(); ResetGUIEnabled(); } handleDeclaringTypeChange(); ifs(); if (hideField) return; GUI.enabled = !disableField; tabs(); if (selectedTabPath != drawingTabPath && !selectedTabPath.StartsWith(drawingTabPath + "/") && drawingTabPath != "") return; noVariablesShown = false; foldouts(); if (!rootFoldout.IsSubfoldoutContentVisible(drawingFoldoutPath)) return; updateIndentLevel(); field(); } void drawButton(Button button, ref bool noButtonsShown) { if (button.tabAttribute != null && !selectedTabPath.StartsWith(button.tabAttribute.name)) return; if (button.ifAttribute is HideIfAttribute && button.ifAttribute.Evaluate(target)) return; if (button.ifAttribute is ShowIfAttribute && !button.ifAttribute.Evaluate(target)) return; var prevGuiEnabled = GUI.enabled; if (button.ifAttribute is DisableIfAttribute && button.ifAttribute.Evaluate(target)) GUI.enabled = false; if (button.ifAttribute is EnableIfAttribute && !button.ifAttribute.Evaluate(target)) GUI.enabled = false; Rect buttonRect; Color color = Color.white; void set_buttonRect() { GUILayout.BeginHorizontal(); GUILayout.Space(EditorGUI.indentLevel * 15); buttonRect = ExpandWidthLabelRect(height: button.size); GUILayout.EndHorizontal(); } void set_color() { if (button.color.ToLower() == "grey" || button.color.ToLower() == "gray") return; var hue = 0f; var saturation = .6f; var lightness = isDarkTheme ? .57f : .64f; if (button.color.ToLower() == "red") hue = 0; else if (button.color.ToLower() == "orange") hue = .08f; else if (button.color.ToLower() == "yellow") hue = .13f; else if (button.color.ToLower() == "green") { hue = .32f; saturation = .49f; lightness = isDarkTheme ? .56f : .6f; } else if (button.color.ToLower() == "blue") hue = .55f; else if (button.color.ToLower() == "pink") hue = .94f; else return; color = HSLToRGB(hue, saturation, lightness); color *= 2f; color.a = 1; } void argumentsBackground() { if (!button.parameterInfos.Any()) return; if (!button.isExpanded) return; if (!curEvent.isRepaint) return; var backgroundColor = Greyscale(isDarkTheme ? .27f : .83f); var outlineColor = Greyscale(isDarkTheme ? .15f : .65f); var cornerRadius = 3; var backgroundRect = buttonRect.AddHeight(button.parameterInfos.Count * (EditorGUIUtility.singleLineHeight + 2) + 8); backgroundRect.DrawRounded(outlineColor, cornerRadius); backgroundRect.Resize(1).DrawRounded(backgroundColor, cornerRadius - 1); } void buttonItself() { var prevBackgroundColor = GUI.backgroundColor; GUI.backgroundColor = button.isPressed() ? GUIColors.pressedButtonBackground : color; var clicked = GUI.Button(buttonRect, button.name); GUI.backgroundColor = prevBackgroundColor; if (!clicked) return; foreach (var target in targets) { if (target is Object unityObject && unityObject) unityObject.RecordUndo(); button.action(target); } } void expandButton() { if (!button.parameterInfos.Any()) return; var expandButtonRect = buttonRect.SetWidth(24).MoveX(1); var colorNormal = Greyscale(isDarkTheme ? (buttonRect.IsHovered() ? .85f : .8f) : .7f); var colorHovered = Greyscale(isDarkTheme ? 10f : 0f, 10f); var colorPressed = Greyscale(.85f); var iconSize = 12; if (!IconButton(expandButtonRect, button.isExpanded ? "d_IN_foldout_act_on" : "d_IN_foldout_act", iconSize, colorNormal, colorHovered, colorPressed)) return; button.isExpanded = !button.isExpanded; // GUI.DrawTexture(buttonRect.SetWidth(24).SetHeightFromMid(24).Resize(6).MoveX(2), EditorIcons.GetIcon("d_IN_foldout_on")); // GUI.DrawTexture(buttonRect.SetWidth(24).SetHeightFromMid(24).Resize(6).MoveX(2), EditorIcons.GetIcon("d_IN_foldout_on")); } void parameters() { if (!button.isExpanded) return; if (!button.parameterInfos.Any()) return; void parameter(int i) { var type = button.parameterInfos[i].ParameterType; var name = button.parameterInfos[i].Name.PrettifyVarName(); var curValue = button.GetParameterValue(i); var newValue = curValue; if (type == typeof(int)) newValue = EditorGUILayout.IntField(name, (int)curValue); else if (type == typeof(float)) newValue = EditorGUILayout.FloatField(name, (float)curValue); else if (type == typeof(double)) newValue = EditorGUILayout.DoubleField(name, (float)curValue); else if (type == typeof(string)) newValue = EditorGUILayout.TextField(name, (string)curValue); else if (type == typeof(bool)) newValue = EditorGUILayout.Toggle(name, (bool)curValue); else if (type == typeof(Vector2)) newValue = EditorGUILayout.Vector2Field(name, (Vector2)curValue); else if (type == typeof(Vector3)) newValue = EditorGUILayout.Vector3Field(name, (Vector3)curValue); else if (type == typeof(Vector4)) newValue = EditorGUILayout.Vector4Field(name, (Vector4)curValue); else if (type == typeof(Color)) newValue = EditorGUILayout.ColorField(name, (Color)curValue); else if (type == typeof(Rect)) newValue = EditorGUILayout.RectField(name, (Rect)curValue); else if (type == typeof(RectInt)) newValue = EditorGUILayout.RectIntField(name, (RectInt)curValue); else if (type == typeof(Bounds)) newValue = EditorGUILayout.BoundsField(name, (Bounds)curValue); else if (type == typeof(BoundsInt)) newValue = EditorGUILayout.BoundsIntField(name, (BoundsInt)curValue); else if (type == typeof(Vector2Int)) newValue = EditorGUILayout.Vector2IntField(name, (Vector2Int)curValue); else if (type == typeof(Vector3Int)) newValue = EditorGUILayout.Vector3IntField(name, (Vector3Int)curValue); else if (type.IsEnum) newValue = EditorGUILayout.EnumPopup(name, (System.Enum)curValue); else if (typeof(Object).IsAssignableFrom(type)) newValue = EditorGUILayout.ObjectField(name, (Object)curValue, type, true); else EditorGUILayout.PrefixLabel(name); button.SetParameterValue(i, newValue); } BeginIndent(7); Space(1); for (int i = 0; i < button.parameterInfos.Count; i++) parameter(i); Space(11); EndIndent(5); } GUILayout.Space(button.space - 2); set_buttonRect(); set_color(); argumentsBackground(); if (!curEvent.isRepaint) expandButton(); buttonItself(); if (curEvent.isRepaint) expandButton(); parameters(); if (button.isExpanded) Space(6); GUI.enabled = prevGuiEnabled; noButtonsShown = false; } void scriptField() { if (scriptFieldProperty == null) return; if (VInspectorMenu.hideScriptFieldEnabled) return; using (new EditorGUI.DisabledScope(true)) EditorGUILayout.PropertyField(scriptFieldProperty); } void topMargin() { if (scriptFieldProperty == null) return; if (!VInspectorMenu.hideScriptFieldEnabled) return; Space(2); } void members() { EditorGUI.indentLevel = baseIndentLevel; foreach (var memberInfo in drawableMemberLists_byTargetType[targetType]) drawMember(memberInfo); EditorGUI.indentLevel = baseIndentLevel; } void noVariablesToShow() { if (!noVariablesShown) return; using (new EditorGUI.DisabledScope(true)) GUILayout.Label("No variables to show"); } void endUnendedFoldouts() { if (drawingFoldoutPath == "") return; void drawButtons(Foldout foldout) { var noButtonsToShow = true; Space(10); foreach (var button in foldout.buttons) drawButton(button, ref noButtonsToShow); if (noButtonsToShow) Space(-10); else Space(5); } while (drawingFoldoutPath != "") { var foldout = rootFoldout.GetSubfoldout(drawingFoldoutPath); EditorGUI.indentLevel = baseIndentLevel + drawingFoldoutPath.Split('/').Where(r => r != "").Count(); if (rootFoldout.IsSubfoldoutContentVisible(drawingFoldoutPath)) drawButtons(foldout); drawingFoldoutPath = drawingFoldoutPath.HasParentPath() ? drawingFoldoutPath.GetParentPath() : ""; } EditorGUI.indentLevel = 0; } void buttons() { GUI.enabled = true; var noButtonsToShow = true; foreach (var button in this.buttons) drawButton(button, ref noButtonsToShow); if (noButtonsToShow) Space(-17); } rootTab.ResetSubtabsDrawn(); scriptField(); topMargin(); members(); noVariablesToShow(); endUnendedFoldouts(); Space(16); buttons(); Space(4); } public int baseIndentLevel; public SerializedProperty rootProperty => rootPropertyGetter.Invoke(); public IEnumerable targets => targetsGetter.Invoke(); public object target => targets.FirstOrDefault(); public Type targetType => _targetType ??= target?.GetType(); public Type _targetType; static Dictionary fieldRects_byLastControlId = new(); public VInspectorEditor(System.Func rootPropertyGetter, System.Func> targetsGetter) { this.rootPropertyGetter = rootPropertyGetter; this.targetsGetter = targetsGetter; void createTabs() { void setupTab(Tab tab, IEnumerable allSubtabPaths) { void refreshSubtabs() { var names = allSubtabPaths.Select(r => r.Split('/').First()).ToList(); foreach (var name in names) if (!tab.subtabs.Any(r => r.name == name)) tab.subtabs.Add(new Tab() { name = name }); foreach (var subtab in tab.subtabs.ToList()) if (!names.Any(r => r == subtab.name)) tab.subtabs.Remove(subtab); tab.subtabs.SortBy(r => names.IndexOf(r.name)); } void setupSubtabs() { foreach (var subtab in tab.subtabs) setupTab(subtab, allSubtabPaths.Where(r => r.StartsWith(subtab.name + "/")).Select(r => r.Remove(subtab.name + "/")).ToList()); } refreshSubtabs(); setupSubtabs(); } void findAttributes() { if (tabAttributes_byTargetType.ContainsKey(targetType)) return; var attributes = TypeCache.GetFieldsWithAttribute() .Where(r => r.DeclaringType.IsAssignableFrom(targetType)) .OrderBy(r => r.MetadataToken) .Select(r => r.GetCustomAttributeCached()); tabAttributes_byTargetType[targetType] = attributes.ToList(); } void createTabs() { rootTab = new Tab() { isRootTab = true }; var allTabPaths = tabAttributes_byTargetType[targetType].Select(r => r.name); setupTab(rootTab, allTabPaths); } findAttributes(); createTabs(); } void createFoldouts() { void setupFoldout(Foldout foldout, IEnumerable allSubfoldoutPaths) { void refreshSubfoldouts() { var names = allSubfoldoutPaths.Select(r => r.Split('/').First()).ToList(); foreach (var name in names) if (foldout.subfoldouts.Find(r => r.name == name) == null) foldout.subfoldouts.Add(new Foldout() { name = name }); foreach (var subtab in foldout.subfoldouts.ToList()) if (names.Find(r => r == subtab.name) == null) foldout.subfoldouts.Remove(subtab); foldout.subfoldouts.SortBy(r => names.IndexOf(r.name)); } void setupSubfoldouts() { foreach (var subtab in foldout.subfoldouts) setupFoldout(subtab, allSubfoldoutPaths.Where(r => r.StartsWith(subtab.name + "/")).Select(r => r.Remove(subtab.name + "/")).ToList()); } refreshSubfoldouts(); setupSubfoldouts(); } void findAttributes() { if (foldoutAttributes_byTargetType.ContainsKey(targetType)) return; var attributes = TypeCache.GetFieldsWithAttribute() .Where(r => r.DeclaringType.IsAssignableFrom(targetType)) .OrderBy(r => r.MetadataToken) .Select(r => r.GetCustomAttributeCached()); foldoutAttributes_byTargetType[targetType] = attributes.ToList(); } void createFoldouts() { rootFoldout = new Foldout() { isRootFoldout = true }; var allFoldoutPaths = foldoutAttributes_byTargetType[targetType].Select(r => r.name); setupFoldout(rootFoldout, allFoldoutPaths); } findAttributes(); createFoldouts(); } void createButtons() { void createButton(MemberInfo member, ButtonAttribute buttonAttribute) { var button = new Button(); button.size = buttonAttribute.size; button.space = buttonAttribute.space; button.color = buttonAttribute.color; if (member.GetCustomAttributeCached() is TabAttribute tabAttribute) button.tabAttribute = tabAttribute; if (member.GetCustomAttributeCached() is FoldoutAttribute foldoutAttribute) button.foldoutAttribute = foldoutAttribute; if (member.GetCustomAttributeCached() is IfAttribute ifAttribute) button.ifAttribute = ifAttribute; if (member is FieldInfo field && field.FieldType == typeof(bool)) { var fieldTarget = field.IsStatic ? null : target; button.action = (o) => field.SetValue(o, !(bool)field.GetValue(o)); button.name = buttonAttribute.name != "" ? buttonAttribute.name : field.Name.PrettifyVarName(false); button.isPressed = () => (bool)field.GetValue(fieldTarget); } if (member is MethodInfo method) if (!method.GetParameters().Any()) { var methodTarget = method.IsStatic ? null : target; button.action = (methodTarget) => method.Invoke(methodTarget, null); button.name = buttonAttribute.name != "" ? buttonAttribute.name : method.Name.PrettifyVarName(false); button.isPressed = () => false; } else { var methodTarget = method.IsStatic ? null : target; button.action = (methodTarget) => method.Invoke(methodTarget, Enumerable.Range(0, button.parameterInfos.Count).Select(i => button.GetParameterValue(i)).ToArray()); button.name = buttonAttribute.name != "" ? buttonAttribute.name : method.Name.PrettifyVarName(false); button.isPressed = () => false; button.parameterInfos = method.GetParameters().ToList(); } if (button.action != null) if (button.foldoutAttribute != null && rootFoldout.GetSubfoldout(button.foldoutAttribute.name) is Foldout foldout) foldout.buttons.Add(button); else this.buttons.Add(button); } void findFields() { if (fieldsWithButtonAttributes_byTargetType.ContainsKey(targetType)) return; var fields = TypeCache.GetFieldsWithAttribute() .Where(r => r.DeclaringType.IsAssignableFrom(targetType)) .OrderBy(r => r.MetadataToken); fieldsWithButtonAttributes_byTargetType[targetType] = fields.ToList(); } void findMethods() { if (methodsWithButtonAttributes_byTargetType.ContainsKey(targetType)) return; var methods = TypeCache.GetMethodsWithAttribute() .Where(r => r.DeclaringType.IsAssignableFrom(targetType)) .OrderBy(r => r.MetadataToken); methodsWithButtonAttributes_byTargetType[targetType] = methods.ToList(); } void createButtons() { this.buttons = new List