diff --git a/.github/workflows/activation.yml b/.github/workflows/activation.yml index 1d856e59..439e7092 100644 --- a/.github/workflows/activation.yml +++ b/.github/workflows/activation.yml @@ -12,7 +12,7 @@ jobs: uses: game-ci/unity-request-activation-file@v2 # Upload artifact (Unity_v20XX.X.XXXX.alf) - name: Expose as artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: ${{ steps.getManualLicenseFile.outputs.filePath }} path: ${{ steps.getManualLicenseFile.outputs.filePath }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 66cda1f3..b408643d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: testMode: ${{ matrix.testMode }} artifactsPath: ${{ matrix.testMode }}-artifacts checkName: ${{ matrix.testMode }} Test Results - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: Test results for ${{ matrix.testMode }} diff --git a/.github/workflows/upm-dev.yml b/.github/workflows/upm-dev.yml index 8e3b9997..e25a96f7 100644 --- a/.github/workflows/upm-dev.yml +++ b/.github/workflows/upm-dev.yml @@ -8,7 +8,7 @@ jobs: name: split upm branch runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: split upm branch diff --git a/.github/workflows/upm.yml b/.github/workflows/upm.yml index 286d4a45..59f3f282 100644 --- a/.github/workflows/upm.yml +++ b/.github/workflows/upm.yml @@ -8,7 +8,7 @@ jobs: name: split upm branch runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: split upm branch diff --git a/Assets/Editor Toolbox/CHANGELOG.md b/Assets/Editor Toolbox/CHANGELOG.md index 035bcd06..a6a47413 100644 --- a/Assets/Editor Toolbox/CHANGELOG.md +++ b/Assets/Editor Toolbox/CHANGELOG.md @@ -1,3 +1,48 @@ +## 0.14.3 [29.12.2025] + +### Added: +- Ability to specify 'ValueStep' in the ProgressBar attribute + +### Changed: +- Toolbar support for Unity 6.3+ +- Material Drawers support for Unity 6.3+ +- Serialized Scene support for Unity 6.3+ +- Fix issues with copy/past operations for the [SerializeReference]-based arrays +- Ability to display parent objects in the SceneView tool +- Ability to display managed reference values while using LabelByChild attribute + +## 0.14.2 [10.08.2025] + +### Added: +- `AddConfirmationBox` property to the [ReferencePicker]; allows to display an intermediate confirmation window before assigning a new reference + +### Changed: +- Fix [FormattedNumber] behaviour while multi-editing different values +- [LabelByChild] now displays ToString() value for UnityEngine.Object references +- Fix ReorderableLists indentation level for nested objects +- Fix various smaller issues related to [SerializeReference]-based properties while being in the multi-editing mode +- Rename 'Copy Serialize Reference' to 'Copy Serialized References', from now this operation also works on arrays and lists +- Rename 'Paste Serialize Reference' to 'Paste Serialized References', from now this operation also works on arrays and lists + +## 0.14.1 [22.04.2025] + +### Changed: +- Ability to define [EditorButton] position (below or above the target property) +- Improvements to the SerializedScene class & linked drawer +- Fix rare issue with nested layouts + +## 0.14.0 [23.02.2025] + +### Added: +- OnToolbarGuiRight callback (ability to draw GUI elements on the right side of the toolbar container); OnToolbarGui replaced with the OnToolbarGuiLeft callback + +### Changed: +- Fix fetching private members from base classes in various cases (e.g. [EditorButton] or conditionals) +- Move FolderData to the Runtime assembly to fix issues caused by the Visual Scripting package +- Fix minor rendering issues caused by the ReoerdableList's footer position +- Fix clearing cached Editor instances in the [InLineEditor] (fix for the AudioClip still playing) +- Improve displaying [SerializeReference]-based properties in the multi-editing mode + ## 0.13.2 [29.11.2024] ### Added: diff --git a/Assets/Editor Toolbox/Editor/ContextMenu/Management/ToolboxContextMenuManager.cs b/Assets/Editor Toolbox/Editor/ContextMenu/Management/ToolboxContextMenuManager.cs index 69004448..733500f8 100644 --- a/Assets/Editor Toolbox/Editor/ContextMenu/Management/ToolboxContextMenuManager.cs +++ b/Assets/Editor Toolbox/Editor/ContextMenu/Management/ToolboxContextMenuManager.cs @@ -15,9 +15,11 @@ static ToolboxContextMenuManager() { registeredOperations = new List() { +#if UNITY_2021_3_OR_NEWER new CopySerializeReferenceOperation(), new PasteSerializeReferenceOperation(), new DuplicateSerializeReferenceArrayElementOperation() +#endif }; EditorApplication.contextualPropertyMenu -= OnContextMenuOpening; diff --git a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceCache.cs b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceCache.cs index b88da35e..4c6d9d65 100644 --- a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceCache.cs +++ b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceCache.cs @@ -1,16 +1,26 @@ using System; +using System.Collections.Generic; namespace Toolbox.Editor.ContextMenu.Operations { + /// + /// Cache created by the copy operation. + /// All properties are based on data from the source . + /// internal class CopySerializeReferenceCache { - public CopySerializeReferenceCache(Type referenceType, string data) + public CopySerializeReferenceCache(Type referenceType, IReadOnlyList entires, bool isArrayCopy) { ReferenceType = referenceType; - Data = data; + Entries = entires; + IsArrayCopy = isArrayCopy; } + /// + /// Base managed reference type of the source . + /// public Type ReferenceType { get; } - public string Data { get; } + public IReadOnlyList Entries { get; } + public bool IsArrayCopy { get; } } } \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceEntry.cs b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceEntry.cs new file mode 100644 index 00000000..0eb7097c --- /dev/null +++ b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceEntry.cs @@ -0,0 +1,16 @@ +using System; + +namespace Toolbox.Editor.ContextMenu.Operations +{ + internal class CopySerializeReferenceEntry + { + public CopySerializeReferenceEntry(Type referenceType, string referenceData) + { + ReferenceType = referenceType; + ReferenceData = referenceData; + } + + public Type ReferenceType { get; } + public string ReferenceData { get; } + } +} \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceEntry.cs.meta b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceEntry.cs.meta new file mode 100644 index 00000000..931a758d --- /dev/null +++ b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceEntry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b98e16e462cb4b941af6d616402c8e78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceOperation.cs b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceOperation.cs index c25eb218..e7850684 100644 --- a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceOperation.cs +++ b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/CopySerializeReferenceOperation.cs @@ -1,4 +1,7 @@ -using UnityEditor; +#if UNITY_2021_3_OR_NEWER +using System.Collections.Generic; + +using UnityEditor; using UnityEngine; namespace Toolbox.Editor.ContextMenu.Operations @@ -9,40 +12,69 @@ internal class CopySerializeReferenceOperation : IContextMenuOperation [InitializeOnLoadMethod] private static void Initialize() + { + Reset(); + } + + private CopySerializeReferenceEntry CreateEntry(SerializedProperty property) + { + if (property == null) + { + return new CopySerializeReferenceEntry(null, null); + } + + var value = property.managedReferenceValue; + if (value == null) + { + return new CopySerializeReferenceEntry(null, null); + } + + var referenceType = value.GetType(); + var data = JsonUtility.ToJson(value); + return new CopySerializeReferenceEntry(referenceType, data); + } + + internal static void Reset() { Cache = null; } public bool IsVisible(SerializedProperty property) { -#if UNITY_2021_3_OR_NEWER - return property != null && property.propertyType == SerializedPropertyType.ManagedReference; -#else - return false; -#endif + return PropertyUtility.IsSerializeReferenceProperty(property); } public bool IsEnabled(SerializedProperty property) { - return true; + return property.isArray ? property.arraySize > 0 : true; } public void Perform(SerializedProperty property) { -#if UNITY_2021_3_OR_NEWER - var value = property.managedReferenceValue; - if (value != null) + var isArrayCopy = false; + var entries = new List(); + if (property.propertyType == SerializedPropertyType.ManagedReference) + { + var entry = CreateEntry(property); + entries.Add(entry); + } + else if (property.isArray) { - var referenceType = value.GetType(); - var data = JsonUtility.ToJson(value); - Cache = new CopySerializeReferenceCache(referenceType, data); - return; + isArrayCopy = true; + var propertiesCount = property.arraySize; + for (var i = 0; i < propertiesCount; i++) + { + var childProperty = property.GetArrayElementAtIndex(i); + var entry = CreateEntry(childProperty); + entries.Add(entry); + } } - Cache = new CopySerializeReferenceCache(null, null); -#endif + PropertyUtility.TryGetSerializeReferenceType(property, out var referenceType); + Cache = new CopySerializeReferenceCache(referenceType, entries, isArrayCopy); } - public GUIContent Label => new GUIContent("Copy Serialize Reference"); + public GUIContent Label => new GUIContent("Copy Serialized References"); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/DuplicateSerializeReferenceArrayElementOperation.cs b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/DuplicateSerializeReferenceArrayElementOperation.cs index e8b70b29..06a2d992 100644 --- a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/DuplicateSerializeReferenceArrayElementOperation.cs +++ b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/DuplicateSerializeReferenceArrayElementOperation.cs @@ -1,4 +1,5 @@ -using UnityEditor; +#if UNITY_2021_3_OR_NEWER +using UnityEditor; using UnityEngine; namespace Toolbox.Editor.ContextMenu.Operations @@ -7,12 +8,8 @@ internal class DuplicateSerializeReferenceArrayElementOperation : IContextMenuOp { public bool IsVisible(SerializedProperty property) { -#if UNITY_2021_3_OR_NEWER return property != null && property.propertyType == SerializedPropertyType.ManagedReference && PropertyUtility.IsSerializableArrayElement(property); -#else - return false; -#endif } public bool IsEnabled(SerializedProperty property) @@ -22,7 +19,6 @@ public bool IsEnabled(SerializedProperty property) public void Perform(SerializedProperty property) { -#if UNITY_2021_3_OR_NEWER var sourceProperty = property.Copy(); sourceProperty.serializedObject.Update(); var sourceValue = sourceProperty.managedReferenceValue; @@ -40,9 +36,9 @@ public void Perform(SerializedProperty property) } sourceProperty.serializedObject.ApplyModifiedProperties(); -#endif } public GUIContent Label => new GUIContent("Duplicate Serialize Reference Array Element"); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/PasteSerializeReferenceOperation.cs b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/PasteSerializeReferenceOperation.cs index 09f0d342..65d560c8 100644 --- a/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/PasteSerializeReferenceOperation.cs +++ b/Assets/Editor Toolbox/Editor/ContextMenu/Operations/SerializeReference/PasteSerializeReferenceOperation.cs @@ -1,4 +1,6 @@ -using System; +#if UNITY_2021_3_OR_NEWER +using System; +using System.Collections.Generic; using UnityEditor; using UnityEngine; @@ -7,34 +9,67 @@ namespace Toolbox.Editor.ContextMenu.Operations { internal class PasteSerializeReferenceOperation : IContextMenuOperation { - private bool IsAssignmentValid(SerializedProperty property, object newValue) + private bool IsAssignmentValid(SerializedProperty targetProperty, IReadOnlyList entires) { -#if UNITY_2021_3_OR_NEWER - if (newValue == null) + if (!PropertyUtility.TryGetSerializeReferenceType(targetProperty, out var targetType)) + { + return false; + } + + for (var i = 0; i < entires.Count; i++) + { + var entry = entires[i]; + if (!IsAssignmentValid(targetType, entry)) + { + return false; + } + } + + return true; + } + + private bool IsAssignmentValid(Type targetType, CopySerializeReferenceEntry entry) + { + var entryType = entry.ReferenceType; + if (entryType == null) { return true; } - if (!TypeUtility.TryGetTypeFromManagedReferenceFullTypeName(property.managedReferenceFieldTypename, out var referenceType)) + return TypeUtility.IsTypeAssignableFrom(targetType, entryType); + } + + private bool IsOperationSupported(SerializedProperty targetProperty, CopySerializeReferenceCache cache) + { + if (cache == null) + { + return false; + } + + var entries = cache.Entries; + if (entries == null || entries.Count == 0) + { + return false; + } + + if (cache.IsArrayCopy && targetProperty.isArray) { return true; } - var newValueType = newValue.GetType(); - if (TypeUtility.IsTypeAssignableFrom(referenceType, newValueType)) + if (!cache.IsArrayCopy && !targetProperty.isArray) { return true; } -#endif + return false; } - private object GetCachedManagedReferenceValue() + private object GetManagedReferenceValue(CopySerializeReferenceEntry entry) { - var cachedData = CopySerializeReferenceOperation.Cache; - if (cachedData.ReferenceType != null) + if (entry.ReferenceType != null) { - var newValue = JsonUtility.FromJson(cachedData.Data, cachedData.ReferenceType); + var newValue = JsonUtility.FromJson(entry.ReferenceData, entry.ReferenceType); return newValue; } else @@ -43,42 +78,60 @@ private object GetCachedManagedReferenceValue() } } + private void PasteEntry(SerializedProperty targetProperty, CopySerializeReferenceEntry entry) + { + var newValue = GetManagedReferenceValue(entry); + targetProperty.managedReferenceValue = newValue; + } + public bool IsVisible(SerializedProperty property) { -#if UNITY_2021_3_OR_NEWER - return property != null && property.propertyType == SerializedPropertyType.ManagedReference; -#else - return false; -#endif + return PropertyUtility.IsSerializeReferenceProperty(property); } public bool IsEnabled(SerializedProperty property) { - return CopySerializeReferenceOperation.Cache != null; + return IsOperationSupported(property, CopySerializeReferenceOperation.Cache); } public void Perform(SerializedProperty property) { -#if UNITY_2019_3_OR_NEWER + var cache = CopySerializeReferenceOperation.Cache; + var entries = cache.Entries; + if (!IsAssignmentValid(property, entries)) + { + ToolboxEditorLog.LogWarning("Cannot perform paste operation, types are mismatched."); + return; + } + var targetProperty = property.Copy(); try { - var newValue = GetCachedManagedReferenceValue(); - if (!IsAssignmentValid(targetProperty, newValue)) + targetProperty.serializedObject.Update(); + if (targetProperty.isArray) + { + var arraySize = entries.Count; + targetProperty.arraySize = arraySize; + for (var i = 0; i < arraySize; i++) + { + var entry = entries[i]; + var childProperty = targetProperty.GetArrayElementAtIndex(i); + PasteEntry(childProperty, entry); + } + } + else { - ToolboxEditorLog.LogWarning("Cannot perform paste operation, types are mismatched."); - return; + var entry = entries[0]; + PasteEntry(targetProperty, entry); } - targetProperty.serializedObject.Update(); - targetProperty.managedReferenceValue = newValue; targetProperty.serializedObject.ApplyModifiedProperties(); } catch (Exception) { } -#endif } - public GUIContent Label => new GUIContent("Paste Serialize Reference"); + public GUIContent Label => new GUIContent("Paste Serialized References"); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/FieldValueExtractor.cs b/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/FieldValueExtractor.cs index 53d59da4..589c9764 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/FieldValueExtractor.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/FieldValueExtractor.cs @@ -11,7 +11,7 @@ public bool TryGetValue(string source, object declaringObject, out object value) } var type = declaringObject.GetType(); - var info = type.GetField(source, ReflectionUtility.allBindings); + var info = ReflectionUtility.GetField(type, source); if (info == null) { return false; diff --git a/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/MethodValueExtractor.cs b/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/MethodValueExtractor.cs index 652f67e2..166b001b 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/MethodValueExtractor.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/MethodValueExtractor.cs @@ -1,7 +1,4 @@ -using System; -using System.Reflection; - -namespace Toolbox.Editor.Drawers +namespace Toolbox.Editor.Drawers { public class MethodValueExtractor : IValueExtractor { @@ -14,7 +11,7 @@ public bool TryGetValue(string source, object declaringObject, out object value) } var type = declaringObject.GetType(); - var info = type.GetMethod(source, ReflectionUtility.allBindings, null, CallingConventions.Any, new Type[0], null); + var info = ReflectionUtility.GetMethod(type, source); if (info == null) { return false; diff --git a/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/PropertyValueExtractor.cs b/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/PropertyValueExtractor.cs index 933bda56..691a18e3 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/PropertyValueExtractor.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Helpers/Extraction/PropertyValueExtractor.cs @@ -11,7 +11,7 @@ public bool TryGetValue(string source, object declaringObject, out object value) } var type = declaringObject.GetType(); - var info = type.GetProperty(source, ReflectionUtility.allBindings); + var info = ReflectionUtility.GetProperty(type, source); if (info == null) { return false; diff --git a/Assets/Editor Toolbox/Editor/Drawers/Internal/FolderDataDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Internal/FolderDataDrawer.cs index 1634c939..920b80e3 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Internal/FolderDataDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Internal/FolderDataDrawer.cs @@ -3,7 +3,7 @@ namespace Toolbox.Editor.Drawers { - using Toolbox.Editor.Folders; + using Toolbox.Folders; [CustomPropertyDrawer(typeof(FolderData))] internal class FolderDataDrawer : PropertyDrawer diff --git a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialCompactTextureDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialCompactTextureDrawer.cs index b0c39af5..e2720ca4 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialCompactTextureDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialCompactTextureDrawer.cs @@ -27,7 +27,11 @@ protected override void OnGUISafe(Rect position, MaterialProperty prop, string l protected override bool IsPropertyValid(MaterialProperty prop) { +#if UNITY_6000_3_OR_NEWER + return prop.propertyType == UnityEngine.Rendering.ShaderPropertyType.Texture; +#else return prop.type == MaterialProperty.PropType.Texture; +#endif } } } \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialConditionalDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialConditionalDrawer.cs index f7baa652..9930ad9d 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialConditionalDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialConditionalDrawer.cs @@ -12,6 +12,17 @@ protected MaterialConditionalDrawer(string togglePropertyName) this.togglePropertyName = togglePropertyName; } + private bool IsValidToggleType(MaterialProperty toggleProp) + { +#if UNITY_6000_3_OR_NEWER + return toggleProp.propertyType == UnityEngine.Rendering.ShaderPropertyType.Float || + toggleProp.propertyType == UnityEngine.Rendering.ShaderPropertyType.Range; +#else + return toggleProp.type == MaterialProperty.PropType.Float || + toggleProp.type == MaterialProperty.PropType.Range; +#endif + } + protected override float GetPropertyHeightSafe(MaterialProperty prop, string label, MaterialEditor editor) { if (!HasToggle(prop)) @@ -48,7 +59,7 @@ protected virtual bool HasToggle(MaterialProperty prop) { var targets = prop.targets; var toggle = MaterialEditor.GetMaterialProperty(targets, togglePropertyName); - return toggle != null && toggle.type == MaterialProperty.PropType.Float || toggle.type == MaterialProperty.PropType.Range; + return toggle != null && IsValidToggleType(toggle); } protected virtual bool? GetValue(MaterialProperty prop) diff --git a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialMinMaxSliderDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialMinMaxSliderDrawer.cs index 0e9c2157..a649d601 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialMinMaxSliderDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialMinMaxSliderDrawer.cs @@ -44,7 +44,11 @@ protected override void OnGUISafe(Rect position, MaterialProperty prop, string l protected override bool IsPropertyValid(MaterialProperty prop) { +#if UNITY_6000_3_OR_NEWER + return prop.propertyType == UnityEngine.Rendering.ShaderPropertyType.Vector; +#else return prop.type == MaterialProperty.PropType.Vector; +#endif } } } \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialVector2Drawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialVector2Drawer.cs index 85e2e794..54cb2d65 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialVector2Drawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialVector2Drawer.cs @@ -30,7 +30,11 @@ protected override void OnGUISafe(Rect position, MaterialProperty prop, string l protected override bool IsPropertyValid(MaterialProperty prop) { +#if UNITY_6000_3_OR_NEWER + return prop.propertyType == UnityEngine.Rendering.ShaderPropertyType.Vector; +#else return prop.type == MaterialProperty.PropType.Vector; +#endif } } } \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialVector3Drawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialVector3Drawer.cs index 6768c73e..bd8e8cf5 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialVector3Drawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Material/MaterialVector3Drawer.cs @@ -30,7 +30,11 @@ protected override void OnGUISafe(Rect position, MaterialProperty prop, string l protected override bool IsPropertyValid(MaterialProperty prop) { +#if UNITY_6000_3_OR_NEWER + return prop.propertyType == UnityEngine.Rendering.ShaderPropertyType.Vector; +#else return prop.type == MaterialProperty.PropType.Vector; +#endif } } } \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/Drawers/Regular/FormattedNumberAttributeDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Regular/FormattedNumberAttributeDrawer.cs index 64bbecae..66e34d4d 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Regular/FormattedNumberAttributeDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Regular/FormattedNumberAttributeDrawer.cs @@ -18,7 +18,6 @@ public class FormattedNumberAttributeDrawer : PropertyDrawerBase CurrencyDecimalSeparator = "." }; - private void ApplyControlName(string propertyKey) { GUI.SetNextControlName(propertyKey); @@ -42,7 +41,6 @@ private string GetFormat(SerializedProperty property, FormattedNumberAttribute a return string.Format("{0}{1}", attribute.Format, isInt ? 0 : attribute.DecimalsToShow); } - protected override void OnGUISafe(Rect position, SerializedProperty property, GUIContent label) { var key = property.GetPropertyHashKey(); @@ -62,6 +60,11 @@ protected override void OnGUISafe(Rect position, SerializedProperty property, GU #endif } + if (property.hasMultipleDifferentValues) + { + return; + } + var targetAttribute = attribute as FormattedNumberAttribute; var single = GetSingle(property); var format = GetFormat(property, targetAttribute); @@ -79,7 +82,6 @@ protected override void OnGUISafe(Rect position, SerializedProperty property, GU } } - public override bool IsPropertyValid(SerializedProperty property) { return property.propertyType == SerializedPropertyType.Integer || diff --git a/Assets/Editor Toolbox/Editor/Drawers/Regular/ProgressBarAttributeDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Regular/ProgressBarAttributeDrawer.cs index dffbddf2..a7f42777 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Regular/ProgressBarAttributeDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Regular/ProgressBarAttributeDrawer.cs @@ -43,12 +43,18 @@ private void SetProgressValue(SerializedProperty property, Rect progressBarRect, { var minValue = Attribute.MinValue; var maxValue = Attribute.MaxValue; + var valueStep = Attribute.ValueStep; var range = progressBarRect.xMax - progressBarRect.xMin; xPosition = Mathf.Clamp(xPosition - progressBarRect.xMin, 0, range); var fill = Mathf.Clamp01(xPosition / range); var newValue = (maxValue - minValue) * fill + minValue; + if (!Mathf.Approximately(valueStep, 0.0f)) + { + newValue = Mathf.Round(newValue / valueStep) * valueStep; + newValue = Mathf.Clamp(newValue, minValue, maxValue); + } switch (property.propertyType) { diff --git a/Assets/Editor Toolbox/Editor/Drawers/Regular/PropertyDrawerBase.cs b/Assets/Editor Toolbox/Editor/Drawers/Regular/PropertyDrawerBase.cs index 02dad0d9..5472ef65 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Regular/PropertyDrawerBase.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Regular/PropertyDrawerBase.cs @@ -48,8 +48,7 @@ public sealed override void OnGUI(Rect position, SerializedProperty property, GU } ToolboxEditorLog.WrongAttributeUsageWarning(attribute, property); - //create additional warning label based on the property name - var warningContent = new GUIContent(property.displayName + " has invalid property drawer"); + var warningContent = new GUIContent($"{property.displayName} has invalid property drawer"); ToolboxEditorGui.DrawEmptyProperty(position, property, warningContent); } diff --git a/Assets/Editor Toolbox/Editor/Drawers/Regular/SerializedSceneDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Regular/SerializedSceneDrawer.cs index b555f02b..ad7946be 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Regular/SerializedSceneDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Regular/SerializedSceneDrawer.cs @@ -1,4 +1,5 @@ -using UnityEditor; +using Toolbox.Editor.Internal; +using UnityEditor; using UnityEngine; namespace Toolbox.Editor.Drawers @@ -28,29 +29,39 @@ private void OpenBuildSettings() EditorWindow.GetWindow(typeof(BuildPlayerWindow)); } - - protected override float GetPropertyHeightSafe(SerializedProperty property, GUIContent label) - { - var lineHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; - return HasSceneDetails(property) - ? base.GetPropertyHeightSafe(property, label) + lineHeight * 2 - : base.GetPropertyHeightSafe(property, label); - } - protected override void OnGUISafe(Rect position, SerializedProperty property, GUIContent label) { + var hasDetails = HasSceneDetails(property); EditorGUI.BeginProperty(position, label, property); position.height = EditorGUIUtility.singleLineHeight; - position = EditorGUI.PrefixLabel(position, label); + if (hasDetails) + { + position.xMax -= Style.foldoutWidth; + } + var sceneProperty = property.FindPropertyRelative("sceneReference"); - EditorGUI.ObjectField(position, sceneProperty, GUIContent.none); + EditorGUI.ObjectField(position, sceneProperty, label); EditorGUI.EndProperty(); - if (!HasSceneDetails(property)) + if (hasDetails) + { + var prevXMin = position.xMin; + position.xMin = position.xMax; + position.xMax += Style.foldoutWidth; + using (new DisabledScope(true)) + { + property.isExpanded = GUI.Toggle(position, property.isExpanded, Style.foldoutContent, Style.foldoutStyle); + } + + position.xMin = prevXMin; + } + + if (!hasDetails || !property.isExpanded) { return; } + EditorGUI.indentLevel++; var sceneData = SceneData.GetSceneDataFromIndex(property); var spacing = EditorGUIUtility.standardVerticalSpacing; position.y += EditorGUIUtility.singleLineHeight + spacing; @@ -64,20 +75,21 @@ protected override void OnGUISafe(Rect position, SerializedProperty property, GU EditorGUI.LabelField(position, Style.notInBuildContent); position.y += EditorGUIUtility.singleLineHeight + spacing; EditorGUI.EndDisabledGroup(); - if (GUI.Button(position, Style.showDetailsContent)) + var buttonRect = EditorGUI.IndentedRect(position); + if (GUI.Button(buttonRect, Style.showDetailsContent)) { OpenBuildSettings(); } } - } + EditorGUI.indentLevel--; + } public override bool IsPropertyValid(SerializedProperty property) { return property.type == nameof(SerializedScene); } - private struct SceneData { public int index; @@ -134,10 +146,28 @@ public static SceneData GetSceneDataFromScene(SerializedProperty property) private static class Style { + internal const float foldoutWidth = 50.0f; + + internal static readonly GUIContent foldoutContent = new GUIContent("Details", "Show/Hide Scene Details"); internal static readonly GUIContent buildIndexContent = new GUIContent("Build Index"); internal static readonly GUIContent isEnabledContent = new GUIContent("Is Enabled"); internal static readonly GUIContent notInBuildContent = new GUIContent("Not in Build"); internal static readonly GUIContent showDetailsContent = new GUIContent("Open Build Settings"); + + internal static readonly GUIStyle foldoutStyle; + + static Style() + { + foldoutStyle = new GUIStyle(EditorStyles.miniButton) + { +#if UNITY_2019_3_OR_NEWER + fontSize = 10, +#else + fontSize = 9, +#endif + alignment = TextAnchor.MiddleCenter + }; + } } } } \ No newline at end of file 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/Editor/Drawers/Toolbox/Decorator/EditorButtonAttributeDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/EditorButtonAttributeDrawer.cs index fdc0d496..f5ea2b60 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/EditorButtonAttributeDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/Decorator/EditorButtonAttributeDrawer.cs @@ -8,9 +8,34 @@ namespace Toolbox.Editor.Drawers { public class EditorButtonAttributeDrawer : ToolboxDecoratorDrawer { + private void DrawButton(EditorButtonAttribute attribute) + { + var declaringObjects = GetDeclaringObjects(); + if (declaringObjects == null || declaringObjects.Length == 0) + { + //NOTE: something went really wrong, internal bug or OnGuiBeginSafe was called out of the Toolbox scope + return; + } + + var disable = !IsClickable(attribute, declaringObjects); + using (new EditorGUI.DisabledScope(disable)) + { + var label = string.IsNullOrEmpty(attribute.ExtraLabel) + ? ObjectNames.NicifyVariableName(attribute.MethodName) + : attribute.ExtraLabel; + var tooltip = attribute.Tooltip; + var content = new GUIContent(label, tooltip); + + if (GUILayout.Button(content, Style.buttonStyle)) + { + CallMethods(attribute, declaringObjects); + } + } + } + private MethodInfo GetMethod(EditorButtonAttribute attribute, object[] targetObjects, string methodName) { - var methodInfo = ReflectionUtility.GetObjectMethod(methodName, targetObjects); + var methodInfo = ReflectionUtility.GetMethod(methodName, targetObjects); if (methodInfo == null) { ToolboxEditorLog.AttributeUsageWarning(attribute, string.Format("{0} method not found.", methodName)); @@ -119,29 +144,25 @@ private void CallMethods(EditorButtonAttribute attribute, object[] targetObjects } } - protected override void OnGuiCloseSafe(EditorButtonAttribute attribute) + protected override void OnGuiBeginSafe(EditorButtonAttribute attribute) { - var declaringObjects = GetDeclaringObjects(); - if (declaringObjects == null || declaringObjects.Length == 0) + if (attribute.PositionType != ButtonPositionType.Above) { - //NOTE: something went really wrong, internal bug or OnGuiBeginSafe was called out of the Toolbox scope return; } - var disable = !IsClickable(attribute, declaringObjects); - using (new EditorGUI.DisabledScope(disable)) - { - var label = string.IsNullOrEmpty(attribute.ExtraLabel) - ? ObjectNames.NicifyVariableName(attribute.MethodName) - : attribute.ExtraLabel; - var tooltip = attribute.Tooltip; - var content = new GUIContent(label, tooltip); + DrawButton(attribute); + } - if (GUILayout.Button(content, Style.buttonStyle)) - { - CallMethods(attribute, declaringObjects); - } + protected override void OnGuiCloseSafe(EditorButtonAttribute attribute) + { + if (attribute.PositionType != ButtonPositionType.Default && + attribute.PositionType != ButtonPositionType.Below) + { + return; } + + DrawButton(attribute); } private static class Style diff --git a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertyList/ReorderableListExposedAttributeDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertyList/ReorderableListExposedAttributeDrawer.cs index e048332e..476d623a 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertyList/ReorderableListExposedAttributeDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertyList/ReorderableListExposedAttributeDrawer.cs @@ -56,7 +56,7 @@ private static MethodInfo FindMethod(SerializedObject target, string methodName, return null; } - var methodInfo = ReflectionUtility.GetObjectMethod(methodName, target); + var methodInfo = ReflectionUtility.GetMethod(methodName, target); if (methodInfo == null) { ToolboxEditorLog.AttributeUsageWarning(typeof(ReorderableListExposedAttribute), string.Format("{0} method not found.", methodName)); diff --git a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/InLineEditorAttributeDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/InLineEditorAttributeDrawer.cs index 125e077b..93b8cf8e 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/InLineEditorAttributeDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/InLineEditorAttributeDrawer.cs @@ -46,7 +46,6 @@ static InLineEditorAttributeDrawer() private static readonly PropertyDataStorage storage; - private Editor GetTargetsEditor(SerializedProperty property, InLineEditorAttribute attribute) { var editor = storage.ReturnItem(property, attribute); @@ -108,7 +107,6 @@ private void DrawEditor(Editor editor, bool disableEditor, bool drawPreview, boo } } - /// /// Handles the property drawing process and tries to create a inlined version of the . /// @@ -151,15 +149,17 @@ protected override void OnGuiSafe(SerializedProperty property, GUIContent label, DrawEditor(editor, attribute); } } + else + { + storage.ClearItem(property); + } } - public override bool IsPropertyValid(SerializedProperty property) { return property.propertyType == SerializedPropertyType.ObjectReference; } - private static class Style { internal static readonly GUIStyle backgroundStyle; diff --git a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/ReferencePickerAttributeDrawer.cs b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/ReferencePickerAttributeDrawer.cs index a854feb5..64bb2a20 100644 --- a/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/ReferencePickerAttributeDrawer.cs +++ b/Assets/Editor Toolbox/Editor/Drawers/Toolbox/PropertySelf/ReferencePickerAttributeDrawer.cs @@ -41,26 +41,75 @@ private Type GetParentType(ReferencePickerAttribute attribute, SerializedPropert return fieldType; } + private static Type GetCurrentManagedReferenceType(SerializedProperty property, out bool hasMixedValues) + { + var fullTypeName = property.managedReferenceFullTypename; + hasMixedValues = false; + TypeUtility.TryGetTypeFromManagedReferenceFullTypeName(fullTypeName, out var targetType); + + var currentSerializedObject = property.serializedObject; + if (currentSerializedObject.isEditingMultipleObjects) + { + var targets = currentSerializedObject.targetObjects; + foreach (var target in targets) + { + using (var tempSerializedObject = new SerializedObject(target)) + { + var tempProperty = tempSerializedObject.FindProperty(property.propertyPath); + if (tempProperty == null) + { + break; + } + + if (tempProperty.managedReferenceFullTypename != fullTypeName) + { + hasMixedValues = true; + break; + } + } + } + } + + return targetType; + } + private void CreateTypeProperty(SerializedProperty property, Type parentType, ReferencePickerAttribute attribute, Rect position) { - TypeUtility.TryGetTypeFromManagedReferenceFullTypeName(property.managedReferenceFullTypename, out var currentType); - typeField.OnGui(position, attribute.AddTextSearchField, (type) => + var searchFieldRequired = attribute.AddTextSearchField; + var confirmationRequired = attribute.AddConfirmationBox; + + var currentType = GetCurrentManagedReferenceType(property, out var hasMixedValues); + var hadMixedValues = EditorGUI.showMixedValue; + EditorGUI.showMixedValue = hasMixedValues; + typeField.OnGui(position, searchFieldRequired, (type) => { try { - if (!property.serializedObject.isEditingMultipleObjects) + var isOperationAllowed = true; + if (confirmationRequired) { - UpdateTypeProperty(property, type, attribute); + isOperationAllowed = EditorUtility.DisplayDialog("Confirm Assignment", + "Are you sure you want to assign a new reference? This action will replace the current one.", + "Assign", + "Cancel"); } - else + + if (isOperationAllowed) { - var targets = property.serializedObject.targetObjects; - foreach (var target in targets) + if (!property.serializedObject.isEditingMultipleObjects) + { + UpdateTypeProperty(property, type, attribute); + } + else { - using (var so = new SerializedObject(target)) + var targets = property.serializedObject.targetObjects; + foreach (var target in targets) { - var sp = so.FindProperty(property.propertyPath); - UpdateTypeProperty(sp, type, attribute); + using (var so = new SerializedObject(target)) + { + var sp = so.FindProperty(property.propertyPath); + UpdateTypeProperty(sp, type, attribute); + } } } } @@ -70,6 +119,7 @@ private void CreateTypeProperty(SerializedProperty property, Type parentType, Re ToolboxEditorLog.LogWarning("Invalid attempt to update disposed property."); } }, currentType, parentType); + EditorGUI.showMixedValue = false; } private void UpdateTypeProperty(SerializedProperty property, Type targetType, ReferencePickerAttribute attribute) diff --git a/Assets/Editor Toolbox/Editor/Internal/ReorderableListBase.cs b/Assets/Editor Toolbox/Editor/Internal/ReorderableListBase.cs index b4c3e9bc..56bf8d08 100644 --- a/Assets/Editor Toolbox/Editor/Internal/ReorderableListBase.cs +++ b/Assets/Editor Toolbox/Editor/Internal/ReorderableListBase.cs @@ -334,6 +334,8 @@ protected virtual bool DoListFooter(Rect footerRect) return false; } + footerRect.yMax += Style.footerXOffset; + footerRect.yMin += Style.footerYOffset; //draw the background on repaint event if (Event.current.type == EventType.Repaint) { @@ -455,7 +457,10 @@ public void CutKeyboardFocus() public void AppendElement() { - Index = (List.arraySize += 1) - 1; + var newSize = Size.intValue + 1; + Size.intValue = newSize; + Index = newSize - 1; + if (overrideNewElementCallback == null) { return; @@ -520,6 +525,7 @@ public virtual void DrawStandardFooter(Rect rect) //set button area rect rect = new Rect(rect.xMax - Style.footerWidth, rect.y, Style.footerWidth, rect.height); + var actionPerformed = false; //set rect properties from style var buttonWidth = Style.footerButtonWidth; var buttonHeight = Style.footerButtonHeight; @@ -529,7 +535,7 @@ public virtual void DrawStandardFooter(Rect rect) //set proper rect for each button var appendButtonRect = new Rect(rect.xMin + margin, rect.y - padding, buttonWidth, buttonHeight); - EditorGUI.BeginDisabledGroup(List.hasMultipleDifferentValues); + EditorGUI.BeginDisabledGroup(Size.hasMultipleDifferentValues); EditorGUI.BeginDisabledGroup(onCanAppendCallback != null && !onCanAppendCallback(this)); if (GUI.Button(appendButtonRect, onAppendDropdownCallback != null ? Style.iconToolbarDropContent @@ -549,9 +555,10 @@ public virtual void DrawStandardFooter(Rect rect) } onChangedCallback?.Invoke(this); + actionPerformed = true; } - EditorGUI.EndDisabledGroup(); + EditorGUI.EndDisabledGroup(); var removeButtonRect = new Rect(rect.xMax - buttonWidth - margin, rect.y - padding, buttonWidth, buttonHeight); EditorGUI.BeginDisabledGroup((onCanRemoveCallback != null && !onCanRemoveCallback(this) || Index < 0 || Index >= Count)); @@ -567,9 +574,17 @@ public virtual void DrawStandardFooter(Rect rect) } onChangedCallback?.Invoke(this); + actionPerformed = true; } + EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup(); + + if (actionPerformed) + { + SerializedObject.ApplyModifiedProperties(); + GUIUtility.ExitGUI(); + } } /// @@ -870,6 +885,8 @@ protected static class Style #else internal static readonly float lineHeight = 16.0f; #endif + internal static readonly float footerXOffset = 0.0f; + internal static readonly float footerYOffset = -1.0f; internal static readonly float footerWidth = 60.0f; internal static readonly float footerButtonWidth = 25.0f; internal static readonly float footerButtonHeight = 13.0f; diff --git a/Assets/Editor Toolbox/Editor/Internal/ToolboxEditorList.cs b/Assets/Editor Toolbox/Editor/Internal/ToolboxEditorList.cs index a8fc07fc..83836097 100644 --- a/Assets/Editor Toolbox/Editor/Internal/ToolboxEditorList.cs +++ b/Assets/Editor Toolbox/Editor/Internal/ToolboxEditorList.cs @@ -12,6 +12,14 @@ namespace Toolbox.Editor.Internal /// public class ToolboxEditorList : ReorderableListBase { + /// + /// Style used for the vertical layout built around all list controls. + /// + private static readonly GUIStyle listGroupStyle = new GUIStyle() + { + margin = new RectOffset() + }; + private int lastCoveredIndex = -1; private Rect[] elementsRects; @@ -207,6 +215,13 @@ private void DrawTargetGap(int targetIndex, int draggingIndex) EditorGUI.DrawRect(rect, Style.selectionColor); } + private GUIStyle RequestBodyStyle() + { + var indent = EditorGuiUtility.IndentSize; + listGroupStyle.margin.left = (int)indent; + return listGroupStyle; + } + protected override void DoListMiddle() { using (var middleGroup = new EditorGUILayout.VerticalScope()) @@ -357,9 +372,10 @@ protected override void HandleHeaderEvents(Rect rect) /// public override void DoList(GUIContent label) { + var style = RequestBodyStyle(); //pack eveything in one, vertical scope //it will keep sections always in order - using (new EditorGUILayout.VerticalScope()) + using (new EditorGUILayout.VerticalScope(style)) { base.DoList(label); } diff --git a/Assets/Editor Toolbox/Editor/Management/ToolboxManager.cs b/Assets/Editor Toolbox/Editor/Management/ToolboxManager.cs index e66e1f14..7471f00e 100644 --- a/Assets/Editor Toolbox/Editor/Management/ToolboxManager.cs +++ b/Assets/Editor Toolbox/Editor/Management/ToolboxManager.cs @@ -213,7 +213,12 @@ void ReintializeProvider() if (GUILayout.Button("Create a new settings file")) { - var settingsInstance = ScriptableObject.CreateInstance(settingsType); + var settingsInstance = ScriptableObject.CreateInstance(); + if (settingsInstance == null) + { + ToolboxEditorLog.LogError($"Something went wrong. Cannot create a new Settings file."); + return; + } var locationPath = EditorUtility.OpenFolderPanel("New Settings file location", "Assets", ""); //validate returned path and create relative one if possible diff --git a/Assets/Editor Toolbox/Editor/SceneView/ToolboxEditorSceneViewObjectSelector.cs b/Assets/Editor Toolbox/Editor/SceneView/ToolboxEditorSceneViewObjectSelector.cs index 0f6f834e..f392b780 100644 --- a/Assets/Editor Toolbox/Editor/SceneView/ToolboxEditorSceneViewObjectSelector.cs +++ b/Assets/Editor Toolbox/Editor/SceneView/ToolboxEditorSceneViewObjectSelector.cs @@ -7,16 +7,18 @@ namespace Toolbox.Editor.SceneView public class ToolboxEditorSceneViewObjectSelector : EditorWindow { private static readonly Color selectionColor = new Color(0.50f, 0.70f, 1.00f); + private static readonly Color highlightWireColor = Color.yellow; private const float sizeXPadding = 2f; private const float sizeYPadding = 2f; private const float buttonYSpacing = 0.0f; private const float buttonYSize = 20f; private const float sizeXOffset = -30f; + private const float indentWidth = 12f; - private List gameObjects; - private List gameObjectPaths; + private readonly List highlightedRenderers = new List(); + private List displayEntries; private GameObject highlightedObject; private Vector2 size; private Vector2 buttonSize; @@ -32,12 +34,27 @@ public static void Show(List gameObjects, Vector2 position) var window = CreateInstance(); window.wantsMouseMove = true; window.wantsMouseEnterLeaveWindow = true; - window.gameObjects = gameObjects; - window.InitializeGameObjectPaths(); + window.displayEntries = window.BuildDisplayEntries(gameObjects); window.CalculateSize(); window.ShowAsDropDown(rect, window.size); } + private void OnEnable() + { +#if UNITY_2019_1_OR_NEWER + UnityEditor.SceneView.duringSceneGui += OnSceneViewGui; +#endif + } + + private void OnDisable() + { +#if UNITY_2019_1_OR_NEWER + UnityEditor.SceneView.duringSceneGui -= OnSceneViewGui; +#endif + highlightedRenderers.Clear(); + highlightedObject = null; + } + private void OnGUI() { if (Event.current.type == EventType.Layout) @@ -67,18 +84,24 @@ private void OnGuiMouseLeave() private void OnGuiMouseMove() { var rect = new Rect(sizeXPadding, sizeYPadding, buttonSize.x, buttonSize.y); - for (var i = 0; i < gameObjects.Count; i++) + for (var i = 0; i < displayEntries.Count; i++) { - var gameObject = gameObjects[i]; + var entry = displayEntries[i]; + var gameObject = entry.GameObject; if (gameObject == null) { //Can happen when something removes the gameobject during the window display. continue; } + var indentOffset = entry.Depth * indentWidth; + var drawRect = new Rect(rect); + drawRect.x += indentOffset; + drawRect.width -= indentOffset; + var content = EditorGUIUtility.ObjectContent(gameObject, typeof(GameObject)); - GUI.Button(rect, content, Style.buttonStyle); - if (rect.Contains(Event.current.mousePosition)) + GUI.Button(drawRect, content, Style.buttonStyle); + if (drawRect.Contains(Event.current.mousePosition)) { HighlightedObject = gameObject; } @@ -90,15 +113,21 @@ private void OnGuiMouseMove() private void OnGuiNormal() { var rect = new Rect(sizeXPadding, sizeYPadding, buttonSize.x, buttonSize.y); - for (var i = 0; i < gameObjects.Count; i++) + for (var i = 0; i < displayEntries.Count; i++) { - var gameObject = gameObjects[i]; + var entry = displayEntries[i]; + var gameObject = entry.GameObject; if (gameObject == null) { //Can happen when something removes the gameobject during the window display. continue; } + var indentOffset = entry.Depth * indentWidth; + var drawRect = new Rect(rect); + drawRect.x += indentOffset; + drawRect.width -= indentOffset; + var content = EditorGUIUtility.ObjectContent(gameObject, typeof(GameObject)); var objectSelected = Selection.Contains(gameObject); if (objectSelected) @@ -106,7 +135,7 @@ private void OnGuiNormal() GUI.backgroundColor = selectionColor; } - if (GUI.Button(rect, content, Style.buttonStyle)) + if (GUI.Button(drawRect, content, Style.buttonStyle)) { GameObjectButtonPress(i); } @@ -120,47 +149,107 @@ private Vector2 CalculateSize() { size = Vector2.zero; - foreach (var go in gameObjects) + var maxIndent = 0f; + foreach (var entry in displayEntries) { - var content = EditorGUIUtility.ObjectContent(go, typeof(GameObject)); + var content = EditorGUIUtility.ObjectContent(entry.GameObject, typeof(GameObject)); var currentSize = Style.buttonStyle.CalcSize(content); if (currentSize.x > size.x) { size.x = currentSize.x; } + + var currentIndent = entry.Depth * indentWidth; + if (currentIndent > maxIndent) + { + maxIndent = currentIndent; + } } + size.x += maxIndent; //This is needed because CalcSize calculates content drawing with icon at full size. size.x += sizeXOffset; buttonSize.x = size.x; buttonSize.y = buttonYSize; - size.y = gameObjects.Count * buttonYSize + sizeYPadding * 2.0f + buttonYSpacing * gameObjects.Count - 1; + size.y = displayEntries.Count * buttonYSize + sizeYPadding * 2.0f + buttonYSpacing * displayEntries.Count - 1; size.x += sizeXPadding * 2.0f; return size; } - private void InitializeGameObjectPaths() + private List BuildDisplayEntries(List objectsUnderCursor) { - gameObjectPaths = new List(); - var pathStack = new Stack(); + var entries = new List(); + + if (objectsUnderCursor == null || objectsUnderCursor.Count == 0) + { + return entries; + } + + var underCursorSet = new HashSet(); + foreach (var gameObject in objectsUnderCursor) + { + if (gameObject == null) + { + continue; + } + + underCursorSet.Add(gameObject.transform); + } + + var relevantTransforms = new HashSet(); + foreach (var transform in underCursorSet) + { + var current = transform; + while (current != null && relevantTransforms.Add(current)) + { + current = current.parent; + } + } + + var orderedRoots = new List(); + foreach (var gameObject in objectsUnderCursor) + { + if (gameObject == null) + { + continue; + } + + var top = gameObject.transform; + while (top.parent != null && relevantTransforms.Contains(top.parent)) + { + top = top.parent; + } + + if (!orderedRoots.Contains(top)) + { + orderedRoots.Add(top); + } + } - for (var i = 0; i < gameObjects.Count; i++) + foreach (var root in orderedRoots) { - pathStack.Clear(); - var transform = gameObjects[i].transform; - pathStack.Push(transform.gameObject.name); + AppendHierarchy(root, 0, relevantTransforms, entries); + } + + return entries; + } - while (transform.parent != null) + private void AppendHierarchy(Transform current, int depth, HashSet relevantTransforms, List entries) + { + entries.Add(new DisplayEntry(current.gameObject, depth)); + + for (var i = 0; i < current.childCount; i++) + { + var child = current.GetChild(i); + if (!relevantTransforms.Contains(child)) { - transform = transform.parent; - pathStack.Push(transform.gameObject.name); + continue; } - var path = string.Join("/", pathStack.ToArray()); - gameObjectPaths.Add(path); + AppendHierarchy(child, depth + 1, relevantTransforms, entries); } } @@ -218,7 +307,7 @@ private void UpdateShiftSelectionIDs(int id) private void SelectObject(int id, bool control, bool shift) { - var gameObject = gameObjects[id]; + var gameObject = displayEntries[id].GameObject; if (shift) { @@ -239,7 +328,10 @@ private void SelectObject(int id, bool control, bool shift) } else { - Selection.objects = new Object[] { gameObject }; + Selection.objects = new Object[] + { + gameObject + }; } } @@ -252,7 +344,7 @@ private void SelectObjects(int minId, int maxId) for (var i = minId; i <= maxId; i++) { - newSelection[index] = gameObjects[i]; + newSelection[index] = displayEntries[i].GameObject; index++; } @@ -291,6 +383,48 @@ private void RemoveSelectedObject(GameObject gameObject) Selection.objects = newSelection; } + private void UpdateHighlightedRenderers() + { + highlightedRenderers.Clear(); + + if (highlightedObject == null) + { + return; + } + + highlightedRenderers.AddRange(highlightedObject.GetComponentsInChildren(true)); + } + +#if UNITY_2019_1_OR_NEWER + private void OnSceneViewGui(UnityEditor.SceneView sceneView) + { + if (highlightedRenderers.Count == 0 || + Event.current.type != EventType.Repaint) + { + return; + } + + // Unity 6.1+ provides Handles.DrawOutline which also highlights children in one call. +#if UNITY_6000_1_OR_NEWER + Handles.DrawOutline(highlightedRenderers.ToArray(), highlightWireColor, highlightWireColor, 0.5f); +#else + using (new Handles.DrawingScope(highlightWireColor)) + { + foreach (var renderer in highlightedRenderers) + { + if (renderer == null) + { + continue; + } + + var bounds = renderer.bounds; + Handles.DrawWireCube(bounds.center, bounds.size); + } + } +#endif + } +#endif + private GameObject HighlightedObject { set @@ -307,9 +441,23 @@ private GameObject HighlightedObject { EditorGUIUtility.PingObject(highlightedObject); } + + UpdateHighlightedRenderers(); } } + private readonly struct DisplayEntry + { + internal DisplayEntry(GameObject gameObject, int depth) + { + GameObject = gameObject; + Depth = depth; + } + + internal GameObject GameObject { get; } + internal int Depth { get; } + } + private static class Style { internal static readonly GUIStyle buttonStyle; diff --git a/Assets/Editor Toolbox/Editor/ToolboxEditorGui.cs b/Assets/Editor Toolbox/Editor/ToolboxEditorGui.cs index a7287573..d8977f25 100644 --- a/Assets/Editor Toolbox/Editor/ToolboxEditorGui.cs +++ b/Assets/Editor Toolbox/Editor/ToolboxEditorGui.cs @@ -528,12 +528,7 @@ public static void DrawPropertyChildren(SerializedProperty property, Action /// Static GUI representation for the Project Overlay. diff --git a/Assets/Editor Toolbox/Editor/ToolboxEditorSettings.cs b/Assets/Editor Toolbox/Editor/ToolboxEditorSettings.cs index 7f623c03..28197497 100644 --- a/Assets/Editor Toolbox/Editor/ToolboxEditorSettings.cs +++ b/Assets/Editor Toolbox/Editor/ToolboxEditorSettings.cs @@ -6,8 +6,8 @@ namespace Toolbox.Editor { using Toolbox.Editor.Drawers; - using Toolbox.Editor.Folders; using Toolbox.Editor.Hierarchy; + using Toolbox.Folders; internal interface IToolboxGeneralSettings { } diff --git a/Assets/Editor Toolbox/Editor/ToolboxEditorToolbar.cs b/Assets/Editor Toolbox/Editor/ToolboxEditorToolbar.cs index bf5285f7..fa3db88d 100644 --- a/Assets/Editor Toolbox/Editor/ToolboxEditorToolbar.cs +++ b/Assets/Editor Toolbox/Editor/ToolboxEditorToolbar.cs @@ -5,16 +5,20 @@ using System.Reflection; using UnityEditor; -using UnityEngine; using Object = UnityEngine.Object; using Unity.EditorCoroutines.Editor; +using UnityEngine; + #if UNITY_2019_1_OR_NEWER using UnityEngine.UIElements; #else using UnityEngine.Experimental.UIElements; #endif -//NOTE: since everything in this class is reflection-based it is a little bit "hacky" +//NOTE: since everything in this class is reflection-based it is a little bit "hacky"; supporting each version makes it very hard to maintain - +// consider implementing different 'drawers' for each Toolbar iteration + +//NOTE: unfortunately latest (6.3) official API is very limited and can't be used to port this implementation namespace Toolbox.Editor { @@ -30,7 +34,11 @@ static ToolboxEditorToolbar() } private static readonly Type containterType = typeof(IMGUIContainer); +#if UNITY_6000_3_OR_NEWER + private static readonly Type toolbarType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.MainToolbarWindow"); +#else private static readonly Type toolbarType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.Toolbar"); +#endif private static readonly Type guiViewType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GUIView"); #if UNITY_2020_1_OR_NEWER private static readonly Type backendType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.IWindowBackend"); @@ -53,26 +61,59 @@ static ToolboxEditorToolbar() private static IEnumerator Initialize() { - while (ToolboxEditorToolbar.toolbar == null) + while (toolbar == null) { - var toolbars = Resources.FindObjectsOfTypeAll(toolbarType); - if (toolbars == null || toolbars.Length == 0) + if (!TryGetToolbarInstance(out toolbar)) { yield return null; continue; } - else + } + +#if UNITY_6000_3_OR_NEWER + VisualElement root = null; + if (toolbar is EditorWindow editorWindow) + { + root = editorWindow.rootVisualElement; + } + + var builder = root.Query(name: "DockArea"); + var states = builder.Build(); + + var toolbarLeftZone = states.AtIndex(0); + AddIMGUIContainer(toolbarLeftZone, OnGuiLeft, "Editor Toolbox Left Area"); + + var toolbarRightZone = states.AtIndex(1); + AddIMGUIContainer(toolbarRightZone, OnGuiRight, "Editor Toolbox Right Area"); + + static void AddIMGUIContainer(VisualElement parentElement, Action guiCallback, string name) + { + if (parentElement == null) { - ToolboxEditorToolbar.toolbar = toolbars[0]; + return; } - } + var element = new VisualElement(); + element.name = name; + element.StretchToParentSize(); + element.style.left = 10; + element.style.right = 10; + element.style.flexGrow = 1; + element.style.flexDirection = FlexDirection.Row; + + var guiContainer = new IMGUIContainer(); + guiContainer.style.flexGrow = 1; + guiContainer.onGUIHandler = guiCallback; + element.Add(guiContainer); + parentElement.Add(element); + } +#else #if UNITY_2021_1_OR_NEWER - var rootField = ToolboxEditorToolbar.toolbar.GetType().GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance); - var root = rootField.GetValue(ToolboxEditorToolbar.toolbar) as VisualElement; - var toolbar = root.Q("ToolbarZoneLeftAlign"); + var rootField = toolbar.GetType().GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance); + var root = rootField.GetValue(toolbar) as VisualElement; - var element = new VisualElement() + var toolbarLeftZone = root.Q("ToolbarZoneLeftAlign"); + var leftElement = new VisualElement() { style = { @@ -81,12 +122,27 @@ private static IEnumerator Initialize() } }; - var container = new IMGUIContainer(); - container.style.flexGrow = 1; - container.onGUIHandler += OnGui; + var leftContainer = new IMGUIContainer(); + leftContainer.style.flexGrow = 1; + leftContainer.onGUIHandler = OnGuiLeft; + leftElement.Add(leftContainer); + toolbarLeftZone.Add(leftElement); - element.Add(container); - toolbar.Add(element); + var toolbarRightZone = root.Q("ToolbarZoneRightAlign"); + var rightElement = new VisualElement() + { + style = + { + flexGrow = 1, + flexDirection = FlexDirection.Row, + } + }; + + var rightContainer = new IMGUIContainer(); + rightContainer.style.flexGrow = 1; + rightContainer.onGUIHandler = OnGuiRight; + rightElement.Add(rightContainer); + toolbarRightZone.Add(rightElement); #else #if UNITY_2020_1_OR_NEWER var backend = guiBackend.GetValue(toolbar); @@ -94,30 +150,56 @@ private static IEnumerator Initialize() #else var elements = visualTree.GetValue(toolbar, null) as VisualElement; #endif - #if UNITY_2019_1_OR_NEWER var container = elements[0]; #else var container = elements[0] as IMGUIContainer; #endif var handler = onGuiHandler.GetValue(container) as Action; - handler -= OnGui; - handler += OnGui; + handler -= OnGuiLeft; + handler += OnGuiLeft; onGuiHandler.SetValue(container, handler); +#endif +#endif + } + + private static bool TryGetToolbarInstance(out Object toolbarInstance) + { +#if UNITY_6000_3_OR_NEWER + toolbarInstance = EditorWindow.GetWindow(toolbarType); + return toolbarInstance != null; +#else + var toolbars = Resources.FindObjectsOfTypeAll(toolbarType); + if (toolbars == null || toolbars.Length == 0) + { + toolbarInstance = null; + return false; + } + else + { + toolbarInstance = toolbars[0]; + return true; + } #endif } - private static void OnGui() + private static void OnGuiLeft() { - if (!IsToolbarAllowed || !IsToolbarValid) + if (!IsToolbarAllowed || !IsLeftToolbarValid) { return; } #if UNITY_2021_1_OR_NEWER - using (new GUILayout.HorizontalScope()) + using (new EditorGUILayout.VerticalScope()) { - OnToolbarGui.Invoke(); + GUILayout.FlexibleSpace(); + using (new EditorGUILayout.HorizontalScope()) + { + OnToolbarGuiLeft(); + } + + GUILayout.FlexibleSpace(); } #else var screenWidth = EditorGUIUtility.currentViewWidth; @@ -139,12 +221,31 @@ private static void OnGui() { using (new GUILayout.HorizontalScope()) { - OnToolbarGui?.Invoke(); + OnToolbarGuiLeft(); } } #endif } + private static void OnGuiRight() + { + if (!IsToolbarAllowed || !IsRightToolbarValid) + { + return; + } + + using (new EditorGUILayout.VerticalScope()) + { + GUILayout.FlexibleSpace(); + using (new EditorGUILayout.HorizontalScope()) + { + OnToolbarGuiRight(); + } + + GUILayout.FlexibleSpace(); + } + } + public static void Repaint() { if (toolbar == null) @@ -152,15 +253,26 @@ public static void Repaint() return; } +#if UNITY_6000_3_OR_NEWER + var toolbarWindow = EditorWindow.GetWindow(toolbarType); + toolbarWindow.Repaint(); +#else repaintMethod?.Invoke(toolbar, null); +#endif } public static bool IsToolbarAllowed { get; set; } = true; - public static bool IsToolbarValid => toolbar != null && OnToolbarGui != null; + public static bool IsLeftToolbarValid => toolbar != null && OnToolbarGuiLeft != null; + public static bool IsRightToolbarValid => toolbar != null && OnToolbarGuiRight != null; public static float FromToolsOffset { get; set; } = 400.0f; public static float FromStripOffset { get; set; } = 150.0f; +#pragma warning disable 0067 + [Obsolete("Use OnToolbarGuiLeft instead")] public static event Action OnToolbarGui; +#pragma warning restore 0067 + public static event Action OnToolbarGuiLeft; + public static event Action OnToolbarGuiRight; private static class Style { diff --git a/Assets/Editor Toolbox/Editor/Utilities/EditorGuiUtility.cs b/Assets/Editor Toolbox/Editor/Utilities/EditorGuiUtility.cs index bf0e8e33..12cc3786 100644 --- a/Assets/Editor Toolbox/Editor/Utilities/EditorGuiUtility.cs +++ b/Assets/Editor Toolbox/Editor/Utilities/EditorGuiUtility.cs @@ -12,7 +12,7 @@ namespace Toolbox.Editor /// internal static class EditorGuiUtility { - private const float indentPerLevel = 15.0f; + internal const float indentPerLevel = 15.0f; private static readonly Dictionary loadedTextures = new Dictionary(); diff --git a/Assets/Editor Toolbox/Editor/Utilities/InspectorUtility.cs b/Assets/Editor Toolbox/Editor/Utilities/InspectorUtility.cs index 4e36831a..e3a92a1f 100644 --- a/Assets/Editor Toolbox/Editor/Utilities/InspectorUtility.cs +++ b/Assets/Editor Toolbox/Editor/Utilities/InspectorUtility.cs @@ -52,7 +52,7 @@ internal static bool GetIsEditorExpanded(Editor editor) /// internal static void SetIsEditorExpanded(Editor editor, bool value) { - InternalEditorUtility.SetIsInspectorExpanded(editor.target, true); + InternalEditorUtility.SetIsInspectorExpanded(editor.target, value); //NOTE: in older versions Editor's foldouts are based on the m_IsVisible field and the Awake() method #if !UNITY_2019_1_OR_NEWER const string isVisibleFieldName = "m_IsVisible"; @@ -60,7 +60,7 @@ internal static void SetIsEditorExpanded(Editor editor, bool value) BindingFlags.Instance | BindingFlags.NonPublic); if (isVisible != null) { - isVisible.SetValue(editor, true); + isVisible.SetValue(editor, value); } #endif } diff --git a/Assets/Editor Toolbox/Editor/Utilities/PropertyUtility.cs b/Assets/Editor Toolbox/Editor/Utilities/PropertyUtility.cs index e3b85044..32e57b07 100644 --- a/Assets/Editor Toolbox/Editor/Utilities/PropertyUtility.cs +++ b/Assets/Editor Toolbox/Editor/Utilities/PropertyUtility.cs @@ -278,9 +278,7 @@ public static Type GetProperType(this SerializedProperty property, FieldInfo fie //handle situation when property is an array element if (IsSerializableArrayElement(property, fieldInfo)) { - return fieldType.IsGenericType - ? fieldType.GetGenericArguments()[0] - : fieldType.GetElementType(); + return GetElementTypeFromArrayType(fieldType); } //return fieldInfo type based on property's target object else @@ -306,6 +304,13 @@ public static Type GetScriptTypeFromProperty(SerializedProperty property) return scriptInstance.GetClass(); } + public static Type GetElementTypeFromArrayType(Type arrayType) + { + return arrayType.IsGenericType + ? arrayType.GetGenericArguments()[0] + : arrayType.GetElementType(); + } + public static FieldInfo GetFieldInfo(this SerializedProperty property) { return GetFieldInfo(property, out _); @@ -349,7 +354,7 @@ public static FieldInfo GetFieldInfoFromProperty(SerializedProperty property, ou { if (IsSerializableArrayType(type)) { - type = type.IsGenericType ? type.GetGenericArguments()[0] : type.GetElementType(); + type = GetElementTypeFromArrayType(type); } continue; @@ -421,8 +426,13 @@ public static void OverrideLabelByValue(GUIContent label, SerializedProperty pro label.text = property.colorValue.ToString(); break; case SerializedPropertyType.ObjectReference: - label.text = property.objectReferenceValue ? property.objectReferenceValue.name : "null"; + label.text = property.objectReferenceValue ? property.objectReferenceValue.ToString() : "null"; break; +#if UNITY_2021_3_OR_NEWER + case SerializedPropertyType.ManagedReference: + label.text = property.managedReferenceValue?.ToString() ?? "null"; + break; +#endif case SerializedPropertyType.LayerMask: switch (property.intValue) { @@ -637,6 +647,56 @@ internal static bool IsSerializableArrayElement(string indexField, out int index return false; } + internal static bool IsSerializeReferenceProperty(SerializedProperty property) + { +#if UNITY_2019_3_OR_NEWER + if (property == null) + { + return false; + } + + if (property.propertyType == SerializedPropertyType.ManagedReference) + { + return true; + } + + //NOTE: seems to bit slighlty better option than checking children (we need to support empty arrays) + // checking FieldInfo + Attribute will be much more expensive + const string managedReferenceType = "managedReference"; + if (property.isArray) + { + var elementType = property.arrayElementType; + return elementType.Contains(managedReferenceType); + } + + return false; +#else + return false; +#endif + } + + internal static bool TryGetSerializeReferenceType(SerializedProperty property, out Type referenceType) + { + var fieldInfo = GetFieldInfo(property, propertyType: out _); + if (fieldInfo == null) + { + referenceType = null; + return false; + } + + var fieldType = fieldInfo.FieldType; + if (property.isArray || IsSerializableArrayElement(property)) + { + referenceType = GetElementTypeFromArrayType(fieldType); + } + else + { + referenceType = fieldType; + } + + return true; + } + internal static bool IsDefaultScriptProperty(SerializedProperty property) { return IsDefaultScriptPropertyByPath(property.propertyPath); diff --git a/Assets/Editor Toolbox/Editor/Utilities/ReflectionUtility.cs b/Assets/Editor Toolbox/Editor/Utilities/ReflectionUtility.cs index bc0ec8fe..4a52c74e 100644 --- a/Assets/Editor Toolbox/Editor/Utilities/ReflectionUtility.cs +++ b/Assets/Editor Toolbox/Editor/Utilities/ReflectionUtility.cs @@ -13,6 +13,34 @@ internal static class ReflectionUtility public const BindingFlags allBindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; + internal static FieldInfo GetField(Type targetType, string fieldName) + { + return GetField(targetType, fieldName, allBindings); + } + + internal static FieldInfo GetField(Type targetType, string fieldName, BindingFlags bindingFlags) + { + bindingFlags |= BindingFlags.DeclaredOnly; + var field = targetType.GetField(fieldName, bindingFlags); + if (field == null) + { + //NOTE: if a method is not found and we searching for a private method we should look into parent classes + Type baseType = targetType.BaseType; + while (baseType != null) + { + field = baseType.GetField(fieldName, bindingFlags); + if (field != null) + { + break; + } + + baseType = baseType.BaseType; + } + } + + return field; + } + /// /// Returns of the searched method within the Editor . /// @@ -21,17 +49,17 @@ internal static MethodInfo GetEditorMethod(string classType, string methodName, return editorAssembly.GetType(classType).GetMethod(methodName, falgs); } - internal static MethodInfo GetObjectMethod(string methodName, SerializedObject serializedObject) + internal static MethodInfo GetMethod(string methodName, SerializedObject serializedObject) { - return GetObjectMethod(methodName, serializedObject.targetObjects); + return GetMethod(methodName, serializedObject.targetObjects); } - internal static MethodInfo GetObjectMethod(string methodName, params object[] targetObjects) + internal static MethodInfo GetMethod(string methodName, params object[] targetObjects) { - return GetObjectMethod(methodName, allBindings, targetObjects); + return GetMethod(methodName, allBindings, targetObjects); } - internal static MethodInfo GetObjectMethod(string methodName, BindingFlags bindingFlags, params object[] targetObjects) + internal static MethodInfo GetMethod(string methodName, BindingFlags bindingFlags, params object[] targetObjects) { if (targetObjects == null || targetObjects.Length == 0) { @@ -39,14 +67,24 @@ internal static MethodInfo GetObjectMethod(string methodName, BindingFlags bindi } var targetType = targetObjects[0].GetType(); - var methodInfo = GetObjectMethod(targetType, methodName, bindingFlags); + return GetMethod(targetType, methodName, bindingFlags); + } + + internal static MethodInfo GetMethod(Type targetType, string methodName) + { + return GetMethod(targetType, methodName, allBindings); + } + + internal static MethodInfo GetMethod(Type targetType, string methodName, BindingFlags bindingFlags) + { + var methodInfo = targetType.GetMethod(methodName, bindingFlags, null, CallingConventions.Any, new Type[0], null); if (methodInfo == null && bindingFlags.HasFlag(BindingFlags.NonPublic)) { //NOTE: if a method is not found and we searching for a private method we should look into parent classes var baseType = targetType.BaseType; while (baseType != null) { - methodInfo = GetObjectMethod(baseType, methodName, bindingFlags); + methodInfo = baseType.GetMethod(methodName, bindingFlags, null, CallingConventions.Any, new Type[0], null); if (methodInfo != null) { break; @@ -59,9 +97,32 @@ internal static MethodInfo GetObjectMethod(string methodName, BindingFlags bindi return methodInfo; } - internal static MethodInfo GetObjectMethod(Type targetType, string methodName, BindingFlags bindingFlags) + internal static PropertyInfo GetProperty(Type targetType, string propertyName) + { + return GetProperty(targetType, propertyName, allBindings); + } + + internal static PropertyInfo GetProperty(Type targetType, string propertyName, BindingFlags bindingFlags) { - return targetType.GetMethod(methodName, bindingFlags, null, CallingConventions.Any, new Type[0], null); + bindingFlags |= BindingFlags.DeclaredOnly; + var property = targetType.GetProperty(propertyName, bindingFlags); + if (property == null) + { + //NOTE: if a method is not found and we searching for a private method we should look into parent classes + Type baseType = targetType.BaseType; + while (baseType != null) + { + property = baseType.GetProperty(propertyName, bindingFlags); + if (property != null) + { + break; + } + + baseType = baseType.BaseType; + } + } + + return property; } /// @@ -70,7 +131,7 @@ internal static MethodInfo GetObjectMethod(Type targetType, string methodName, B internal static bool TryInvokeMethod(string methodName, SerializedObject serializedObject) { var targetObjects = serializedObject.targetObjects; - var method = GetObjectMethod(methodName, targetObjects); + var method = GetMethod(methodName, targetObjects); if (method == null) { return false; 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/README.md b/Assets/Editor Toolbox/README.md index c5d629df..bedb6b66 100644 --- a/Assets/Editor Toolbox/README.md +++ b/Assets/Editor Toolbox/README.md @@ -56,6 +56,11 @@ Unity 2018.x or newer - [Toolbox Custom Editors](#toolboxeditors) - [Material Drawers](#materialdrawers) - [Serialized Types](#serialized-types) + - [SerializedType](#serializedtype) + - [SerializedScene](#serializedscene) + - [SerializedDictionary](#serializeddictionary) + - [SerializedDateTime](#serializeddatetime) + - [SerializedDirectory](#serializeddirectory) - [Editor Extensions](#editor-extensions) - [Hierarchy](#hierarchy) - [Project](#project) @@ -417,7 +422,7 @@ public int var1; ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/horizontal.png) ```csharp -[EditorButton(nameof(MyMethod), "My Custom Label", activityType: ButtonActivityType.OnPlayMode, ValidateMethodName = nameof(ValidationMethod))] +[EditorButton(nameof(MyMethod), "My Custom Label", activityType: ButtonActivityType.OnPlayMode, ValidateMethodName = nameof(ValidationMethod), PositionType = ButtonPositionType.Above)] public int var1; private void MyMethod() @@ -738,11 +743,12 @@ To prevent issues after renaming types use `UnityEngine.Scripting.APIUpdating.Mo ```csharp [SerializeReference, ReferencePicker(TypeGrouping = TypeGrouping.ByFlatName)] public ISampleInterface var1; -[SerializeReference, ReferencePicker(ForceUninitializedInstance = true)] -public ISampleInterface var1; [SerializeReference, ReferencePicker(ParentType = typeof(ClassWithInterface2)] public ClassWithInterfaceBase var2; - +[SerializeReference, ReferencePicker(ForceUninitializedInstance = true)] +public ISampleInterface var3; +``` +```csharp public interface ISampleInterface { } [Serializable] @@ -776,6 +782,14 @@ public class ClassWithInterface3 : ClassWithInterfaceBase ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/referencepicker.png) +##### ReferencePicker properties + +- **ParentType**: Indicates what *System.Type* should be used as 'base' to create a collection of all available inherited types. +- **ForceUninitializedInstance** (false): If *true* - a new reference instance will be created without the standard construction flow and object will be uninitialized (constructor won't be called). +- **TypeGrouping** (TypeGrouping.None): Indicates how the available types are displayed. +- **AddTextSearchField** (true): If *true* - the popup picker will be extended with a text search field. It may be useful for larger type collections. +- **AddConfimartionBox** (false): If *true* - creates an additional confirmation box to make sure that the new assignment is intended. + ##### SerializeReference generics support Unity 2023.x introduced support for serializing generic references. @@ -811,9 +825,9 @@ public class GenericInterfaceImplementation : IGenericInterface ##### SerializeReference context menu operations You can use few custom context menu operations for the **[SerializeReference]** fields: -- **Copy Serialize Reference**: creates a deep copy of the linked reference -- **Paste Serialize Reference**: allows to paste preserved copy to a field -- **Duplicate Serialize Reference**: allows to duplicate the linked reference (works only on collection elements) +- **Copy Serialized References**: creates a deep copy of the linked reference +- **Paste Serialized References**: allows to paste preserved copy to a field +- **Duplicate Serialize Reference Array Element**: allows to duplicate the linked reference (works only on collection elements) ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/serializereferenceoperations.png) @@ -884,7 +898,7 @@ _HideIfExample ("Range", Range(0, 1)) = 0.75 ## Serialized Types -#### SerializedType +#### SerializedType Allows to serialize Types and pick them through a dedicated picker. @@ -900,7 +914,7 @@ public void Usage() ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/serializedtype.png) -#### SerializedScene +#### SerializedScene Allows to serialize SceneAssets and use them in Runtime. @@ -925,7 +939,7 @@ Keep in mind that SerializedScene stores Scene's index, name and path. These pro Unfortunately, you need to handle associated objects reserialization by yourself, otherwise e.g. updated indexes won't be saved. I prepared for you a static event `SceneSerializationUtility.OnCacheRefreshed` that can be used to validate SerializedScenes in your project. You can link SerializedScene in a ScriptableObject and trigger reserialization (`EditorUtility.SetDirty()`) if needed, it's really convinient approach. -#### SerializedDictionary +#### SerializedDictionary Allows to serialize and use Dictionaries. The presented class implements the IDictionary interface, so it can be easily used like the standard version. @@ -951,7 +965,7 @@ public void Usage() ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/dictionary2.png) -#### SerializedDateTime +#### SerializedDateTime Allows to serialize DateTime. @@ -966,16 +980,16 @@ public void Usage() ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/serializeddate.png) -#### SerializedDirectory +#### SerializedDirectory Allows to serialize folders in form of assets and retrieve direct paths in Runtime. ```csharp -public SerializedDirectory serializeDirectory; +public SerializedDirectory serializedDirectory; public void Usage() { - string path = serializeDirectory.DirectoryPath; + string path = serializedDirectory.DirectoryPath; } ``` @@ -1015,6 +1029,9 @@ Properties that can be edited include: ### Toolbar +> [!IMPORTANT] +> Unity 6.3 provides a new [official API](https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Toolbars.MainToolbarElement.html) for extending the main toolbar. Toolbox implementation still relies on the Reflection and overriding predefined VisualElements setup, therefore consider using the new API. + > Editor Toolbox/Editor/ToolboxEditorToolbar.cs Check **Examples** for more details. @@ -1029,10 +1046,11 @@ public static class MyEditorUtility { static MyEditorUtility() { - ToolboxEditorToolbar.OnToolbarGui += OnToolbarGui; + ToolboxEditorToolbar.OnToolbarGuiLeft += OnToolbarGuiLeft; + ToolboxEditorToolbar.OnToolbarGuiRight += OnToolbarGuiRight; } - private static void OnToolbarGui() + private static void OnToolbarGuiLeft() { GUILayout.FlexibleSpace(); if (GUILayout.Button("1", Style.commandLeftStyle)) @@ -1056,6 +1074,14 @@ public static class MyEditorUtility Debug.Log("5"); } } + + private static void OnToolbarGuiRight() + { + if (GUILayout.Button("1")) + { + Debug.Log("1"); + } + } } ``` @@ -1065,6 +1091,8 @@ public static class MyEditorUtility Select a specific object that is under the cursor (default key: tab). +Requires at least Unity 2019.1.x to work. + ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/sceneview.png) ### Utilities diff --git a/Assets/Editor Toolbox/Runtime/Attributes/Property/Regular/ProgressBarAttribute.cs b/Assets/Editor Toolbox/Runtime/Attributes/Property/Regular/ProgressBarAttribute.cs index 0db77f9c..ec69cfc5 100644 --- a/Assets/Editor Toolbox/Runtime/Attributes/Property/Regular/ProgressBarAttribute.cs +++ b/Assets/Editor Toolbox/Runtime/Attributes/Property/Regular/ProgressBarAttribute.cs @@ -26,6 +26,13 @@ public ProgressBarAttribute(string name = "", float minValue = 0, float maxValue public float MaxValue { get; private set; } + /// + /// Indicates value change step if is interactable. + /// + public float ValueStep { get; set; } = 0.001f; + + public bool IsInteractable { get; set; } + public Color Color { get => ColorUtility.TryParseHtmlString(HexColor, out var color) @@ -34,7 +41,5 @@ public Color Color } public string HexColor { get; set; } - - public bool IsInteractable { get; set; } } } \ No newline at end of file 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/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/EditorButtonAttribute.cs b/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/EditorButtonAttribute.cs index 383a6ee2..b4b50328 100644 --- a/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/EditorButtonAttribute.cs +++ b/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/DecoratorAttributes/EditorButtonAttribute.cs @@ -27,7 +27,15 @@ public EditorButtonAttribute(string methodName, string extraLabel = null, Button public string ValidateMethodName { get; set; } public string ExtraLabel { get; private set; } public string Tooltip { get; set; } - public ButtonActivityType ActivityType { get; private set; } + public ButtonActivityType ActivityType { get; set; } + public ButtonPositionType PositionType { get; set; } = ButtonPositionType.Default; + } + + public enum ButtonPositionType + { + Default, + Below, + Above } [Flags] diff --git a/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/PropertySelfAttributes/ReferencePickerAttribute.cs b/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/PropertySelfAttributes/ReferencePickerAttribute.cs index 7aff647f..0a6d2c52 100644 --- a/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/PropertySelfAttributes/ReferencePickerAttribute.cs +++ b/Assets/Editor Toolbox/Runtime/Attributes/Property/Toolbox/PropertySelfAttributes/ReferencePickerAttribute.cs @@ -42,6 +42,11 @@ public ReferencePickerAttribute(Type parentType, TypeGrouping typeGrouping) /// Indicates if created popup menu should have an additional search field. /// public bool AddTextSearchField { get; set; } = true; + + /// + /// Indicates if confirmation box should appear after picking a new reference. + /// + public bool AddConfirmationBox { get; set; } } } #endif \ No newline at end of file diff --git a/Assets/Editor Toolbox/Editor/Folders.meta b/Assets/Editor Toolbox/Runtime/Folders.meta similarity index 77% rename from Assets/Editor Toolbox/Editor/Folders.meta rename to Assets/Editor Toolbox/Runtime/Folders.meta index 81033450..aebcfcd3 100644 --- a/Assets/Editor Toolbox/Editor/Folders.meta +++ b/Assets/Editor Toolbox/Runtime/Folders.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: be549d804ac1aff4689632341de6835f +guid: c422f5efbb0f4d94297a48755fbe306c folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Editor Toolbox/Editor/Folders/FolderData.cs b/Assets/Editor Toolbox/Runtime/Folders/FolderData.cs similarity index 92% rename from Assets/Editor Toolbox/Editor/Folders/FolderData.cs rename to Assets/Editor Toolbox/Runtime/Folders/FolderData.cs index 221e1311..e5325bef 100644 --- a/Assets/Editor Toolbox/Editor/Folders/FolderData.cs +++ b/Assets/Editor Toolbox/Runtime/Folders/FolderData.cs @@ -3,20 +3,8 @@ using UnityEngine; using UnityEngine.Serialization; -namespace Toolbox.Editor.Folders +namespace Toolbox.Folders { - public enum FolderDataType - { - Path, - Name - } - - public enum FolderIconType - { - Custom, - Editor - } - [Serializable] public struct FolderData { diff --git a/Assets/Editor Toolbox/Editor/Folders/FolderData.cs.meta b/Assets/Editor Toolbox/Runtime/Folders/FolderData.cs.meta similarity index 83% rename from Assets/Editor Toolbox/Editor/Folders/FolderData.cs.meta rename to Assets/Editor Toolbox/Runtime/Folders/FolderData.cs.meta index 3bf8b975..67810eff 100644 --- a/Assets/Editor Toolbox/Editor/Folders/FolderData.cs.meta +++ b/Assets/Editor Toolbox/Runtime/Folders/FolderData.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4680b935459476d4d8ebb3425add6b9a +guid: 571656423e7c7024ea709ce9009cfe15 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Editor Toolbox/Runtime/Folders/FolderDataType.cs b/Assets/Editor Toolbox/Runtime/Folders/FolderDataType.cs new file mode 100644 index 00000000..501b092b --- /dev/null +++ b/Assets/Editor Toolbox/Runtime/Folders/FolderDataType.cs @@ -0,0 +1,8 @@ +namespace Toolbox.Folders +{ + public enum FolderDataType + { + Path, + Name + } +} \ No newline at end of file diff --git a/Assets/Editor Toolbox/Runtime/Folders/FolderDataType.cs.meta b/Assets/Editor Toolbox/Runtime/Folders/FolderDataType.cs.meta new file mode 100644 index 00000000..fc82e171 --- /dev/null +++ b/Assets/Editor Toolbox/Runtime/Folders/FolderDataType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d87c1403eec547b4f80d466b4c2dfa5b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor Toolbox/Runtime/Folders/FolderIconType.cs b/Assets/Editor Toolbox/Runtime/Folders/FolderIconType.cs new file mode 100644 index 00000000..b935fcdc --- /dev/null +++ b/Assets/Editor Toolbox/Runtime/Folders/FolderIconType.cs @@ -0,0 +1,8 @@ +namespace Toolbox.Folders +{ + public enum FolderIconType + { + Custom, + Editor + } +} \ No newline at end of file diff --git a/Assets/Editor Toolbox/Runtime/Folders/FolderIconType.cs.meta b/Assets/Editor Toolbox/Runtime/Folders/FolderIconType.cs.meta new file mode 100644 index 00000000..1dc979be --- /dev/null +++ b/Assets/Editor Toolbox/Runtime/Folders/FolderIconType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac05f3425c2d59b43a1161838d0f463a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor Toolbox/Runtime/Serialization/SceneSerializationUtility.cs b/Assets/Editor Toolbox/Runtime/Serialization/SceneSerializationUtility.cs index cd4d9d3d..62e09219 100644 --- a/Assets/Editor Toolbox/Runtime/Serialization/SceneSerializationUtility.cs +++ b/Assets/Editor Toolbox/Runtime/Serialization/SceneSerializationUtility.cs @@ -27,6 +27,30 @@ private static void Initialize() isInitialized = true; } + private static bool TryCreateSceneData(SceneAsset sceneAsset, out SceneData sceneData) + { + var path = AssetDatabase.GetAssetPath(sceneAsset); + if (string.IsNullOrEmpty(path)) + { + sceneData = null; + return false; + } + + sceneData = new SceneData() + { + BuildIndex = SceneUtility.GetBuildIndexByScenePath(path), + SceneName = sceneAsset.name, + ScenePath = path, + }; + + return true; + } + + private static bool CanRefreshCache() + { + return !EditorApplication.isUpdating; + } + internal static void ConfirmCache() { //NOTE: refresh data only if the cache is empty, @@ -39,6 +63,11 @@ internal static void ConfirmCache() internal static void RefreshCache() { + if (!CanRefreshCache()) + { + return; + } + cachedScenes.Clear(); foreach (var scene in EditorBuildSettings.scenes) { @@ -59,12 +88,10 @@ internal static void RefreshCache() continue; } - cachedScenes.Add(sceneAsset, new SceneData() + if (TryCreateSceneData(sceneAsset, out var sceneData)) { - BuildIndex = SceneUtility.GetBuildIndexByScenePath(path), - SceneName = sceneAsset.name, - ScenePath = path - }); + cachedScenes.Add(sceneAsset, sceneData); + } } OnCacheRefreshed?.Invoke(); @@ -72,14 +99,30 @@ internal static void RefreshCache() internal static bool TryGetSceneData(SceneAsset sceneAsset, out SceneData data) { - ConfirmCache(); - if (!sceneAsset || !cachedScenes.TryGetValue(sceneAsset, out data)) + if (sceneAsset == null) { data = null; return false; } - return true; + if (CanRefreshCache()) + { + ConfirmCache(); + if (cachedScenes.TryGetValue(sceneAsset, out data)) + { + return true; + } + } + else + { + if (TryCreateSceneData(sceneAsset, out data)) + { + return true; + } + } + + data = null; + return false; } #endif /// diff --git a/Assets/Editor Toolbox/Runtime/Serialization/SerializedDictionary.cs b/Assets/Editor Toolbox/Runtime/Serialization/SerializedDictionary.cs index 0cb63422..28cd41db 100644 --- a/Assets/Editor Toolbox/Runtime/Serialization/SerializedDictionary.cs +++ b/Assets/Editor Toolbox/Runtime/Serialization/SerializedDictionary.cs @@ -10,7 +10,7 @@ namespace UnityEngine /// Highly suggested to cast it to a when used in runtime. /// [Serializable] - public sealed class SerializedDictionary : IDictionary, ISerializationCallbackReceiver + public sealed class SerializedDictionary : IDictionary, IReadOnlyDictionary, ISerializationCallbackReceiver { [Serializable] private struct KeyValuePair @@ -224,6 +224,10 @@ public TV this[TK key] public bool IsReadOnly => false; + IEnumerable IReadOnlyDictionary.Keys => Keys; + + IEnumerable IReadOnlyDictionary.Values => Values; + public static implicit operator Dictionary(SerializedDictionary serializedDictionary) { return serializedDictionary.dictionary; @@ -235,4 +239,4 @@ public static implicit operator SerializedDictionary(Dictionary } } } -#endif \ No newline at end of file +#endif diff --git a/Assets/Editor Toolbox/Runtime/Serialization/SerializedScene.cs b/Assets/Editor Toolbox/Runtime/Serialization/SerializedScene.cs index 0bda74f8..4cd7a9ee 100644 --- a/Assets/Editor Toolbox/Runtime/Serialization/SerializedScene.cs +++ b/Assets/Editor Toolbox/Runtime/Serialization/SerializedScene.cs @@ -1,5 +1,4 @@ using System; - using Toolbox.Serialization; #if UNITY_EDITOR @@ -33,18 +32,42 @@ void ISerializationCallbackReceiver.OnBeforeSerialize() void ISerializationCallbackReceiver.OnAfterDeserialize() { } + public void UpdateProperties() + { + UpdateProperties(out _); + } - private void UpdateProperties() + public void UpdateProperties(out bool anythingChanged) { + anythingChanged = false; #if UNITY_EDITOR if (SceneSerializationUtility.TryGetSceneData(sceneReference, out var sceneData)) { - SceneName = sceneData.SceneName; - ScenePath = sceneData.ScenePath; - BuildIndex = sceneData.BuildIndex; + if (SceneName != sceneData.SceneName) + { + SceneName = sceneData.SceneName; + anythingChanged = true; + } + + if (ScenePath != sceneData.ScenePath) + { + ScenePath = sceneData.ScenePath; + anythingChanged = true; + } + + if (BuildIndex != sceneData.BuildIndex) + { + BuildIndex = sceneData.BuildIndex; + anythingChanged = true; + } } else { + if (!string.IsNullOrEmpty(sceneName)) + { + anythingChanged = true; + } + SceneName = string.Empty; ScenePath = string.Empty; BuildIndex = -1; diff --git a/Assets/Editor Toolbox/Toolbox.asmdef b/Assets/Editor Toolbox/Runtime/Toolbox.asmdef similarity index 100% rename from Assets/Editor Toolbox/Toolbox.asmdef rename to Assets/Editor Toolbox/Runtime/Toolbox.asmdef diff --git a/Assets/Editor Toolbox/Toolbox.asmdef.meta b/Assets/Editor Toolbox/Runtime/Toolbox.asmdef.meta similarity index 100% rename from Assets/Editor Toolbox/Toolbox.asmdef.meta rename to Assets/Editor Toolbox/Runtime/Toolbox.asmdef.meta diff --git a/Assets/Editor Toolbox/package.json b/Assets/Editor Toolbox/package.json index 4c6cdc76..737b33e0 100644 --- a/Assets/Editor Toolbox/package.json +++ b/Assets/Editor Toolbox/package.json @@ -1,7 +1,7 @@ { "name": "com.browar.editor-toolbox", "displayName": "Editor Toolbox", - "version": "0.13.2", + "version": "0.14.3", "unity": "2018.1", "description": "Tools, custom attributes, drawers, hierarchy overlay, and other extensions for the Unity Editor.", "keywords": [ diff --git a/Assets/Examples/Editor/SampleToolbar.cs b/Assets/Examples/Editor/SampleToolbar.cs index dba275b9..d43e926c 100644 --- a/Assets/Examples/Editor/SampleToolbar.cs +++ b/Assets/Examples/Editor/SampleToolbar.cs @@ -10,7 +10,7 @@ public static class SampleToolbar /// /// This field will be used to exclude toolbar buttons for all scenes except this one. /// - private readonly static string mySampleSceneName = "SampleScene"; + private static readonly string mySampleSceneName = "SampleScene"; static SampleToolbar() { @@ -43,19 +43,18 @@ private static void ValidateFirstScene() /// private static void SceneOpenedCallback(Scene scene, OpenSceneMode mode) { - ToolboxEditorToolbar.OnToolbarGui -= OnToolbarGui; + ToolboxEditorToolbar.OnToolbarGuiLeft -= OnToolbarGuiLeft; + ToolboxEditorToolbar.OnToolbarGuiRight -= OnToolbarGuiRight; if (scene.name != mySampleSceneName) { return; } - ToolboxEditorToolbar.OnToolbarGui += OnToolbarGui; + ToolboxEditorToolbar.OnToolbarGuiLeft += OnToolbarGuiLeft; + ToolboxEditorToolbar.OnToolbarGuiRight += OnToolbarGuiRight; } - /// - /// Layout-based GUI call. - /// - private static void OnToolbarGui() + private static void OnToolbarGuiLeft() { GUILayout.FlexibleSpace(); if (GUILayout.Button("1", Style.commandLeftStyle)) @@ -79,6 +78,29 @@ private static void OnToolbarGui() } } + private static void OnToolbarGuiRight() + { + if (GUILayout.Button("1")) + { + Debug.Log("1"); + } + + if (GUILayout.Button("2")) + { + Debug.Log("2"); + } + + if (GUILayout.Button("3")) + { + Debug.Log("3"); + } + + if (GUILayout.Button("4")) + { + Debug.Log("4"); + } + } + private static class Style { internal static readonly GUIStyle commandMidStyle = new GUIStyle("CommandMid") diff --git a/Assets/Examples/Editor/UI.meta b/Assets/Examples/Editor/UI.meta new file mode 100644 index 00000000..ed8e199e --- /dev/null +++ b/Assets/Examples/Editor/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 885e23d794b7f814da83e77483560126 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Examples/Editor/UI/CustomSelectableEditor.cs b/Assets/Examples/Editor/UI/CustomSelectableEditor.cs new file mode 100644 index 00000000..b3ec85d6 --- /dev/null +++ b/Assets/Examples/Editor/UI/CustomSelectableEditor.cs @@ -0,0 +1,53 @@ +using Toolbox.Editor; +using Toolbox.Editor.Drawers; +using UnityEditor; +using UnityEditor.UI; + +[CustomEditor(typeof(CustomSelectable))] +public class CustomSelectableEditor : SelectableEditor, IToolboxEditor +{ + private static readonly string[] selectableProperties = new string[] + { + "m_Script", + "m_Interactable", + "m_TargetGraphic", + "m_Transition", + "m_Colors", + "m_SpriteState", + "m_AnimationTriggers", + "m_Navigation" + }; + + protected override void OnEnable() + { + base.OnEnable(); + foreach (var property in selectableProperties) + { + IgnoreProperty(property); + } + } + + public sealed override void OnInspectorGUI() + { + base.OnInspectorGUI(); + ToolboxEditorHandler.HandleToolboxEditor(this); + } + + public void DrawCustomInspector() + { + Drawer.DrawEditor(serializedObject); + } + + public void IgnoreProperty(SerializedProperty property) + { + Drawer.IgnoreProperty(property); + } + + public void IgnoreProperty(string propertyPath) + { + Drawer.IgnoreProperty(propertyPath); + } + + Editor IToolboxEditor.ContextEditor => this; + public IToolboxEditorDrawer Drawer { get; } = new ToolboxEditorDrawer(); +} \ No newline at end of file diff --git a/Assets/Examples/Editor/UI/CustomSelectableEditor.cs.meta b/Assets/Examples/Editor/UI/CustomSelectableEditor.cs.meta new file mode 100644 index 00000000..5da85772 --- /dev/null +++ b/Assets/Examples/Editor/UI/CustomSelectableEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: defe90321736145448a9124a4d6afb19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Examples/Materials/TestMat.mat b/Assets/Examples/Materials/TestMat.mat index dc71e8b7..e2060a2f 100644 --- a/Assets/Examples/Materials/TestMat.mat +++ b/Assets/Examples/Materials/TestMat.mat @@ -2,14 +2,15 @@ %TAG !u! tag:unity3d.com,2011: --- !u!21 &2100000 Material: - serializedVersion: 6 + serializedVersion: 8 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: TestMat m_Shader: {fileID: 4800000, guid: 026435d85e5fd1f429a0ff9e8492c6b8, type: 3} - m_ShaderKeywords: + m_ValidKeywords: [] + m_InvalidKeywords: [] m_LightmapFlags: 4 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 @@ -59,6 +60,7 @@ Material: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} + m_Ints: [] m_Floats: - _BumpScale: 1 - _Cutoff: 0.5 diff --git a/Assets/Examples/Scenes/SampleScene.unity b/Assets/Examples/Scenes/SampleScene.unity index afd0d538..e1824939 100644 --- a/Assets/Examples/Scenes/SampleScene.unity +++ b/Assets/Examples/Scenes/SampleScene.unity @@ -1051,9 +1051,9 @@ MonoBehaviour: var1: rid: 7033609514626056197 var2: - rid: 7033609514626056196 + rid: 2222411192291557380 var3: - rid: 7033609514626056198 + rid: 2222411192291557381 var4: rid: 7033609514626056199 vars: @@ -1062,6 +1062,20 @@ MonoBehaviour: references: version: 2 RefIds: + - rid: -2 + type: {class: , ns: , asm: } + - rid: 2222411192291557380 + type: {class: SampleBehaviour6/ClassWithInterface1, ns: , asm: Assembly-CSharp} + data: + go: {fileID: 580334316} + var1: + rid: -2 + - rid: 2222411192291557381 + type: {class: SampleBehaviour6/ClassWithInterface4, ns: , asm: Assembly-CSharp} + data: + var1: 1 + mat: {fileID: 2100000, guid: 7404c70251f9d0045a4aabaa49d83963, type: 2} + var33: 0 - rid: 7033609514626056193 type: {class: SampleBehaviour6/ClassWithInterface1, ns: , asm: Assembly-CSharp} data: @@ -1073,22 +1087,11 @@ MonoBehaviour: data: var1: 0 var2: 0 - - rid: 7033609514626056196 - type: {class: SampleBehaviour6/ClassWithInterface4, ns: , asm: Assembly-CSharp} - data: - var1: 1 - mat: {fileID: 0} - var33: 11 - rid: 7033609514626056197 type: {class: SampleBehaviour6/SampleStruct, ns: , asm: Assembly-CSharp} data: var1: 1 var2: 1 - - rid: 7033609514626056198 - type: {class: SampleBehaviour6/ClassWithInterface2, ns: , asm: Assembly-CSharp} - data: - var1: 0 - mat: {fileID: 0} - rid: 7033609514626056199 type: {class: SampleBehaviour6/ClassWithInterface2, ns: , asm: Assembly-CSharp} data: @@ -1284,7 +1287,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: targetTag: Untagged - progressBar1: 9.159664 + progressBar1: 30 progressBar2: 25.4 minMaxVector: {x: 10, y: 73.893524} minMaxVectorInt: {x: 2, y: 7} @@ -1828,7 +1831,7 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 5 + m_RootOrder: 7 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1220714192 MonoBehaviour: @@ -2207,7 +2210,7 @@ GameObject: - component: {fileID: 1438743619} - component: {fileID: 1438743618} m_Layer: 0 - m_Name: DecoratorDrawers [Sample] + m_Name: ToolboxDecoratorDrawers [Sample] m_TagString: Untagged m_Icon: {fileID: 2800000, guid: b105bf1fb7fe62e4baba0ffd7b433e7b, type: 3} m_NavMeshLayer: 0 @@ -2259,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 @@ -2285,7 +2306,7 @@ GameObject: - component: {fileID: 1583836799} - component: {fileID: 1583836798} m_Layer: 0 - m_Name: Special & Others [Sample] + m_Name: Toolbox Special & Others [Sample] m_TagString: Untagged m_Icon: {fileID: 2800000, guid: b105bf1fb7fe62e4baba0ffd7b433e7b, type: 3} m_NavMeshLayer: 0 @@ -2336,7 +2357,7 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 7 + m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1584091011 GameObject: @@ -2456,7 +2477,7 @@ GameObject: - component: {fileID: 1670253092} - component: {fileID: 1670253093} m_Layer: 0 - m_Name: ConditionDrawers [Sample] + m_Name: ToolboxConditionDrawers [Sample] m_TagString: Untagged m_Icon: {fileID: 2800000, guid: b105bf1fb7fe62e4baba0ffd7b433e7b, type: 3} m_NavMeshLayer: 0 diff --git a/Assets/Examples/Scriptables/Sample Scriptable Object.asset b/Assets/Examples/Scriptables/Sample Scriptable Object.asset index 96cdd21a..1f6d8c05 100644 --- a/Assets/Examples/Scriptables/Sample Scriptable Object.asset +++ b/Assets/Examples/Scriptables/Sample Scriptable Object.asset @@ -14,5 +14,9 @@ MonoBehaviour: m_EditorClassIdentifier: var1: 1 var2: 32 - vars: - - abc + vars1: [] + vars2: 00000000 + var3: + var1: 0 + vars: 0000000000000000 + var2: 0 diff --git a/Assets/Examples/Scripts/SampleBehaviour1.cs b/Assets/Examples/Scripts/SampleBehaviour1.cs index 2f657de1..42383b86 100644 --- a/Assets/Examples/Scripts/SampleBehaviour1.cs +++ b/Assets/Examples/Scripts/SampleBehaviour1.cs @@ -11,7 +11,7 @@ public class SampleBehaviour1 : MonoBehaviour [Label("Progress Bar", skinStyle: SkinStyle.Box)] - [ProgressBar(minValue: -10.0f, maxValue: 50.0f, HexColor = "#234DEA", IsInteractable = true)] + [ProgressBar(minValue: -10.0f, maxValue: 50.0f, HexColor = "#234DEA", IsInteractable = true, ValueStep = 2.5f)] public float progressBar1 = 25.4f; [ProgressBar(minValue: -10.0f, maxValue: 50.0f, HexColor = "#32A852", IsInteractable = false)] public float progressBar2 = 25.4f; diff --git a/Assets/Examples/Scripts/SampleBehaviour4.cs b/Assets/Examples/Scripts/SampleBehaviour4.cs index 4c6224ca..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))] - [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; +} diff --git a/Assets/Examples/Scripts/SampleBehaviour6.cs b/Assets/Examples/Scripts/SampleBehaviour6.cs index 259aa6e7..fcbb747a 100644 --- a/Assets/Examples/Scripts/SampleBehaviour6.cs +++ b/Assets/Examples/Scripts/SampleBehaviour6.cs @@ -12,7 +12,7 @@ public class SampleBehaviour6 : MonoBehaviour public ISampleInterface var1; [SerializeReference, ReferencePicker(ForceUninitializedInstance = true)] public ClassWithInterfaceBase var2; - [SerializeReference, ReferencePicker] + [SerializeReference, ReferencePicker(AddConfirmationBox = true)] public ClassWithInterfaceBase var3; [SerializeReference, ReferencePicker(ParentType = typeof(ClassWithInterface2), AddTextSearchField = false)] public ClassWithInterfaceBase var4; @@ -96,6 +96,7 @@ public interface IGenericInterface TValue Value { get; } } + [Serializable] public class IntInterfaceImplementationInt : IGenericInterface { [SerializeField] @@ -104,6 +105,7 @@ public class IntInterfaceImplementationInt : IGenericInterface public int Value => value; } + [Serializable] public class StringInterfaceImplementation : IGenericInterface { [SerializeField] @@ -112,6 +114,7 @@ public class StringInterfaceImplementation : IGenericInterface public string Value => value; } + [Serializable] public class GenericInterfaceImplementation : IGenericInterface { [SerializeField] @@ -122,6 +125,7 @@ public class GenericInterfaceImplementation : IGenericInterface public TValue Value => value; } + [Serializable] public class WrongConstraintGenericInterfaceImplementation : IGenericInterface where TValue : struct { [SerializeField] diff --git a/Assets/Examples/Scripts/SampleScriptableObject.cs b/Assets/Examples/Scripts/SampleScriptableObject.cs index 218ff2ca..59bdb560 100644 --- a/Assets/Examples/Scripts/SampleScriptableObject.cs +++ b/Assets/Examples/Scripts/SampleScriptableObject.cs @@ -1,12 +1,24 @@ -using UnityEngine; - +using System; using Toolbox.Attributes; +using UnityEngine; [CreateInWizard] public class SampleScriptableObject : ScriptableObject { + [Serializable] + public class NestedClass + { + public bool var1; + [ReorderableList] + public int[] vars; + public bool var2; + } + public bool var1; [EnableIf(nameof(var1), true)] public int var2; - public string[] vars; + public string[] vars1; + [ReorderableList] + public int[] vars2; + public NestedClass var3; } \ No newline at end of file diff --git a/Assets/Examples/Scripts/UI.meta b/Assets/Examples/Scripts/UI.meta new file mode 100644 index 00000000..66376f2f --- /dev/null +++ b/Assets/Examples/Scripts/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4886eeed85fe8be4790ea10fde578a2d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Examples/Scripts/UI/CustomSelectable.cs b/Assets/Examples/Scripts/UI/CustomSelectable.cs new file mode 100644 index 00000000..b6d4fcd2 --- /dev/null +++ b/Assets/Examples/Scripts/UI/CustomSelectable.cs @@ -0,0 +1,13 @@ +using UnityEngine; +using UnityEngine.UI; + +public class CustomSelectable : Selectable +{ + private interface ICustomLogic + { } + +#if UNITY_2019_3_OR_NEWER + [SerializeReference] + private ICustomLogic customLogic; +#endif +} \ No newline at end of file diff --git a/Assets/Examples/Scripts/UI/CustomSelectable.cs.meta b/Assets/Examples/Scripts/UI/CustomSelectable.cs.meta new file mode 100644 index 00000000..604e73dd --- /dev/null +++ b/Assets/Examples/Scripts/UI/CustomSelectable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b8cd6b8c1b344f4382ef956b38c1c4e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Docs/scenedetails.png b/Docs/scenedetails.png index 9e6cec7d..17ed7308 100644 Binary files a/Docs/scenedetails.png and b/Docs/scenedetails.png differ diff --git a/Docs/serializereferenceoperations.png b/Docs/serializereferenceoperations.png index b23663f8..57986264 100644 Binary files a/Docs/serializereferenceoperations.png and b/Docs/serializereferenceoperations.png differ diff --git a/Packages/manifest.json b/Packages/manifest.json index 9e4d1c2d..794f1ad1 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -7,7 +7,6 @@ "com.unity.editorcoroutines": "1.0.0", "com.unity.ide.rider": "3.0.18", "com.unity.ide.visualstudio": "2.0.17", - "com.unity.ide.vscode": "1.2.5", "com.unity.test-framework": "1.1.31", "com.unity.textmeshpro": "3.0.6", "com.unity.timeline": "1.6.4", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 4ae6aadb..12bc5cc1 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -60,13 +60,6 @@ }, "url": "https://packages.unity.com" }, - "com.unity.ide.vscode": { - "version": "1.2.5", - "depth": 0, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, "com.unity.sysroot": { "version": "1.0.0", "depth": 1, @@ -108,9 +101,9 @@ "depth": 0, "source": "registry", "dependencies": { + "com.unity.modules.audio": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.animation": "1.0.0", - "com.unity.modules.audio": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" diff --git a/ProjectSettings/TimelineSettings.asset b/ProjectSettings/TimelineSettings.asset new file mode 100644 index 00000000..cfaebd7a --- /dev/null +++ b/ProjectSettings/TimelineSettings.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a287be6c49135cd4f9b2b8666c39d999, type: 3} + m_Name: + m_EditorClassIdentifier: + assetDefaultFramerate: 60 + m_DefaultFrameRate: 60 diff --git a/README.md b/README.md index c5d629df..bedb6b66 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,11 @@ Unity 2018.x or newer - [Toolbox Custom Editors](#toolboxeditors) - [Material Drawers](#materialdrawers) - [Serialized Types](#serialized-types) + - [SerializedType](#serializedtype) + - [SerializedScene](#serializedscene) + - [SerializedDictionary](#serializeddictionary) + - [SerializedDateTime](#serializeddatetime) + - [SerializedDirectory](#serializeddirectory) - [Editor Extensions](#editor-extensions) - [Hierarchy](#hierarchy) - [Project](#project) @@ -417,7 +422,7 @@ public int var1; ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/horizontal.png) ```csharp -[EditorButton(nameof(MyMethod), "My Custom Label", activityType: ButtonActivityType.OnPlayMode, ValidateMethodName = nameof(ValidationMethod))] +[EditorButton(nameof(MyMethod), "My Custom Label", activityType: ButtonActivityType.OnPlayMode, ValidateMethodName = nameof(ValidationMethod), PositionType = ButtonPositionType.Above)] public int var1; private void MyMethod() @@ -738,11 +743,12 @@ To prevent issues after renaming types use `UnityEngine.Scripting.APIUpdating.Mo ```csharp [SerializeReference, ReferencePicker(TypeGrouping = TypeGrouping.ByFlatName)] public ISampleInterface var1; -[SerializeReference, ReferencePicker(ForceUninitializedInstance = true)] -public ISampleInterface var1; [SerializeReference, ReferencePicker(ParentType = typeof(ClassWithInterface2)] public ClassWithInterfaceBase var2; - +[SerializeReference, ReferencePicker(ForceUninitializedInstance = true)] +public ISampleInterface var3; +``` +```csharp public interface ISampleInterface { } [Serializable] @@ -776,6 +782,14 @@ public class ClassWithInterface3 : ClassWithInterfaceBase ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/referencepicker.png) +##### ReferencePicker properties + +- **ParentType**: Indicates what *System.Type* should be used as 'base' to create a collection of all available inherited types. +- **ForceUninitializedInstance** (false): If *true* - a new reference instance will be created without the standard construction flow and object will be uninitialized (constructor won't be called). +- **TypeGrouping** (TypeGrouping.None): Indicates how the available types are displayed. +- **AddTextSearchField** (true): If *true* - the popup picker will be extended with a text search field. It may be useful for larger type collections. +- **AddConfimartionBox** (false): If *true* - creates an additional confirmation box to make sure that the new assignment is intended. + ##### SerializeReference generics support Unity 2023.x introduced support for serializing generic references. @@ -811,9 +825,9 @@ public class GenericInterfaceImplementation : IGenericInterface ##### SerializeReference context menu operations You can use few custom context menu operations for the **[SerializeReference]** fields: -- **Copy Serialize Reference**: creates a deep copy of the linked reference -- **Paste Serialize Reference**: allows to paste preserved copy to a field -- **Duplicate Serialize Reference**: allows to duplicate the linked reference (works only on collection elements) +- **Copy Serialized References**: creates a deep copy of the linked reference +- **Paste Serialized References**: allows to paste preserved copy to a field +- **Duplicate Serialize Reference Array Element**: allows to duplicate the linked reference (works only on collection elements) ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/serializereferenceoperations.png) @@ -884,7 +898,7 @@ _HideIfExample ("Range", Range(0, 1)) = 0.75 ## Serialized Types -#### SerializedType +#### SerializedType Allows to serialize Types and pick them through a dedicated picker. @@ -900,7 +914,7 @@ public void Usage() ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/serializedtype.png) -#### SerializedScene +#### SerializedScene Allows to serialize SceneAssets and use them in Runtime. @@ -925,7 +939,7 @@ Keep in mind that SerializedScene stores Scene's index, name and path. These pro Unfortunately, you need to handle associated objects reserialization by yourself, otherwise e.g. updated indexes won't be saved. I prepared for you a static event `SceneSerializationUtility.OnCacheRefreshed` that can be used to validate SerializedScenes in your project. You can link SerializedScene in a ScriptableObject and trigger reserialization (`EditorUtility.SetDirty()`) if needed, it's really convinient approach. -#### SerializedDictionary +#### SerializedDictionary Allows to serialize and use Dictionaries. The presented class implements the IDictionary interface, so it can be easily used like the standard version. @@ -951,7 +965,7 @@ public void Usage() ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/dictionary2.png) -#### SerializedDateTime +#### SerializedDateTime Allows to serialize DateTime. @@ -966,16 +980,16 @@ public void Usage() ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/serializeddate.png) -#### SerializedDirectory +#### SerializedDirectory Allows to serialize folders in form of assets and retrieve direct paths in Runtime. ```csharp -public SerializedDirectory serializeDirectory; +public SerializedDirectory serializedDirectory; public void Usage() { - string path = serializeDirectory.DirectoryPath; + string path = serializedDirectory.DirectoryPath; } ``` @@ -1015,6 +1029,9 @@ Properties that can be edited include: ### Toolbar +> [!IMPORTANT] +> Unity 6.3 provides a new [official API](https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Toolbars.MainToolbarElement.html) for extending the main toolbar. Toolbox implementation still relies on the Reflection and overriding predefined VisualElements setup, therefore consider using the new API. + > Editor Toolbox/Editor/ToolboxEditorToolbar.cs Check **Examples** for more details. @@ -1029,10 +1046,11 @@ public static class MyEditorUtility { static MyEditorUtility() { - ToolboxEditorToolbar.OnToolbarGui += OnToolbarGui; + ToolboxEditorToolbar.OnToolbarGuiLeft += OnToolbarGuiLeft; + ToolboxEditorToolbar.OnToolbarGuiRight += OnToolbarGuiRight; } - private static void OnToolbarGui() + private static void OnToolbarGuiLeft() { GUILayout.FlexibleSpace(); if (GUILayout.Button("1", Style.commandLeftStyle)) @@ -1056,6 +1074,14 @@ public static class MyEditorUtility Debug.Log("5"); } } + + private static void OnToolbarGuiRight() + { + if (GUILayout.Button("1")) + { + Debug.Log("1"); + } + } } ``` @@ -1065,6 +1091,8 @@ public static class MyEditorUtility Select a specific object that is under the cursor (default key: tab). +Requires at least Unity 2019.1.x to work. + ![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/sceneview.png) ### Utilities