Files
Fishing2/Assets/Plugins/vInspector/VInspectorEditor.cs
2025-05-10 12:49:47 +08:00

2003 lines
74 KiB
C#

#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<EndIfAttribute>();
if (endIfAttribute != null) hideField = disableField = false;
var ifAttribute = memberInfo.GetCustomAttributeCached<IfAttribute>();
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<EndTabAttribute>() is not EndTabAttribute endTabAttribute) return;
drawingTabPath = "";
drawingFoldoutPath = "";
hideField = false;
disableField = false;
}
void beginTab()
{
if (memberInfo.GetCustomAttributeCached<TabAttribute>() 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<EndFoldoutAttribute>() is not null)
newFoldoutPath = "";
if (memberInfo.GetCustomAttributeCached<FoldoutAttribute>() 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<string>();
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<int>("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<object> targets => targetsGetter.Invoke();
public object target => targets.FirstOrDefault();
public Type targetType => _targetType ??= target?.GetType();
public Type _targetType;
static Dictionary<int, Rect> fieldRects_byLastControlId = new();
public VInspectorEditor(System.Func<SerializedProperty> rootPropertyGetter, System.Func<IEnumerable<object>> targetsGetter)
{
this.rootPropertyGetter = rootPropertyGetter;
this.targetsGetter = targetsGetter;
void createTabs()
{
void setupTab(Tab tab, IEnumerable<string> 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<TabAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType))
.OrderBy(r => r.MetadataToken)
.Select(r => r.GetCustomAttributeCached<TabAttribute>());
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<string> 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<FoldoutAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType))
.OrderBy(r => r.MetadataToken)
.Select(r => r.GetCustomAttributeCached<FoldoutAttribute>());
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<TabAttribute>() is TabAttribute tabAttribute)
button.tabAttribute = tabAttribute;
if (member.GetCustomAttributeCached<FoldoutAttribute>() is FoldoutAttribute foldoutAttribute)
button.foldoutAttribute = foldoutAttribute;
if (member.GetCustomAttributeCached<IfAttribute>() 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<ButtonAttribute>()
.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<ButtonAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType))
.OrderBy(r => r.MetadataToken);
methodsWithButtonAttributes_byTargetType[targetType] = methods.ToList();
}
void createButtons()
{
this.buttons = new List<Button>();
foreach (var method in methodsWithButtonAttributes_byTargetType[targetType])
createButton(method, method.GetCustomAttributeCached<ButtonAttribute>());
foreach (var field in fieldsWithButtonAttributes_byTargetType[targetType])
createButton(field, field.GetCustomAttributeCached<ButtonAttribute>());
}
findFields();
findMethods();
createButtons();
}
void findTypesUsingVInspector()
{
if (typesUsingVInspector != null) return;
typesUsingVInspector = new();
typesUsingVInspector.UnionWith(TypeCache.GetFieldsWithAttribute<FoldoutAttribute>().Select(r => r.DeclaringType));
typesUsingVInspector.UnionWith(TypeCache.GetFieldsWithAttribute<TabAttribute>().Select(r => r.DeclaringType));
typesUsingVInspector.UnionWith(TypeCache.GetFieldsWithAttribute<ButtonAttribute>().Select(r => r.DeclaringType));
typesUsingVInspector.UnionWith(TypeCache.GetMethodsWithAttribute<ButtonAttribute>().Select(r => r.DeclaringType));
typesUsingVInspector.UnionWith(TypeCache.GetFieldsWithAttribute<VariantsAttribute>().Select(r => r.DeclaringType));
typesUsingVInspector.UnionWith(TypeCache.GetFieldsWithAttribute<IfAttribute>().Select(r => r.DeclaringType));
typesUsingVInspector.UnionWith(TypeCache.GetFieldsWithAttribute<ReadOnlyAttribute>().Select(r => r.DeclaringType));
typesUsingVInspector.UnionWith(TypeCache.GetFieldsWithAttribute<ShowInInspectorAttribute>().Select(r => r.DeclaringType));
typesUsingVInspector.UnionWith(TypeCache.GetFieldsWithAttribute<OnValueChangedAttribute>().Select(r => r.DeclaringType));
}
void linkToState()
{
if (!rootTab.subtabs.Any() && !rootFoldout.subfoldouts.Any() && !this.buttons.Any(r => r.parameterInfos.Any())) return;
AttributesState attributesState;
void set_attributesState()
{
var scriptName = rootProperty.serializedObject.targetObject.GetType().Name;
if (!VInspectorState.instance.attributeStates_byScriptName.ContainsKey(scriptName))
VInspectorState.instance.attributeStates_byScriptName[scriptName] = new();
attributesState = VInspectorState.instance.attributeStates_byScriptName[scriptName];
}
void linkTab(Tab tab, string parentPath)
{
tab.attributesState = attributesState;
tab.path = parentPath + "/" + tab.name;
attributesState.selectedSubtabIndexes_byTabPath.TryGetValue(tab.path, out tab._selectedSubtabIndex);
foreach (var subtab in tab.subtabs)
linkTab(subtab, tab.path);
}
void linkFoldout(Foldout foldout, string parentPath)
{
foldout.attributesState = attributesState;
foldout.path = parentPath + "/" + foldout.name;
attributesState.isExpandeds_byFoldoutPath.TryGetValue(foldout.path, out foldout._isExpanded);
foreach (var subfoldout in foldout.subfoldouts)
linkFoldout(subfoldout, foldout.path);
}
void linkButton(Button button, string parentPath)
{
button.attributesState = attributesState;
button.path = parentPath + "/" + button.name;
attributesState.isExpandeds_byButtonPath.TryGetValue(button.path, out button._isExpanded);
}
set_attributesState();
linkTab(rootTab, rootProperty.propertyPath);
linkFoldout(rootFoldout, rootProperty.propertyPath);
foreach (var r in this.buttons.Where(r => r.parameterInfos.Any()))
linkButton(r, rootProperty.propertyPath);
}
void createValueChangedCallbacks()
{
if (valueChangedCallbacks_byFieldInfos != null) return;
valueChangedCallbacks_byFieldInfos = new();
var methodInfos = TypeCache.GetMethodsWithAttribute<OnValueChangedAttribute>()
.Where(r => r.GetParameters().Length == 0)
.OrderBy(r => r.MetadataToken);
foreach (var methodInfo in methodInfos)
foreach (var attribute in methodInfo.GetCustomAttributes<OnValueChangedAttribute>())
foreach (var variableName in attribute.variableNames)
if (methodInfo.DeclaringType.GetFieldInfo(variableName) is FieldInfo fieldInfo)
if (valueChangedCallbacks_byFieldInfos.TryGetValue(fieldInfo, out var alreadyCreatedList))
alreadyCreatedList.Add(methodInfo);
else
valueChangedCallbacks_byFieldInfos[fieldInfo] = new List<MethodInfo> { methodInfo };
}
void fillPropertiesDictionary()
{
serializedProperties_byMemberInfos = new();
var curProperty = rootProperty.Copy();
curProperty.NextVisible(true);
if (curProperty.name == "m_Script")
{
scriptFieldProperty = curProperty.Copy();
if (!curProperty.NextVisible(false)) return;
}
do
if (targetType.GetFieldInfo(curProperty.name) is FieldInfo fieldInfo)
serializedProperties_byMemberInfos[fieldInfo] = curProperty.Copy();
while (curProperty.NextVisible(false));
}
void fillDrawableMembers()
{
if (drawableMemberLists_byTargetType.ContainsKey(targetType)) return;
var members = new HashSet<MemberInfo>();
void serializedFields()
{
var curProperty = rootProperty.Copy();
curProperty.NextVisible(true);
if (curProperty.name == "m_Script")
if (!curProperty.NextVisible(false)) return;
do if (targetType.GetFieldInfo(curProperty.name) is FieldInfo fieldInfo)
members.Add(fieldInfo);
while (curProperty.NextVisible(false));
}
void showInInspectorFields()
{
members.UnionWith(TypeCache.GetFieldsWithAttribute<ShowInInspectorAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType))
.Select(r => r as MemberInfo));
}
void showInInspectorProperties()
{
members.UnionWith(targetType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.Where(r => Attribute.IsDefined(r, typeof(ShowInInspectorAttribute)))
.Where(r => r.CanRead)
.Select(r => r as MemberInfo));
}
void groupingAttributesMembers()
{
members.UnionWith(TypeCache.GetFieldsWithAttribute<TabAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType)));
members.UnionWith(TypeCache.GetFieldsWithAttribute<EndTabAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType)));
members.UnionWith(TypeCache.GetFieldsWithAttribute<FoldoutAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType)));
members.UnionWith(TypeCache.GetFieldsWithAttribute<EndFoldoutAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType)));
members.UnionWith(TypeCache.GetFieldsWithAttribute<IfAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType)));
members.UnionWith(TypeCache.GetFieldsWithAttribute<EndIfAttribute>()
.Where(r => r.DeclaringType.IsAssignableFrom(targetType)));
// these members aren't necessarily visible
// but need to be drawn anyway for grouping to work as users would expect
}
serializedFields();
showInInspectorFields();
showInInspectorProperties();
groupingAttributesMembers();
drawableMemberLists_byTargetType[targetType] = members.OrderBy(r => r.MetadataToken).ToList();
}
createTabs();
createFoldouts();
createButtons();
findTypesUsingVInspector();
linkToState();
createValueChangedCallbacks();
fillPropertiesDictionary();
fillDrawableMembers();
}
public System.Func<IEnumerable<object>> targetsGetter;
public System.Func<SerializedProperty> rootPropertyGetter;
public Tab rootTab;
public Foldout rootFoldout;
public List<Button> buttons = new();
public Dictionary<string, VInspectorEditor> nestedEditors_byPropertyPath = new();
public SerializedProperty scriptFieldProperty;
public Dictionary<MemberInfo, SerializedProperty> serializedProperties_byMemberInfos = new();
static Dictionary<Type, List<MemberInfo>> drawableMemberLists_byTargetType = new();
static Dictionary<Type, List<FieldInfo>> fieldsWithButtonAttributes_byTargetType = new();
static Dictionary<Type, List<MethodInfo>> methodsWithButtonAttributes_byTargetType = new();
static Dictionary<Type, List<TabAttribute>> tabAttributes_byTargetType = new();
static Dictionary<Type, List<FoldoutAttribute>> foldoutAttributes_byTargetType = new();
static Dictionary<Type, List<MemberInfo>> showInInspectorMembers_byTargetType = new();
static Dictionary<FieldInfo, List<MethodInfo>> valueChangedCallbacks_byFieldInfos;
static HashSet<Type> typesUsingVInspector;
}
class Tab
{
public string name;
public List<Tab> subtabs = new();
public bool isRootTab;
public Tab selectedSubtab => selectedSubtabIndex.IsInRange(0, subtabs.Count - 1) ? subtabs[selectedSubtabIndex] : null;
public int selectedSubtabIndex
{
get => _selectedSubtabIndex;
set
{
_selectedSubtabIndex = value;
if (attributesState != null)
attributesState.selectedSubtabIndexes_byTabPath[path] = value;
}
}
public int _selectedSubtabIndex;
public AttributesState attributesState;
public string path;
public string GetSelectedTabPath()
{
if (!subtabs.Any()) return "";
if (selectedSubtab == null)
selectedSubtabIndex = 0;
return (selectedSubtab.name + "/" + selectedSubtab.GetSelectedTabPath()).Trim('/');
}
public void ResetSubtabsDrawn()
{
subtabsDrawn = false;
foreach (var r in subtabs)
r.ResetSubtabsDrawn();
}
public bool subtabsDrawn;
}
class Foldout
{
public string name;
public List<Foldout> subfoldouts = new();
public List<Button> buttons = new();
public bool isRootFoldout;
public bool isExpanded
{
get => _isExpanded || isRootFoldout;
set
{
_isExpanded = value;
if (attributesState != null)
attributesState.isExpandeds_byFoldoutPath[path] = value;
}
}
public bool _isExpanded;
public AttributesState attributesState;
public string path;
public Foldout GetSubfoldout(string path)
{
if (path == "")
return this;
else if (!path.Contains('/'))
return subfoldouts.Find(r => r.name == path);
else
return subfoldouts.Find(r => r.name == path.Split('/').First()).GetSubfoldout(path.Substring(path.IndexOf('/') + 1));
}
public bool IsSubfoldoutContentVisible(string path)
{
if (path == "")
return isExpanded;
else if (!path.Contains('/'))
return isExpanded && subfoldouts.Find(r => r.name == path).isExpanded;
else
return isExpanded && subfoldouts.Find(r => r.name == path.Split('/').First()).IsSubfoldoutContentVisible(path.Substring(path.IndexOf('/') + 1));
}
}
class Button
{
public string name;
public float size;
public float space;
public string color;
public TabAttribute tabAttribute;
public FoldoutAttribute foldoutAttribute;
public IfAttribute ifAttribute;
public System.Action<object> action;
public System.Func<bool> isPressed;
public bool isExpanded
{
get => _isExpanded;
set
{
_isExpanded = value;
if (attributesState != null)
attributesState.isExpandeds_byButtonPath[path] = value;
}
}
public bool _isExpanded;
public string path;
public AttributesState attributesState;
public object GetParameterValue(int i)
{
if (!parameterValues_byIndex.ContainsKey(i))
{
var parameterInfo = parameterInfos[i];
var defaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : parameterInfo.ParameterType.IsValueType ? System.Activator.CreateInstance(parameterInfo.ParameterType) : null;
parameterValues_byIndex[i] = defaultValue;
}
return parameterValues_byIndex[i];
}
public void SetParameterValue(int i, object value)
{
parameterValues_byIndex[i] = value;
}
public Dictionary<int, object> parameterValues_byIndex = new();
public List<ParameterInfo> parameterInfos = new();
}
#region custom editors
class AbstractEditor : Editor
{
public override void OnInspectorGUI()
{
if (isScriptMissing) { MissingScriptGUI(); return; }
serializedObject.UpdateIfRequiredOrScript();
rootEditor.OnGUI();
if (serializedObject.ApplyModifiedProperties())
toCallAfterModifyingSO?.Invoke();
toCallAfterModifyingSO = null;
}
public static System.Action toCallAfterModifyingSO;
void MissingScriptGUI()
{
SetGUIEnabled(true);
if (serializedObject.FindProperty("m_Script") is SerializedProperty scriptProperty)
{
EditorGUILayout.PropertyField(scriptProperty);
serializedObject.ApplyModifiedProperties();
}
var s = "Script cannot be loaded";
s += "\nPossible reasons:";
s += "\n- Compile erros";
s += "\n- Script is deleted";
s += "\n- Script file name doesn't match class name";
s += "\n- Class doesn't inherit from MonoBehaviour";
Space(4);
EditorGUILayout.HelpBox(s, MessageType.Warning, true);
Space(4);
ResetGUIEnabled();
}
void OnEnable()
{
if (target)
isScriptMissing = target.GetType() == typeof(MonoBehaviour) || target.GetType() == typeof(ScriptableObject);
else
isScriptMissing = target is MonoBehaviour || target is ScriptableObject;
if (isScriptMissing) return;
rootEditor = new VInspectorEditor(rootPropertyGetter: () => serializedObject.GetIterator(),
targetsGetter: () => serializedObject.targetObjects);
}
VInspectorEditor rootEditor;
bool isScriptMissing;
}
#if !VINSPECTOR_ATTRIBUTES_DISABLED
[CustomEditor(typeof(MonoBehaviour), true), CanEditMultipleObjects]
#endif
class ScriptEditor : AbstractEditor { }
#if !VINSPECTOR_ATTRIBUTES_DISABLED
[CustomEditor(typeof(ScriptableObject), true), CanEditMultipleObjects]
#endif
class ScriptableObjectEditor : AbstractEditor { }
#endregion
#region static inspector
class StaticInspector
{
static void HeaderGUI(Editor editor)
{
if (editor.GetType().Name != "MonoScriptImporterInspector") return;
if ((editor.target as MonoImporter)?.GetScript() is not MonoScript script) return;
if (script.GetClass() is not Type classType) return;
List<FieldInfo> fields;
List<Button> buttons;
void findFields()
{
if (fields_byClassType.TryGetValue(classType, out fields)) return;
fields = classType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.Where(r => !r.IsLiteral && !r.IsInitOnly)
.Where(r => Attribute.IsDefined(r, typeof(ShowInInspectorAttribute))).ToList();
fields_byClassType[classType] = fields;
}
void createButtons()
{
if (buttons_byClassType.TryGetValue(classType, out buttons)) return;
buttons = new List<Button>();
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<TabAttribute>() is TabAttribute tabAttribute)
button.tabAttribute = tabAttribute;
if (member.GetCustomAttributeCached<FoldoutAttribute>() is FoldoutAttribute foldoutAttribute)
button.foldoutAttribute = foldoutAttribute;
if (member.GetCustomAttributeCached<IfAttribute>() is IfAttribute ifAttribute)
button.ifAttribute = ifAttribute;
if (member is MethodInfo method)
if (!method.GetParameters().Any())
{
var methodTarget = method.IsStatic ? null : new object();
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 : new object();
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)
buttons.Add(button);
}
var staticMethods = classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.Where(r => Attribute.IsDefined(r, typeof(ButtonAttribute)));
foreach (var method in staticMethods)
createButton(method, method.GetCustomAttributeCached<ButtonAttribute>());
buttons_byClassType[classType] = buttons;
}
findFields();
createButtons();
if (!fields.Any() && !buttons.Any()) return;
void drawField(FieldInfo fieldInfo)
{
var type = fieldInfo.FieldType;
var name = fieldInfo.Name.PrettifyVarName();
var curValue = fieldInfo.GetValue(fieldInfo.IsStatic ? null : new object());
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 : new object(), newValue);
}
void drawButton(Button button, ref bool noButtonsShown)
{
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;
button.action(null);
}
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);
// if (button.parameterInfos[i].ParameterType == typeof(int))
// button.parameterValues[i] = Field<int>(button.parameterInfos[i].Name, (int)button.parameterValues[i]);
// else if (button.parameterInfos[i].ParameterType == typeof(float))
// button.parameterValues[i] = Field<float>(button.parameterInfos[i].Name, (float)button.parameterValues[i]);
// else if (button.parameterInfos[i].ParameterType == typeof(string))
// button.parameterValues[i] = Field<string>(button.parameterInfos[i].Name, (string)(button.parameterValues[i] ?? ""));
// else if (button.parameterInfos[i].ParameterType == typeof(bool))
// button.parameterValues[i] = Field<bool>(button.parameterInfos[i].Name, (bool)button.parameterValues[i]);
// else if (typeof(Object).IsAssignableFrom(button.parameterInfos[i].ParameterType))
// button.parameterValues[i] = Field<Object>(button.parameterInfos[i].Name, (Object)button.parameterValues[i]);
// else
// GUILayout.Label("asd");
}
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);
noButtonsShown = false;
}
void background()
{
var lineColor = Greyscale(.1f);
var backgroundColor = GUIColors.windowBackground;
Space(10);
var lineRect = ExpandWidthLabelRect(height: -1).AddWidthFromMid(123).SetHeight(1);
lineRect.Draw(lineColor);
Space(-10);
var bgRect = lineRect.MoveY(1).SetHeight(123123);
bgRect.Draw(GUIColors.windowBackground);
}
void drawFields()
{
foreach (var field in fields_byClassType[classType])
drawField(field);
}
void drawButtons()
{
var noButtonsToShow = true;
foreach (var button in buttons_byClassType[classType])
drawButton(button, ref noButtonsToShow);
if (noButtonsToShow)
Space(-16);
}
background();
Space(fields.Any() ? 20 : 14);
EditorGUI.indentLevel = 1;
drawFields();
Space(16);
drawButtons();
EditorGUI.indentLevel = 0;
Space(buttons.Any() ? -18 : -20);
}
static Dictionary<Type, List<Button>> buttons_byClassType = new();
static Dictionary<Type, List<FieldInfo>> fields_byClassType = new();
#if !VINSPECTOR_ATTRIBUTES_DISABLED
[InitializeOnLoadMethod]
#endif
static void Subscribe() => Editor.finishedDefaultHeaderGUI += HeaderGUI;
}
#endregion
}
#endif