From e9903c9785cbb197495a96e1a38ffe882062ac1c Mon Sep 17 00:00:00 2001 From: "Mayke Rodrigues (Mike)" <54370952+MaykerStudio@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:03:07 -0300 Subject: [PATCH] Add tabbed inspector UI and attributes Introduce tabbed inspector support: add runtime attributes (BeginTabGroupAttribute, TabAttribute, EndTabGroupAttribute and TabGroupVisual) and an editor drawer (BeginTabGroupAttributeDrawer) with responsive, styled tab rendering (Default/Flat/Segmented), tab state management and type/field discovery caching. Register new drawers in Editor Toolbox settings and add TabAttributeDrawer/EndTabGroup handling. Update example scene and SampleBehaviour4 to demonstrate tab groups and polish formatting. Also include small EditorUserSettings tweaks. --- .../Decorator/BeginTabGroupAttributeDrawer.cs | 621 ++++++++++++++++++ .../BeginTabGroupAttributeDrawer.cs.meta | 11 + Assets/Editor Toolbox/EditorSettings.asset | 3 + .../BeginTabGroupAttribute.cs | 41 ++ .../BeginTabGroupAttribute.cs.meta | 11 + Assets/Examples/Scenes/SampleScene.unity | 18 + Assets/Examples/Scripts/SampleBehaviour4.cs | 111 +++- 7 files changed, 797 insertions(+), 19 deletions(-) create mode 100644 Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/BeginTabGroupAttributeDrawer.cs create mode 100644 Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/BeginTabGroupAttributeDrawer.cs.meta create mode 100644 Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/BeginTabGroupAttribute.cs create mode 100644 Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/BeginTabGroupAttribute.cs.meta diff --git a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/BeginTabGroupAttributeDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/BeginTabGroupAttributeDrawer.cs new file mode 100644 index 00000000..83759418 --- /dev/null +++ b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/BeginTabGroupAttributeDrawer.cs @@ -0,0 +1,621 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace Toolbox.Editor.Drawers +{ + public sealed class BeginTabGroupAttributeDrawer + : ToolboxDecoratorDrawer + { + #region CONSTANTS + private const float ViewWidthPadding = 32f; + private const float TabSpacing = 8f; + private const float MinTabWidth = 40f; + private const float TabHeight = 22f; + private const float RowSpacing = 4f; + private const float TopSpacing = 2f; + + private static readonly Color InactiveBgMultiplier = new(0.6f, 0.6f, 0.6f, 0.6f); + private static readonly Color ActiveBgColor = new(0.8f, 0.8f, 0.8f, 1f); + + #endregion + + #region STYLE + + private static GUIStyle _baseTabStyle; + private static GUIStyle _activeTabStyle; + private static GUIStyle _headerStyle; + + private static GUIStyle BaseTabStyle + { + get + { + _baseTabStyle ??= new GUIStyle(EditorStyles.toolbarButton) + { + fixedHeight = TabHeight, + padding = new RectOffset(10, 10, 4, 4), + }; + return _baseTabStyle; + } + } + + private static GUIStyle ActiveTabStyle + { + get + { + _activeTabStyle ??= new GUIStyle(BaseTabStyle) { fontStyle = FontStyle.Bold }; + return _activeTabStyle; + } + } + + private static GUIStyle HeaderStyle + { + get + { + _headerStyle ??= new GUIStyle(EditorStyles.boldLabel) + { + alignment = TextAnchor.MiddleCenter, + padding = new RectOffset(0, 0, 2, 2), + }; + return _headerStyle; + } + } + + private static GUIStyle _flatStyle; + private static GUIStyle _flatActiveStyle; + private static GUIStyle _segmentLeft; + private static GUIStyle _segmentMid; + private static GUIStyle _segmentRight; + private static GUIStyle _segmentActive; + + private static GUIStyle FlatStyle + { + get + { + if (_flatStyle == null) + { + _flatStyle = new GUIStyle(EditorStyles.label) + { + alignment = TextAnchor.MiddleCenter, + fixedHeight = TabHeight, + padding = new RectOffset(10, 10, 4, 4), + margin = new RectOffset(2, 2, 0, 0), + }; + } + return _flatStyle; + } + } + + private static GUIStyle FlatActiveStyle + { + get + { + if (_flatActiveStyle == null) + { + _flatActiveStyle = new GUIStyle(FlatStyle) { fontStyle = FontStyle.Bold }; + } + return _flatActiveStyle; + } + } + + private static GUIStyle SegmentLeft => + _segmentLeft ??= new GUIStyle(EditorStyles.miniButtonLeft) { fixedHeight = TabHeight }; + private static GUIStyle SegmentMid => + _segmentMid ??= new GUIStyle(EditorStyles.miniButtonMid) { fixedHeight = TabHeight }; + private static GUIStyle SegmentRight => + _segmentRight ??= new GUIStyle(EditorStyles.miniButtonRight) + { + fixedHeight = TabHeight, + }; + + private static GUIStyle SegmentActive + { + get + { + _segmentActive ??= new GUIStyle(EditorStyles.miniButtonMid) + { + fontStyle = FontStyle.Bold, + }; + return _segmentActive; + } + } + + #endregion + + protected override void OnGuiBeginSafe(BeginTabGroupAttribute attribute) + { + var targetType = TabDiscovery.GetContextType(attribute.GroupId); + if (targetType == null) + return; + + var tabs = TabDiscovery.GetTabsForGroup(targetType, attribute.GroupId); + if (tabs == null || tabs.Count == 0) + return; + + InitializeDefaultTab(attribute.GroupId, tabs); + + var currentTab = GetActiveTab(attribute.GroupId, tabs); + int currentIndex = tabs.IndexOf(currentTab); + + if (currentIndex == -1) + currentIndex = 0; + + DrawGroupHeader(attribute.GroupId); + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + + int newIndex = DrawResponsiveTabs(currentIndex, tabs, attribute.Visual); + + if (newIndex != currentIndex) + { + TabState.Set(attribute.GroupId, tabs[newIndex]); + + GUI.FocusControl(null); + EditorGUIUtility.keyboardControl = 0; + EditorWindow.focusedWindow?.Repaint(); + GUIUtility.ExitGUI(); + } + } + + private static void InitializeDefaultTab(string groupId, List tabs) + { + if (!TabState.Has(groupId)) + { + TabState.Set(groupId, tabs[0]); + } + } + + private static string GetActiveTab(string groupId, List tabs) + { + if (TabState.TryGet(groupId, out var activeTab) && tabs.Contains(activeTab)) + return activeTab; + + return tabs[0]; + } + + private static void DrawGroupHeader(string groupId) + { + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label(groupId, HeaderStyle); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + GUILayout.Space(TopSpacing); + } + + private static int DrawResponsiveTabs( + int currentIndex, + List tabs, + TabGroupVisual visual + ) + { + float viewWidth = EditorGUIUtility.currentViewWidth - ViewWidthPadding; + + var tabWidths = CalculateTabWidths(tabs, ActiveTabStyle, viewWidth); + var rows = DistributeTabsIntoRows(tabs, tabWidths, viewWidth); + + RotateRowsToShowActiveTabLast(rows, currentIndex); + + int newIndex = DrawTabRows(rows, currentIndex, tabs, tabWidths, visual); + + return newIndex; + } + + private static List CalculateTabWidths( + List tabs, + GUIStyle style, + float viewWidth + ) + { + var tabWidths = new List(tabs.Count); + + foreach (var tab in tabs) + { + var content = new GUIContent(tab); + float width = style.CalcSize(content).x + TabSpacing; + + width = Mathf.Max(width, MinTabWidth); + width = Mathf.Min(width, Mathf.Max(MinTabWidth, viewWidth - TabSpacing)); + + tabWidths.Add(width); + } + + return tabWidths; + } + + private static List> DistributeTabsIntoRows( + List tabs, + List tabWidths, + float viewWidth + ) + { + var rows = new List>(); + var currentRow = new List(); + float rowAccumulator = 0f; + + for (int i = 0; i < tabs.Count; i++) + { + float width = tabWidths[i]; + + if (currentRow.Count == 0) + { + currentRow.Add(i); + rowAccumulator = width; + continue; + } + + if (rowAccumulator + width > viewWidth) + { + rows.Add(currentRow); + currentRow = new List { i }; + rowAccumulator = width; + } + else + { + currentRow.Add(i); + rowAccumulator += width; + } + } + + if (currentRow.Count > 0) + rows.Add(currentRow); + + return rows; + } + + private static void RotateRowsToShowActiveTabLast(List> rows, int currentIndex) + { + if (rows.Count <= 1) + return; + + int activeRowIndex = FindRowContainingTab(rows, currentIndex); + + if (activeRowIndex == rows.Count - 1) + return; + + var rotated = new List>(); + + for (int r = activeRowIndex + 1; r < rows.Count; r++) + rotated.Add(rows[r]); + + for (int r = 0; r <= activeRowIndex; r++) + rotated.Add(rows[r]); + + rows.Clear(); + rows.AddRange(rotated); + } + + private static int FindRowContainingTab(List> rows, int tabIndex) + { + for (int r = 0; r < rows.Count; r++) + { + if (rows[r].Contains(tabIndex)) + return r; + } + return 0; + } + + private static int DrawTabRows( + List> rows, + int currentIndex, + List tabs, + List tabWidths, + TabGroupVisual visual + ) + { + int newIndex = currentIndex; + + EditorGUILayout.BeginVertical(); + + for (int r = 0; r < rows.Count; r++) + { + var row = rows[r]; + + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + newIndex = DrawTabRow(row, currentIndex, newIndex, tabs, tabWidths, visual); + + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + if (r < rows.Count - 1) + GUILayout.Space(RowSpacing); + } + + GUILayout.Space(RowSpacing); + EditorGUILayout.EndVertical(); + + return newIndex; + } + + private static int DrawTabRow( + List row, + int currentIndex, + int newIndex, + List tabs, + List tabWidths, + TabGroupVisual visual + ) + { + for (int i = 0; i < row.Count; i++) + { + int tabIndex = row[i]; + bool isActive = tabIndex == currentIndex; + var content = new GUIContent(tabs[tabIndex]); + float width = tabWidths[tabIndex]; + + GUIStyle style = GetStyleForVisual(visual, i, row.Count, isActive); + + Color prevBg = GUI.backgroundColor; + + switch (visual) + { + case TabGroupVisual.Flat: + GUI.backgroundColor = isActive ? ActiveBgColor : GUI.backgroundColor; + break; + + case TabGroupVisual.Segmented: + + GUI.backgroundColor = isActive + ? ActiveBgColor + : GUI.backgroundColor * InactiveBgMultiplier; + break; + + default: + GUI.backgroundColor = isActive + ? ActiveBgColor + : GUI.backgroundColor * InactiveBgMultiplier; + style = isActive ? ActiveTabStyle : BaseTabStyle; + break; + } + + bool pressed = GUILayout.Toggle(isActive, content, style, GUILayout.Width(width)); + GUI.backgroundColor = prevBg; + + if (pressed && !isActive) + newIndex = tabIndex; + } + + return newIndex; + } + + private static GUIStyle GetStyleForVisual( + TabGroupVisual visual, + int index, + int count, + bool isActive + ) + { + switch (visual) + { + case TabGroupVisual.Flat: + return isActive ? FlatActiveStyle : FlatStyle; + + case TabGroupVisual.Segmented: + if (count == 1) + return isActive ? SegmentActive : SegmentMid; + + if (index == 0) + return SegmentLeft; + + if (index == count - 1) + return SegmentRight; + + return isActive ? SegmentActive : SegmentMid; + + default: + return isActive ? ActiveTabStyle : BaseTabStyle; + } + } + } + + public sealed class TabAttributeDrawer : ToolboxConditionDrawer + { + protected override PropertyCondition OnGuiValidateSafe( + SerializedProperty property, + TabAttribute attribute + ) + { + var targetType = property.serializedObject.targetObject.GetType(); + var groupId = TabDiscovery.GetGroupForField(targetType, property.name); + + if (string.IsNullOrEmpty(groupId)) + return PropertyCondition.Valid; + + return TabState.IsActive(groupId, attribute.Tab) + ? PropertyCondition.Valid + : PropertyCondition.NonValid; + } + } + + public sealed class EndTabGroupAttributeDrawer : ToolboxDecoratorDrawer + { + protected override void OnGuiCloseSafe(EndTabGroupAttribute attribute) + { + EditorGUILayout.EndVertical(); + } + } + + internal static class TabState + { + private static readonly Dictionary ActiveTabs = new(); + + public static void Set(string groupId, string tab) + { + ActiveTabs[groupId] = tab; + } + + public static bool TryGet(string groupId, out string tab) + { + return ActiveTabs.TryGetValue(groupId, out tab); + } + + public static bool IsActive(string groupId, string tab) + { + return ActiveTabs.TryGetValue(groupId, out var active) && active == tab; + } + + public static bool Has(string groupId) + { + return ActiveTabs.ContainsKey(groupId); + } + } + + internal static class TabDiscovery + { + private struct GroupData + { + public Dictionary> GroupToTabs; + public Dictionary FieldToGroup; + } + + private static readonly Dictionary TypeCache = new(); + + private struct ContextKey : IEquatable + { + public int InstanceId; + public string GroupId; + + public bool Equals(ContextKey other) => + InstanceId == other.InstanceId && GroupId == other.GroupId; + + public override int GetHashCode() => + (InstanceId * 397) ^ (GroupId != null ? GroupId.GetHashCode() : 0); + } + + private static readonly Dictionary ContextCache = new(); + private static int _lastSelectionId = -1; + + static TabDiscovery() + { + Selection.selectionChanged += ClearContextCache; + } + + private static void ClearContextCache() + { + ContextCache.Clear(); + _lastSelectionId = -1; + } + + public static Type GetContextType(string groupId) + { + var target = Selection.activeObject; + if (target == null) + return null; + + int instanceId = target.GetInstanceID(); + + if (_lastSelectionId != instanceId) + { + ContextCache.Clear(); + _lastSelectionId = instanceId; + } + + var key = new ContextKey { InstanceId = instanceId, GroupId = groupId }; + + if (ContextCache.TryGetValue(key, out var cached)) + return cached; + + Type result = ResolveContextType(target, groupId); + ContextCache[key] = result; + return result; + } + + private static Type ResolveContextType(UnityEngine.Object target, string groupId) + { + if (target is not GameObject go) + return target.GetType(); + + var components = go.GetComponents(); + for (int i = 0; i < components.Length; i++) + { + var comp = components[i]; + if (comp == null) + continue; + + var type = comp.GetType(); + EnsureCached(type); + + if (TypeCache[type].GroupToTabs.ContainsKey(groupId)) + return type; + } + + return null; + } + + public static List GetTabsForGroup(Type type, string groupId) + { + EnsureCached(type); + return TypeCache[type].GroupToTabs.TryGetValue(groupId, out var tabs) ? tabs : null; + } + + public static string GetGroupForField(Type type, string fieldName) + { + EnsureCached(type); + return TypeCache[type].FieldToGroup.TryGetValue(fieldName, out var group) + ? group + : null; + } + + private static void EnsureCached(Type type) + { + if (!TypeCache.ContainsKey(type)) + BuildCache(type); + } + + private static void BuildCache(Type type) + { + var groupToTabs = new Dictionary>(); + var fieldToGroup = new Dictionary(); + string currentGroup = null; + + var fields = type.GetFields( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ); + + for (int i = 0; i < fields.Length; i++) + { + var field = fields[i]; + + var groupAttr = field.GetCustomAttribute(); + if (groupAttr != null) + { + currentGroup = groupAttr.GroupId; + if (!groupToTabs.ContainsKey(currentGroup)) + groupToTabs[currentGroup] = new List(); + } + + var tabAttr = field.GetCustomAttribute(); + if (tabAttr != null && !string.IsNullOrEmpty(currentGroup)) + { + var tabs = groupToTabs[currentGroup]; + if (!tabs.Contains(tabAttr.Tab)) + tabs.Add(tabAttr.Tab); + + fieldToGroup[field.Name] = currentGroup; + } + } + + TypeCache[type] = new GroupData + { + GroupToTabs = groupToTabs, + FieldToGroup = fieldToGroup, + }; + } + + public static void ClearCache() + { + TypeCache.Clear(); + ContextCache.Clear(); + } + + [InitializeOnLoadMethod] + private static void ClearCachesOnDomainReload() + { + ClearCache(); + } + } +} diff --git a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/BeginTabGroupAttributeDrawer.cs.meta b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/BeginTabGroupAttributeDrawer.cs.meta new file mode 100644 index 00000000..42d8eeb5 --- /dev/null +++ b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/BeginTabGroupAttributeDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9418b0c77c4a3f8438cb67edad1cb58a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor Toolbox/EditorSettings.asset b/Assets/Editor Toolbox/EditorSettings.asset index 760893bd..9df20a40 100644 --- a/Assets/Editor Toolbox/EditorSettings.asset +++ b/Assets/Editor Toolbox/EditorSettings.asset @@ -55,6 +55,8 @@ MonoBehaviour: - typeReference: Toolbox.Editor.Drawers.BeginHorizontalAttributeDrawer, Toolbox.Editor - typeReference: Toolbox.Editor.Drawers.BeginHorizontalGroupAttributeDrawer, Toolbox.Editor - typeReference: Toolbox.Editor.Drawers.BeginIndentAttributeDrawer, Toolbox.Editor + - typeReference: Toolbox.Editor.Drawers.BeginTabGroupAttributeDrawer, Toolbox.Editor + - typeReference: Toolbox.Editor.Drawers.EndTabGroupAttributeDrawer, Toolbox.Editor - typeReference: Toolbox.Editor.Drawers.BeginVerticalAttributeDrawer, Toolbox.Editor - typeReference: Toolbox.Editor.Drawers.DynamicHelpAttributeDrawer, Toolbox.Editor - typeReference: Toolbox.Editor.Drawers.EditorButtonAttributeDrawer, Toolbox.Editor @@ -84,6 +86,7 @@ MonoBehaviour: - typeReference: Toolbox.Editor.Drawers.ShowDisabledIfAttributeDrawer, Toolbox.Editor - typeReference: Toolbox.Editor.Drawers.ShowIfAttributeDrawer, Toolbox.Editor - typeReference: Toolbox.Editor.Drawers.ShowWarningIfAttributeDrawer, Toolbox.Editor + - typeReference: Toolbox.Editor.Drawers.TabAttributeDrawer, Toolbox.Editor selfPropertyDrawerHandlers: - typeReference: Toolbox.Editor.Drawers.DynamicMinMaxSliderAttributeDrawer, Toolbox.Editor - typeReference: Toolbox.Editor.Drawers.DynamicRangeAttributeDrawer, Toolbox.Editor diff --git a/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/BeginTabGroupAttribute.cs b/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/BeginTabGroupAttribute.cs new file mode 100644 index 00000000..e807d089 --- /dev/null +++ b/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/BeginTabGroupAttribute.cs @@ -0,0 +1,41 @@ +using System; + +namespace UnityEngine +{ + public enum TabGroupVisual + { + Default, + Flat, // modern flat buttons + Segmented, // connected segmented control + } + + [AttributeUsage(AttributeTargets.Field)] + public sealed class BeginTabGroupAttribute : ToolboxDecoratorAttribute + { + public string GroupId { get; } + public TabGroupVisual Visual { get; } + + public BeginTabGroupAttribute( + string groupId = "Default", + TabGroupVisual visual = TabGroupVisual.Default + ) + { + GroupId = groupId; + Visual = visual; + } + } + + [AttributeUsage(AttributeTargets.Field)] + public sealed class TabAttribute : ToolboxConditionAttribute + { + public string Tab { get; } + + public TabAttribute(string tab) + { + Tab = tab; + } + } + + [AttributeUsage(AttributeTargets.Field)] + public sealed class EndTabGroupAttribute : ToolboxDecoratorAttribute { } +} diff --git a/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/BeginTabGroupAttribute.cs.meta b/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/BeginTabGroupAttribute.cs.meta new file mode 100644 index 00000000..0a565feb --- /dev/null +++ b/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/BeginTabGroupAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2943103fdabde2b42aa7746bbc0be679 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Examples/Scenes/SampleScene.unity b/Assets/Examples/Scenes/SampleScene.unity index cefc5421..e1824939 100644 --- a/Assets/Examples/Scenes/SampleScene.unity +++ b/Assets/Examples/Scenes/SampleScene.unity @@ -2262,6 +2262,24 @@ MonoBehaviour: var57: 0 nestedObject: var0: -12 + CharacterName: Name + Level: 0 + Health: 100 + Mana: 0 + Stamina: 0 + MoveSpeed: 4 + Acceleration: 10 + JumpForce: 9 + attackForce: {x: 0, y: 0} + attackDamage: 0 + testVar1: test string + testVar2: 0 + testVar3: 0 + testVar4: {x: 0, y: 0} + testVar5: {x: 0, y: 0, z: 0} + testVar6: {fileID: 0} + testVar7: 0 + testVar8: asdads --- !u!4 &1438743619 Transform: m_ObjectHideFlags: 2 diff --git a/Assets/Examples/Scripts/SampleBehaviour4.cs b/Assets/Examples/Scripts/SampleBehaviour4.cs index c52e0ff6..26525b85 100644 --- a/Assets/Examples/Scripts/SampleBehaviour4.cs +++ b/Assets/Examples/Scripts/SampleBehaviour4.cs @@ -12,7 +12,7 @@ private class SampleNestedClass [DynamicHelp(nameof(GetHelpMessage), UnityMessageType.Info)] [EditorButton(nameof(TestNestedMethod))] public int var0; - + private void TestNestedMethod() { Debug.Log(nameof(TestNestedMethod) + " is called"); @@ -25,7 +25,6 @@ private string GetHelpMessage() } [Label("Help", skinStyle: SkinStyle.Box)] - [Disable] [Help("Very useful warning", UnityMessageType.Warning)] [Help("This error example", UnityMessageType.Error, ApplyCondition = true)] @@ -33,9 +32,17 @@ private string GetHelpMessage() public int var0; [Label("Button", skinStyle: SkinStyle.Box)] - - [EditorButton(nameof(TestMethod), Tooltip = "Custom Tooltip", ValidateMethodName = nameof(ValidationMethod), PositionType = ButtonPositionType.Above)] - [EditorButton(nameof(TestCoroutine), "Test Coroutine", activityType: ButtonActivityType.OnPlayMode)] + [EditorButton( + nameof(TestMethod), + Tooltip = "Custom Tooltip", + ValidateMethodName = nameof(ValidationMethod), + PositionType = ButtonPositionType.Above + )] + [EditorButton( + nameof(TestCoroutine), + "Test Coroutine", + activityType: ButtonActivityType.OnPlayMode + )] [EditorButton(nameof(TestStaticMethod), activityType: ButtonActivityType.OnEditMode)] public int var1; @@ -64,95 +71,161 @@ private static void TestStaticMethod() } [Label("Vertical Layout", skinStyle: SkinStyle.Box)] - [BeginGroup("Parent group")] public int y; + [BeginGroup("Nested group", Style = GroupStyle.Boxed)] public int var14; + [Line] public int var15; + [SpaceArea(20, 20)] public int var16; + [BeginIndent] public int var17; public int var18; + [Title("Standard Header")] public GameObject go; + [Label("Custom Header")] [EndIndent] public int var19; + [EndGroup] [Line] [Line(HexColor = "#9800FF")] public int var20; + [EndGroup] public int x; [Label("Horizontal Layout", skinStyle: SkinStyle.Box)] - [BeginHorizontal(LabelWidth = 50.0f)] public int var29; + [SpaceArea(10)] public int var30; + [EndHorizontal] public int var31; [Label("Horizontal Layout (Group)", skinStyle: SkinStyle.Box)] - - [BeginHorizontalGroup(Label = "Horizontal Group", ControlFieldWidth = true, ElementsInLayout = 2, Style = GroupStyle.Round)] + [BeginHorizontalGroup( + Label = "Horizontal Group", + ControlFieldWidth = true, + ElementsInLayout = 2, + Style = GroupStyle.Round + )] [ReorderableList(Foldable = true), InLineEditor] public GameObject[] gameObjects; + [SpaceArea] [EndHorizontalGroup] [ReorderableList] public float[] floats; [Label("Indentation", skinStyle: SkinStyle.Box)] - public int var2; + [BeginIndent] public int var3; + [EndIndent] public int var4; + [IndentArea(3)] public int var5; [Label("Highlight", skinStyle: SkinStyle.Box)] - [Highlight(0.8f, 1.0f, 0.2f)] public GameObject var28; [Label("Dynamic Help", skinStyle: SkinStyle.Box)] - [DynamicHelp(nameof(MessageSource))] public int var39; - public string MessageSource => string.Format("Dynamic Message Source. {0} = {1}", nameof(var39), var39); + public string MessageSource => + string.Format("Dynamic Message Source. {0} = {1}", nameof(var39), var39); [Label("Image Area", skinStyle: SkinStyle.Box)] - [ImageArea("https://img.itch.zone/aW1nLzE5Mjc3NzUucG5n/original/Viawjm.png", 180.0f)] public int var55; [Label("GUI Color", skinStyle: SkinStyle.Box)] - [GuiColor(1, 0, 0)] public int var56; [Label("Label Width", skinStyle: SkinStyle.Box)] - [LabelWidth(220.0f)] public int veryVeryVeryVeryVeryLongName; [Label("Title", skinStyle: SkinStyle.Box)] - [Title("Standard Title")] public int var57; [Label("Nested Objects", skinStyle: SkinStyle.Box)] - [Help("You can use Toolbox Attributes inside serializable types without limitations.")] [SerializeField] private SampleNestedClass nestedObject; -} \ No newline at end of file + [BeginTabGroup("Tab Example")] + [Tab("General")] + public string CharacterName; + + [Tab("General")] + public int Level; + + [Tab("Stats")] + public int Health; + + [Tab("Stats")] + public int Mana; + + [Tab("Stats")] + public int Stamina; + + [Tab("Movement")] + public float MoveSpeed; + + [Tab("Movement")] + public float Acceleration; + + [Tab("Movement")] + public float JumpForce; + + [Tab("Attack")] + public Vector2 attackForce; + + [Tab("Attack")] + [EndTabGroup] + public int attackDamage; + + [BeginTabGroup("Test Tab Group", TabGroupVisual.Segmented)] + [Tab("Tab 1")] + public string testVar1; + + [Tab("Tab 1")] + public float testVar2; + + [Tab("Tab 1")] + public int testVar3; + + [Tab("Tab 2")] + public Vector2 testVar4; + + [Tab("Tab 2")] + public Vector3 testVar5; + + [Tab("Tab 2")] + public GameObject testVar6; + + [Tab("Tab 3")] + public int testVar7; + + [Tab("Tab 3")] + [EndTabGroup] + public string testVar8; +}