From 71b432e25373e1a993b458da772e7ac4ea37e0b5 Mon Sep 17 00:00:00 2001 From: Sebastian Bularca Date: Sun, 29 Mar 2026 18:59:24 +0200 Subject: [PATCH] added more utilty packges --- .../GameState/Entities/ModifiersFactory.cs | 6 +- Assets/Code/GameState/Entities/PerkFactory.cs | 12 +- Assets/Settings/Resources.meta | 8 + .../Settings/Resources/logger-settings.asset | 23 + .../Resources/logger-settings.asset.meta | 8 + Packages/com.jovian.inspector/Editor.meta | 8 + .../Editor/Attributes.meta | 8 + .../Attributes/AnimatorParameterAttribute.cs | 105 ++++ .../AnimatorParameterAttribute.cs.meta | 11 + .../Editor/Attributes/ArrayAttribute.cs | 107 ++++ .../Editor/Attributes/ArrayAttribute.cs.meta | 11 + .../Attributes/AssetDropdownAttribute.cs | 88 +++ .../Attributes/AssetDropdownAttribute.cs.meta | 11 + .../Editor/Attributes/AttributeBase.cs | 47 ++ .../Editor/Attributes/AttributeBase.cs.meta | 3 + .../Editor/Attributes/AttributeBaseDrawer.cs | 62 ++ .../Attributes/AttributeBaseDrawer.cs.meta | 3 + .../Editor/Attributes/AutoFindAttribute.cs | 102 +++ .../Attributes/AutoFindAttribute.cs.meta | 11 + .../Editor/Attributes/BitmaskAttribute.cs | 47 ++ .../Attributes/BitmaskAttribute.cs.meta | 11 + .../Attributes/ButtonMethodAttribute.cs | 198 ++++++ .../Attributes/ButtonMethodAttribute.cs.meta | 11 + .../Attributes/CharactersRangeAttribute.cs | 135 ++++ .../CharactersRangeAttribute.cs.meta | 11 + .../Attributes/CompactDrawerAttribute.cs | 79 +++ .../Attributes/CompactDrawerAttribute.cs.meta | 11 + .../Attributes/CompactVector4Attribute.cs | 70 +++ .../CompactVector4Attribute.cs.meta | 11 + .../Attributes/ConditionalFieldAttribute.cs | 322 ++++++++++ .../ConditionalFieldAttribute.cs.meta | 11 + .../Attributes/ConstantsSelectionAttribute.cs | 127 ++++ .../ConstantsSelectionAttribute.cs.meta | 3 + .../Attributes/DefinedValuesAttribute.cs | 85 +++ .../Attributes/DefinedValuesAttribute.cs.meta | 11 + .../Attributes/DisplayInspectorAttribute.cs | 153 +++++ .../DisplayInspectorAttribute.cs.meta | 11 + .../Editor/Attributes/FoldoutAttribute.cs | 329 ++++++++++ .../Attributes/FoldoutAttribute.cs.meta | 11 + .../Editor/Attributes/InfoAttribute.cs | 60 ++ .../Editor/Attributes/InfoAttribute.cs.meta | 11 + .../InitializationFieldAttribute.cs | 34 + .../InitializationFieldAttribute.cs.meta | 3 + .../Attributes/InterfaceComponentAttribute.cs | 93 +++ .../InterfaceComponentAttribute.cs.meta | 11 + .../Editor/Attributes/LayerAttribute.cs | 40 ++ .../Editor/Attributes/LayerAttribute.cs.meta | 11 + .../Editor/Attributes/LinkAttribute.cs | 47 ++ .../Editor/Attributes/LinkAttribute.cs.meta | 11 + .../Editor/Attributes/MaxValueAttribute.cs | 201 ++++++ .../Attributes/MaxValueAttribute.cs.meta | 3 + .../Editor/Attributes/MinMaxRangeAttribute.cs | 133 ++++ .../Attributes/MinMaxRangeAttribute.cs.meta | 11 + .../Editor/Attributes/MinValueAttribute.cs | 202 ++++++ .../Attributes/MinValueAttribute.cs.meta | 3 + .../Attributes/ObjectsPropertyAttribute.cs | 99 +++ .../ObjectsPropertyAttribute.cs.meta | 11 + .../Attributes/OnValueChangedAttribute.cs | 38 ++ .../OnValueChangedAttribute.cs.meta | 11 + .../Editor/Attributes/OptionalAttribute.cs | 54 ++ .../Attributes/OptionalAttribute.cs.meta | 11 + .../Attributes/OverrideLabelAttribute.cs | 28 + .../Attributes/OverrideLabelAttribute.cs.meta | 11 + .../Attributes/PositiveValueOnlyAttribute.cs | 161 +++++ .../PositiveValueOnlyAttribute.cs.meta | 11 + .../Editor/Attributes/ReadOnlyAttribute.cs | 48 ++ .../Attributes/ReadOnlyAttribute.cs.meta | 11 + .../Editor/Attributes/RegexStringAttribute.cs | 121 ++++ .../Attributes/RegexStringAttribute.cs.meta | 3 + .../Attributes/RequireArraySizeAttribute.cs | 86 +++ .../RequireArraySizeAttribute.cs.meta | 3 + .../Attributes/RequireLayerAttribute.cs | 21 + .../Attributes/RequireLayerAttribute.cs.meta | 3 + .../RequireLayerOrTagAttributeHandler.cs | 54 ++ .../RequireLayerOrTagAttributeHandler.cs.meta | 11 + .../Attributes/RequireMethodAttribute.cs | 95 +++ .../Attributes/RequireMethodAttribute.cs.meta | 11 + .../Editor/Attributes/RequireTagAttribute.cs | 15 + .../Attributes/RequireTagAttribute.cs.meta | 3 + .../Editor/Attributes/RequiredAttribute.cs | 280 +++++++++ .../Attributes/RequiredAttribute.cs.meta | 11 + .../Editor/Attributes/SceneAttribute.cs | 77 +++ .../Editor/Attributes/SceneAttribute.cs.meta | 11 + .../Attributes/SearchableEnumAttribute.cs | 496 +++++++++++++++ .../SearchableEnumAttribute.cs.meta | 11 + .../Editor/Attributes/SeparatorAttribute.cs | 58 ++ .../Attributes/SeparatorAttribute.cs.meta | 11 + .../Attributes/ShowInInspectorAttribute.cs | 374 +++++++++++ .../ShowInInspectorAttribute.cs.meta | 11 + .../Editor/Attributes/SpriteLayerAttribute.cs | 113 ++++ .../Attributes/SpriteLayerAttribute.cs.meta | 11 + .../Editor/Attributes/SuffixAttribute.cs | 34 + .../Editor/Attributes/SuffixAttribute.cs.meta | 11 + .../Editor/Attributes/TagAttribute.cs | 48 ++ .../Editor/Attributes/TagAttribute.cs.meta | 11 + .../Attributes/UniformVector3Attribute.cs | 132 ++++ .../UniformVector3Attribute.cs.meta | 11 + .../Editor/Extensions.meta | 8 + .../Editor/Extensions/EditorExtensions.meta | 8 + .../Extensions/EditorExtensions/MyGUI.cs | 580 ++++++++++++++++++ .../Extensions/EditorExtensions/MyGUI.cs.meta | 11 + .../EditorExtensions/MySerializedProperty.cs | 254 ++++++++ .../MySerializedProperty.cs.meta | 3 + .../Editor/Extensions/MyCollections.cs | 64 ++ .../Editor/Extensions/MyCollections.cs.meta | 11 + .../Editor/Extensions/MyRegex.cs | 24 + .../Editor/Extensions/MyRegex.cs.meta | 3 + .../Editor/Jovian.InspectorTools.asmdef | 19 + .../Editor/Jovian.InspectorTools.asmdef.meta | 7 + .../com.jovian.inspector/Editor/Tools.meta | 8 + .../Editor/Tools/AutoFindUtil.cs | 141 +++++ .../Editor/Tools/AutoFindUtil.cs.meta | 11 + .../Editor/Tools/IActionableAttribute.cs | 11 + .../Editor/Tools/IActionableAttribute.cs.meta | 11 + .../Editor/Tools/IPropertyCondition.cs | 11 + .../Editor/Tools/IPropertyCondition.cs.meta | 11 + .../Editor/Tools/Internal.meta | 8 + .../Tools/Internal/PlayModeVisibility.cs | 24 + .../Tools/Internal/PlayModeVisibility.cs.meta | 11 + .../Tools/Internal/UnityObjectEditor.cs | 149 +++++ .../Tools/Internal/UnityObjectEditor.cs.meta | 3 + .../Editor/Tools/RequiredUtil.cs | 44 ++ .../Editor/Tools/RequiredUtil.cs.meta | 11 + Packages/com.jovian.inspector/package.json | 7 + .../com.jovian.inspector/package.json.meta | 7 + .../Editor/EditorSerializationUtility.cs | 428 +++++++++++++ .../Editor/EditorSerializationUtility.cs.meta | 2 + .../com.jovian.utilties/Runtime/BowserLog.cs | 42 -- .../Runtime/BowserLog.cs.meta | 2 - Packages/com.jovian.utilties/package.json | 2 +- Packages/packages-lock.json | 6 + 131 files changed, 7674 insertions(+), 54 deletions(-) create mode 100644 Assets/Settings/Resources.meta create mode 100644 Assets/Settings/Resources/logger-settings.asset create mode 100644 Assets/Settings/Resources/logger-settings.asset.meta create mode 100644 Packages/com.jovian.inspector/Editor.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AnimatorParameterAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AnimatorParameterAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ArrayAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ArrayAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AssetDropdownAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AssetDropdownAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AttributeBase.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AttributeBase.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AttributeBaseDrawer.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AttributeBaseDrawer.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AutoFindAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/AutoFindAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/BitmaskAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/BitmaskAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ButtonMethodAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ButtonMethodAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/CharactersRangeAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/CharactersRangeAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/CompactDrawerAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/CompactDrawerAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/CompactVector4Attribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/CompactVector4Attribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ConditionalFieldAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ConditionalFieldAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ConstantsSelectionAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ConstantsSelectionAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/DefinedValuesAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/DefinedValuesAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/DisplayInspectorAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/DisplayInspectorAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/InfoAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/InfoAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/InitializationFieldAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/InitializationFieldAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/InterfaceComponentAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/InterfaceComponentAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/LayerAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/LayerAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/LinkAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/LinkAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/MaxValueAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/MaxValueAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/MinMaxRangeAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/MinMaxRangeAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/MinValueAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/MinValueAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ObjectsPropertyAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ObjectsPropertyAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/OnValueChangedAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/OnValueChangedAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/OptionalAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/OptionalAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/OverrideLabelAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/OverrideLabelAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/PositiveValueOnlyAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/PositiveValueOnlyAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ReadOnlyAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ReadOnlyAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RegexStringAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RegexStringAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireArraySizeAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireArraySizeAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireLayerAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireLayerAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireLayerOrTagAttributeHandler.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireLayerOrTagAttributeHandler.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireMethodAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireMethodAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireTagAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequireTagAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequiredAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/RequiredAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SceneAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SceneAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SearchableEnumAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SearchableEnumAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SeparatorAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SeparatorAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ShowInInspectorAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/ShowInInspectorAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SpriteLayerAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SpriteLayerAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SuffixAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/SuffixAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/TagAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/TagAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/UniformVector3Attribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Attributes/UniformVector3Attribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Extensions.meta create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions.meta create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MyGUI.cs create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MyGUI.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MySerializedProperty.cs create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MySerializedProperty.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/MyCollections.cs create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/MyCollections.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/MyRegex.cs create mode 100644 Packages/com.jovian.inspector/Editor/Extensions/MyRegex.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Jovian.InspectorTools.asmdef create mode 100644 Packages/com.jovian.inspector/Editor/Jovian.InspectorTools.asmdef.meta create mode 100644 Packages/com.jovian.inspector/Editor/Tools.meta create mode 100644 Packages/com.jovian.inspector/Editor/Tools/AutoFindUtil.cs create mode 100644 Packages/com.jovian.inspector/Editor/Tools/AutoFindUtil.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Tools/IActionableAttribute.cs create mode 100644 Packages/com.jovian.inspector/Editor/Tools/IActionableAttribute.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Tools/IPropertyCondition.cs create mode 100644 Packages/com.jovian.inspector/Editor/Tools/IPropertyCondition.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Tools/Internal.meta create mode 100644 Packages/com.jovian.inspector/Editor/Tools/Internal/PlayModeVisibility.cs create mode 100644 Packages/com.jovian.inspector/Editor/Tools/Internal/PlayModeVisibility.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Tools/Internal/UnityObjectEditor.cs create mode 100644 Packages/com.jovian.inspector/Editor/Tools/Internal/UnityObjectEditor.cs.meta create mode 100644 Packages/com.jovian.inspector/Editor/Tools/RequiredUtil.cs create mode 100644 Packages/com.jovian.inspector/Editor/Tools/RequiredUtil.cs.meta create mode 100644 Packages/com.jovian.inspector/package.json create mode 100644 Packages/com.jovian.inspector/package.json.meta create mode 100644 Packages/com.jovian.utilties/Editor/EditorSerializationUtility.cs create mode 100644 Packages/com.jovian.utilties/Editor/EditorSerializationUtility.cs.meta delete mode 100644 Packages/com.jovian.utilties/Runtime/BowserLog.cs delete mode 100644 Packages/com.jovian.utilties/Runtime/BowserLog.cs.meta diff --git a/Assets/Code/GameState/Entities/ModifiersFactory.cs b/Assets/Code/GameState/Entities/ModifiersFactory.cs index 9df7f50..375f882 100644 --- a/Assets/Code/GameState/Entities/ModifiersFactory.cs +++ b/Assets/Code/GameState/Entities/ModifiersFactory.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using Jovian.Utilities; namespace Nox.Game { public interface IModfiersFactory { IReadOnlyCollection GetAll(); - ModifierDefinition GetById(ModifierIds modifierId); + ModifierDefinition GetById(Guid modifierId); IReadOnlyCollection GetModifiersFor(CharacterDefinition character); bool TryAddModifier(CharacterDefinition character, string modiferId); } @@ -20,7 +21,6 @@ namespace Nox.Game { [Serializable] public sealed class ModifierDefinition { - [ReadOnlyField] public System.Guid id = Guid.NewGuid(); public StatType statType; public AttributeType attributeType; @@ -44,7 +44,7 @@ namespace Nox.Game { public IReadOnlyCollection GetAll() { return modifiersRegistry.modifiersData.modifiers; } - public ModifierDefinition GetById(ModifierIds modifierId) { + public ModifierDefinition GetById(Guid modifierId) { return modifiersRegistry.modifiersData.modifiers.FirstOrDefault(m => m.id == modifierId); } public IReadOnlyCollection GetModifiersFor(CharacterDefinition character) { diff --git a/Assets/Code/GameState/Entities/PerkFactory.cs b/Assets/Code/GameState/Entities/PerkFactory.cs index b77e5bb..69a3d1f 100644 --- a/Assets/Code/GameState/Entities/PerkFactory.cs +++ b/Assets/Code/GameState/Entities/PerkFactory.cs @@ -6,14 +6,14 @@ namespace Nox.Game { public interface IPerkFactory { IReadOnlyCollection GetAll(); - PerkDefinition GetById(PerksIds perkId); + PerkDefinition GetById(Guid perkId); IReadOnlyCollection GetPerksFor(CharacterDefinition character); - bool TryAddPerk(CharacterDefinition character, PerksIds perkId); + bool TryAddPerk(CharacterDefinition character, Guid perkId); } [Serializable] public sealed class PerkDefinition { - public PerksIds id; + public Guid id; public string name; public ModifiersData modifiers = new (); } @@ -24,7 +24,7 @@ namespace Nox.Game { } public sealed class PerkFactory : IPerkFactory { - private readonly Dictionary perkPool = new (); + private readonly Dictionary perkPool = new (); public PerkFactory(PerksRegistry perksRegistry) { if(!perksRegistry) { @@ -40,7 +40,7 @@ namespace Nox.Game { return perkPool.Values.ToList(); } - public PerkDefinition GetById(PerksIds perkId) { + public PerkDefinition GetById(Guid perkId) { perkPool.TryGetValue(perkId, out var perk); return perk; } @@ -57,7 +57,7 @@ namespace Nox.Game { return perkPool.Values.Where(p => !ownedPerkIds.Contains(p.id)).ToList(); } - public bool TryAddPerk(CharacterDefinition character, PerksIds perkId) { + public bool TryAddPerk(CharacterDefinition character, Guid perkId) { if(character == null) { return false; } diff --git a/Assets/Settings/Resources.meta b/Assets/Settings/Resources.meta new file mode 100644 index 0000000..2be8811 --- /dev/null +++ b/Assets/Settings/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 40aa0aa493d1bb745a531cdb360e7e62 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Resources/logger-settings.asset b/Assets/Settings/Resources/logger-settings.asset new file mode 100644 index 0000000..f91d4cd --- /dev/null +++ b/Assets/Settings/Resources/logger-settings.asset @@ -0,0 +1,23 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + 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: 67fe3f48aa2b3b349a7b99381bebb12d, type: 3} + m_Name: logger-settings + m_EditorClassIdentifier: Jovian.Logger::Jovian.Logger.LoggerSettings + enableGlobalLogging: 1 + globalFilters: [] + loggerColors: + infoColor: {r: 1, g: 1, b: 1, a: 1} + warningColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} + errorColor: {r: 1, g: 0, b: 0, a: 1} + assertColor: {r: 1, g: 0.3, b: 0.2, a: 1} + exceptionColor: {r: 1, g: 0, b: 0.7, a: 1} + spamColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} diff --git a/Assets/Settings/Resources/logger-settings.asset.meta b/Assets/Settings/Resources/logger-settings.asset.meta new file mode 100644 index 0000000..3a444e1 --- /dev/null +++ b/Assets/Settings/Resources/logger-settings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 97634890f80d79941abb209746593eef +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor.meta b/Packages/com.jovian.inspector/Editor.meta new file mode 100644 index 0000000..f739b69 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 400491600b7856d4dbb01f428e9dfbfb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes.meta b/Packages/com.jovian.inspector/Editor/Attributes.meta new file mode 100644 index 0000000..e283cdc --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8dc5c6745ff8d4443955c7fd0c3dfb0d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AnimatorParameterAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/AnimatorParameterAttribute.cs new file mode 100644 index 0000000..2f60be0 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AnimatorParameterAttribute.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit { + public class AnimatorParameterAttribute : PropertyAttribute { + public readonly string animatorPropertyName; + public bool allParameterTypes; + public AnimatorControllerParameterType parameterType; + + public AnimatorParameterAttribute(string animatorPropertyName) { + this.animatorPropertyName = animatorPropertyName; + allParameterTypes = true; + } + + public AnimatorParameterAttribute(string animatorPropertyName, AnimatorControllerParameterType parameterType) { + this.animatorPropertyName = animatorPropertyName; + allParameterTypes = false; + this.parameterType = parameterType; + } + } +} +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + [CustomPropertyDrawer(typeof(AnimatorParameterAttribute))] + public class AnimatorParameterAttributeDrawer : PropertyDrawer { + private bool hasAnimator; + private Animator animator; + + private string[] parameterNames; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + label = EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, label); + + if(parameterNames == null) { + var animatorParameterAttribute = (AnimatorParameterAttribute)attribute; + string animatorFieldName = animatorParameterAttribute.animatorPropertyName; + parameterNames = UpdateParameterNames(property, animatorFieldName, animatorParameterAttribute.allParameterTypes, animatorParameterAttribute.parameterType); + } + + EditorGUI.BeginChangeCheck(); + + bool didChange = false; + if(parameterNames is { Length: > 0 }) { + int parameterIndex = Array.IndexOf(parameterNames, property.stringValue); + int newIndex = EditorGUI.Popup(position, parameterIndex, parameterNames); + if(parameterIndex != newIndex) { + property.stringValue = parameterNames[newIndex]; + didChange = true; + } + } + else { + EditorGUI.PropertyField(position, property, GUIContent.none); + } + + int indentLevel = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + if(EditorGUI.EndChangeCheck() || didChange) { + property.serializedObject.ApplyModifiedProperties(); + } + + EditorGUI.indentLevel = indentLevel; + EditorGUI.EndProperty(); + } + + private string[] UpdateParameterNames(SerializedProperty property, string animatorFieldName, bool allParameterTypesAllowed, AnimatorControllerParameterType parameterType) { + int pathIndexOfPropertyName = property.propertyPath.LastIndexOf(property.name, StringComparison.Ordinal); + bool isTopLevelObject = pathIndexOfPropertyName == 0; + SerializedProperty animatorProperty; + if(isTopLevelObject) { + animatorProperty = property.serializedObject.FindProperty(animatorFieldName); + } + else { + SerializedProperty parentProperty = property.serializedObject.FindProperty(property.propertyPath.Substring(0, pathIndexOfPropertyName - 1)); + animatorProperty = parentProperty.FindPropertyRelative(animatorFieldName); + } + + if(animatorProperty is { objectReferenceValue: Animator targetAnimator }) { + if(!targetAnimator.gameObject.activeSelf || + !targetAnimator.gameObject.activeInHierarchy || + targetAnimator.runtimeAnimatorController == null) { + return new string[] { }; // return an empty array so it uses the fallback of just text + } + + AnimatorControllerParameter[] parameters = targetAnimator.parameters; + + if(allParameterTypesAllowed) { + return parameters.Select(p => p.name).ToArray(); + } + else { + return parameters.Where(p => p.type == parameterType).Select(p => p.name).ToArray(); + } + } + return new[] { "" }; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AnimatorParameterAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/AnimatorParameterAttribute.cs.meta new file mode 100644 index 0000000..fec7dd8 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AnimatorParameterAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c43d05448be2cce45a0c4a41227752aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ArrayAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/ArrayAttribute.cs new file mode 100644 index 0000000..f15cc54 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ArrayAttribute.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +#if UNITY_EDITOR +using System.Reflection; +using UnityEditor; +#endif + +namespace InspectorToolkit { + + [AttributeUsage(AttributeTargets.Field)] + public abstract class ArrayAttribute : Attribute { + #if UNITY_EDITOR + public virtual void OnPreGUI(ref Rect position, SerializedProperty property, ref GUIContent label) { + } + public virtual void OnPostGUI(ref Rect position, SerializedProperty property, ref GUIContent label) { + } + #endif + } + +#if UNITY_EDITOR + public static class ArrayAttributePropertyHandler { + public static bool DrawArrayProperty(SerializedProperty property) { + return DrawArrayProperty(property, + new GUIContent(ObjectNames.NicifyVariableName(property.name), property.tooltip), + true + ); + } + + public static bool DrawArrayProperty(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options) { + Rect rect = EditorGUILayout.GetControlRect(LabelHasContent(label), EditorGUI.GetPropertyHeight(property, label, includeChildren), options); + + bool hasArrayAttributes = TryGetAttributes(property, true, out List arrayAttributes); + + bool isGuiEnabled = GUI.enabled; + Color contentColor = GUI.contentColor; + Color backgroundColor = GUI.backgroundColor; + Color guiColor = GUI.color; + + Rect preRect = new Rect(rect); + Rect postRect = new Rect(rect); + + if (hasArrayAttributes) { + foreach (ArrayAttribute arrayAttribute in arrayAttributes) { + arrayAttribute.OnPreGUI(ref preRect, property, ref label); + } + } + + bool hasChildPropertiesNotShown = EditorGUI.PropertyField(preRect, property, label, includeChildren); + + if (hasArrayAttributes) { + foreach (ArrayAttribute arrayAttribute in arrayAttributes) { + arrayAttribute.OnPostGUI(ref postRect, property, ref label); + } + } + + GUI.enabled = isGuiEnabled; + GUI.contentColor = contentColor; + GUI.backgroundColor = backgroundColor; + GUI.color = guiColor; + + return hasChildPropertiesNotShown; + } + + private static bool LabelHasContent(GUIContent label) + { + return label == null || label.text != string.Empty || (UnityEngine.Object) label.image != (UnityEngine.Object) null; + } + + public static bool TryGetAttributes(SerializedProperty serializedProperty, bool inherit, out List attributes) + where TAttribute : Attribute { + if(serializedProperty == null) { + throw new ArgumentNullException(nameof(serializedProperty)); + } + + Type targetObjectType = serializedProperty.serializedObject.targetObject.GetType(); + + if(targetObjectType == null) { + throw new ArgumentException($"Could not find the {nameof(targetObjectType)} of {nameof(serializedProperty)}"); + } + + attributes = new List(); + + foreach(string pathSegment in serializedProperty.propertyPath.Split('.')) { + FieldInfo fieldInfo = targetObjectType.GetRuntimeField(pathSegment); + if(fieldInfo != null) { + IEnumerable fieldAttributes = fieldInfo.GetCustomAttributes(inherit); + foreach (TAttribute fieldAttribute in fieldAttributes) { + attributes.Add(fieldAttribute); + } + } + + PropertyInfo propertyInfo = targetObjectType.GetRuntimeProperty(pathSegment); + if(propertyInfo != null) { + IEnumerable fieldAttributes = propertyInfo.GetCustomAttributes(inherit); + foreach (TAttribute fieldAttribute in fieldAttributes) { + attributes.Add(fieldAttribute); + } + } + } + + return attributes.Count > 0; + } + } + #endif +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ArrayAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/ArrayAttribute.cs.meta new file mode 100644 index 0000000..344265c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ArrayAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08ca9e3facae5974ca19daee6aa978ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AssetDropdownAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/AssetDropdownAttribute.cs new file mode 100644 index 0000000..1bed383 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AssetDropdownAttribute.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Jovian.Utilities; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace InspectorToolkit +{ + /// + /// Finds all objects in the project matching filter string and lists alphabetically in a dropdown (if they are assetType) + /// + public class AssetDropdownAttribute : PropertyAttribute + { + public readonly Type assetType; + public readonly string filter; + + public AssetDropdownAttribute(Type assetType, string filter) + { + this.assetType = assetType; + this.filter = filter; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + + [CustomPropertyDrawer(typeof(AssetDropdownAttribute))] + public class AssetDropdownAttributeDrawer : PropertyDrawer + { + private bool isInitialized; + private bool hasValidTarget; + private List objects; + private string[] values; + + private int selectedIndex = -1; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (property.propertyType != SerializedPropertyType.ObjectReference) + { + Debug.LogError("Required property to be an ObjectReference."); + EditorGUI.PropertyField(position, property, label); + return; + } + + if (!isInitialized) + { + Initialize(); + } + + EditorGUI.BeginChangeCheck(); + selectedIndex = property.objectReferenceValue != null ? Array.IndexOf(values, property.objectReferenceValue.name) : -1; + selectedIndex = EditorGUI.Popup(position, label.text, selectedIndex, values); + + if (Event.current.type == EventType.MouseDown && Event.current.modifiers == EventModifiers.Shift && + GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) + { + isInitialized = false; + } + + if (EditorGUI.EndChangeCheck()) + { + property.objectReferenceValue = objects[selectedIndex]; + property.serializedObject.ApplyModifiedProperties(); + } + } + + private void Initialize() + { + AssetDropdownAttribute assetDropdownAttribute = (AssetDropdownAttribute)attribute; + + objects = AssetUtility.FindAllObjectsInProject(assetDropdownAttribute.assetType, assetDropdownAttribute.filter); + objects.Sort(SortByName); + values = objects.Select(obj => obj.name).ToArray(); + + isInitialized = true; + } + + private static int SortByName(Object a, Object b) { + return string.Compare(a.name, b.name, StringComparison.Ordinal); + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AssetDropdownAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/AssetDropdownAttribute.cs.meta new file mode 100644 index 0000000..790a73c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AssetDropdownAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3b5e8f6d90c14e4ca8c2af794bcf4f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AttributeBase.cs b/Packages/com.jovian.inspector/Editor/Attributes/AttributeBase.cs new file mode 100644 index 0000000..91968e7 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AttributeBase.cs @@ -0,0 +1,47 @@ +using System; +using UnityEngine; + +namespace InspectorToolkit.Internal +{ + [AttributeUsage(AttributeTargets.Field)] + public abstract class AttributeBase : PropertyAttribute + { +#if UNITY_EDITOR + /// + /// Validation is called before all other methods. + /// Once in OnGUI and once in GetPropertyHeight + /// + public virtual void ValidateProperty(UnityEditor.SerializedProperty property) + { + } + + + public virtual void OnBeforeGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label) + { + } + + /// + /// Called once per AttributeBase group. + /// I.e. if something with higher order is drawn, later will be skipped + /// + /// false if nothing is drawn + public virtual bool OnGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label) + { + return false; + } + + public virtual void OnAfterGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label) + { + } + + /// + /// Overriding occurs just like OnGUI. Once per group, attribute with higher priority first + /// + /// Null if not overrided + public virtual float? OverrideHeight() + { + return null; + } +#endif + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AttributeBase.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/AttributeBase.cs.meta new file mode 100644 index 0000000..4537d9f --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AttributeBase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 816fc9d7c08c47f8b84318dce8cecf88 +timeCreated: 1582017264 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AttributeBaseDrawer.cs b/Packages/com.jovian.inspector/Editor/Attributes/AttributeBaseDrawer.cs new file mode 100644 index 0000000..01b1ef7 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AttributeBaseDrawer.cs @@ -0,0 +1,62 @@ +#if UNITY_EDITOR +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace InspectorToolkit.Internal +{ + [CustomPropertyDrawer(typeof(AttributeBase), true)] + public class AttributeBaseDrawer : PropertyDrawer + { + private AttributeBase[] _cachedAttributes; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + CacheAttributes(); + + for (var i = _cachedAttributes.Length - 1; i >= 0; i--) + { + var overriden = _cachedAttributes[i].OverrideHeight(); + if (overriden != null) return overriden.Value; + } + + return base.GetPropertyHeight(property, label); + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + CacheAttributes(); + + bool drawn = false; + for (var i = _cachedAttributes.Length - 1; i >= 0; i--) + { + var ab = _cachedAttributes[i]; + ab.ValidateProperty(property); + + ab.OnBeforeGUI(position, property, label); + + // Draw the things with higher priority first. If drawn once - skip drawing + if (!drawn) + { + if (ab.OnGUI(position, property, label)) drawn = true; + } + + ab.OnAfterGUI(position, property, label); + } + + if (!drawn) EditorGUI.PropertyField(position, property, label); + } + + private void CacheAttributes() + { + if (_cachedAttributes.IsNullOrEmpty()) + { + _cachedAttributes = fieldInfo + .GetCustomAttributes(typeof(AttributeBase), false) + .OrderBy(s => ((PropertyAttribute) s).order) + .Select(a => a as AttributeBase).ToArray(); + } + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AttributeBaseDrawer.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/AttributeBaseDrawer.cs.meta new file mode 100644 index 0000000..bd6913b --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AttributeBaseDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ce6a191490f7425a984c36a53cebd36d +timeCreated: 1582017102 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AutoFindAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/AutoFindAttribute.cs new file mode 100644 index 0000000..d79869f --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AutoFindAttribute.cs @@ -0,0 +1,102 @@ +using Jovian.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; +#if UNITY_EDITOR +#endif + +namespace InspectorToolkit { + public enum AutoFindScope { + Self, + SelfAndChildren, + SelfAndParent, + Scene, + Project + } + + public class AutoFindAttribute : PropertyAttribute, IActionableAttribute { + public readonly AutoFindScope scope; + public readonly string filter; + public readonly bool isPrefab; + + public AutoFindAttribute() : this(string.Empty) { } + + public AutoFindAttribute(string filter) : this(AutoFindScope.SelfAndChildren, filter) { } + + public AutoFindAttribute(AutoFindScope scope) : this(scope, string.Empty) { } + + public AutoFindAttribute(AutoFindScope scope, bool isPrefab) : this(scope, isPrefab, string.Empty) { } + + public AutoFindAttribute(AutoFindScope scope, string filter) : this(scope, false, filter) { } + + public AutoFindAttribute(AutoFindScope scope, bool isPrefab, string filter) { + this.scope = scope; + this.isPrefab = isPrefab; + this.filter = filter; + } + +#if UNITY_EDITOR + public void Trigger(SerializedProperty property) { + Search(property, false); + } + + public void Search(SerializedProperty property, bool autoAddIfMissing) { + AutoFindUtil.Search(this, property, autoAddIfMissing); + } +#endif + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + [CustomPropertyDrawer(typeof(AutoFindAttribute))] + public class AutoFindAttributeDrawer : PropertyDrawer { + private GUIContent searchButtonContent; + private GUIStyle searchButtonStyle; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + if (searchButtonStyle == null || searchButtonContent == null) { + searchButtonStyle = new GUIStyle(GUI.skin.button); + searchButtonStyle.padding = new RectOffset(1, 1, 1, 1); + searchButtonStyle.imagePosition = ImagePosition.ImageOnly; + searchButtonContent = + EditorGUIUtility.IconContent(EditorGUIUtility.isProSkin ? "d_Search Icon" : "Search Icon"); + } + + label = EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, label); + + EditorGUI.BeginChangeCheck(); + + float height = Mathf.Min(20f, position.height) - 2f; + + var buttonRect = new Rect(position); + buttonRect.width = 18f; + buttonRect.height = height; + buttonRect.y += 1f; + buttonRect.x -= buttonRect.width + 2f; + + if (GUI.Button(buttonRect, searchButtonContent, searchButtonStyle)) { + if (attribute is AutoFindAttribute autoFindAttribute) { + autoFindAttribute.Search(property, (Event.current.modifiers & EventModifiers.Shift) != 0); + } + } + + EditorGUI.PropertyField(position, property, GUIContent.none); + + int indentLevel = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + if (EditorGUI.EndChangeCheck()) { + property.serializedObject.ApplyModifiedProperties(); + } + + EditorGUI.indentLevel = indentLevel; + EditorGUI.EndProperty(); + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/AutoFindAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/AutoFindAttribute.cs.meta new file mode 100644 index 0000000..75553b8 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/AutoFindAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 51175a247ebc7d24eb3034828da2e103 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/BitmaskAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/BitmaskAttribute.cs new file mode 100644 index 0000000..8cea482 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/BitmaskAttribute.cs @@ -0,0 +1,47 @@ +using UnityEditor; +using UnityEngine; + +namespace InspectorToolkit +{ + public class BitmaskAttribute : PropertyAttribute { + public string[] groupNames; + public BitmaskAttribute(params string[] groupNames) { + this.groupNames = groupNames; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + + [CustomPropertyDrawer(typeof(BitmaskAttribute))] + public class BitmaskAttributeDrawer : PropertyDrawer { + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if(property.propertyType != SerializedPropertyType.Integer) { + GUI.Label(position, "Error, SerializedProperty must be 'Integer' type"); + return; + } + + if(attribute is not BitmaskAttribute bitmaskAttribute) { + return; + } + + string[] groups = bitmaskAttribute.groupNames; + if(groups.Length == 0 || groups.Length > 8) { + GUI.Label(position, "Error, bitmask group count not valid"); + return; + } + + EditorGUI.BeginProperty(position, label, property); + property.intValue = EditorGUI.MaskField(position, label, property.intValue, groups); + int indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + EditorGUI.indentLevel = indent; + EditorGUI.EndProperty(); + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/BitmaskAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/BitmaskAttribute.cs.meta new file mode 100644 index 0000000..5e57d50 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/BitmaskAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d458f9ebb11d49449bbb0acefc84ea2e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ButtonMethodAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/ButtonMethodAttribute.cs new file mode 100644 index 0000000..43d8a1a --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ButtonMethodAttribute.cs @@ -0,0 +1,198 @@ +// ---------------------------------------------------------------------------- +// Author: Kaynn, Yeo Wen Qin +// https://github.com/Kaynn-Cahya +// Date: 26/02/2019 +// ---------------------------------------------------------------------------- + +using System; +using Jovian.Utilities; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace InspectorToolkit { + [AttributeUsage(AttributeTargets.Method)] + public class ButtonMethodAttribute : PropertyAttribute { + public readonly ButtonMethodDrawOrder DrawOrder; + public readonly PlayModeVisibility Visibility; + + public ButtonMethodAttribute(PlayModeVisibility visibility, ButtonMethodDrawOrder drawOrder = ButtonMethodDrawOrder.AfterInspector) { + Visibility = visibility; + DrawOrder = drawOrder; + } + + public ButtonMethodAttribute(ButtonMethodDrawOrder drawOrder = ButtonMethodDrawOrder.AfterInspector) { + DrawOrder = drawOrder; + Visibility = PlayModeVisibility.Always; + } + } + + public enum ButtonMethodDrawOrder { + BeforeInspector, + AfterInspector + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + + public class ButtonMethodHandler { + + public class MethodData { + public MethodInfo methodInfo; + public ParameterData[] parameterDatas; + public string niceName; + public PlayModeVisibility visibility; + public ButtonMethodDrawOrder order; + } + + public class ParameterData { + public ParameterInfo parameterInfo; + public string niceName; + public object value; + } + + // TODO - replace with MethodDatas; + public readonly List<(MethodInfo Method, string Name, PlayModeVisibility visibility, ButtonMethodDrawOrder order)> TargetMethods; + public int Amount => TargetMethods?.Count ?? 0; + + public readonly List MethodDatas; + + public bool HasMethods => MethodDatas?.Count > 0; + + private readonly Object _target; + + public Object Target => _target; + + public ButtonMethodHandler(Object target) { + _target = target; + + Type type = target.GetType(); + BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; + IEnumerable members = type.GetMembers(bindings).Where(IsButtonMethod); + + foreach(MemberInfo member in members) { + MethodInfo method = member as MethodInfo; + if(method == null) continue; + + if(IsValidMember(method, member)) { + ButtonMethodAttribute attribute = (ButtonMethodAttribute)Attribute.GetCustomAttribute(method, typeof(ButtonMethodAttribute)); + TargetMethods ??= new List<(MethodInfo, string, PlayModeVisibility, ButtonMethodDrawOrder)>(); + TargetMethods.Add((method, ObjectNames.NicifyVariableName(method.Name), attribute.Visibility, attribute.DrawOrder)); + MethodDatas ??= new(); + + ParameterInfo[] parameterInfos = method.GetParameters(); + ParameterData[] parameterDatas = new ParameterData[parameterInfos.Length]; + for(int i = 0; i < parameterInfos.Length; i++) { + ParameterInfo info = parameterInfos[i]; + + object value; + if(info.HasDefaultValue) { + value = info.DefaultValue; + } + else if(info.ParameterType == typeof(string)) { + value = "String"; + } + else if(info.ParameterType == typeof(Object) || info.ParameterType.IsSubclassOf(typeof(Object))) { + value = null; + } + else { + value = Activator.CreateInstance(info.ParameterType); + } + + parameterDatas[i] = new ParameterData() { + niceName = ObjectNames.NicifyVariableName(info.Name), + parameterInfo = info, + value = value + }; + } + + MethodData methodData = new MethodData() { + methodInfo = method, + niceName = ObjectNames.NicifyVariableName(method.Name), + visibility = attribute.Visibility, + order = attribute.DrawOrder, + parameterDatas = parameterDatas + }; + MethodDatas.Add(methodData); + } + } + } + + public bool HasAnyVisibleMethods() { + if(MethodDatas == null || MethodDatas.Count == 0) return false; + foreach (MethodData methodData in MethodDatas) { + if (methodData.visibility.IsVisible()) { + return true; + } + } + return false; + } + + public void OnBeforeInspectorGUI() { + if(MethodDatas == null || MethodDatas.Count == 0) return; + + DrawButtonMethods(ButtonMethodDrawOrder.BeforeInspector); + } + + private void DrawButtonMethods(ButtonMethodDrawOrder drawOrder) { + foreach(MethodData method in MethodDatas) { + if(method.order != drawOrder) continue; + if(!method.visibility.IsVisible()) continue; + + if(method.parameterDatas.Length > 0) { + GUILayout.BeginVertical(EditorStyles.helpBox); + } + + if(GUILayout.Button(method.niceName)) { + object result = method.methodInfo.Invoke(_target, method.parameterDatas.Select(p=>p.value).ToArray()); + if(result != null) { + Debug.Log($"{method.niceName} => {result}"); + } + } + + foreach (ParameterData p in method.parameterDatas) { + p.value = InspectorGUIUtility.DrawField(p.niceName, p.parameterInfo.ParameterType, p.value); + } + + if(method.parameterDatas.Length > 0) { + GUILayout.EndVertical(); + } + } + } + + public void OnAfterInspectorGUI() { + if(MethodDatas == null || MethodDatas.Count == 0) return; + DrawButtonMethods(ButtonMethodDrawOrder.AfterInspector); + } + + public void Invoke(MethodInfo method) => InvokeMethod(_target, method); + + + private void InvokeMethod(Object target, MethodInfo method) { + object result = method.Invoke(target, null); + + if(result != null) { + string message = $"{result} \nResult of Method '{method.Name}' invocation on object {target.name}"; + Debug.Log(message, target); + } + } + + private bool IsButtonMethod(MemberInfo memberInfo) { + return Attribute.IsDefined(memberInfo, typeof(ButtonMethodAttribute)); + } + + private bool IsValidMember(MethodInfo method, MemberInfo member) { + if(method == null) { + Debug.LogWarning( + $"Property {member.Name}.Reason: Member is not a method but has EditorButtonAttribute!"); + return false; + } + return true; + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ButtonMethodAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/ButtonMethodAttribute.cs.meta new file mode 100644 index 0000000..d4daf49 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ButtonMethodAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e96e2e6d8d1626c4abf4c413bc489af0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/CharactersRangeAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/CharactersRangeAttribute.cs new file mode 100644 index 0000000..723ce32 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/CharactersRangeAttribute.cs @@ -0,0 +1,135 @@ +using UnityEngine; + +namespace InspectorToolkit +{ + /// + /// Validate a string field to only allow or disallow a set of pre-defined + /// characters on typing. + /// + public class CharactersRangeAttribute : PropertyAttribute + { + public readonly string Characters; + public readonly CharacterRangeMode Mode; + public readonly bool IgnoreCase; + + public CharactersRangeAttribute(string characters, CharacterRangeMode mode = CharacterRangeMode.Allow, + bool ignoreCase = true) + { + Characters = characters; + Mode = mode; + IgnoreCase = ignoreCase; + } + } + + public enum CharacterRangeMode + { + /// + /// Only characters in range will be allowed + /// + Allow, + /// + /// Characters in range will be removed from the string + /// + Disallow, + /// + /// Highlight the field if any of the specified characters were fould + /// + WarningIfAny, + /// + /// Highlight the field if some characters in string are not the characters from specified range + /// + WarningIfNotMatch + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + using EditorTools; + using System.Linq; + + [CustomPropertyDrawer(typeof(CharactersRangeAttribute))] + public class CharacterRangeAttributeDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (property.propertyType != SerializedPropertyType.String) + { + MyGUI.DrawColouredRect(position, MyGUI.Colors.Red); + EditorGUI.LabelField(position, new GUIContent("", "[CharacterRangeAttribute] used with non-string property")); + } + else + { + var charactersRange = (CharactersRangeAttribute)attribute; + var mode = charactersRange.Mode; + var ignoreCase = charactersRange.IgnoreCase; + var filter = charactersRange.Characters; + var allow = mode == CharacterRangeMode.Allow || mode == CharacterRangeMode.WarningIfNotMatch; + var warning = mode == CharacterRangeMode.WarningIfAny || mode == CharacterRangeMode.WarningIfNotMatch; + + if (ignoreCase) filter = filter.ToUpper(); + var filteredCharacters = property.stringValue.Distinct() + .Where(c => + { + if (ignoreCase) c = char.ToUpper(c); + return filter.Contains(c) ^ allow; + }); + bool ifMatch = mode == CharacterRangeMode.WarningIfAny; + bool ifNotMatch = mode == CharacterRangeMode.WarningIfNotMatch; + bool anyFiltered = filteredCharacters.Any(); + bool warn = (ifMatch && anyFiltered || ifNotMatch && anyFiltered); + var originalPosition = position; + + DrawWarning(); + position.width -= 20; + + property.stringValue = EditorGUI.TextField(position, label, property.stringValue); + DrawTooltip(); + + if (!warning) + { + property.stringValue = filteredCharacters.Aggregate( + property.stringValue, + (p, c) => p.Replace(c.ToString(), "")); + } + + if (warn) + { + GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.objectField); + position = originalPosition; + position.y += EditorGUIUtility.singleLineHeight; + DrawWarning(); + position.x += EditorGUIUtility.labelWidth; + var warningContent = new GUIContent("Containing disallowed characters!"); + EditorGUI.LabelField(position, warningContent, EditorStyles.miniBoldLabel); + } + + property.serializedObject.ApplyModifiedProperties(); + + + void DrawWarning() + { + if (!warning) return; + if (property.stringValue.Length == 0) warn = false; + MyGUI.DrawColouredRect(position, warn ? MyGUI.Colors.Yellow : Color.clear); + } + + void DrawTooltip() + { + string tooltip = "Characters range "; + if (mode == CharacterRangeMode.Allow || mode == CharacterRangeMode.WarningIfNotMatch) tooltip += "is allowed:"; + else tooltip += "not allowed:"; + tooltip += $"\n[{filter}]"; + + position.x += position.width + 2; + position.width = 18; + var tooltipContent = new GUIContent(MyGUI.EditorIcons.Help); + tooltipContent.tooltip = tooltip; + EditorGUI.LabelField(position, tooltipContent, EditorStyles.label); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/CharactersRangeAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/CharactersRangeAttribute.cs.meta new file mode 100644 index 0000000..0c0a6bb --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/CharactersRangeAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: add63a4947a64f2428030472426ecba4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/CompactDrawerAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/CompactDrawerAttribute.cs new file mode 100644 index 0000000..792de9d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/CompactDrawerAttribute.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +#if UNITY_EDITOR +using System.Text; +using UnityEditor; +#endif + +namespace InspectorToolkit { + public class CompactDrawerAttribute : PropertyAttribute { } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + [CustomPropertyDrawer(typeof(CompactDrawerAttribute))] + public class CompactDrawerAttributeDrawer : PropertyDrawer { + private static readonly List cacheProperties = new(); + private static readonly StringBuilder stringBuilder = new(); + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + return EditorGUIUtility.singleLineHeight; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + cacheProperties.Clear(); + stringBuilder.Clear(); + + EditorGUI.BeginProperty(position, label, property); + + stringBuilder.Append(" ("); + IEnumerator enumerator = property.GetEnumerator(); + while (enumerator.MoveNext()) { + SerializedProperty childProperty = (SerializedProperty)enumerator.Current; + cacheProperties.Add(childProperty!.Copy()); // Copy is very important since .Current changes its content while enumerating + stringBuilder.Append(ObjectNames.NicifyVariableName(childProperty!.name)); + stringBuilder.Append(", "); + } + + if (cacheProperties.Count > 0) { + stringBuilder.Remove(stringBuilder.Length - 2, 2); + stringBuilder.Append(")"); + } + else { + stringBuilder.Clear(); + } + + GUIContent newLabel = new($"{label}{stringBuilder}", label.tooltip); + + Rect labelRect = new(position) { height = EditorGUIUtility.singleLineHeight }; + Rect fieldRect = EditorGUI.PrefixLabel(labelRect, newLabel); + fieldRect.height = EditorGUIUtility.singleLineHeight; + + int childCount = cacheProperties.Count; + int childIndex = 0; + float spacing = EditorGUIUtility.standardVerticalSpacing; + float fullWidth = fieldRect.width / childCount; + float widthMinusSpacing = fullWidth - spacing * (childCount - 1) / childCount; + + int indentLevel = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + for (int i = 0, c = cacheProperties.Count; i < c; i++) { + SerializedProperty childProperty = cacheProperties[i]; + Rect childFieldRect = new(fieldRect) { + x = Mathf.CeilToInt(fieldRect.x + fullWidth * childIndex), + width = widthMinusSpacing + }; + EditorGUI.PropertyField(childFieldRect, childProperty, GUIContent.none); + childIndex++; + } + + EditorGUI.indentLevel = indentLevel; + + EditorGUI.EndProperty(); + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/CompactDrawerAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/CompactDrawerAttribute.cs.meta new file mode 100644 index 0000000..d2b97bb --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/CompactDrawerAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d56876fd24830a4c99f6963c8f15435 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/CompactVector4Attribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/CompactVector4Attribute.cs new file mode 100644 index 0000000..afe0f6c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/CompactVector4Attribute.cs @@ -0,0 +1,70 @@ +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit { + public class CompactVector4Attribute : PropertyAttribute { + public CompactVector4Attribute() { + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + [CustomPropertyDrawer(typeof(CompactVector4Attribute))] + public class CompactVector4AttributeDrawer : PropertyDrawer { + + private const int CHILD_COUNT = 4; + private const float LABEL_WIDTH = 13f; + private const float INDENT_WIDTH = 15f; + private const float CHILD_HORIZONTAL_SPACING = 4f; + private static readonly string[] childPropertyNames = new string[] { "x", "y", "z", "w" }; + private bool IsCompactLayout => Screen.width <= 333f; + private bool IsNoLabelLayout => Screen.width <= 240f; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + if(IsCompactLayout) { // Unity Vector2 + Vector3 go multi-line at this point + return EditorGUIUtility.singleLineHeight * 2f + EditorGUIUtility.standardVerticalSpacing; + } + return EditorGUIUtility.singleLineHeight; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + if(property.propertyType != SerializedPropertyType.Vector4) { + GUI.Label(position, "Error, SerializedProperty must be 'Vector4' type"); + return; + } + + EditorGUI.BeginProperty(position, label, property); + + if(IsCompactLayout) { + EditorGUIUtility.labelWidth = position.width; + } + Rect labelRect = new Rect(position) { height = EditorGUIUtility.singleLineHeight }; + Rect fieldRect = EditorGUI.PrefixLabel(labelRect, label); + fieldRect.height = EditorGUIUtility.singleLineHeight; + + if(IsCompactLayout) { + fieldRect.y = labelRect.yMax + EditorGUIUtility.standardVerticalSpacing; + fieldRect.x = labelRect.x + (IsNoLabelLayout ? 0f : INDENT_WIDTH); // 15 = single indent + fieldRect.width = position.xMax - fieldRect.xMin; + } + + EditorGUIUtility.labelWidth = IsNoLabelLayout ? 0.01f : LABEL_WIDTH; + float childSpacing = IsNoLabelLayout ? CHILD_HORIZONTAL_SPACING / 2f : CHILD_HORIZONTAL_SPACING; + float childWidth = ((fieldRect.width + childSpacing) / CHILD_COUNT) - childSpacing; + + for(int i = 0; i < CHILD_COUNT; i++) { + SerializedProperty childProperty = property.FindPropertyRelative(childPropertyNames[i]); + Rect childRect = new Rect(fieldRect); + childRect.width = childWidth; // add spacing + childRect.x += i * (childWidth + childSpacing); + EditorGUI.PropertyField(childRect, childProperty); + } + + EditorGUI.EndProperty(); + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/CompactVector4Attribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/CompactVector4Attribute.cs.meta new file mode 100644 index 0000000..152b360 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/CompactVector4Attribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc29630bf159f4742a4ea87bb9eaccdc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ConditionalFieldAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/ConditionalFieldAttribute.cs new file mode 100644 index 0000000..8d39a99 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ConditionalFieldAttribute.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace InspectorToolkit { + /// + /// Conditionally Show/Hide field in inspector, based on some other field value + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] + public class ConditionalFieldAttribute : PropertyAttribute { + public readonly string FieldToCheck; + public readonly string[] CompareValues; + public readonly bool Inverse; + + /// String name of field to check value + /// Inverse check result + /// On which values field will be shown in inspector + public ConditionalFieldAttribute(string fieldToCheck, bool inverse = false, params object[] compareValues) { + FieldToCheck = fieldToCheck; + Inverse = inverse; + CompareValues = compareValues.Select(c => c.ToString().ToUpper()).ToArray(); + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + using EditorTools; + using UnityEditor; + + [CustomPropertyDrawer(typeof(ConditionalFieldAttribute))] + public class ConditionalFieldAttributeDrawer : PropertyDrawer { + private bool _toShow = true; + + + /// + /// Key is Associated with drawer type (the T in [CustomPropertyDrawer(typeof(T))]) + /// Value is PropertyDrawer Type + /// + private static Dictionary _allPropertyDrawersInDomain; + + + private bool _initialized; + private PropertyDrawer _customAttributeDrawer; + private PropertyDrawer _customTypeDrawer; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + if(!(attribute is ConditionalFieldAttribute conditional)) return 0; + + Initialize(property); + + var propertyToCheck = ConditionalFieldUtility.FindRelativeProperty(property, conditional.FieldToCheck); + _toShow = ConditionalFieldUtility.PropertyIsVisible(propertyToCheck, conditional.Inverse, conditional.CompareValues); + if(!_toShow) return 0; + + if(_customAttributeDrawer != null) return _customAttributeDrawer.GetPropertyHeight(property, label); + if(_customTypeDrawer != null) return _customTypeDrawer.GetPropertyHeight(property, label); + + return EditorGUI.GetPropertyHeight(property); + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + if(!_toShow) return; + + if(_customAttributeDrawer != null) TryUseAttributeDrawer(); + else if(_customTypeDrawer != null) TryUseTypeDrawer(); + else EditorGUI.PropertyField(position, property, label, true); + + + void TryUseAttributeDrawer() { + try { + _customAttributeDrawer.OnGUI(position, property, label); + } + catch(Exception e) { + EditorGUI.PropertyField(position, property, label); + LogWarning("Unable to use Custom Attribute Drawer " + _customAttributeDrawer.GetType() + " : " + e, property); + } + } + + void TryUseTypeDrawer() { + try { + _customTypeDrawer.OnGUI(position, property, label); + } + catch(Exception e) { + EditorGUI.PropertyField(position, property, label); + LogWarning("Unable to instantiate " + fieldInfo.FieldType + " : " + e, property); + } + } + } + + + private void Initialize(SerializedProperty property) { + if(_initialized) return; + + CacheAllDrawersInDomain(); + + TryGetCustomAttributeDrawer(); + TryGetCustomTypeDrawer(); + + _initialized = true; + + + void CacheAllDrawersInDomain() { + if(!_allPropertyDrawersInDomain.IsNullOrEmpty()) return; + + _allPropertyDrawersInDomain = new Dictionary(); + var propertyDrawerType = typeof(PropertyDrawer); + + var allDrawerTypesInDomain = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(t => propertyDrawerType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + + foreach(var type in allDrawerTypesInDomain) { + var drawerAttribute = CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(); + if(drawerAttribute == null) continue; + var associatedType = drawerAttribute.ConstructorArguments.FirstOrDefault().Value as Type; + if(associatedType == null) continue; + + if(_allPropertyDrawersInDomain.ContainsKey(associatedType)) continue; + _allPropertyDrawersInDomain.Add(associatedType, type); + } + } + + void TryGetCustomAttributeDrawer() { + if(fieldInfo == null) return; + //Get the second attribute flag + var secondAttribute = (PropertyAttribute)fieldInfo.GetCustomAttributes(typeof(PropertyAttribute), false) + .FirstOrDefault(a => !(a is ConditionalFieldAttribute)); + if(secondAttribute == null) return; + var genericAttributeType = secondAttribute.GetType(); + + //Get the associated attribute drawer + if(!_allPropertyDrawersInDomain.ContainsKey(genericAttributeType)) return; + + var customAttributeDrawerType = _allPropertyDrawersInDomain[genericAttributeType]; + var customAttributeData = fieldInfo.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType == secondAttribute.GetType()); + if(customAttributeData == null) return; + + + //Create drawer for custom attribute + try { + _customAttributeDrawer = (PropertyDrawer)Activator.CreateInstance(customAttributeDrawerType); + var attributeField = customAttributeDrawerType.GetField("m_Attribute", BindingFlags.Instance | BindingFlags.NonPublic); + if(attributeField != null) attributeField.SetValue(_customAttributeDrawer, secondAttribute); + } + catch(Exception e) { + LogWarning("Unable to construct drawer for " + secondAttribute.GetType() + " : " + e, property); + } + } + + void TryGetCustomTypeDrawer() { + if(fieldInfo == null) return; + // Skip checks for mscorlib.dll + if(fieldInfo.FieldType.Module.ScopeName.Equals(typeof(int).Module.ScopeName)) return; + + + // Of all property drawers in the assembly we need to find one that affects target type + // or one of the base types of target type + Type fieldDrawerType = null; + Type fieldType = fieldInfo.FieldType; + while(fieldType != null) { + if(_allPropertyDrawersInDomain.ContainsKey(fieldType)) { + fieldDrawerType = _allPropertyDrawersInDomain[fieldType]; + break; + } + + fieldType = fieldType.BaseType; + } + + if(fieldDrawerType == null) return; + + //Create instances of each (including the arguments) + try { + _customTypeDrawer = (PropertyDrawer)Activator.CreateInstance(fieldDrawerType); + } + catch(Exception e) { + LogWarning("No constructor available in " + fieldType + " : " + e, property); + return; + } + + //Reassign the attribute field in the drawer so it can access the argument values + var attributeField = fieldDrawerType.GetField("m_Attribute", BindingFlags.Instance | BindingFlags.NonPublic); + if(attributeField != null) attributeField.SetValue(_customTypeDrawer, attribute); + var fieldInfoField = fieldDrawerType.GetField("m_FieldInfo", BindingFlags.Instance | BindingFlags.NonPublic); + if(fieldInfoField != null) fieldInfoField.SetValue(_customTypeDrawer, fieldInfo); + } + } + + private void LogWarning(string log, SerializedProperty property) { + var warning = "Property " + fieldInfo.Name + ""; + if(fieldInfo != null && fieldInfo.DeclaringType != null) + warning += " on behaviour " + fieldInfo.DeclaringType.Name + ""; + warning += " caused: " + log; + + Debug.LogWarning(warning, property.serializedObject.targetObject); + } + } + + public static class ConditionalFieldUtility { + #region Property Is Visible + + public static bool PropertyIsVisible(SerializedProperty property, bool inverse, string[] compareAgainst) { + if(property == null) return true; + + string asString = property.AsStringValue().ToUpper(); + + if(compareAgainst != null && compareAgainst.Length > 0) { + var matchAny = CompareAgainstValues(asString, compareAgainst, IsFlagsEnum()); + if(inverse) matchAny = !matchAny; + return matchAny; + } + + bool someValueAssigned = asString != "FALSE" && asString != "0" && asString != "NULL"; + if(someValueAssigned) return !inverse; + + return inverse; + + + bool IsFlagsEnum() { + if(property.propertyType != SerializedPropertyType.Enum) return false; + var value = property.GetValue(); + if(value == null) return false; + return value.GetType().GetCustomAttribute() != null; + } + } + + + /// + /// True if the property value matches any of the values in '_compareValues' + /// + private static bool CompareAgainstValues(string propertyValueAsString, string[] compareAgainst, bool handleFlags) { + if(!handleFlags) return ValueMatches(propertyValueAsString); + + var separateFlags = propertyValueAsString.Split(','); + foreach(var flag in separateFlags) { + if(ValueMatches(flag.Trim())) return true; + } + + return false; + + + bool ValueMatches(string value) { + foreach(var compare in compareAgainst) if(value == compare) return true; + return false; + } + } + + #endregion + + + #region Find Relative Property + + public static SerializedProperty FindRelativeProperty(SerializedProperty property, string propertyName) { + if(property.depth == 0) return property.serializedObject.FindProperty(propertyName); + + var path = property.propertyPath.Replace(".Array.data[", "["); + var elements = path.Split('.'); + + var nestedProperty = NestedPropertyOrigin(property, elements); + + // if nested property is null = we hit an array property + if(nestedProperty == null) { + var cleanPath = path.Substring(0, path.IndexOf('[')); + var arrayProp = property.serializedObject.FindProperty(cleanPath); + var target = arrayProp.serializedObject.targetObject; + + var who = "Property " + arrayProp.name + " in object " + target.name + " caused: "; + var warning = who + "Array fields is not supported by [ConditionalFieldAttribute]. Consider to use CollectionWrapper"; + + Debug.LogWarning(warning, target); + + return null; + } + + return nestedProperty.FindPropertyRelative(propertyName); + } + + // For [Serialized] types with [Conditional] fields + private static SerializedProperty NestedPropertyOrigin(SerializedProperty property, string[] elements) { + SerializedProperty parent = null; + + for(int i = 0; i < elements.Length - 1; i++) { + var element = elements[i]; + int index = -1; + if(element.Contains("[")) { + index = Convert.ToInt32(element.Substring(element.IndexOf("[", StringComparison.Ordinal)) + .Replace("[", "").Replace("]", "")); + element = element.Substring(0, element.IndexOf("[", StringComparison.Ordinal)); + } + + parent = i == 0 + ? property.serializedObject.FindProperty(element) + : parent != null + ? parent.FindPropertyRelative(element) + : null; + + if(index >= 0 && parent != null) parent = parent.GetArrayElementAtIndex(index); + } + + return parent; + } + + #endregion + + #region Behaviour Property Is Visible + + public static bool BehaviourPropertyIsVisible(UnityEngine.Object obj, string propertyName, ConditionalFieldAttribute appliedAttribute) { + if(string.IsNullOrEmpty(appliedAttribute.FieldToCheck)) return true; + + var so = new SerializedObject(obj); + var property = so.FindProperty(propertyName); + var targetProperty = FindRelativeProperty(property, appliedAttribute.FieldToCheck); + + return PropertyIsVisible(targetProperty, appliedAttribute.Inverse, appliedAttribute.CompareValues); + } + + #endregion + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ConditionalFieldAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/ConditionalFieldAttribute.cs.meta new file mode 100644 index 0000000..febbf70 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ConditionalFieldAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f0c8c14a34cc7b4c8ee94ff351a2902 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ConstantsSelectionAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/ConstantsSelectionAttribute.cs new file mode 100644 index 0000000..0ddfbf0 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ConstantsSelectionAttribute.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace InspectorToolkit +{ + public class ConstantsSelectionAttribute : PropertyAttribute + { + public readonly Type SelectFromType; + + public ConstantsSelectionAttribute(Type type) + { + SelectFromType = type; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + using EditorTools; + + [CustomPropertyDrawer(typeof(ConstantsSelectionAttribute))] + public class ConstantsSelectionAttributeDrawer : PropertyDrawer + { + private ConstantsSelectionAttribute _attribute; + private readonly List _constants = new List(); + private string[] _names; + private object[] _values; + private Type _targetType; + private int _selectedValueIndex; + private bool _valueFound; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (_attribute == null) Initialize(property); + if (_values.IsNullOrEmpty() || _selectedValueIndex < 0) + { + EditorGUI.PropertyField(position, property, label); + return; + } + + if (!_valueFound && _selectedValueIndex == 0) MyGUI.DrawColouredRect(position, MyGUI.Colors.Yellow); + + EditorGUI.BeginChangeCheck(); + _selectedValueIndex = EditorGUI.Popup(position, label.text, _selectedValueIndex, _names); + if (EditorGUI.EndChangeCheck()) + { + fieldInfo.SetValue(property.serializedObject.targetObject, _values[_selectedValueIndex]); + property.serializedObject.ApplyModifiedProperties(); + EditorUtility.SetDirty(property.serializedObject.targetObject); + } + } + + private object GetValue(SerializedProperty property) + { + return fieldInfo.GetValue(property.serializedObject.targetObject); + } + + private void Initialize(SerializedProperty property) + { + _attribute = (ConstantsSelectionAttribute) attribute; + _targetType = fieldInfo.FieldType; + + var searchFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy; + var allPublicStaticFields = _attribute.SelectFromType.GetFields(searchFlags); + var allPublicStaticProperties = _attribute.SelectFromType.GetProperties(searchFlags); + + // IsLiteral determines if its value is written at compile time and not changeable + // IsInitOnly determines if the field can be set in the body of the constructor + // for C# a field which is readonly keyword would have both true but a const field would have only IsLiteral equal to true + foreach (FieldInfo field in allPublicStaticFields) + { + if ((field.IsInitOnly || field.IsLiteral) && field.FieldType == _targetType) + _constants.Add(field); + } + foreach (var prop in allPublicStaticProperties) + { + if (prop.PropertyType == _targetType) _constants.Add(prop); + } + + + if (_constants.IsNullOrEmpty()) return; + _names = new string[_constants.Count]; + _values = new object[_constants.Count]; + for (var i = 0; i < _constants.Count; i++) + { + _names[i] = _constants[i].Name; + _values[i] = GetValue(i); + } + + var currentValue = GetValue(property); + if (currentValue != null) + { + for (var i = 0; i < _values.Length; i++) + { + if (currentValue.Equals(_values[i])) + { + _valueFound = true; + _selectedValueIndex = i; + } + } + } + + if (!_valueFound) + { + _names = _names.InsertAt(0); + _values = _values.InsertAt(0); + var actualValue = GetValue(property); + var value = actualValue != null ? actualValue : "NULL"; + _names[0] = "NOT FOUND: " + value; + _values[0] = actualValue; + } + } + + private object GetValue(int index) + { + var member = _constants[index]; + if (member.MemberType == MemberTypes.Field) return ((FieldInfo) member).GetValue(null); + if (member.MemberType == MemberTypes.Property) return ((PropertyInfo) member).GetValue(null); + return null; + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ConstantsSelectionAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/ConstantsSelectionAttribute.cs.meta new file mode 100644 index 0000000..aa7ac9d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ConstantsSelectionAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d93e4517d0ec49138d37edab20cacd2f +timeCreated: 1582028131 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/DefinedValuesAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/DefinedValuesAttribute.cs new file mode 100644 index 0000000..ba60b6b --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/DefinedValuesAttribute.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using UnityEngine; + +namespace InspectorToolkit +{ + /// + /// Create Popup with predefined values for string, int or float property + /// + public class DefinedValuesAttribute : PropertyAttribute + { + public readonly object[] ValuesArray; + + public DefinedValuesAttribute(params object[] definedValues) + { + ValuesArray = definedValues; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + + [CustomPropertyDrawer(typeof(DefinedValuesAttribute))] + public class DefinedValuesAttributeDrawer : PropertyDrawer + { + private string[] _values; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var values = ((DefinedValuesAttribute) attribute).ValuesArray; + + if (values.IsNullOrEmpty() || !TypeMatch(values[0].GetType(), property)) + { + EditorGUI.PropertyField(position, property, label); + return; + } + + if (_values.IsNullOrEmpty()) _values = values.Select(v => v.ToString()).ToArray(); + + var valType = values[0].GetType(); + bool isString = valType == typeof(string); + bool isInt = valType == typeof(int); + bool isFloat = valType == typeof(float); + + EditorGUI.BeginChangeCheck(); + var newIndex = EditorGUI.Popup(position, label.text, GetSelectedIndex(), _values); + if (EditorGUI.EndChangeCheck()) ApplyNewValue(_values[newIndex]); + + + int GetSelectedIndex() + { + for (var i = 0; i < _values.Length; i++) + { + if (isString && property.stringValue == _values[i]) return i; + if (isInt && property.intValue == Convert.ToInt32(_values[i])) return i; + if (isFloat && Mathf.Approximately(property.floatValue, Convert.ToSingle(_values[i]))) return i; + } + + return 0; + } + + void ApplyNewValue(string newValue) + { + if (isString) property.stringValue = newValue; + if (isInt) property.intValue = Convert.ToInt32(newValue); + if (isFloat) property.floatValue = Convert.ToSingle(newValue); + + property.serializedObject.ApplyModifiedProperties(); + } + } + + private bool TypeMatch(Type valType, SerializedProperty property) + { + if (valType == typeof(string) && property.propertyType == SerializedPropertyType.String) return true; + if (valType == typeof(int) && property.propertyType == SerializedPropertyType.Integer) return true; + if (valType == typeof(float) && property.propertyType == SerializedPropertyType.Float) return true; + + return false; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/DefinedValuesAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/DefinedValuesAttribute.cs.meta new file mode 100644 index 0000000..b1e1b6a --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/DefinedValuesAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb25c9dc1c7ff1345ac286ba31c0af36 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/DisplayInspectorAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/DisplayInspectorAttribute.cs new file mode 100644 index 0000000..389f193 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/DisplayInspectorAttribute.cs @@ -0,0 +1,153 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace InspectorToolkit +{ + /// + /// Use to display inspector of property object + /// + public class DisplayInspectorAttribute : PropertyAttribute + { + public readonly bool DisplayScript; + + public DisplayInspectorAttribute(bool displayScriptField = true) + { + DisplayScript = displayScriptField; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using EditorTools; + using UnityEditor; + + [CustomPropertyDrawer(typeof(DisplayInspectorAttribute))] + public class DisplayInspectorAttributeDrawer : PropertyDrawer + { + private ButtonMethodHandler _buttonMethods; + + private readonly Dictionary _targets = new Dictionary(); + private SerializedObject GetTargetSO(Object targetObject) + { + SerializedObject target; + if (_targets.ContainsKey(targetObject)) target = _targets[targetObject]; + else + { + _targets.Add(targetObject, new SerializedObject(targetObject)); + target = _targets[targetObject]; + } + target.Update(); + return target; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + bool notValidType = property.propertyType != SerializedPropertyType.ObjectReference; + if (notValidType) + { + EditorGUI.LabelField(position, label.text, "Use [DisplayInspector] with MB or SO"); + return; + } + + if (((DisplayInspectorAttribute)attribute).DisplayScript || property.objectReferenceValue == null) + { + position.height = EditorGUI.GetPropertyHeight(property); + EditorGUI.PropertyField(position, property, label); + position.y += EditorGUI.GetPropertyHeight(property) + 4; + if (GUI.changed) property.serializedObject.ApplyModifiedProperties(); + } + if (property.objectReferenceValue == null) return; + + + if (_buttonMethods == null) _buttonMethods = new ButtonMethodHandler(property.objectReferenceValue); + + var startY = position.y - 2; + float startX = position.x; + + var target = GetTargetSO(property.objectReferenceValue); + var propertyObject = target.GetIterator(); + propertyObject.Next(true); + propertyObject.NextVisible(true); + + var xPos = position.x + 10; + var width = position.width - 10; + + bool expandedReorderable = false; + while (propertyObject.NextVisible(propertyObject.isExpanded && !expandedReorderable)) + { +#if UNITY_2020_2_OR_NEWER + expandedReorderable = propertyObject.isExpanded && propertyObject.isArray && + !propertyObject.IsAttributeDefined(); +#endif + position.x = xPos + 10 * propertyObject.depth; + position.width = width - 10 * propertyObject.depth; + + position.height = EditorGUI.GetPropertyHeight(propertyObject, expandedReorderable); + EditorGUI.PropertyField(position, propertyObject, expandedReorderable); + + position.y += position.height + 4; + } + + if (!_buttonMethods.TargetMethods.IsNullOrEmpty()) + { + foreach (var method in _buttonMethods.TargetMethods) + { + position.height = EditorGUIUtility.singleLineHeight; + if (GUI.Button(position, method.Name)) _buttonMethods.Invoke(method.Method); + position.y += position.height; + } + } + + var bgRect = position; + bgRect.y = startY - 5; + bgRect.x = startX - 10; + bgRect.width = 10; + bgRect.height = position.y - startY; + if (_buttonMethods.Amount > 0) bgRect.height += 5; + + DrawColouredRect(bgRect, new Color(.6f, .6f, .8f, .5f)); + + + target.ApplyModifiedProperties(); + property.serializedObject.ApplyModifiedProperties(); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + bool notValidType = property.propertyType != SerializedPropertyType.ObjectReference; + if (notValidType || property.objectReferenceValue == null) return base.GetPropertyHeight(property, label); + if (_buttonMethods == null) _buttonMethods = new ButtonMethodHandler(property.objectReferenceValue); + + float height = ((DisplayInspectorAttribute)attribute).DisplayScript ? EditorGUI.GetPropertyHeight(property) + 4 : 0; + + var target = GetTargetSO(property.objectReferenceValue); + var propertyObject = target.GetIterator(); + propertyObject.Next(true); + propertyObject.NextVisible(true); + + bool expandedReorderable = false; + while (propertyObject.NextVisible(propertyObject.isExpanded && !expandedReorderable)) + { +#if UNITY_2020_2_OR_NEWER + expandedReorderable = propertyObject.isExpanded && propertyObject.isArray && + !propertyObject.IsAttributeDefined(); +#endif + height += EditorGUI.GetPropertyHeight(propertyObject, expandedReorderable) + 4; + } + + if (_buttonMethods.Amount > 0) height += 4 + _buttonMethods.Amount * EditorGUIUtility.singleLineHeight; + return height; + } + + private void DrawColouredRect(Rect rect, Color color) + { + var defaultBackgroundColor = GUI.backgroundColor; + GUI.backgroundColor = color; + GUI.Box(rect, ""); + GUI.backgroundColor = defaultBackgroundColor; + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/DisplayInspectorAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/DisplayInspectorAttribute.cs.meta new file mode 100644 index 0000000..1cfe93e --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/DisplayInspectorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0a699aecc941574e9faf6a546c0e663 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs new file mode 100644 index 0000000..8eeca80 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs @@ -0,0 +1,329 @@ +// ---------------------------------------------------------------------------- +// Author: Dimitry, PixeyeHQ +// Project : UNITY FOLDOUT +// https://github.com/PixeyeHQ/InspectorFoldoutGroup +// Contacts : Pix - ask@pixeye.games +// Website : http://www.pixeye.games +// ---------------------------------------------------------------------------- + +using UnityEngine; + +namespace InspectorToolkit +{ + public class FoldoutAttribute : PropertyAttribute + { + public readonly string Name; + public readonly bool FoldEverything; + + /// Adds the property to the specified foldout group. + /// Name of the foldout group. + /// Toggle to put all properties to the specified group + public FoldoutAttribute(string name, bool foldEverything = false) + { + FoldEverything = foldEverything; + Name = name; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + + public class FoldoutAttributeHandler + { + + + private readonly Dictionary _cacheFoldouts = new Dictionary(); + private readonly List _props = new List(); + private bool _initialized; + + private readonly IGUILayoutPropertyField guiLayoutPropertyField; + + private readonly UnityEngine.Object _target; + private readonly SerializedObject _serializedObject; + + + public bool OverrideInspector => _props.Count > 0; + + public FoldoutAttributeHandler(UnityEngine.Object target, SerializedObject serializedObject, IGUILayoutPropertyField guiLayoutPropertyField) + { + _target = target; + _serializedObject = serializedObject; + this.guiLayoutPropertyField = guiLayoutPropertyField; + } + + public void OnDisable() + { + if (_target == null) return; + + foreach (var c in _cacheFoldouts) + { + EditorPrefs.SetBool(string.Format($"{c.Value.Attribute.Name}{c.Value.Properties[0].name}{_target.name}"), c.Value.Expanded); + c.Value.Dispose(); + } + } + + public void Update() + { + _serializedObject.Update(); + Setup(); + } + + public void OnInspectorGUI() + { + Header(); + Body(); + + _serializedObject.ApplyModifiedProperties(); + } + + private void Header() + { + using (new EditorGUI.DisabledScope("m_Script" == _props[0].propertyPath)) + { + //EditorGUILayout.Space(); + EditorGUILayout.PropertyField(_props[0], true); + //EditorGUILayout.Space(); + } + } + + private void Body() + { + foreach (var pair in _cacheFoldouts) + { + EditorGUILayout.BeginVertical(StyleFramework.Box); + Foldout(pair.Value); + EditorGUILayout.EndVertical(); + + EditorGUI.indentLevel = 0; + } + + for (var i = 1; i < _props.Count; i++) { + SerializedProperty prop = _props[i]; + + guiLayoutPropertyField.GUILayoutPropertyField(prop); + } + } + + private void Foldout(CacheFoldProp cache) + { + cache.Expanded = EditorGUILayout.Foldout(cache.Expanded, cache.Attribute.Name, true, StyleFramework.FoldoutHeader); + var rect = GUILayoutUtility.GetLastRect(); + rect.x -= 18; + rect.y -= 4; + rect.height += 8; + rect.width += 18; + EditorGUI.LabelField(rect, GUIContent.none, EditorStyles.helpBox); + + if (cache.Expanded) + { + EditorGUILayout.Space(2); + + foreach (SerializedProperty property in cache.Properties) + { + EditorGUILayout.BeginVertical(StyleFramework.BoxChild); + guiLayoutPropertyField.GUILayoutPropertyField( + property, + new GUIContent(ObjectNames.NicifyVariableName(property.name), property.tooltip), + true); + EditorGUILayout.EndVertical(); + } + } + } + + private void Setup() + { + if (_initialized) return; + + FoldoutAttribute prevFold = default; + + var length = EditorTypes.GetFields(_target, out var objectFields); + + for (var i = 0; i < length; i++) + { + #region FOLDERS + + var fold = Attribute.GetCustomAttribute(objectFields[i], typeof(FoldoutAttribute)) as FoldoutAttribute; + CacheFoldProp c; + if (fold == null) + { + if (prevFold != null && prevFold.FoldEverything) + { + if (!_cacheFoldouts.TryGetValue(prevFold.Name, out c)) + { + _cacheFoldouts.Add(prevFold.Name, + new CacheFoldProp {Attribute = prevFold, Types = new HashSet {objectFields[i].Name}}); + } + else + { + c.Types.Add(objectFields[i].Name); + } + } + + continue; + } + + prevFold = fold; + + if (!_cacheFoldouts.TryGetValue(fold.Name, out c)) + { + var expanded = EditorPrefs.GetBool(string.Format($"{fold.Name}{objectFields[i].Name}{_target.name}"), false); + _cacheFoldouts.Add(fold.Name, + new CacheFoldProp {Attribute = fold, Types = new HashSet {objectFields[i].Name}, Expanded = expanded}); + } + else c.Types.Add(objectFields[i].Name); + + #endregion + } + + var property = _serializedObject.GetIterator(); + var next = property.NextVisible(true); + if (next) + { + do + { + HandleFoldProp(property); + } while (property.NextVisible(false)); + } + + _initialized = true; + } + + private void HandleFoldProp(SerializedProperty prop) + { + bool shouldBeFolded = false; + + foreach (var pair in _cacheFoldouts) + { + if (pair.Value.Types.Contains(prop.name)) + { + var pr = prop.Copy(); + shouldBeFolded = true; + pair.Value.Properties.Add(pr); + + break; + } + } + + if (shouldBeFolded == false) + { + var pr = prop.Copy(); + _props.Add(pr); + } + } + + private class CacheFoldProp + { + public HashSet Types = new HashSet(); + public readonly List Properties = new List(); + public FoldoutAttribute Attribute; + public bool Expanded; + + public void Dispose() + { + Properties.Clear(); + Types.Clear(); + Attribute = null; + } + } + } + + + static class StyleFramework + { + public static readonly GUIStyle Box; + public static readonly GUIStyle BoxChild; + public static readonly GUIStyle FoldoutHeader; + + static StyleFramework() + { + FoldoutHeader = new GUIStyle(EditorStyles.foldout); + FoldoutHeader.overflow = new RectOffset(-10, 0, 3, 0); + FoldoutHeader.padding = new RectOffset(20, 0, 0, 0); + FoldoutHeader.margin = new RectOffset(0, 0, 0, 2); + FoldoutHeader.border = new RectOffset(2, 2, 2, 2); + + Box = new GUIStyle(GUI.skin.box); + Box.padding = new RectOffset(18, 0, 4, 0); + + BoxChild = new GUIStyle(GUI.skin.box); + BoxChild.padding = new RectOffset(0, 0, 0, 0); + BoxChild.margin = new RectOffset(0, 0, 0, 0); + } + } + + static class EditorTypes + { + private static readonly Dictionary> Fields = new Dictionary>(FastComparable.Default); + private static readonly Dictionary> Properties = new Dictionary>(FastComparable.Default); + + public static int GetFields(Object target, out List objectFields) + { + var t = target.GetType(); + var hash = t.GetHashCode(); + + if (!Fields.TryGetValue(hash, out objectFields)) + { + var typeTree = GetTypeTree(t); + objectFields = target.GetType() + .GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic) + .OrderByDescending(x => typeTree.IndexOf(x.DeclaringType)) + .ToList(); + Fields.Add(hash, objectFields); + } + + return objectFields.Count; + } + + public static int GetProperties(Object target, out List objectProperties) { + var t = target.GetType(); + var hash = t.GetHashCode(); + + if(!Properties.TryGetValue(hash, out objectProperties)) { + var typeTree = GetTypeTree(t); + objectProperties = target.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic) + .OrderByDescending(x => typeTree.IndexOf(x.DeclaringType)) + .ToList(); + Properties.Add(hash, objectProperties); + } + + return objectProperties.Count; + } + + static IList GetTypeTree(Type t) + { + var types = new List(); + while (t.BaseType != null) + { + types.Add(t); + t = t.BaseType; + } + + return types; + } + } + + + internal class FastComparable : IEqualityComparer + { + public static readonly FastComparable Default = new FastComparable(); + + public bool Equals(int x, int y) + { + return x == y; + } + + public int GetHashCode(int obj) + { + return obj.GetHashCode(); + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs.meta new file mode 100644 index 0000000..6e6264d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/FoldoutAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 69f6d9b4f0e9f594dbcc767598fe91c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/InfoAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/InfoAttribute.cs new file mode 100644 index 0000000..cec600e --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/InfoAttribute.cs @@ -0,0 +1,60 @@ +using UnityEngine; + +namespace InspectorToolkit { + public class InfoAttribute : PropertyAttribute { + public enum MessageType { + Info, + Warning, + Error + } + +#if UNITY_EDITOR + public readonly UnityEditor.MessageType messageType; + public readonly string message; +#endif + + public InfoAttribute(string message, MessageType messageType = MessageType.Info) { +#if UNITY_EDITOR + this.message = message; + switch(messageType) { + case MessageType.Error: + this.messageType = UnityEditor.MessageType.Error; + break; + case MessageType.Warning: + this.messageType = UnityEditor.MessageType.Warning; + break; + case MessageType.Info: + default: + this.messageType = UnityEditor.MessageType.Info; + break; + } +#endif + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + using UnityEditor; + + [CustomPropertyDrawer(typeof(InfoAttribute))] + public class InfoAttributeDrawer : PropertyDrawer { + private const float MESSAGEBOX_HEIGHT = 40f; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + return base.GetPropertyHeight(property, label) + MESSAGEBOX_HEIGHT; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + var message = ((InfoAttribute)attribute).message; + var messageType = ((InfoAttribute)attribute).messageType; + + var boxPosition = new Rect(position.x, position.y, position.width, MESSAGEBOX_HEIGHT); + EditorGUI.HelpBox(boxPosition, message, messageType); + + var fieldPosition = new Rect(position.x, position.y + MESSAGEBOX_HEIGHT, position.width, 18f); + EditorGUI.PropertyField(fieldPosition, property, label); + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/InfoAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/InfoAttribute.cs.meta new file mode 100644 index 0000000..6c10dff --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/InfoAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 621eca357dcb59841acf613ba4b7dee5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/InitializationFieldAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/InitializationFieldAttribute.cs new file mode 100644 index 0000000..366eb9d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/InitializationFieldAttribute.cs @@ -0,0 +1,34 @@ +using UnityEngine; + +namespace InspectorToolkit +{ + /// + /// Field will be Read-Only in Playmode + /// + public class InitializationFieldAttribute : PropertyAttribute + { + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + + [CustomPropertyDrawer(typeof(InitializationFieldAttribute))] + public class InitializationFieldAttributeDrawer : PropertyDrawer + { + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return EditorGUI.GetPropertyHeight(property, label, true); + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (Application.isPlaying) GUI.enabled = false; + EditorGUI.PropertyField(position, property, label, true); + GUI.enabled = true; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/InitializationFieldAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/InitializationFieldAttribute.cs.meta new file mode 100644 index 0000000..969e6f4 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/InitializationFieldAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dd5ef49202d6492bb39be27d203b0163 +timeCreated: 1588577864 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/InterfaceComponentAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/InterfaceComponentAttribute.cs new file mode 100644 index 0000000..0df4f5e --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/InterfaceComponentAttribute.cs @@ -0,0 +1,93 @@ +using System; +using UnityEngine; + +namespace InspectorToolkit { + public class InterfaceComponentAttribute : PropertyAttribute { + public readonly Type interfaceType; + public readonly bool allowMismatch; + + public InterfaceComponentAttribute(Type interfaceType, bool allowMismatch = false) { + this.interfaceType = interfaceType; + this.allowMismatch = allowMismatch; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + using UnityEditor; + + [CustomPropertyDrawer(typeof(InterfaceComponentAttribute))] + public class InterfaceComponentAttributeDrawer : PropertyDrawer { + + private const float MESSAGE_HEIGHT = 40f; + private const float FIELD_HEIGHT = 18f; + + private enum ValueImplementation { + Null, + Match, + Mismatch + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + ValueImplementation valueImplementation = GetValueInterfaceImplementation(property, ((InterfaceComponentAttribute)attribute).interfaceType); + return base.GetPropertyHeight(property, label) + (valueImplementation != ValueImplementation.Match ? MESSAGE_HEIGHT : 0f); + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + InterfaceComponentAttribute interfaceAttribute = (InterfaceComponentAttribute)attribute; + Type interfaceType = interfaceAttribute.interfaceType; + bool allowMismatch = interfaceAttribute.allowMismatch; + UnityEngine.Object value = property.objectReferenceValue; + if(value is GameObject gameObject) { + value = gameObject.GetComponent(interfaceType) as Component; + if(value != null) { + property.objectReferenceValue = value; + property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); + } + } + else if(value is Transform transform) { + value = transform.GetComponent(interfaceType) as Component; + if(value != null) { + property.objectReferenceValue = value; + property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); + } + } + + ValueImplementation valueImplementation = GetValueInterfaceImplementation(property, interfaceType); + + string message = null; + if(valueImplementation == ValueImplementation.Null) { + message = $"Component must implement '{interfaceType.Name}'"; + } + else if(valueImplementation == ValueImplementation.Mismatch) { + message = $"Component does not implement '{interfaceType.Name}'"; + + if(allowMismatch == false) { + property.objectReferenceValue = null; + property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); + } + } + + if(string.IsNullOrEmpty(message) == false) { + Rect boxPosition = new Rect(position.x, position.y, position.width, MESSAGE_HEIGHT - 2f); + EditorGUI.HelpBox(boxPosition, message, valueImplementation == ValueImplementation.Mismatch ? MessageType.Warning : MessageType.Info); + } + + Rect fieldPosition = new Rect(position.x, position.yMax - FIELD_HEIGHT, position.width, FIELD_HEIGHT); + EditorGUI.PropertyField(fieldPosition, property, label); + } + + private ValueImplementation GetValueInterfaceImplementation(SerializedProperty property, Type interfaceType) { + UnityEngine.Object value = property.objectReferenceValue; + if(value == null) { + return ValueImplementation.Null; + } + else if(interfaceType.IsAssignableFrom(property.objectReferenceValue.GetType())) { + return ValueImplementation.Match; + } + return ValueImplementation.Mismatch; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/InterfaceComponentAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/InterfaceComponentAttribute.cs.meta new file mode 100644 index 0000000..e32f069 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/InterfaceComponentAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c3f94f6ba2d5454f8944e8f0f9479b2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/LayerAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/LayerAttribute.cs new file mode 100644 index 0000000..67f8a49 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/LayerAttribute.cs @@ -0,0 +1,40 @@ +using UnityEngine; + +namespace InspectorToolkit +{ + public class LayerAttribute : PropertyAttribute + { + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + + [CustomPropertyDrawer(typeof(LayerAttribute))] + public class LayerAttributeDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (property.propertyType != SerializedPropertyType.Integer) + { + if (!_checked) Warning(property); + EditorGUI.PropertyField(position, property, label); + return; + } + + property.intValue = EditorGUI.LayerField(position, label, property.intValue); + } + + private bool _checked; + + private void Warning(SerializedProperty property) + { + Debug.LogWarning(string.Format("Property {0} in object {1} is of wrong type. Expected: Int", + property.name, property.serializedObject.targetObject)); + _checked = true; + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/LayerAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/LayerAttribute.cs.meta new file mode 100644 index 0000000..d5c3ca8 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/LayerAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 979d3d332ae931a4f8dc8aaf52644f0c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/LinkAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/LinkAttribute.cs new file mode 100644 index 0000000..083bec8 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/LinkAttribute.cs @@ -0,0 +1,47 @@ +using UnityEditor; +using UnityEngine; + +namespace InspectorToolkit { + public class LinkAttribute : PropertyAttribute { + + public readonly string message; + public readonly string url; + + public LinkAttribute(string message, string url) { + this.message = message; + this.url = url; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + + [CustomPropertyDrawer(typeof(LinkAttribute))] + public class LinkAttributeDrawer : PropertyDrawer { + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + return base.GetPropertyHeight(property, label) + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + + //If you don't capture these and recreate below, GUIContent label becomes replaces with LinkButtons GUIContent + string labelText = label.text; + string labelTooltip = label.tooltip; + + string message = ((LinkAttribute)attribute).message; + string url = ((LinkAttribute)attribute).url; + + var linkPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + + if(EditorGUI.LinkButton(linkPosition, message)) { + Application.OpenURL(url); + } + + var fieldPosition = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing, position.width, EditorGUIUtility.singleLineHeight); + + EditorGUI.PropertyField(fieldPosition, property, new GUIContent(labelText,labelTooltip)); + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/LinkAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/LinkAttribute.cs.meta new file mode 100644 index 0000000..c92541c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/LinkAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65d3077f048889344952a1fd170f5810 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/MaxValueAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/MaxValueAttribute.cs new file mode 100644 index 0000000..45ab7bf --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/MaxValueAttribute.cs @@ -0,0 +1,201 @@ +using InspectorToolkit.Internal; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +using InspectorToolkit.EditorTools; +#endif + +#pragma warning disable 0414 +namespace InspectorToolkit +{ + public class MaxValueAttribute : AttributeBase + { + private readonly float _x; + private readonly float _y; + private readonly float _z; + + private readonly bool _vectorValuesSet; + + public MaxValueAttribute(float value) + { + _x = value; + } + + public MaxValueAttribute(float x, float y, float z) + { + _x = x; + _y = y; + _z = z; + _vectorValuesSet = true; + } + +#if UNITY_EDITOR + private string _warning; + + public override void ValidateProperty(SerializedProperty property) + { + if (!property.IsNumerical()) + { + if (_warning == null) _warning = property.name + " caused: [MaxValueAttribute] used with non-numeric property"; + return; + } + + bool valueHandled = HandleValues(property); + if (valueHandled) property.serializedObject.ApplyModifiedProperties(); + } + + public override bool OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (_warning == null) return false; + EditorGUI.HelpBox(position, _warning, MessageType.Warning); + return true; + } + + public override float? OverrideHeight() + { + if (_warning == null) return null; + return EditorGUIUtility.singleLineHeight; + } + + + #region Handle Value + /// true if fixed + private bool HandleValues(SerializedProperty property) + { + switch (property.propertyType) + { + case SerializedPropertyType.Float: + case SerializedPropertyType.Integer: + return HandleNumerics(property); + + case SerializedPropertyType.Vector2: + case SerializedPropertyType.Vector3: + case SerializedPropertyType.Vector4: + return HandleVectors(property); + + case SerializedPropertyType.Vector2Int: + case SerializedPropertyType.Vector3Int: + return HandleIntVectors(property); + } + + return false; + } + + + private bool HandleNumerics(SerializedProperty property) + { + var maxValue = _x; + + if (property.propertyType == SerializedPropertyType.Integer && property.intValue > maxValue) + { + property.intValue = (int) maxValue; + return true; + } + + if (property.propertyType == SerializedPropertyType.Float && property.floatValue > maxValue) + { + property.floatValue = maxValue; + return true; + } + + return false; + } + + + private bool HandleVectors(SerializedProperty property) + { + var x = _x; + var y = _vectorValuesSet ? _y : x; + var z = _vectorValuesSet ? _z : x; + + Vector4 vector = Vector4.zero; + switch (property.propertyType) + { + case SerializedPropertyType.Vector2: + vector = property.vector2Value; + break; + case SerializedPropertyType.Vector3: + vector = property.vector3Value; + break; + case SerializedPropertyType.Vector4: + vector = property.vector4Value; + break; + } + + bool handled = false; + if (vector[0] > x) + { + vector[0] = x; + handled = true; + } + + if (vector[1] > y) + { + vector[1] = y; + handled = true; + } + + if (vector[2] > z) + { + vector[2] = z; + handled = true; + } + + switch (property.propertyType) + { + case SerializedPropertyType.Vector2: + property.vector2Value = vector; + break; + case SerializedPropertyType.Vector3: + property.vector3Value = vector; + break; + case SerializedPropertyType.Vector4: + property.vector4Value = vector; + break; + } + + return handled; + } + + + private bool HandleIntVectors(SerializedProperty property) + { + var x = (int) _x; + var y = _vectorValuesSet ? (int) _y : x; + var z = _vectorValuesSet ? (int) _z : x; + + if (property.propertyType == SerializedPropertyType.Vector2Int) + { + var vector = property.vector2IntValue; + if (vector.x > x || vector.y > y) + { + property.vector2IntValue = new Vector2Int( + vector.x > x ? x : vector.x, + vector.y > y ? y : vector.y); + return true; + } + + return false; + } + + if (property.propertyType == SerializedPropertyType.Vector3Int) + { + var vector = property.vector3IntValue; + if (vector.x > x || vector.y > y || vector.z > z) + { + property.vector3IntValue = new Vector3Int( + vector.x > x ? x : vector.x, + vector.y > y ? y : vector.y, + vector.z > z ? z : vector.z); + return true; + } + + return false; + } + + return false; + } + #endregion +#endif + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/MaxValueAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/MaxValueAttribute.cs.meta new file mode 100644 index 0000000..6cabb5c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/MaxValueAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8930225c73d74e5dbb7784e4c97eb1ef +timeCreated: 1582014887 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/MinMaxRangeAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/MinMaxRangeAttribute.cs new file mode 100644 index 0000000..dcf66eb --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/MinMaxRangeAttribute.cs @@ -0,0 +1,133 @@ +// ---------------------------------------------------------------------------- +// Author: Richard Fine +// Source: https://bitbucket.org/richardfine/scriptableobjectdemo +// ---------------------------------------------------------------------------- + +using System; +using UnityEngine; + +namespace InspectorToolkit { + public class MinMaxRangeAttribute : PropertyAttribute { + public MinMaxRangeAttribute(float min, float max) { + Min = min; + Max = max; + } + + public readonly float Min; + public readonly float Max; + } + + [Serializable] + public struct RangedFloat { + public float Min; + public float Max; + + public RangedFloat(float min, float max) { + Min = min; + Max = max; + } + } + + [Serializable] + public struct RangedInt { + public int Min; + public int Max; + + public RangedInt(int min, int max) { + Min = min; + Max = max; + } + } + + public static class RangedExtensions { + public static float LerpFromRange(this RangedFloat ranged, float t) { + return Mathf.Lerp(ranged.Min, ranged.Max, t); + } + + public static float LerpFromRangeUnclamped(this RangedFloat ranged, float t) { + return Mathf.LerpUnclamped(ranged.Min, ranged.Max, t); + } + + public static float LerpFromRange(this RangedInt ranged, float t) { + return Mathf.Lerp(ranged.Min, ranged.Max, t); + } + + public static float LerpFromRangeUnclamped(this RangedInt ranged, float t) { + return Mathf.LerpUnclamped(ranged.Min, ranged.Max, t); + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + using UnityEditor; + + [CustomPropertyDrawer(typeof(MinMaxRangeAttribute))] + public class MinMaxRangeIntAttributeDrawer : PropertyDrawer { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + SerializedProperty minProp = property.FindPropertyRelative("Min"); + SerializedProperty maxProp = property.FindPropertyRelative("Max"); + if(minProp == null || maxProp == null) { + Debug.LogWarning("MinMaxRangeAttribute used on " + + property.name + + ". Must be used on types with Min and Max fields", + property.serializedObject.targetObject); + + return; + } + + var minValid = minProp.propertyType == SerializedPropertyType.Integer || minProp.propertyType == SerializedPropertyType.Float; + var maxValid = maxProp.propertyType == SerializedPropertyType.Integer || maxProp.propertyType == SerializedPropertyType.Float; + if(!maxValid || !minValid || minProp.propertyType != maxProp.propertyType) { + Debug.LogWarning("MinMaxRangeAttribute used on " + + property.name + + ". Min and Max fields must be of int or float type", + property.serializedObject.targetObject); + + return; + } + + MinMaxRangeAttribute rangeAttribute = (MinMaxRangeAttribute)attribute; + + label = EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, label); + + bool isInt = minProp.propertyType == SerializedPropertyType.Integer; + + float minValue = isInt ? minProp.intValue : minProp.floatValue; + float maxValue = isInt ? maxProp.intValue : maxProp.floatValue; + float rangeMin = rangeAttribute.Min; + float rangeMax = rangeAttribute.Max; + + + const float rangeBoundsLabelWidth = 40f; + + var rangeBoundsLabel1Rect = new Rect(position); + rangeBoundsLabel1Rect.width = rangeBoundsLabelWidth; + GUI.Label(rangeBoundsLabel1Rect, new GUIContent(minValue.ToString(isInt ? "F0" : "F2"))); + position.xMin += rangeBoundsLabelWidth; + + var rangeBoundsLabel2Rect = new Rect(position); + rangeBoundsLabel2Rect.xMin = rangeBoundsLabel2Rect.xMax - rangeBoundsLabelWidth; + GUI.Label(rangeBoundsLabel2Rect, new GUIContent(maxValue.ToString(isInt ? "F0" : "F2"))); + position.xMax -= rangeBoundsLabelWidth; + + EditorGUI.BeginChangeCheck(); + EditorGUI.MinMaxSlider(position, ref minValue, ref maxValue, rangeMin, rangeMax); + + if(EditorGUI.EndChangeCheck()) { + if(isInt) { + minProp.intValue = Mathf.RoundToInt(minValue); + maxProp.intValue = Mathf.RoundToInt(maxValue); + } + else { + minProp.floatValue = minValue; + maxProp.floatValue = maxValue; + } + } + + EditorGUI.EndProperty(); + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/MinMaxRangeAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/MinMaxRangeAttribute.cs.meta new file mode 100644 index 0000000..bf76ca2 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/MinMaxRangeAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aa6fba45efae4ac4b9830e11066fb969 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/MinValueAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/MinValueAttribute.cs new file mode 100644 index 0000000..da61418 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/MinValueAttribute.cs @@ -0,0 +1,202 @@ +using InspectorToolkit.Internal; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +using InspectorToolkit.EditorTools; +#endif + +#pragma warning disable 0414 +namespace InspectorToolkit +{ + public class MinValueAttribute : AttributeBase + { + private readonly float _x; + private readonly float _y; + private readonly float _z; + + private readonly bool _vectorValuesSet; + + public MinValueAttribute(float value) + { + _x = value; + } + + public MinValueAttribute(float x, float y, float z) + { + _x = x; + _y = y; + _z = z; + _vectorValuesSet = true; + } + +#if UNITY_EDITOR + private string _warning; + + public override void ValidateProperty(SerializedProperty property) + { + if (!property.IsNumerical()) + { + if (_warning == null) _warning = property.name + " caused: [MinValueAttribute] used with non-numeric property"; + return; + } + + bool valueHandled = HandleValues(property); + if (valueHandled) property.serializedObject.ApplyModifiedProperties(); + } + + public override bool OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (_warning == null) return false; + EditorGUI.HelpBox(position, _warning, MessageType.Warning); + return true; + } + + public override float? OverrideHeight() + { + if (_warning == null) return null; + return EditorGUIUtility.singleLineHeight; + } + + #region Handle Value + + /// true if fixed + private bool HandleValues(SerializedProperty property) + { + switch (property.propertyType) + { + case SerializedPropertyType.Float: + case SerializedPropertyType.Integer: + return HandleNumerics(property); + + case SerializedPropertyType.Vector2: + case SerializedPropertyType.Vector3: + case SerializedPropertyType.Vector4: + return HandleVectors(property); + + case SerializedPropertyType.Vector2Int: + case SerializedPropertyType.Vector3Int: + return HandleIntVectors(property); + } + + return false; + } + + + private bool HandleNumerics(SerializedProperty property) + { + var minValue = _x; + + if (property.propertyType == SerializedPropertyType.Integer && property.intValue < minValue) + { + property.intValue = (int) minValue; + return true; + } + + if (property.propertyType == SerializedPropertyType.Float && property.floatValue < minValue) + { + property.floatValue = minValue; + return true; + } + + return false; + } + + + private bool HandleVectors(SerializedProperty property) + { + var x = _x; + var y = _vectorValuesSet ? _y : x; + var z = _vectorValuesSet ? _z : x; + + Vector4 vector = Vector4.zero; + switch (property.propertyType) + { + case SerializedPropertyType.Vector2: + vector = property.vector2Value; + break; + case SerializedPropertyType.Vector3: + vector = property.vector3Value; + break; + case SerializedPropertyType.Vector4: + vector = property.vector4Value; + break; + } + + bool handled = false; + if (vector[0] < x) + { + vector[0] = x; + handled = true; + } + + if (vector[1] < y) + { + vector[1] = y; + handled = true; + } + + if (vector[2] < z) + { + vector[2] = z; + handled = true; + } + + switch (property.propertyType) + { + case SerializedPropertyType.Vector2: + property.vector2Value = vector; + break; + case SerializedPropertyType.Vector3: + property.vector3Value = vector; + break; + case SerializedPropertyType.Vector4: + property.vector4Value = vector; + break; + } + + return handled; + } + + + private bool HandleIntVectors(SerializedProperty property) + { + var x = (int) _x; + var y = _vectorValuesSet ? (int) _y : x; + var z = _vectorValuesSet ? (int) _z : x; + + if (property.propertyType == SerializedPropertyType.Vector2Int) + { + var vector = property.vector2IntValue; + if (vector.x < x || vector.y < y) + { + property.vector2IntValue = new Vector2Int( + vector.x < x ? x : vector.x, + vector.y < y ? y : vector.y); + return true; + } + + return false; + } + + if (property.propertyType == SerializedPropertyType.Vector3Int) + { + var vector = property.vector3IntValue; + if (vector.x < x || vector.y < y || vector.z < z) + { + property.vector3IntValue = new Vector3Int( + vector.x < x ? x : vector.x, + vector.y < y ? y : vector.y, + vector.z < z ? z : vector.z); + return true; + } + + return false; + } + + return false; + } + + #endregion +#endif + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/MinValueAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/MinValueAttribute.cs.meta new file mode 100644 index 0000000..aaae7ff --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/MinValueAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7a6fc14799014e3ea82c8158bd477aca +timeCreated: 1582013139 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ObjectsPropertyAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/ObjectsPropertyAttribute.cs new file mode 100644 index 0000000..170ee56 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ObjectsPropertyAttribute.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Jovian.Utilities; +using UnityEngine; + +namespace InspectorToolkit +{ + /// + /// Finds all objects in the project of 'objectType' then creates a dropdown showing all values matching 'memberName' + /// E.g. Find all 'ScriptableObject' and list their 'name' values in a list. + /// + public class ObjectsPropertyAttribute : PropertyAttribute + { + public readonly Type objectType; + public readonly string memberName; + + public ObjectsPropertyAttribute(Type objectType, string memberName) + { + this.objectType = objectType; + this.memberName = memberName; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + using System.Reflection; + + [CustomPropertyDrawer(typeof(ObjectsPropertyAttribute))] + public class ObjectsPropertyAttributeDrawer : PropertyDrawer + { + private bool isInitialized; + private bool hasValidTarget; + private string[] values; + + private int selectedIndex = -1; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (property.propertyType != SerializedPropertyType.String) + { + label.text = $"(!) {label.text}"; + label.tooltip = $"ObjectPropertiesAttribute only works on strings.\n\n{label.tooltip}"; + EditorGUI.PropertyField(position, property, label); + return; + } + + if (!isInitialized) + { + Initialize(property.stringValue); + } + + EditorGUI.BeginChangeCheck(); + selectedIndex = Array.IndexOf(values, property.stringValue); + selectedIndex = EditorGUI.Popup(position, label.text, selectedIndex, values); + if (EditorGUI.EndChangeCheck()) + { + property.stringValue = values[selectedIndex]; + property.serializedObject.ApplyModifiedProperties(); + } + } + + private void Initialize(string currentValue) + { + ObjectsPropertyAttribute objectsPropertyAttribute = (ObjectsPropertyAttribute)attribute; + + List objectsOfType = + AssetUtility.FindAllAssetsInProject(objectsPropertyAttribute.objectType); + + const BindingFlags searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance; + FieldInfo field = objectsPropertyAttribute.objectType.GetFields(searchFlags) + .FirstOrDefault(memberInfo => memberInfo.Name == objectsPropertyAttribute.memberName); + PropertyInfo property = objectsPropertyAttribute.objectType.GetProperties(searchFlags) + .FirstOrDefault(memberInfo => memberInfo.Name == objectsPropertyAttribute.memberName); + + if (field != null) + { + values = objectsOfType.Select(obj => field.GetValue(obj).ToString()).ToArray(); + } + else if (property != null) + { + values = objectsOfType.Select(obj => property.GetValue(obj).ToString()).ToArray(); + } + else + { + values = new string[] { }; + Debug.LogError($"Cannot find member '{objectsPropertyAttribute.memberName}' for type '{objectsPropertyAttribute.objectType.Name}'"); + } + + isInitialized = true; + selectedIndex = Array.IndexOf(values, currentValue); + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ObjectsPropertyAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/ObjectsPropertyAttribute.cs.meta new file mode 100644 index 0000000..ae030c3 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ObjectsPropertyAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01fff4c8e285bbc43b74bc4b6e76eb04 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/OnValueChangedAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/OnValueChangedAttribute.cs new file mode 100644 index 0000000..65465f2 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/OnValueChangedAttribute.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace InspectorToolkit { + public class OnValueChangedAttribute : PropertyAttribute { + public readonly string methodName; + public OnValueChangedAttribute(string methodName) { + this.methodName = methodName; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + + [CustomPropertyDrawer(typeof(OnValueChangedAttribute))] + public class OnValueChangedAttributeDrawer : PropertyDrawer { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + EditorGUI.BeginChangeCheck(); + EditorGUI.PropertyField(position, property, label); + if(EditorGUI.EndChangeCheck()) { + property.serializedObject.ApplyModifiedProperties(); + + Object target = property.serializedObject.targetObject; + string methodName = ((OnValueChangedAttribute)attribute).methodName; + MethodInfo methodInfo = target.GetType().GetMethod(methodName); + if (methodInfo != null) { + methodInfo.Invoke(target, null); + } + else { + Debug.LogError($"No method named '{methodName}' found on {target}", target); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/OnValueChangedAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/OnValueChangedAttribute.cs.meta new file mode 100644 index 0000000..205ecb9 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/OnValueChangedAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88837c4ffdcc87f4c927e0ef8d2c4ba5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/OptionalAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/OptionalAttribute.cs new file mode 100644 index 0000000..c0e3d60 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/OptionalAttribute.cs @@ -0,0 +1,54 @@ +using UnityEditor; +using UnityEngine; + +namespace InspectorToolkit { + public class OptionalAttribute : PropertyAttribute { } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + + [CustomPropertyDrawer(typeof(OptionalAttribute))] + public class OptionalAttributePropertyDrawer : PropertyDrawer { + private const float ICON_X_OFFSET = 17f; + private const float ICON_SIZE = 18f; + private GUIContent icon; + private GUIStyle style; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + if (property.propertyType != SerializedPropertyType.ObjectReference) { + Debug.LogError($"Optional should only be used on Unity Object references. Target='{property.serializedObject.targetObject.name}.{property.propertyPath}'"); + EditorGUI.PropertyField(position, property, label); + return; + } + + if(icon == null || style == null) { + icon = new GUIContent(EditorGUIUtility.IconContent("DotFrameDotted").image, "Optional"); + style = new GUIStyle(GUI.skin.label) + { + padding = new RectOffset(0, 0, 0, 0), + imagePosition = ImagePosition.ImageOnly + }; + } + + if(property.objectReferenceValue == null) { + Rect iconRect = new Rect(position.x - ICON_X_OFFSET, position.y + 1, ICON_SIZE, EditorGUIUtility.singleLineHeight); + GUI.Label(iconRect, icon, style); + } + + label = EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, label); + + EditorGUI.BeginChangeCheck(); + + EditorGUI.PropertyField(position, property, GUIContent.none); + + if(EditorGUI.EndChangeCheck()) { + property.serializedObject.ApplyModifiedProperties(); + } + + EditorGUI.EndProperty(); + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/OptionalAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/OptionalAttribute.cs.meta new file mode 100644 index 0000000..94ab3a3 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/OptionalAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 303db5223d8c8674791938a5180540e1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/OverrideLabelAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/OverrideLabelAttribute.cs new file mode 100644 index 0000000..7383a9b --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/OverrideLabelAttribute.cs @@ -0,0 +1,28 @@ +using UnityEngine; + +namespace InspectorToolkit +{ + public class OverrideLabelAttribute : PropertyAttribute + { + public readonly string NewLabel; + + public OverrideLabelAttribute(string newLabel) => NewLabel = newLabel; + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + + [CustomPropertyDrawer(typeof(OverrideLabelAttribute))] + public class OverrideLabelDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + label.text = ((OverrideLabelAttribute)attribute).NewLabel; + EditorGUI.PropertyField(position, property, label); + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/OverrideLabelAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/OverrideLabelAttribute.cs.meta new file mode 100644 index 0000000..e12bd20 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/OverrideLabelAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a3118b2fddf33042bce8519708bf318 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/PositiveValueOnlyAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/PositiveValueOnlyAttribute.cs new file mode 100644 index 0000000..d37bd6d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/PositiveValueOnlyAttribute.cs @@ -0,0 +1,161 @@ +// ---------------------------------------------------------------------------- +// Author: Kaynn, Yeo Wen Qin +// https://github.com/Kaynn-Cahya +// Date: 17/02/2019 +// ---------------------------------------------------------------------------- + + +using UnityEngine; + +namespace InspectorToolkit +{ + public class PositiveValueOnlyAttribute : PropertyAttribute + { + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + using EditorTools; + + [CustomPropertyDrawer(typeof(PositiveValueOnlyAttribute))] + public class PositiveValueOnlyAttributeDrawer : PropertyDrawer + { + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return EditorGUI.GetPropertyHeight(property); + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (!property.IsNumerical()) + { + MyGUI.DrawColouredRect(position, MyGUI.Colors.Red); + EditorGUI.LabelField(position, new GUIContent("", "[PositiveValueOnly] used with non-numeric property")); + } + else + { + if (HandleNegativeValues(property)) property.serializedObject.ApplyModifiedProperties(); + } + + EditorGUI.PropertyField(position, property, label, true); + } + + + /// + /// Set number value to 0 if less than 0 + /// + /// true if fixed + private bool HandleNegativeValues(SerializedProperty property) + { + switch (property.propertyType) + { + case SerializedPropertyType.Float: + case SerializedPropertyType.Integer: + return HandleNumerics(property); + + case SerializedPropertyType.Vector2: + case SerializedPropertyType.Vector3: + case SerializedPropertyType.Vector4: + return HandleVectors(property); + + case SerializedPropertyType.Vector2Int: + case SerializedPropertyType.Vector3Int: + return HandleIntVectors(property); + } + + return false; + } + + + private bool HandleNumerics(SerializedProperty property) + { + if (property.propertyType == SerializedPropertyType.Integer && property.intValue < 0) + { + property.intValue = 0; + return true; + } + + if (property.propertyType == SerializedPropertyType.Float && property.floatValue < 0) + { + property.floatValue = 0; + return true; + } + + return false; + } + + + private bool HandleVectors(SerializedProperty property) + { + Vector4 vector = Vector4.zero; + switch (property.propertyType) + { + case SerializedPropertyType.Vector2: + vector = property.vector2Value; + break; + case SerializedPropertyType.Vector3: + vector = property.vector3Value; + break; + case SerializedPropertyType.Vector4: + vector = property.vector4Value; + break; + } + + bool handled = false; + for (int i = 0; i < 4; ++i) + { + if (vector[i] < 0f) + { + vector[i] = 0; + handled = true; + } + } + + switch (property.propertyType) + { + case SerializedPropertyType.Vector2: + property.vector2Value = vector; + break; + case SerializedPropertyType.Vector3: + property.vector3Value = vector; + break; + case SerializedPropertyType.Vector4: + property.vector4Value = vector; + break; + } + + return handled; + } + + + private bool HandleIntVectors(SerializedProperty property) + { + if (property.propertyType == SerializedPropertyType.Vector2Int) + { + var vector = property.vector2IntValue; + if (vector.x > 0 && vector.y > 0) return false; + property.vector2IntValue = new Vector2Int( + vector.x < 0 ? 0 : vector.x, + vector.y < 0 ? 0 : vector.y); + return true; + } + + if (property.propertyType == SerializedPropertyType.Vector3Int) + { + var vector = property.vector3IntValue; + if (vector.x > 0 && vector.y > 0 && vector.z > 0) return false; + property.vector3Value = new Vector3( + vector.x < 0 ? 0 : vector.x, + vector.y < 0 ? 0 : vector.y, + vector.z < 0 ? 0 : vector.z); + return true; + } + + return false; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/PositiveValueOnlyAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/PositiveValueOnlyAttribute.cs.meta new file mode 100644 index 0000000..ed0ebf0 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/PositiveValueOnlyAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f63e31912630bf4e88f4f2e9eab2f7b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ReadOnlyAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/ReadOnlyAttribute.cs new file mode 100644 index 0000000..33adf86 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ReadOnlyAttribute.cs @@ -0,0 +1,48 @@ +using UnityEngine; + +namespace InspectorToolkit +{ + public class ReadOnlyAttribute : ConditionalFieldAttribute + { + public ReadOnlyAttribute() : base("") { } + + /// String name of field to check value + /// Inverse check result + /// On which values field will be shown in inspector + public ReadOnlyAttribute(string fieldToCheck, bool inverse = false, params object[] compareValues) + : base(fieldToCheck, inverse, compareValues) { } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + + [CustomPropertyDrawer(typeof(ReadOnlyAttribute))] + public class ReadOnlyAttributeDrawer : PropertyDrawer + { + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return EditorGUI.GetPropertyHeight(property, label, true); + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (!(attribute is ReadOnlyAttribute conditional)) return; + + bool enabled = false; + + if (!string.IsNullOrEmpty(conditional.FieldToCheck)) + { + var propertyToCheck = ConditionalFieldUtility.FindRelativeProperty(property, conditional.FieldToCheck); + enabled = !ConditionalFieldUtility.PropertyIsVisible(propertyToCheck, conditional.Inverse, conditional.CompareValues); + } + + GUI.enabled = enabled; + EditorGUI.PropertyField(position, property, label, true); + GUI.enabled = true; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ReadOnlyAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/ReadOnlyAttribute.cs.meta new file mode 100644 index 0000000..c1092fc --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ReadOnlyAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44c78368338beb948bdffa8db7179a0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RegexStringAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/RegexStringAttribute.cs new file mode 100644 index 0000000..bf58fa9 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RegexStringAttribute.cs @@ -0,0 +1,121 @@ +using System.Text.RegularExpressions; +using UnityEngine; + +namespace InspectorToolkit +{ + /// + /// Validate a string field by regex expression + /// RegexMode.Match will keep only matching content + /// RegexMode.Replace will keep all except regex match + /// + public class RegexStringAttribute : PropertyAttribute + { + public readonly Regex Regex; + public readonly RegexStringMode AttributeMode; + + public RegexStringAttribute(string regex, RegexStringMode mode = RegexStringMode.Match, RegexOptions options = RegexOptions.None) + { + Regex = new Regex(regex, options); + AttributeMode = mode; + } + } + + public enum RegexStringMode + { + /// + /// Keep only parts of the string that Match the Expression + /// + Match, + /// + /// Remove from the string parts that not match the Expression + /// + Replace, + /// + /// Highlight the field if any of the parts of the string matching the Expression + /// + WarningIfMatch, + /// + /// Highlight the field if some parts of the string not matching the Expression + /// + WarningIfNotMatch + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + using EditorTools; + + [CustomPropertyDrawer(typeof(RegexStringAttribute))] + public class RegexStringAttributeDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (property.propertyType != SerializedPropertyType.String) + { + MyGUI.DrawColouredRect(position, MyGUI.Colors.Red); + EditorGUI.LabelField(position, new GUIContent("", "[RegexStringAttribute] used with non-string property")); + } + else + { + var regex = (RegexStringAttribute)attribute; + var mode = regex.AttributeMode; + bool ifMatch = mode == RegexStringMode.WarningIfMatch; + bool ifNotMatch = mode == RegexStringMode.WarningIfNotMatch; + bool anyMatching = regex.Regex.IsMatch(property.stringValue); + bool warn = (ifMatch && anyMatching) || (ifNotMatch && !anyMatching); + var originalPosition = position; + + DrawWarning(); + position.width -= 20; + EditorGUI.PropertyField(position, property, label, true); + DrawTooltip(); + + if (GUI.changed) + { + if (mode == RegexStringMode.Replace) OnReplace(); + if (mode == RegexStringMode.Match) OnKeepMatching(); + } + + if (warn) + { + GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.objectField); + position = originalPosition; + position.y += EditorGUIUtility.singleLineHeight; + DrawWarning(); + position.x += EditorGUIUtility.labelWidth; + var warningContent = new GUIContent("Regex rule violated!"); + EditorGUI.LabelField(position, warningContent, EditorStyles.miniBoldLabel); + } + + property.serializedObject.ApplyModifiedProperties(); + + + void OnReplace() => property.stringValue = regex.Regex.Replace(property.stringValue, ""); + void OnKeepMatching() => property.stringValue = regex.Regex.KeepMatching(property.stringValue); + + void DrawWarning() + { + if (!ifMatch && !ifNotMatch) return; + MyGUI.DrawColouredRect(position, warn ? MyGUI.Colors.Yellow : Color.clear); + } + + void DrawTooltip() + { + string tooltip = "Regex field: "; + if (mode == RegexStringMode.Match || mode == RegexStringMode.WarningIfNotMatch) tooltip += "match expression"; + else tooltip += "remove expression"; + tooltip += $"\n[{regex.Regex}]"; + + position.x += position.width + 2; + position.width = 18; + var tooltipContent = new GUIContent(MyGUI.EditorIcons.Help); + tooltipContent.tooltip = tooltip; + EditorGUI.LabelField(position, tooltipContent, EditorStyles.label); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RegexStringAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/RegexStringAttribute.cs.meta new file mode 100644 index 0000000..f856421 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RegexStringAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 38cf7a3ca1a04872b56d3cc1d90997b6 +timeCreated: 1623076361 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireArraySizeAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/RequireArraySizeAttribute.cs new file mode 100644 index 0000000..d2364e1 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireArraySizeAttribute.cs @@ -0,0 +1,86 @@ +using System; +using InspectorToolkit.Internal; +using Jovian.Utilities.Editor; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit { + public class RequireArraySizeAttribute : ArrayAttribute, IPropertyCondition { + + public readonly int size; + public readonly SizeComparison comparison; + + public enum SizeComparison { + + Equal, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual + + } + + public RequireArraySizeAttribute(int size, SizeComparison comparison = SizeComparison.Equal) { + this.size = size; + this.comparison = comparison; + } + +#if UNITY_EDITOR + private bool DoesArraySizeMeetCondition(int arraySize) { + return comparison switch { + SizeComparison.Equal => arraySize == size, + SizeComparison.LessThan => arraySize < size, + SizeComparison.LessThanOrEqual => arraySize <= size, + SizeComparison.GreaterThan => arraySize > size, + SizeComparison.GreaterThanOrEqual => arraySize >= size, + _ => throw new ArgumentOutOfRangeException() + }; + } + + private string GetConditionString(int arraySize) { + return comparison switch { + SizeComparison.Equal => $"Array.size must be equal to {size} (current={arraySize})", + SizeComparison.LessThan => $"Array.size must be less than {size} (current={arraySize})", + SizeComparison.LessThanOrEqual => $"Array.size must be less than or equal to {size} (current={arraySize})", + SizeComparison.GreaterThan => $"Array.size must be greater than {size} (current={arraySize})", + SizeComparison.GreaterThanOrEqual => $"Array.size must be greater than or equal to {size} (current={arraySize})", + _ => throw new ArgumentOutOfRangeException() + }; + } + + public bool DoesPropertyMeetCondition(SerializedProperty property, out string description) { + description = string.Empty; + if (!property.isArray && (property.propertyPath.EndsWith(".Array.size") || EditorSerializationUtility.IsPropertyAnArrayElement(property))) { + return true; // don't check array conditions for elements + } + + if (!property.isArray) { + description = $"{property.name} must be an array"; + return false; + } + + if (!(property.isArray && DoesArraySizeMeetCondition(property.arraySize))) { + description = GetConditionString(property.arraySize); + return false; + } + + return true; + } + + public override void OnPreGUI(ref Rect position, SerializedProperty property, ref GUIContent label) { + if (!DoesPropertyMeetCondition(property, out string description)) { + RequiredUtil.LayoutRequired(ref position, description, indent: true); + } + } + + public override void OnPostGUI(ref Rect position, SerializedProperty property, ref GUIContent label) { + if (!DoesPropertyMeetCondition(property, out string description)) { + RequiredUtil.DrawRequired(ref position, description, indent: true); + } + } +#endif + + } +} diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireArraySizeAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/RequireArraySizeAttribute.cs.meta new file mode 100644 index 0000000..ae87dac --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireArraySizeAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ebe88cf8a45d4df19c15769d67ccbf11 +timeCreated: 1709801667 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerAttribute.cs new file mode 100644 index 0000000..1124921 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerAttribute.cs @@ -0,0 +1,21 @@ +using System; + +namespace InspectorToolkit +{ + [AttributeUsage(AttributeTargets.Class)] + public class RequireLayerAttribute : Attribute + { + public readonly string LayerName; + public readonly int LayerIndex = -1; + + public RequireLayerAttribute(string layer) + { + LayerName = layer; + } + + public RequireLayerAttribute(int layer) + { + LayerIndex = layer; + } + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerAttribute.cs.meta new file mode 100644 index 0000000..3acb38d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a239ed93c95b4382a478c97d2bec7768 +timeCreated: 1567174003 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerOrTagAttributeHandler.cs b/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerOrTagAttributeHandler.cs new file mode 100644 index 0000000..bd52f27 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerOrTagAttributeHandler.cs @@ -0,0 +1,54 @@ +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + using UnityEngine; + + [InitializeOnLoad] + public class RequireLayerOrTagAttributeHandler + { + static RequireLayerOrTagAttributeHandler() + { + EditorApplication.playModeStateChanged += AutoSaveWhenPlaymodeStarts; + } + + private static void AutoSaveWhenPlaymodeStarts(PlayModeStateChange obj) + { + if (EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying) + { + var components = Object.FindObjectsOfType(); + foreach (var component in components) + { + foreach (var attribute in component.GetType().GetCustomAttributes(true)) + { + var layerAttribute = attribute as RequireLayerAttribute; + if (layerAttribute != null) + { + var requiredLayer = layerAttribute.LayerName != null ? + LayerMask.NameToLayer(layerAttribute.LayerName) : + layerAttribute.LayerIndex; + if (component.gameObject.layer == requiredLayer) continue; + + Debug.LogWarning("Layer of " + component.name + " changed by RequireLayerAttribute to " + layerAttribute.LayerName); + component.gameObject.layer = requiredLayer; + EditorUtility.SetDirty(component); + + continue; + } + + var tagAttribute = attribute as RequireTagAttribute; + if (tagAttribute != null) + { + if (component.CompareTag(tagAttribute.Tag)) continue; + + Debug.LogWarning("Tag of " + component.name + " changed by RequireTagAttribute to " + tagAttribute.Tag); + component.gameObject.tag = tagAttribute.Tag; + EditorUtility.SetDirty(component); + } + } + } + } + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerOrTagAttributeHandler.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerOrTagAttributeHandler.cs.meta new file mode 100644 index 0000000..9ef1291 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireLayerOrTagAttributeHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b54e6bb2f8b4c84abe977357edeb0fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireMethodAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/RequireMethodAttribute.cs new file mode 100644 index 0000000..3b31ae3 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireMethodAttribute.cs @@ -0,0 +1,95 @@ +using UnityEngine; + +#if UNITY_EDITOR +using System.Reflection; +using UnityEditor; +using InspectorToolkit.EditorTools; +#endif + +namespace InspectorToolkit { + /// + /// Does not work on objects with children - will break their UI + /// + public class RequireMethodAttribute : PropertyAttribute, IPropertyCondition { + + private readonly string methodName; + + public RequireMethodAttribute(string methodName) { + this.methodName = methodName; + } + +#if UNITY_EDITOR + public bool DoesPropertyMeetCondition(SerializedProperty property, out string description) { + Object target = property.serializedObject.targetObject; + MethodInfo methodInfo = target.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + description = string.Empty; + if (methodInfo != null && methodInfo.ReturnParameter?.ParameterType == typeof(bool)) { + ParameterInfo[] methodParameters = methodInfo.GetParameters(); + + object[] parameterValues = null; + if (methodParameters.Length == 1 && methodParameters[0].ParameterType == property.GetValue().GetType()) { + parameterValues = new[] { property.GetValue() }; + } + + object returnValue = methodInfo.Invoke(target, parameterValues); + if (returnValue is bool boolReturnValue) { + if (!boolReturnValue) { + description = $"{property.name} is failing {methodName}."; + return false; + } + + return true; + } + else { + description = $"{property.name} {methodName} is invalid - does not return a boolean."; + return false; + } + } + + description = $"{property.name} {methodName} is invalid - no method can be found on {target}."; + return false; + } +#endif + + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + [CustomPropertyDrawer(typeof(RequireMethodAttribute))] + public class RequireMethodAttributePropertyDrawer : PropertyDrawer { + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + RequireMethodAttribute requireMethodAttribute = (RequireMethodAttribute)attribute; + + if (property.hasChildren) { + EditorGUI.LabelField(position, new GUIContent(property.displayName), new GUIContent("RequireMethod GUI does not work on properties with children")); + return; + } + + Color guiColor = GUI.color; + + if (!requireMethodAttribute.DoesPropertyMeetCondition(property, out string description)) { + RequiredUtil.LayoutRequired(ref position, description, false); + RequiredUtil.DrawRequired(ref position, description, false); + } + + label = EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, label); + + EditorGUI.BeginChangeCheck(); + + EditorGUI.PropertyField(position, property, GUIContent.none); + + if (EditorGUI.EndChangeCheck()) { + property.serializedObject.ApplyModifiedProperties(); + } + + EditorGUI.EndProperty(); + + GUI.color = guiColor; + } + + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireMethodAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/RequireMethodAttribute.cs.meta new file mode 100644 index 0000000..e14f25e --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireMethodAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1587de26320cce74981351e58d6e7b26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireTagAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/RequireTagAttribute.cs new file mode 100644 index 0000000..b50d775 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireTagAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace InspectorToolkit +{ + [AttributeUsage(AttributeTargets.Class)] + public class RequireTagAttribute : Attribute + { + public string Tag; + + public RequireTagAttribute(string tag) + { + Tag = tag; + } + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequireTagAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/RequireTagAttribute.cs.meta new file mode 100644 index 0000000..609d547 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequireTagAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4ab3a785f0a04eeeb2b90de6430cef9e +timeCreated: 1567412854 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequiredAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/RequiredAttribute.cs new file mode 100644 index 0000000..702d78c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequiredAttribute.cs @@ -0,0 +1,280 @@ +using System; +using UnityEngine; +using Object = UnityEngine.Object; + +#if UNITY_EDITOR +using UnityEditor; +using UnityEditor.SceneManagement; +#endif + +namespace InspectorToolkit { + public class RequiredAttribute : PropertyAttribute, IPropertyCondition { + + public enum Scope { + + None, + SelfOrChild, + SceneOnly, + SceneOrParentPrefabOnly + // ProjectOnly + + /* + * ProjectOnly has a quirk I can't figure out. If you reference a prefab in the project then it works fine. + * However if you also have an instance of that prefab if the same scene/prefab then it will become a reference to that instance + * instead of the one in the project. Even the icon changes in Unity. I think this may be a Unity Bug. + */ + + } + + public readonly Scope scope; + + public RequiredAttribute() { + this.scope = Scope.None; + } + + public RequiredAttribute(Scope scope) { + this.scope = scope; + } + +#if UNITY_EDITOR + public bool DoesPropertyMeetCondition(SerializedProperty property, out string description) { + description = string.Empty; + + if (property.propertyType != SerializedPropertyType.ObjectReference) { + description = $"{property.name} is not an ObjectReference"; + return false; + } + + bool hasValue = property.objectReferenceValue != null; + + if (scope == Scope.None) { + if (!hasValue) { + description = $"{property.name} requires a value"; + return false; + } + + return true; + } + + if (scope == Scope.SelfOrChild) { + if (!hasValue) { + description = $"{property.name} requires a value"; + return false; + } + + if (!TryGetTargetGameObject(property, out GameObject parentGameObject)) { + description = $"{property.name} does not have a GameObject parent"; + return false; + } + + if (!TryGetGameObject(property.objectReferenceValue, out GameObject childGameObject)) { + description = $"{property.name} has a non-GameObject value"; + return false; + } + + if (!IsObjectSelfOrChild(parentGameObject, childGameObject)) { + description = $"{property.name} value is not itself or it's child"; + return false; + } + + return true; + } + + if (scope == Scope.SceneOnly) { + if (property.serializedObject.targetObject is ScriptableObject) { + description = $"{property.name} SceneOnly is not supported on ScriptableObjects"; + return false; + } + + if (!TryGetTargetGameObject(property, out GameObject thisGameObject)) { + description = $"{property.name} does not have a GameObject parent"; + return false; + } + + // scene is invalid - this is a prefab in the project. + if (thisGameObject.scene.IsValid() == false) { + // scene isn't valid, can't check + description = $"{property.name} has a reference to GameObject not in a Scene"; + return true; + } + + PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + // scene is valid - prefab stage exists - this is a prefab + if (prefabStage != null) { + return true; + } + + if (!hasValue) { + description = $"{property.name} requires a value"; + return false; + } + + return true; + } + + if (scope == Scope.SceneOrParentPrefabOnly) { + if (property.serializedObject.targetObject is ScriptableObject) { + description = $"{property.name} SceneOrParentPrefabOnly is not supported on ScriptableObjects"; + return false; + } + + if (!TryGetTargetGameObject(property, out GameObject thisGameObject)) { + description = $"{property.name} does not have a GameObject parent"; + return false; + } + + // scene is invalid - this is a prefab in the project. + if (thisGameObject.scene.IsValid() == false) { + // scene isn't valid, can't check + description = $"{property.name} has a reference to GameObject not in a Scene"; + return true; + } + + bool hasTargetGameObject = TryGetGameObject(property.objectReferenceValue, out GameObject targetGameObject); + + PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + // scene is valid - prefab stage exists - this is a prefab + if (prefabStage != null) { + // if the prefab open has this requirement on its root object + if (prefabStage.prefabContentsRoot == thisGameObject) { + if (hasTargetGameObject) { + // if the value assigned is a gameObject + if (!IsObjectSelfOrChild(thisGameObject, targetGameObject)) { + // then fail if it's within the prefab + description = $"{property.name} requires an object external to its prefab / hierarchy"; + return false; + } + else { + return true; + } + } + else { + return true; // if we don't have a target then it is valid (only inside this prefab of ourselves) + } + } + + // if the prefab open is a different prefab, then we're valid if a value is assigned + if (!IsObjectSelfOrChild(thisGameObject, targetGameObject)) { + description = $"{property.name} requires an object external to its prefab / hierarchy"; + return false; + } + else { + return true; + } + } + + // not in a prefab, value must be set to an object not within the scope of this attribute + if (!(hasValue && !IsObjectSelfOrChild(thisGameObject, targetGameObject))) { + description = $"{property.name} requires an object external to its prefab / hierarchy"; + return false; + } + else { + return true; + } + } + + /* + See note above why this is not enabled + + if (scope == Scope.ProjectOnly) { + if (!hasValue) { + return false; + } + + bool isPersistent = EditorUtility.IsPersistent(property.objectReferenceValue); + bool objectHasAssetPath = !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(property.objectReferenceValue)); + bool hasNoScene = false; + bool isPrefabRoot = false; + bool isInAssetDatabase = AssetDatabase.Contains(property.objectReferenceValue); + + if (TryGetGameObject(property.objectReferenceValue, out GameObject targetGameObject)) { + isPrefabRoot = PrefabUtility.IsOutermostPrefabInstanceRoot(targetGameObject); + if (targetGameObject.scene.IsValid() == false) { + // scene isn't valid, can't check + //return true; + hasNoScene = true; + } + } + + // if the target has a path, then it is an asset and that is valid! + return objectHasAssetPath; + }*/ + + if (!hasValue) { + description = $"{property.name} requires a value"; + return false; + } + + return true; + } + + private static bool TryGetTargetGameObject(SerializedProperty property, out GameObject gameObject) { + return TryGetGameObject(property.serializedObject.targetObject, out gameObject); + } + + private static bool TryGetGameObject(Object obj, out GameObject gameObject) { + if (obj == null) { + gameObject = null; + return false; + } + + gameObject = obj switch { + Component component => component.gameObject, + GameObject targetGameObject => targetGameObject, + _ => null + }; + return gameObject != null; + } + + private static bool IsObjectSelfOrChild(GameObject parentObject, GameObject queryObject) { + if (parentObject == null || queryObject == null) { + return false; + } + + return parentObject == queryObject || queryObject.transform.IsChildOf(parentObject.transform); + } +#endif + + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + [CustomPropertyDrawer(typeof(RequiredAttribute))] + public class RequiredAttributePropertyDrawer : PropertyDrawer { + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + if (property.propertyType != SerializedPropertyType.ObjectReference) { + Debug.LogError($"Required should only be used on Unity Object references. Target='{property.serializedObject.targetObject.name}.{property.propertyPath}'"); + EditorGUI.PropertyField(position, property, label); + return; + } + + RequiredAttribute requiredAttribute = (RequiredAttribute)attribute; + + Color guiColor = GUI.color; + + if (!requiredAttribute.DoesPropertyMeetCondition(property, out string text)) { + RequiredUtil.LayoutRequired(ref position, text, false); + RequiredUtil.DrawRequired(ref position, text, false); + } + + label = EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, label); + + EditorGUI.BeginChangeCheck(); + + EditorGUI.PropertyField(position, property, GUIContent.none); + + if (EditorGUI.EndChangeCheck()) { + property.serializedObject.ApplyModifiedProperties(); + } + + EditorGUI.EndProperty(); + + GUI.color = guiColor; + } + + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/RequiredAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/RequiredAttribute.cs.meta new file mode 100644 index 0000000..354111d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/RequiredAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 20675fbb6f9e5764a9aa6e8efb4b8150 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SceneAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/SceneAttribute.cs new file mode 100644 index 0000000..d15b546 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SceneAttribute.cs @@ -0,0 +1,77 @@ +// ---------------------------------------------------------------------------- +// Author: Anton +// https://github.com/antontidev +// ---------------------------------------------------------------------------- + +using System; +using System.Linq; +using UnityEngine; + +namespace InspectorToolkit +{ + /// + /// Used to pick scene from inspector. + /// Consider to use type instead as it is more flexible + /// + [AttributeUsage(AttributeTargets.Field)] + public class SceneAttribute : PropertyAttribute + { + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + + [CustomPropertyDrawer(typeof(SceneAttribute))] + public class SceneDrawer : PropertyDrawer + { + private SceneAttribute _attribute; + private string[] _scenesInBuild; + private int _index; + + private void Initialize(string initialValue) + { + if (_attribute != null) return; + + _attribute = (SceneAttribute)attribute; + + _scenesInBuild = new string[EditorBuildSettings.scenes.Length + 1]; + + _index = 0; + for (var i = 0; i < EditorBuildSettings.scenes.Length; i++) + { + var formatted = EditorBuildSettings.scenes[i].path.Split('/').Last().Replace(".unity", string.Empty); + if (initialValue == formatted) _index = i + 1; + formatted += $" [{i}]"; + _scenesInBuild[i + 1] = formatted; + } + + var defaultValue = "NULL"; + if (initialValue.NotNullOrEmpty() && _index == 0) defaultValue = "NOT FOUND: " + initialValue; + _scenesInBuild[0] = defaultValue; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (property.propertyType != SerializedPropertyType.String) + { + EditorGUI.LabelField(position, label.text, "Use [Scene] with strings."); + return; + } + + Initialize(property.stringValue); + + var newIndex = EditorGUI.Popup(position, label.text, _index, _scenesInBuild); + if (newIndex != _index) + { + _index = newIndex; + var value = _scenesInBuild[_index]; + property.stringValue = newIndex == 0 ? string.Empty : value.Substring(0, value.IndexOf('[') - 1); + property.serializedObject.ApplyModifiedProperties(); + } + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SceneAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/SceneAttribute.cs.meta new file mode 100644 index 0000000..70e280b --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SceneAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2e2f0d183cdeeb4a9e0d11deb8f782b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SearchableEnumAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/SearchableEnumAttribute.cs new file mode 100644 index 0000000..86db54d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SearchableEnumAttribute.cs @@ -0,0 +1,496 @@ +// ---------------------------------------------------------------------------- +// Author: Ryan Hipple +// Date: 05/01/2018 +// Source: https://github.com/roboryantron/UnityEditorJunkie +// ---------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using UnityEngine; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit +{ + /// + /// Put this attribute on a public (or SerializeField) enum in a + /// MonoBehaviour or ScriptableObject to get an improved enum selector + /// popup. The enum list is scrollable and can be filtered by typing. + /// + [AttributeUsage(AttributeTargets.Field)] + public class SearchableEnumAttribute : PropertyAttribute + { + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + /// + /// Draws the custom enum selector popup for enum fields using the + /// SearchableEnumAttribute. + /// + [CustomPropertyDrawer(typeof(SearchableEnumAttribute))] + public class SearchableEnumAttributeDrawer : PropertyDrawer + { + private const string TYPE_ERROR = "SearchableEnum can only be used on enum fields."; + + /// + /// Cache of the hash to use to resolve the ID for the drawer. + /// + private int idHash; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + // If this is not used on an enum, show an error + if (property.type != "Enum") + { + GUIStyle errorStyle = "CN EntryErrorIconSmall"; + Rect r = new Rect(position); + r.width = errorStyle.fixedWidth; + position.xMin = r.xMax; + GUI.Label(r, "", errorStyle); + GUI.Label(position, TYPE_ERROR); + return; + } + + // By manually creating the control ID, we can keep the ID for the + // label and button the same. This lets them be selected together + // with the keyboard in the inspector, much like a normal popup. + if (idHash == 0) idHash = "SearchableEnumAttributeDrawer".GetHashCode(); + int id = GUIUtility.GetControlID(idHash, FocusType.Keyboard, position); + + label = EditorGUI.BeginProperty(position, label, property); + position = EditorGUI.PrefixLabel(position, id, label); + + GUIContent buttonText = + new GUIContent(property.enumDisplayNames[property.enumValueIndex]); + if (DropdownButton(id, position, buttonText)) + { + Action onSelect = i => + { + property.enumValueIndex = i; + property.serializedObject.ApplyModifiedProperties(); + }; + + SearchablePopup.Show(position, property.enumDisplayNames, + property.enumValueIndex, onSelect); + } + + EditorGUI.EndProperty(); + } + + /// + /// A custom button drawer that allows for a controlID so that we can + /// sync the button ID and the label ID to allow for keyboard + /// navigation like the built-in enum drawers. + /// + private static bool DropdownButton(int id, Rect position, GUIContent content) + { + Event current = Event.current; + switch (current.type) + { + case EventType.MouseDown: + if (position.Contains(current.mousePosition) && current.button == 0) + { + Event.current.Use(); + return true; + } + + break; + case EventType.KeyDown: + if (GUIUtility.keyboardControl == id && current.character == '\n') + { + Event.current.Use(); + return true; + } + + break; + case EventType.Repaint: + EditorStyles.popup.Draw(position, content, id, false); + break; + } + + return false; + } + } + + + /// + /// A popup window that displays a list of options and may use a search + /// string to filter the displayed content. + /// + public class SearchablePopup : PopupWindowContent + { + #region -- Constants -------------------------------------------------- + + /// Height of each element in the popup list. + private const float ROW_HEIGHT = 16.0f; + + /// How far to indent list entries. + private const float ROW_INDENT = 8.0f; + + /// Name to use for the text field for search. + private const string SEARCH_CONTROL_NAME = "EnumSearchText"; + + #endregion -- Constants ----------------------------------------------- + + #region -- Static Functions ------------------------------------------- + + /// Show a new SearchablePopup. + /// + /// Rectangle of the button that triggered the popup. + /// + /// List of strings to choose from. + /// + /// Index of the currently selected string. + /// + /// + /// Callback to trigger when a choice is made. + /// + public static void Show(Rect activatorRect, string[] options, int current, Action onSelectionMade) + { + SearchablePopup win = + new SearchablePopup(options, current, onSelectionMade); + PopupWindow.Show(activatorRect, win); + } + + /// + /// Force the focused window to redraw. This can be used to make the + /// popup more responsive to mouse movement. + /// + private static void Repaint() + { + EditorWindow.focusedWindow.Repaint(); + } + + /// Draw a generic box. + /// Where to draw. + /// Color to tint the box. + private static void DrawBox(Rect rect, Color tint) + { + Color c = GUI.color; + GUI.color = tint; + GUI.Box(rect, "", Selection); + GUI.color = c; + } + + #endregion -- Static Functions ---------------------------------------- + + #region -- Helper Classes --------------------------------------------- + + /// + /// Stores a list of strings and can return a subset of that list that + /// matches a given filter string. + /// + private class FilteredList + { + /// + /// An entry in the filtered list, mapping the text to the + /// original index. + /// + public struct Entry + { + public int index; + public string text; + } + + /// All possible items in the list. + private readonly string[] allItems; + + /// Create a new filtered list. + /// All The items to filter. + public FilteredList(string[] items) + { + allItems = items; + Entries = new List(); + UpdateFilter(""); + } + + /// The current string filtering the list. + public string Filter { get; private set; } + + /// All valid entries for the current filter. + public List Entries { get; private set; } + + /// Total possible entries in the list. + public int MaxLength + { + get { return allItems.Length; } + } + + /// + /// Sets a new filter string and updates the Entries that match the + /// new filter if it has changed. + /// + /// String to use to filter the list. + /// + /// True if the filter is updated, false if newFilter is the same + /// as the current Filter and no update is necessary. + /// + public bool UpdateFilter(string filter) + { + if (Filter == filter) + return false; + + Filter = filter; + Entries.Clear(); + + for (int i = 0; i < allItems.Length; i++) + { + if (string.IsNullOrEmpty(Filter) || allItems[i].ToLower().Contains(Filter.ToLower())) + { + Entry entry = new Entry + { + index = i, + text = allItems[i] + }; + if (string.Equals(allItems[i], Filter, StringComparison.CurrentCultureIgnoreCase)) + Entries.Insert(0, entry); + else + Entries.Add(entry); + } + } + + return true; + } + } + + #endregion -- Helper Classes ------------------------------------------ + + #region -- Private Variables ------------------------------------------ + + /// Callback to trigger when an item is selected. + private readonly Action onSelectionMade; + + /// + /// Index of the item that was selected when the list was opened. + /// + private readonly int currentIndex; + + /// + /// Container for all available options that does the actual string + /// filtering of the content. + /// + private readonly FilteredList list; + + /// Scroll offset for the vertical scroll area. + private Vector2 scroll; + + /// + /// Index of the item under the mouse or selected with the keyboard. + /// + private int hoverIndex; + + /// + /// An item index to scroll to on the next draw. + /// + private int scrollToIndex; + + /// + /// An offset to apply after scrolling to scrollToIndex. This can be + /// used to control if the selection appears at the top, bottom, or + /// center of the popup. + /// + private float scrollOffset; + + #endregion -- Private Variables --------------------------------------- + + #region -- GUI Styles ------------------------------------------------- + + // GUIStyles implicitly cast from a string. This triggers a lookup into + // the current skin which will be the editor skin and lets us get some + // built-in styles. + + private static readonly GUIStyle SearchBox = "ToolbarSearchTextField"; + private static readonly GUIStyle CancelButton = "ToolbarSearchCancelButton"; + private static readonly GUIStyle DisabledCancelButton = "ToolbarSearchCancelButtonEmpty"; + private static readonly GUIStyle Selection = "SelectionRect"; + + #endregion -- GUI Styles ---------------------------------------------- + + #region -- Initialization --------------------------------------------- + + private SearchablePopup(string[] names, int currentIndex, Action onSelectionMade) + { + list = new FilteredList(names); + this.currentIndex = currentIndex; + this.onSelectionMade = onSelectionMade; + + hoverIndex = currentIndex; + scrollToIndex = currentIndex; + scrollOffset = GetWindowSize().y - ROW_HEIGHT * 2; + } + + #endregion -- Initialization ------------------------------------------ + + #region -- PopupWindowContent Overrides ------------------------------- + + public override void OnOpen() + { + base.OnOpen(); + // Force a repaint every frame to be responsive to mouse hover. + EditorApplication.update += Repaint; + } + + public override void OnClose() + { + base.OnClose(); + EditorApplication.update -= Repaint; + } + + public override Vector2 GetWindowSize() + { + return new Vector2(base.GetWindowSize().x, + Mathf.Min(600, list.MaxLength * ROW_HEIGHT + + EditorStyles.toolbar.fixedHeight)); + } + + public override void OnGUI(Rect rect) + { + Rect searchRect = new Rect(0, 0, rect.width, EditorStyles.toolbar.fixedHeight); + Rect scrollRect = Rect.MinMaxRect(0, searchRect.yMax, rect.xMax, rect.yMax); + + HandleKeyboard(); + DrawSearch(searchRect); + DrawSelectionArea(scrollRect); + } + + #endregion -- PopupWindowContent Overrides ---------------------------- + + #region -- GUI -------------------------------------------------------- + + private void DrawSearch(Rect rect) + { + if (Event.current.type == EventType.Repaint) + EditorStyles.toolbar.Draw(rect, false, false, false, false); + + Rect searchRect = new Rect(rect); + searchRect.xMin += 6; + searchRect.xMax -= 6; + searchRect.y += 2; + searchRect.width -= CancelButton.fixedWidth; + + GUI.FocusControl(SEARCH_CONTROL_NAME); + GUI.SetNextControlName(SEARCH_CONTROL_NAME); + string newText = GUI.TextField(searchRect, list.Filter, SearchBox); + + if (list.UpdateFilter(newText)) + { + hoverIndex = 0; + scroll = Vector2.zero; + } + + searchRect.x = searchRect.xMax; + searchRect.width = CancelButton.fixedWidth; + + if (string.IsNullOrEmpty(list.Filter)) + GUI.Box(searchRect, GUIContent.none, DisabledCancelButton); + else if (GUI.Button(searchRect, "x", CancelButton)) + { + list.UpdateFilter(""); + scroll = Vector2.zero; + } + } + + private void DrawSelectionArea(Rect scrollRect) + { + Rect contentRect = new Rect(0, 0, + scrollRect.width - GUI.skin.verticalScrollbar.fixedWidth, + list.Entries.Count * ROW_HEIGHT); + + scroll = GUI.BeginScrollView(scrollRect, scroll, contentRect); + + Rect rowRect = new Rect(0, 0, scrollRect.width, ROW_HEIGHT); + + for (int i = 0; i < list.Entries.Count; i++) + { + if (scrollToIndex == i && + (Event.current.type == EventType.Repaint + || Event.current.type == EventType.Layout)) + { + Rect r = new Rect(rowRect); + r.y += scrollOffset; + GUI.ScrollTo(r); + scrollToIndex = -1; + scroll.x = 0; + } + + if (rowRect.Contains(Event.current.mousePosition)) + { + if (Event.current.type == EventType.MouseMove || + Event.current.type == EventType.ScrollWheel) + hoverIndex = i; + if (Event.current.type == EventType.MouseDown) + { + onSelectionMade(list.Entries[i].index); + EditorWindow.focusedWindow.Close(); + } + } + + DrawRow(rowRect, i); + + rowRect.y = rowRect.yMax; + } + + GUI.EndScrollView(); + } + + private void DrawRow(Rect rowRect, int i) + { + if (list.Entries[i].index == currentIndex) + DrawBox(rowRect, Color.cyan); + else if (i == hoverIndex) + DrawBox(rowRect, Color.white); + + Rect labelRect = new Rect(rowRect); + labelRect.xMin += ROW_INDENT; + + GUI.Label(labelRect, list.Entries[i].text); + } + + /// + /// Process keyboard input to navigate the choices or make a selection. + /// + private void HandleKeyboard() + { + if (Event.current.type == EventType.KeyDown) + { + if (Event.current.keyCode == KeyCode.DownArrow) + { + hoverIndex = Mathf.Min(list.Entries.Count - 1, hoverIndex + 1); + Event.current.Use(); + scrollToIndex = hoverIndex; + scrollOffset = ROW_HEIGHT; + } + + if (Event.current.keyCode == KeyCode.UpArrow) + { + hoverIndex = Mathf.Max(0, hoverIndex - 1); + Event.current.Use(); + scrollToIndex = hoverIndex; + scrollOffset = -ROW_HEIGHT; + } + + if (Event.current.keyCode == KeyCode.Return) + { + if (hoverIndex >= 0 && hoverIndex < list.Entries.Count) + { + onSelectionMade(list.Entries[hoverIndex].index); + EditorWindow.focusedWindow.Close(); + } + } + + if (Event.current.keyCode == KeyCode.Escape) + { + EditorWindow.focusedWindow.Close(); + } + } + } + + #endregion -- GUI ----------------------------------------------------- + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SearchableEnumAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/SearchableEnumAttribute.cs.meta new file mode 100644 index 0000000..a9aead1 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SearchableEnumAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d4c1ac06e38adc9418191cf707dc1c63 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SeparatorAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/SeparatorAttribute.cs new file mode 100644 index 0000000..31d2ec1 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SeparatorAttribute.cs @@ -0,0 +1,58 @@ +using UnityEngine; + +namespace InspectorToolkit +{ + public class SeparatorAttribute : PropertyAttribute + { + public readonly string Title; + public readonly bool WithOffset; + + + public SeparatorAttribute() + { + Title = ""; + } + + public SeparatorAttribute(string title, bool withOffset = false) + { + Title = title; + WithOffset = withOffset; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + using UnityEditor; + + [CustomPropertyDrawer(typeof(SeparatorAttribute))] + public class SeparatorAttributeDrawer : DecoratorDrawer + { + private SeparatorAttribute Separator => (SeparatorAttribute) attribute; + + public override float GetHeight() => Separator.WithOffset ? 40 : Separator.Title.IsNullOrEmpty() ? 28 : 32; + + public override void OnGUI(Rect position) + { + var title = Separator.Title; + if (title.IsNullOrEmpty()) + { + position.height = 1; + position.y += 14; + GUI.Box(position, string.Empty); + } + else + { + Vector2 textSize = GUI.skin.label.CalcSize(new GUIContent(title)); + float separatorWidth = (position.width - textSize.x) / 2 - 5; + position.y += 19; + + GUI.Box(new Rect(position.xMin, position.yMin, separatorWidth, 1), string.Empty); + GUI.Label(new Rect(position.xMin + separatorWidth + 5, position.yMin - 10, textSize.x, 20), title); + GUI.Box(new Rect(position.xMin + separatorWidth + 10 + textSize.x, position.yMin, separatorWidth, 1), ""); + } + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SeparatorAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/SeparatorAttribute.cs.meta new file mode 100644 index 0000000..57a10c0 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SeparatorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b55cf9f9427edf4f94a66bc2a6b14b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ShowInInspectorAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/ShowInInspectorAttribute.cs new file mode 100644 index 0000000..3fc25a2 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ShowInInspectorAttribute.cs @@ -0,0 +1,374 @@ +using System; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using Jovian.Utilities; +using UnityObject = UnityEngine.Object; +#endif + +namespace InspectorToolkit { + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class ShowInInspectorAttribute : PropertyAttribute { + public readonly PlayModeVisibility Visibility; + public readonly bool isEdittingSupported; + + public ShowInInspectorAttribute() { + Visibility = PlayModeVisibility.PlayMode; + isEdittingSupported = false; + } + + public ShowInInspectorAttribute(PlayModeVisibility playModeVisibility, bool isEdittingSupported = false) { + Visibility = playModeVisibility; + this.isEdittingSupported = isEdittingSupported; + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + public class ShowInInspectorAttributeHandler { + private const int MAX_DEPTH = 10; + + private bool _initialized; + private readonly UnityObject target; + private readonly SerializedObject serializedObject; + private List showMembers; + + private GUIStyle labelStyle; + private GUIStyle tooltipStyle; + + private class Member { + public MemberInfo member; + public readonly PlayModeVisibility visibility; + public readonly bool isEditingSupported; + private readonly object parent; + public bool supportsExpansion; + public bool expanded; + public Member parentMember; + public int depth; + + public string niceName; + public string name; + + public bool IsChild => parentMember != null; + public bool IsParentExpanded => parentMember.expanded; + + public Member(MemberInfo member, + PlayModeVisibility visibility, + bool isEditingSupported, + object parent, + int depth, + Member parentMember) { + this.member = member; + this.visibility = visibility; + this.isEditingSupported = isEditingSupported; + this.parent = parent; + this.parentMember = parentMember; + this.depth = depth; + name = member.Name; + niceName = ObjectNames.NicifyVariableName(name); + } + + public bool TryGetValueAndData(out object value) { + switch (member) { + case FieldInfo fieldInfo: + value = fieldInfo.GetValue(parent); + return true; + case PropertyInfo propertyInfo: + value = propertyInfo.GetValue(parent); + return true; + default: + value = null; + return false; + } + } + + public Type GetValueType() { + return member switch { + FieldInfo fieldInfo => fieldInfo.FieldType, + PropertyInfo propertyInfo => propertyInfo.PropertyType, + _ => throw new NotSupportedException() + }; + } + + public void SetValue(object value) { + switch (member) { + case FieldInfo fieldInfo: + fieldInfo.SetValue(parent, value); + break; + case PropertyInfo propertyInfo: + propertyInfo.SetValue(parent, value); + break; + } + } + } + + public bool HasValues => showMembers.Count > 0; + + public ShowInInspectorAttributeHandler(UnityObject target, SerializedObject serializedObject) { + this.target = target; + this.serializedObject = serializedObject; + showMembers = new(); + } + + public void Update() { + serializedObject.Update(); + Setup(); + } + + private void Setup() { + if (_initialized) { + return; + } + + showMembers.Clear(); + + int fieldCount = EditorTypes.GetFields(target, out List objectFields); + for (int i = 0; i < fieldCount; i++) { + var showAttribute = Attribute.GetCustomAttribute(objectFields[i], typeof(ShowInInspectorAttribute)) as ShowInInspectorAttribute; + if (showAttribute == null) { + continue; + } + + SetupMember(objectFields[i], objectFields[i].GetValue(target), showAttribute.Visibility, showAttribute.isEdittingSupported, target); + } + + int propertiesCount = EditorTypes.GetProperties(target, out List objectProperties); + for (int i = 0; i < propertiesCount; i++) { + var showAttribute = Attribute.GetCustomAttribute(objectProperties[i], typeof(ShowInInspectorAttribute)) as ShowInInspectorAttribute; + if (showAttribute == null) { + continue; + } + + SetupMember(objectProperties[i], objectProperties[i].GetValue(target), showAttribute.Visibility, showAttribute.isEdittingSupported, target); + } + + _initialized = true; + } + + private void SetupMember(MemberInfo memberInfo, + object memberValue, + PlayModeVisibility visibility, + bool isEditingSupported, + object parent, + int depth = 0, + Member parentMember = null) { + Member member = new(memberInfo, visibility, isEditingSupported, parent, depth, parentMember); + + if (depth >= MAX_DEPTH) { + return; + } + + showMembers.Add(member); + if (memberValue != null && CanValueExpand(memberValue)) { + + Type memberType = memberValue.GetType(); + foreach (FieldInfo fieldInfo in memberType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { + ShowInInspectorAttribute attribute = fieldInfo.GetCustomAttribute(); + if (attribute != null) { + SetupMember(fieldInfo, fieldInfo.GetValue(memberValue), attribute.Visibility, attribute.isEdittingSupported, memberValue, depth + 1, member); + member.supportsExpansion = true; + } + } + + foreach (PropertyInfo propertyInfo in memberType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { + ShowInInspectorAttribute attribute = propertyInfo.GetCustomAttribute(); + if (attribute != null) { + SetupMember(propertyInfo, propertyInfo.GetValue(memberValue), attribute.Visibility, attribute.isEdittingSupported, memberValue, depth + 1, member); + member.supportsExpansion = true; + } + } + } + } + + private bool TryGetValueTypeFromEnumerable(object value, out Type type) { + Type[] arguments = value.GetType().GenericTypeArguments; + if (arguments.Length > 0) { + if (value is IDictionary) { + type = value.GetType().GenericTypeArguments[1]; // value + return true; + } + else if (value is IEnumerable) { + type = value.GetType().GenericTypeArguments[0]; + return true; + } + } + + type = null; + return false; + } + + public void OnInspectorGUI(Editor editor) { + labelStyle ??= new GUIStyle(EditorStyles.label) { + alignment = TextAnchor.UpperLeft, + padding = new RectOffset(0, 0, 2, 2) + }; + + tooltipStyle ??= new GUIStyle(EditorStyles.miniTextField) { + fixedHeight = 0f, + stretchHeight = true, + wordWrap = true, + stretchWidth = false, + imagePosition = ImagePosition.TextOnly, + clipping = TextClipping.Overflow, + border = new RectOffset(0, 0, 0, 0) + }; + + int indentLevel = EditorGUI.indentLevel; + + foreach (Member entry in showMembers) { + if (entry.visibility.IsVisible() == false || (entry.IsChild && entry.IsParentExpanded == false)) { + continue; + } + + Type valueType = entry.GetValueType(); + string stringValue = ""; + object value = null; + + EditorGUI.indentLevel = indentLevel + entry.depth; + + if (entry.TryGetValueAndData(out value)) { + if (value is IList list) { + stringValue = list.ListToString(true); + } + else if (value is IEnumerable enumerable) { + stringValue = enumerable.EnumerableToString(true); + } + else { + stringValue = value?.ToString(); + } + } + else { + stringValue = ""; + } + + string labelName = entry.niceName; + + if (entry.supportsExpansion) { + entry.expanded = EditorGUILayout.Foldout(entry.expanded, labelName, true); + } + else { + bool isGUIEnabled = GUI.enabled; + bool allowEdit = entry.isEditingSupported; + GUI.enabled = allowEdit; + + Rect entryRect; + + if (value is IEnumerable enumerable && TryGetValueTypeFromEnumerable(value, out Type enumValueType)) { + + string labelText = $"{labelName}"; + if (value is ICollection collection) { + labelText += $" (Count={collection.Count})"; + } + + string collectionInfo = $"({value.GetType().Name}<{enumValueType.Name}>)"; + + GUILayout.BeginHorizontal(); + entry.expanded = EditorGUILayout.Foldout(entry.expanded, labelText, false); + GUILayout.FlexibleSpace(); + GUILayout.Label(collectionInfo, EditorStyles.miniLabel); + GUILayout.EndHorizontal(); + entryRect = GUILayoutUtility.GetLastRect(); + + if (entry.expanded) { + EditorGUI.indentLevel++; + int counter = 0; + + if (enumerable is IDictionary dictionary) { + foreach (DictionaryEntry keyValuePair in dictionary) { + object newValue = InspectorGUIUtility.DrawField(keyValuePair.Key.ToString(), enumValueType, keyValuePair.Value); + if (newValue != value && allowEdit) { + entry.SetValue(newValue); + } + } + } + else { + foreach (var item in enumerable) { + object newValue = InspectorGUIUtility.DrawField((counter++).ToString(), enumValueType, item); + if (newValue != value && allowEdit) { + entry.SetValue(newValue); + } + } + } + + EditorGUI.indentLevel--; + } + } + else { + object newValue = InspectorGUIUtility.DrawField(labelName, valueType, value); + entryRect = GUILayoutUtility.GetLastRect(); + if (newValue != value && allowEdit) { + entry.SetValue(newValue); + } + } + + + GUI.enabled = isGUIEnabled; + if (entryRect.Contains(Event.current.mousePosition)) { + Vector2 tooltipPosition = Event.current.mousePosition; + float width = entryRect.width - (tooltipPosition.x - entryRect.x); + + tooltipStyle.fixedWidth = width; + + var tooltipContent = new GUIContent(stringValue, stringValue); + float height = tooltipStyle.CalcHeight(tooltipContent, width); + Vector2 tooltipSize = new(width, height); + tooltipPosition.y -= tooltipSize.y; + var tooltipRect = new Rect(tooltipPosition, tooltipSize); + GUI.Label(tooltipRect, tooltipContent, tooltipStyle); + + if (Event.current.type == EventType.ContextClick) { + GenericMenu menu = new(); + menu.AddItem(new GUIContent("Copy"), false, () => { EditorGUIUtility.systemCopyBuffer = stringValue; }); + menu.ShowAsContext(); + Event.current.Use(); + } + + editor.Repaint(); + } + } + } + + EditorGUI.indentLevel = indentLevel; + } + + private static readonly Type[] NonExpandedTypes = new Type[] { + typeof(string), + typeof(decimal), + typeof(DateTime), + typeof(DateTimeOffset), + typeof(TimeSpan), + typeof(Guid) + }; + + // Use this to stop expansion of types we dont want, including Unity Objects - they just link to the object. + private static bool CanValueExpand(object value) { + if (value == null) { + return false; + } + + Type type = value.GetType(); + if (IsSimpleType(type)) { + return false; + } + + if (type == typeof(UnityEngine.Object) || type.IsSubclassOf(typeof(UnityEngine.Object))) { + return false; + } + + return true; + } + + //https://stackoverflow.com/a/32337906/584774 + private static bool IsSimpleType(Type type) { + return type.IsPrimitive || type.IsEnum || NonExpandedTypes.Contains(type) || Convert.GetTypeCode(type) != TypeCode.Object || + (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && IsSimpleType(type.GetGenericArguments()[0])); + + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/ShowInInspectorAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/ShowInInspectorAttribute.cs.meta new file mode 100644 index 0000000..4076acd --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/ShowInInspectorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39458dd0676f9c0449d334871e5e1bb1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SpriteLayerAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/SpriteLayerAttribute.cs new file mode 100644 index 0000000..663cebd --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SpriteLayerAttribute.cs @@ -0,0 +1,113 @@ +// ---------------------------------------------------------------------------- +// Author: Kaynn, Yeo Wen Qin +// https://github.com/Kaynn-Cahya +// Date: 11/02/2019 +// ---------------------------------------------------------------------------- + +using UnityEngine; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit +{ + public class SpriteLayerAttribute : PropertyAttribute + { + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + [CustomPropertyDrawer(typeof(SpriteLayerAttribute))] + public class SpriteLayerAttributeDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (property.propertyType != SerializedPropertyType.Integer) + { + if (!_checkedType) PropertyTypeWarning(property); + EditorGUI.PropertyField(position, property, label); + return; + } + + var spriteLayerNames = GetSpriteLayerNames(); + HandleSpriteLayerSelectionUI(position, property, label, spriteLayerNames); + } + + private bool _checkedType; + private void PropertyTypeWarning(SerializedProperty property) + { + Debug.LogWarning(string.Format("Property {0} in object {1} is of wrong type. Expected: Int", + property.name, property.serializedObject.targetObject)); + _checkedType = true; + } + + private void HandleSpriteLayerSelectionUI(Rect position, SerializedProperty property, GUIContent label, string[] spriteLayerNames) + { + EditorGUI.BeginProperty(position, label, property); + + // To show which sprite layer is currently selected. + int currentSpriteLayerIndex; + bool layerFound = TryGetSpriteLayerIndexFromProperty(out currentSpriteLayerIndex, spriteLayerNames, property); + + if (!layerFound) + { + // Set to default layer. (Previous layer was removed) + Debug.Log(string.Format( + "Property {0} in object {1} is set to the default layer. Reason: previously selected layer was removed.", + property.name, property.serializedObject.targetObject)); + property.intValue = 0; + currentSpriteLayerIndex = 0; + } + + int selectedSpriteLayerIndex = EditorGUI.Popup(position, label.text, currentSpriteLayerIndex, spriteLayerNames); + + // Change property value if user selects a new sprite layer. + if (selectedSpriteLayerIndex != currentSpriteLayerIndex) + { + property.intValue = SortingLayer.NameToID(spriteLayerNames[selectedSpriteLayerIndex]); + } + + EditorGUI.EndProperty(); + } + + #region Util + + private bool TryGetSpriteLayerIndexFromProperty(out int index, string[] spriteLayerNames, SerializedProperty property) + { + // To keep the property's value consistent, after the layers have been sorted around. + string layerName = SortingLayer.IDToName(property.intValue); + + // Return the index where on it matches. + for (int i = 0; i < spriteLayerNames.Length; ++i) + { + if (spriteLayerNames[i].Equals(layerName)) + { + index = i; + return true; + } + } + + // The current layer was removed. + index = -1; + return false; + } + + private string[] GetSpriteLayerNames() + { + string[] result = new string[SortingLayer.layers.Length]; + + for (int i = 0; i < result.Length; ++i) + { + result[i] = SortingLayer.layers[i].name; + } + + return result; + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SpriteLayerAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/SpriteLayerAttribute.cs.meta new file mode 100644 index 0000000..8af2486 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SpriteLayerAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f05661da7d39cf418051ccfb13eb5a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SuffixAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/SuffixAttribute.cs new file mode 100644 index 0000000..2adf159 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SuffixAttribute.cs @@ -0,0 +1,34 @@ +using UnityEngine; + +namespace InspectorToolkit { + public class SuffixAttribute : PropertyAttribute { + public readonly string suffix; + + public SuffixAttribute(string suffix) => this.suffix = suffix; + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + using UnityEditor; + + [CustomPropertyDrawer(typeof(SuffixAttribute))] + public class SuffixDrawer : PropertyDrawer { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + var suffix = new GUIContent(((SuffixAttribute)attribute).suffix); + var suffixSize = GUI.skin.label.CalcSize(suffix); + suffixSize.x += GUI.skin.label.margin.left; + var fieldPosition = position; + fieldPosition.width -= suffixSize.x; + var suffixPosition = position; + suffixPosition.x += fieldPosition.width + GUI.skin.label.margin.left; + suffixPosition.width = suffixSize.x; + EditorGUI.PropertyField(fieldPosition, property, label); + var enabled = GUI.enabled; + GUI.enabled = false; + GUI.Label(suffixPosition, suffix); + GUI.enabled = enabled; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/SuffixAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/SuffixAttribute.cs.meta new file mode 100644 index 0000000..27b6cae --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/SuffixAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 292774cd51b5c464ca08b8bc1805e662 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/TagAttribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/TagAttribute.cs new file mode 100644 index 0000000..bb9dffa --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/TagAttribute.cs @@ -0,0 +1,48 @@ +// ---------------------------------------------------------------------------- +// Author: Kaynn, Yeo Wen Qin +// https://github.com/Kaynn-Cahya +// Date: 11/02/2019 +// ---------------------------------------------------------------------------- + +using UnityEngine; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit +{ + public class TagAttribute : PropertyAttribute + { + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal +{ + [CustomPropertyDrawer(typeof(TagAttribute))] + public class TagAttributeDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (property.propertyType != SerializedPropertyType.String) + { + if (!_checked) Warning(property); + EditorGUI.PropertyField(position, property, label); + return; + } + + property.stringValue = EditorGUI.TagField(position, label, property.stringValue); + } + + private bool _checked; + + private void Warning(SerializedProperty property) + { + Debug.LogWarning(string.Format("Property {0} in object {1} is of wrong type. Expected: String", + property.name, property.serializedObject.targetObject)); + _checked = true; + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Attributes/TagAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/TagAttribute.cs.meta new file mode 100644 index 0000000..0395f9d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/TagAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4112d636c944a774aae43810656bde88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Attributes/UniformVector3Attribute.cs b/Packages/com.jovian.inspector/Editor/Attributes/UniformVector3Attribute.cs new file mode 100644 index 0000000..47d7e67 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/UniformVector3Attribute.cs @@ -0,0 +1,132 @@ +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit { + public class UniformVector3Attribute : PropertyAttribute { + public UniformVector3Attribute() { + } + } +} + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + [CustomPropertyDrawer(typeof(UniformVector3Attribute))] + public class UniformVector3AttributeDrawer : PropertyDrawer { + + private GUIContent linkedIconContent; + private GUIContent unlinkedIconContent; + private GUIStyle linkButtonStyle; + + private const int CHILD_COUNT = 3; + private const float LABEL_WIDTH = 13f; + private const float COMBINED_LABEL_WIDTH = 26f; + private const float INDENT_WIDTH = 15f; + private const float CHILD_HORIZONTAL_SPACING = 4f; + private static readonly string[] childPropertyNames = new string[] { "x", "y", "z" }; + private static bool IsCompactLayout => Screen.width <= 333f; + private static bool IsNoLabelLayout => Screen.width <= 240f; + + private enum LinkState { + Unknown, + Linked, + Unlinked + } + + private LinkState linkState = LinkState.Unknown; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + if(IsCompactLayout) { // Unity Vector2 + Vector3 go multi-line at this point + return EditorGUIUtility.singleLineHeight * 2f + EditorGUIUtility.standardVerticalSpacing; + } + return EditorGUIUtility.singleLineHeight; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + if(property.propertyType != SerializedPropertyType.Vector3) { + GUI.Label(position, "Error, SerializedProperty must be 'Vector3' type"); + return; + } + + if(linkedIconContent == null || unlinkedIconContent == null || linkButtonStyle == null) { + linkedIconContent = EditorGUIUtility.IconContent(EditorGUIUtility.isProSkin ? "d_Linked" : "Linked"); + unlinkedIconContent = EditorGUIUtility.IconContent(EditorGUIUtility.isProSkin ? "d_Unlinked" : "Unlinked"); + linkButtonStyle = new GUIStyle(GUI.skin.button); + linkButtonStyle.padding = new RectOffset(1, 1, 0, 0); + linkButtonStyle.imagePosition = ImagePosition.ImageOnly; + } + + if(linkState == LinkState.Unknown) { + linkState = GetLinkState(property); + } + + EditorGUI.BeginProperty(position, label, property); + + if(IsCompactLayout) { + EditorGUIUtility.labelWidth = position.width; + } + Rect labelRect = new Rect(position) { height = EditorGUIUtility.singleLineHeight }; + Rect fieldRect = EditorGUI.PrefixLabel(labelRect, label); + fieldRect.height = EditorGUIUtility.singleLineHeight; + + float height = Mathf.Min(20f, position.height) - 2f; + Rect buttonRect = new Rect(fieldRect); + buttonRect.width = 18f; + buttonRect.height = height; + buttonRect.y += 1f; + buttonRect.x -= buttonRect.width + 2f; + + if(GUI.Button(buttonRect, linkState == LinkState.Linked ? linkedIconContent : unlinkedIconContent, linkButtonStyle)) { + linkState = linkState == LinkState.Linked ? LinkState.Unlinked : LinkState.Linked; + } + + if(IsCompactLayout) { + fieldRect.y = labelRect.yMax + EditorGUIUtility.standardVerticalSpacing; + fieldRect.x = labelRect.x + (IsNoLabelLayout ? 0f : INDENT_WIDTH); // 15 = single indent + fieldRect.width = position.xMax - fieldRect.xMin; + } + + + if(linkState == LinkState.Linked) { + EditorGUIUtility.labelWidth = IsNoLabelLayout ? 0.01f : COMBINED_LABEL_WIDTH; + SerializedProperty childProperty = property.FindPropertyRelative(childPropertyNames[0]); + EditorGUI.BeginChangeCheck(); + float newValue = EditorGUI.FloatField(fieldRect, "XYZ", childProperty.floatValue); + if(EditorGUI.EndChangeCheck()) { + for(int i = 0; i < CHILD_COUNT; i++) { + SerializedProperty otherChildProperty = property.FindPropertyRelative(childPropertyNames[i]); + otherChildProperty.floatValue = newValue; + } + } + } + else { + EditorGUIUtility.labelWidth = IsNoLabelLayout ? 0.01f : LABEL_WIDTH; + float childSpacing = IsNoLabelLayout ? CHILD_HORIZONTAL_SPACING / 2f : CHILD_HORIZONTAL_SPACING; + float childWidth = ((fieldRect.width + childSpacing) / CHILD_COUNT) - childSpacing; + + for(int i = 0; i < CHILD_COUNT; i++) { + SerializedProperty childProperty = property.FindPropertyRelative(childPropertyNames[i]); + Rect childRect = new Rect(fieldRect); + childRect.width = childWidth; // add spacing + childRect.x += i * (childWidth + childSpacing); + EditorGUI.PropertyField(childRect, childProperty); + } + } + + EditorGUI.EndProperty(); + } + + private LinkState GetLinkState(SerializedProperty property) { + float value0 = property.FindPropertyRelative(childPropertyNames[0]).floatValue; + for(int i = 1; i < CHILD_COUNT; i++) { + SerializedProperty childProperty = property.FindPropertyRelative(childPropertyNames[i]); + if(Mathf.Approximately(value0, childProperty.floatValue) == false) { + return LinkState.Unlinked; + } + } + return LinkState.Linked; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Attributes/UniformVector3Attribute.cs.meta b/Packages/com.jovian.inspector/Editor/Attributes/UniformVector3Attribute.cs.meta new file mode 100644 index 0000000..78b0694 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Attributes/UniformVector3Attribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 140e2c93656440341b95fd7ae6396b7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Extensions.meta b/Packages/com.jovian.inspector/Editor/Extensions.meta new file mode 100644 index 0000000..3b6077f --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6b11d21170b13bc458c03fd935af0f63 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions.meta b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions.meta new file mode 100644 index 0000000..c784781 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd2b08b6aac506f4eabedf9dff5a11cb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MyGUI.cs b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MyGUI.cs new file mode 100644 index 0000000..2143aaf --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MyGUI.cs @@ -0,0 +1,580 @@ +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace InspectorToolkit.EditorTools { + public static class MyGUI { + #region Colors + + public static class Colors { + public static readonly Color Red = new Color(.8f, .6f, .6f); + + public static readonly Color Green = new Color(.4f, .6f, .4f); + + public static readonly Color Blue = new Color(.6f, .6f, .8f); + + public static readonly Color Gray = new Color(.3f, .3f, .3f); + + public static readonly Color Yellow = new Color(.8f, .8f, .2f, .6f); + + public static readonly Color Brown = new Color(.7f, .5f, .2f, .6f); + } + + #endregion + + + #region Characters + + public static class Characters { + public const string ArrowUp = "▲"; + + public const string ArrowDown = "▼"; + + public const string ArrowLeft = "◀"; + + public const string ArrowRight = "▶"; + + public const string ArrowLeftLight = "←"; + + public const string ArrowRightLight = "→"; + + public const string ArrowTopRightLight = "↘"; + + public const string Check = "✓"; + + public const string Cross = "×"; + } + + #endregion + + + #region Editor Icons + + public static class EditorIcons { + public static GUIContent Plus => EditorGUIUtility.IconContent("Toolbar Plus"); + public static GUIContent Minus => EditorGUIUtility.IconContent("Toolbar Minus"); + public static GUIContent Refresh => EditorGUIUtility.IconContent("Refresh"); + + public static GUIContent ConsoleInfo => EditorGUIUtility.IconContent("console.infoicon.sml"); + public static GUIContent ConsoleWarning => EditorGUIUtility.IconContent("console.warnicon.sml"); + public static GUIContent ConsoleError => EditorGUIUtility.IconContent("console.erroricon.sml"); + + public static GUIContent Check => EditorGUIUtility.IconContent("FilterSelectedOnly"); + public static GUIContent Cross => EditorGUIUtility.IconContent("d_winbtn_win_close"); + + public static GUIContent Dropdown => EditorGUIUtility.IconContent("icon dropdown"); + + public static GUIContent EyeOn => EditorGUIUtility.IconContent("d_VisibilityOn"); + public static GUIContent EyeOff => EditorGUIUtility.IconContent("d_VisibilityOff"); + public static GUIContent Zoom => EditorGUIUtility.IconContent("d_ViewToolZoom"); + + public static GUIContent Help => EditorGUIUtility.IconContent("_Help"); + public static GUIContent Favourite => EditorGUIUtility.IconContent("Favorite"); + public static GUIContent Label => EditorGUIUtility.IconContent("FilterByLabel"); + + public static GUIContent Settings => EditorGUIUtility.IconContent("d_Settings"); + public static GUIContent SettingsPopup => EditorGUIUtility.IconContent("_Popup"); + public static GUIContent SettingsMixer => EditorGUIUtility.IconContent("Audio Mixer"); + + public static GUIContent Circle => EditorGUIUtility.IconContent("TestNormal"); + public static GUIContent CircleYellow => EditorGUIUtility.IconContent("TestInconclusive"); + public static GUIContent CircleDotted => EditorGUIUtility.IconContent("TestIgnored"); + public static GUIContent CircleRed => EditorGUIUtility.IconContent("TestFailed"); + } + + #endregion + + + #region Editor Styles + + /// + /// HelpBox with centered text alignment + /// + public static GUIStyle HelpBoxStyle { + get { + if(_helpBoxStyle != null) return _helpBoxStyle; + _helpBoxStyle = new GUIStyle(GUI.skin.GetStyle("HelpBox")); + _helpBoxStyle.alignment = TextAnchor.MiddleCenter; + return _helpBoxStyle; + } + } + + private static GUIStyle _helpBoxStyle; + + + /// + /// ToolbarButtonStyle is not resizable by default + /// + public static GUIStyle ResizableToolbarButtonStyle { + get { + if(_resizableToolbarButtonStyle != null) return _resizableToolbarButtonStyle; + _resizableToolbarButtonStyle = new GUIStyle(EditorStyles.toolbarButton); + _resizableToolbarButtonStyle.fixedHeight = 0; + return _resizableToolbarButtonStyle; + } + } + + private static GUIStyle _resizableToolbarButtonStyle; + + + /// + /// ToolbarButton with Border, Margin and Padding set to 0 + /// + public static GUIStyle BorderlessToolbarButtonStyle { + get { + if(_borderlessToolbarButton != null) return _borderlessToolbarButton; + _borderlessToolbarButton = new GUIStyle(ResizableToolbarButtonStyle); + var emptyOffset = new RectOffset(); + _borderlessToolbarButton.border = emptyOffset; + _borderlessToolbarButton.margin = emptyOffset; + _borderlessToolbarButton.padding = emptyOffset; + return _borderlessToolbarButton; + } + } + + private static GUIStyle _borderlessToolbarButton; + + + /// + /// Style for a toggle button + /// + public static GUIStyle ButtonToggledStyle(bool toggled) { + if(!toggled) return EditorStyles.miniButton; + + if(_buttonToggledStyle != null) return _buttonToggledStyle; + _buttonToggledStyle = new GUIStyle(EditorStyles.miniButton); + _buttonToggledStyle.normal.background = _buttonToggledStyle.active.background; + return _buttonToggledStyle; + } + private static GUIStyle _buttonToggledStyle; + + + /// + /// MiniButtonLeft/Middle/Right style based on array index + /// + /// + /// + /// + public static GUIStyle MiniButtonStyle(int index, Array collection) { + if(collection.Length == 1) return EditorStyles.miniButton; + if(index == 0) return EditorStyles.miniButtonLeft; + if(index == collection.Length - 1) return EditorStyles.miniButtonRight; + return EditorStyles.miniButtonMid; + } + + + #endregion + + + #region Draw Coloured lines and boxes + + /// + /// Draw Separator within GuiLayout + /// + public static void Separator() { + var color = GUI.color; + + EditorGUILayout.Space(); + var spaceRect = EditorGUILayout.GetControlRect(); + var separatorRectPosition = new Vector2(spaceRect.position.x, spaceRect.position.y + spaceRect.height / 2); + var separatorRect = new Rect(separatorRectPosition, new Vector2(spaceRect.width, 1)); + + GUI.color = Color.white; + GUI.Box(separatorRect, GUIContent.none); + GUI.color = color; + } + + /// + /// Draw Line within GUILayout + /// + public static void DrawLine(Color color, bool withSpace = false) { + if(withSpace) EditorGUILayout.Space(); + + var defaultBackgroundColor = GUI.backgroundColor; + GUI.backgroundColor = color; + GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); + GUI.backgroundColor = defaultBackgroundColor; + + if(withSpace) EditorGUILayout.Space(); + } + + /// + /// Draw line within Rect and get Rect back with offset + /// + public static Rect DrawLine(Color color, Rect rect) { + var h = rect.height; + var defaultBackgroundColor = GUI.backgroundColor; + GUI.backgroundColor = color; + rect.y += 5; + rect.height = 1; + GUI.Box(rect, ""); + rect.y += 5; + GUI.backgroundColor = defaultBackgroundColor; + rect.height = h; + return rect; + } + + /// + /// Draw Rect filled with Color + /// + public static void DrawColouredRect(Rect rect, Color color) { + var defaultBackgroundColor = GUI.backgroundColor; + GUI.backgroundColor = color; + GUI.Box(rect, ""); + GUI.backgroundColor = defaultBackgroundColor; + } + + /// + /// Draw background Line within GUILayout + /// + public static void DrawBackgroundLine(Color color, int yOffset = 0) { + var defColor = GUI.color; + GUI.color = color; + var rect = GUILayoutUtility.GetLastRect(); + rect.center = new Vector2(rect.center.x, rect.center.y + 6 + yOffset); + rect.height = 17; + GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture); + GUI.color = defColor; + } + + /// + /// Draw background Line of height + /// + public static void DrawBackgroundBox(Color color, int height, int yOffset = 0) { + var defColor = GUI.color; + GUI.color = color; + var rect = GUILayoutUtility.GetLastRect(); + rect.center = new Vector2(rect.center.x, rect.center.y + 6 + yOffset); + rect.height = height; + GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture); + GUI.color = defColor; + } + + #endregion + + + #region Property Field + + /// + /// Make a field for SerializedProperty and check if changed + /// + public static bool PropertyField(SerializedProperty property, params GUILayoutOption[] options) { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(property, options); + return EditorGUI.EndChangeCheck(); + } + + /// + /// Make a field for SerializedProperty and check if changed + /// + public static bool PropertyField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(property, label, options); + return EditorGUI.EndChangeCheck(); + } + + #endregion + + + #region SerializedProperty Manipulation Buttons + + /// + /// Move array element at index on index-1 position + /// + public static void MoveArrayElementUpButton(this SerializedProperty property, int elementAt) { + var guiState = GUI.enabled; + if(elementAt <= 0) GUI.enabled = false; + + if(UpButton()) { + EditorApplication.delayCall += () => { + property.MoveArrayElement(elementAt, elementAt - 1); + property.serializedObject.ApplyModifiedProperties(); + }; + } + + GUI.enabled = guiState; + } + + + /// + /// Move array element at index on index+1 position + /// + public static void MoveArrayElementDownButton(this SerializedProperty property, int elementAt) { + var guiState = GUI.enabled; + if(elementAt >= property.arraySize - 1) GUI.enabled = false; + + if(DownButton()) { + EditorApplication.delayCall += () => { + property.MoveArrayElement(elementAt, elementAt + 1); + property.serializedObject.ApplyModifiedProperties(); + }; + } + + GUI.enabled = guiState; + } + + /// + /// Add new array element to property and get it as SerializedProperty + /// + public static SerializedProperty NewArrayElementButton(this SerializedProperty property) { + if(PlusButton()) { + return property.NewElement(); + } + + return null; + } + + /// + /// Remove array element at index + /// + public static void RemoveElementButton(this SerializedProperty property, int elementAt) { + var guiState = GUI.enabled; + if(elementAt < 0 || elementAt >= property.arraySize - 1) GUI.enabled = false; + + if(CrossButton()) { + EditorApplication.delayCall += () => { + property.DeleteArrayElementAtIndex(elementAt); + property.serializedObject.ApplyModifiedProperties(); + }; + } + + GUI.enabled = guiState; + } + + #endregion + + + #region Drop Area + + /// + /// Drag-and-Drop Area to catch objects of specific type + /// + /// Asset type to catch + /// Label to display + /// Height of the Drop Area + /// Allow to drag external files and import as unity assets + /// Path relative to Assets folder + /// Received objects. Null if none received + public static T[] DropArea(string areaText, float height, bool allowExternal = false, + string externalImportFolder = null) where T : Object { + Event currentEvent = Event.current; + Rect dropArea = GUILayoutUtility.GetRect(0.0f, height, GUILayout.ExpandWidth(true)); + var style = new GUIStyle(GUI.skin.box); + style.alignment = TextAnchor.MiddleCenter; + GUI.Box(dropArea, areaText, style); + + bool dragEvent = currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.DragPerform; + if(!dragEvent) return null; + bool overDropArea = dropArea.Contains(currentEvent.mousePosition); + if(!overDropArea) return null; + + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + if(currentEvent.type != EventType.DragPerform) return null; + + DragAndDrop.AcceptDrag(); + Event.current.Use(); + + List result = new List(); + + bool anyExternal = DragAndDrop.paths.Length > 0 && + DragAndDrop.paths.Length > DragAndDrop.objectReferences.Length; + if(allowExternal && anyExternal) { + var folderToLoad = "/"; + if(!string.IsNullOrEmpty(externalImportFolder)) { + folderToLoad = "/" + externalImportFolder.Replace("Assets/", "").Trim('/', '\\') + "/"; + } + + List importedFiles = new List(); + + foreach(string externalPath in DragAndDrop.paths) { + if(externalPath.Length == 0) continue; + try { + var filename = Path.GetFileName(externalPath); + var relativePath = folderToLoad + filename; + Directory.CreateDirectory(Application.dataPath + folderToLoad); + FileUtil.CopyFileOrDirectory(externalPath, Application.dataPath + relativePath); + importedFiles.Add("Assets" + relativePath); + } + catch(Exception ex) { + Debug.LogException(ex); + } + } + + AssetDatabase.Refresh(); + + foreach(var importedFile in importedFiles) { + var asset = AssetDatabase.LoadAssetAtPath(importedFile); + if(asset != null) { + result.Add(asset); + Debug.Log("Asset imported at path: " + importedFile); + } + else AssetDatabase.DeleteAsset(importedFile); + } + } + else { + foreach(Object dragged in DragAndDrop.objectReferences) { + var validObject = dragged as T ?? AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(dragged)); + + if(validObject != null) result.Add(validObject); + } + } + + return result.Count > 0 ? result.OrderBy(o => o.name).ToArray() : null; + } + + + /// + /// Drag-and-Drop Area to get paths of received objects + /// + /// Label to display + /// Height of the Drop Area + /// Received paths + public static string[] DropAreaPaths(string areaText, float height) { + Event currentEvent = Event.current; + Rect dropArea = GUILayoutUtility.GetRect(0.0f, height, GUILayout.ExpandWidth(true)); + var style = new GUIStyle(GUI.skin.box); + style.alignment = TextAnchor.MiddleCenter; + GUI.Box(dropArea, areaText, style); + + bool dragEvent = currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.DragPerform; + if(!dragEvent) return null; + bool overDropArea = dropArea.Contains(currentEvent.mousePosition); + if(!overDropArea) return null; + + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + if(currentEvent.type != EventType.DragPerform) return null; + + DragAndDrop.AcceptDrag(); + Event.current.Use(); + + return DragAndDrop.paths; + } + + #endregion + + + #region Browse Buttons + + /// + /// Creates a filepath textfield with a browse button. Opens the open file panel. + /// + public static string BrowsFileLabel(string name, float labelWidth, string path, string extension) { + EditorGUILayout.BeginHorizontal(); + GUILayout.Label(name, GUILayout.MaxWidth(labelWidth)); + string filepath = EditorGUILayout.TextField(path); + if(GUILayout.Button("Browse")) { + filepath = EditorUtility.OpenFilePanel(name, path, extension); + } + + EditorGUILayout.EndHorizontal(); + return filepath; + } + + /// + /// Creates a folder path textfield with a browse button. Opens the save folder panel. + /// + public static string BrowseFolderLabel(string name, float labelWidth, string path) { + EditorGUILayout.BeginHorizontal(); + string filepath = EditorGUILayout.TextField(name, path, GUILayout.MaxWidth(labelWidth)); + if(GUILayout.Button("Browse", GUILayout.MaxWidth(60))) { + filepath = EditorUtility.SaveFolderPanel(name, path, "Folder"); + } + + EditorGUILayout.EndHorizontal(); + return filepath; + } + + #endregion + + + #region Predefined Buttons + + /// + /// Display Button with ArrowUI + /// + public static bool UpButton() { + return GUILayout.Button(Characters.ArrowUp, EditorStyles.toolbarButton, GUILayout.Width(18)); + } + + public static bool DownButton() { + return GUILayout.Button(Characters.ArrowDown, EditorStyles.toolbarButton, GUILayout.Width(18)); + } + + public static bool PlusButton() { + return GUILayout.Button("+", EditorStyles.toolbarButton, GUILayout.Width(18)); + } + + + public static bool CrossButton() { + return GUILayout.Button(Characters.Cross, EditorStyles.toolbarButton, GUILayout.Width(18)); + } + + #endregion + + + /// + /// Creates a toolbar that is filled in from an Enum. Useful for setting tool modes. + /// + public static Enum EnumToolbar(Enum selected) { + string[] toolbar = Enum.GetNames(selected.GetType()); + Array values = Enum.GetValues(selected.GetType()); + + for(int i = 0; i < toolbar.Length; i++) { + string toolName = toolbar[i]; + toolName = toolName.Replace("_", " "); + toolbar[i] = toolName; + } + + int selectedIndex = 0; + while(selectedIndex < values.Length) { + if(selected.ToString() == values.GetValue(selectedIndex).ToString()) { + break; + } + + selectedIndex++; + } + + selectedIndex = GUILayout.Toolbar(selectedIndex, toolbar); + return (Enum)values.GetValue(selectedIndex); + } + + + /// + /// Creates an array foldout like in inspectors + /// + public static string[] ArrayFoldout(string label, string[] array, ref bool foldout) { + EditorGUILayout.BeginVertical(); + + EditorGUIUtility.labelWidth = 0; + EditorGUIUtility.fieldWidth = 0; + foldout = EditorGUILayout.Foldout(foldout, label); + string[] newArray = array; + if(foldout) { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.Space(); + EditorGUILayout.BeginVertical(); + int arraySize = EditorGUILayout.IntField("Size", array.Length); + if(arraySize != array.Length) + newArray = new string[arraySize]; + for(int i = 0; i < arraySize; i++) { + string entry = ""; + if(i < array.Length) + entry = array[i]; + newArray[i] = EditorGUILayout.TextField("Element " + i, entry); + } + + EditorGUILayout.EndVertical(); + EditorGUILayout.EndHorizontal(); + } + + EditorGUILayout.EndVertical(); + return newArray; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MyGUI.cs.meta b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MyGUI.cs.meta new file mode 100644 index 0000000..bf0b530 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MyGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6be3865b984cad4488a515d4047a1e6c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MySerializedProperty.cs b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MySerializedProperty.cs new file mode 100644 index 0000000..a64cdfc --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MySerializedProperty.cs @@ -0,0 +1,254 @@ +#if UNITY_EDITOR +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using Object = UnityEngine.Object; + +namespace InspectorToolkit.EditorTools { + public static class MySerializedProperty { + #region Collections Handling + + /// + /// Get array of property childs, if parent property is array + /// + public static SerializedProperty[] AsArray(this SerializedProperty property) { + List items = new List(); + for(int i = 0; i < property.arraySize; i++) + items.Add(property.GetArrayElementAtIndex(i)); + return items.ToArray(); + } + + /// + /// Get array of property childs casted to specific type + /// + public static T[] AsArray(this SerializedProperty property) { + var propertiesArray = property.AsArray(); + return propertiesArray.Select(s => s.objectReferenceValue).OfType().ToArray(); + } + + /// + /// Get array of property childs, if parent property is array + /// + public static IEnumerable AsIEnumerable(this SerializedProperty property) { + for(int i = 0; i < property.arraySize; i++) + yield return property.GetArrayElementAtIndex(i); + } + + /// + /// Replace array contents of SerializedProperty with another array + /// + public static void ReplaceArray(this SerializedProperty property, Object[] newElements) { + property.arraySize = 0; + property.serializedObject.ApplyModifiedProperties(); + property.arraySize = newElements.Length; + for(var i = 0; i < newElements.Length; i++) { + property.GetArrayElementAtIndex(i).objectReferenceValue = newElements[i]; + } + + property.serializedObject.ApplyModifiedProperties(); + } + + /// + /// If property is array, insert new element at the end and get it as a property + /// + public static SerializedProperty NewElement(this SerializedProperty property) { + int newElementIndex = property.arraySize; + property.InsertArrayElementAtIndex(newElementIndex); + return property.GetArrayElementAtIndex(newElementIndex); + } + + #endregion + + /// + /// Property is float, int, vector or int vector + /// + public static bool IsNumerical(this SerializedProperty property) { + var propertyType = property.propertyType; + switch(propertyType) { + case SerializedPropertyType.Float: + case SerializedPropertyType.Integer: + case SerializedPropertyType.Vector2: + case SerializedPropertyType.Vector3: + case SerializedPropertyType.Vector4: + case SerializedPropertyType.Vector2Int: + case SerializedPropertyType.Vector3Int: + return true; + + default: return false; + } + } + + /// + /// Get string representation of serialized property + /// + public static string AsStringValue(this SerializedProperty property) { + switch(property.propertyType) { + case SerializedPropertyType.String: + return property.stringValue; + + case SerializedPropertyType.Character: + case SerializedPropertyType.Integer: + if(property.type == "char") return Convert.ToChar(property.intValue).ToString(); + return property.intValue.ToString(); + + case SerializedPropertyType.ObjectReference: + return property.objectReferenceValue != null ? property.objectReferenceValue.ToString() : "null"; + + case SerializedPropertyType.Boolean: + return property.boolValue.ToString(); + + case SerializedPropertyType.Enum: + return property.GetValue().ToString(); + + default: + return string.Empty; + } + } + + /// + /// Property path for collection without ".Array.data[x]" in it + /// + public static string GetFixedPropertyPath(this SerializedProperty property) => property.propertyPath.Replace(".Array.data[", "["); + + + /// + /// Get FieldInfo out of SerializedProperty + /// + public static FieldInfo GetFieldInfo(this SerializedProperty property) { + var targetObject = property.serializedObject.targetObject; + var targetType = targetObject.GetType(); + return targetType.GetField(property.propertyPath); + } + + /// + /// Get raw object value out of the SerializedProperty + /// + public static object GetValue(this SerializedProperty property) { + if(property == null) return null; + + object obj = property.serializedObject.targetObject; + var elements = property.GetFixedPropertyPath().Split('.'); + foreach(var element in elements) { + if(element.Contains("[")) { + var elementName = element.Substring(0, element.IndexOf("[", StringComparison.Ordinal)); + var index = Convert.ToInt32(element.Substring(element.IndexOf("[", StringComparison.Ordinal)).Replace("[", "").Replace("]", "")); + obj = GetValueByArrayFieldName(obj, elementName, index); + } + else obj = GetValueByFieldName(obj, element); + } + return obj; + + + object GetValueByArrayFieldName(object source, string name, int index) { + if(!(GetValueByFieldName(source, name) is IEnumerable enumerable)) return null; + var enumerator = enumerable.GetEnumerator(); + + for(var i = 0; i <= index; i++) if(!enumerator.MoveNext()) return null; + return enumerator.Current; + } + + // Search "source" object for a field with "name" and get it's value + object GetValueByFieldName(object source, string name) { + if(source == null) return null; + var type = source.GetType(); + + while(type != null) { + var fieldInfo = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + if(fieldInfo != null) return fieldInfo.GetValue(source); + + var propertyInfo = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + if(propertyInfo != null) return propertyInfo.GetValue(source, null); + + type = type.BaseType; + } + return null; + } + } + + + /// + /// Set raw object value to the SerializedProperty + /// + public static void SetValue(this SerializedProperty property, object value) { + GetFieldInfo(property).SetValue(property.serializedObject.targetObject, value); + } + + /// + /// Is specific attribute defined on SerializedProperty + /// + /// + /// + /// + public static bool IsAttributeDefined(this SerializedProperty property) where T : Attribute { + var fieldInfo = property.GetFieldInfo(); + if(fieldInfo == null) return false; + return Attribute.IsDefined(fieldInfo, typeof(T)); + } + + #region SerializedProperty Get Parent + + // Found here http://answers.unity.com/answers/425602/view.html + // Update here https://gist.github.com/AdrienVR/1548a145c039d2fddf030ebc22f915de to support inherited private members. + /// + /// Get parent object of SerializedProperty + /// + public static object GetParent(this SerializedProperty prop) { + var path = prop.propertyPath.Replace(".Array.data[", "["); + object obj = prop.serializedObject.targetObject; + var elements = path.Split('.'); + foreach(var element in elements.Take(elements.Length - 1)) { + if(element.Contains("[")) { + var elementName = element.Substring(0, element.IndexOf("[", StringComparison.Ordinal)); + var index = Convert.ToInt32(element.Substring(element.IndexOf("[", StringComparison.Ordinal)).Replace("[", "").Replace("]", "")); + obj = GetValueAt(obj, elementName, index); + } + else { + obj = GetValue(obj, element); + } + } + + return obj; + } + + private static object GetValue(object source, string name) { + if(source == null) + return null; + + foreach(var type in GetHierarchyTypes(source.GetType())) { + var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + if(f != null) + return f.GetValue(source); + + var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + if(p != null) + return p.GetValue(source, null); + } + + return null; + } + + private static IEnumerable GetHierarchyTypes(Type sourceType) { + yield return sourceType; + while(sourceType.BaseType != null) { + yield return sourceType.BaseType; + sourceType = sourceType.BaseType; + } + } + + private static object GetValueAt(object source, string name, int index) { + var enumerable = GetValue(source, name) as IEnumerable; + if(enumerable == null) return null; + + var enm = enumerable.GetEnumerator(); + while(index-- >= 0) + enm.MoveNext(); + return enm.Current; + } + + #endregion + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MySerializedProperty.cs.meta b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MySerializedProperty.cs.meta new file mode 100644 index 0000000..fd14a14 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/EditorExtensions/MySerializedProperty.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1647a9f1d48b4cdcbc9d8c0ed8ec8c5d +timeCreated: 1550229037 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Extensions/MyCollections.cs b/Packages/com.jovian.inspector/Editor/Extensions/MyCollections.cs new file mode 100644 index 0000000..de4209c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/MyCollections.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace InspectorToolkit { + public static class MyCollections { + /// + /// Returns new array with inserted empty element at index + /// + public static T[] InsertAt(this T[] array, int index) { + if(index < 0) { + Debug.LogError("Index is less than zero. Array is not modified"); + return array; + } + + if(index > array.Length) { + Debug.LogError("Index exceeds array length. Array is not modified"); + return array; + } + + T[] newArray = new T[array.Length + 1]; + int index1 = 0; + for(int index2 = 0; index2 < newArray.Length; ++index2) { + if(index2 == index) continue; + + newArray[index2] = array[index1]; + ++index1; + } + + return newArray; + } + + #region IsNullOrEmpty and NotNullOrEmpty + + /// + /// Is array null or empty + /// + public static bool IsNullOrEmpty(this T[] collection) => collection == null || collection.Length == 0; + + /// + /// Is list null or empty + /// + public static bool IsNullOrEmpty(this IList collection) => collection == null || collection.Count == 0; + + /// + /// Is collection null or empty. IEnumerable is relatively slow. Use Array or List implementation if possible + /// + public static bool IsNullOrEmpty(this IEnumerable collection) => collection == null || !collection.Any(); + + public static bool NotNullOrEmpty(this IEnumerable collection) => !collection.IsNullOrEmpty(); + + #endregion + + + /// + /// Performs a function on each element of a collection. + /// + public static IEnumerable ForEach(this IEnumerable source, Func func) { + foreach(T element in source) func(element); + return source; + } + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Extensions/MyCollections.cs.meta b/Packages/com.jovian.inspector/Editor/Extensions/MyCollections.cs.meta new file mode 100644 index 0000000..e8e71de --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/MyCollections.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6de18df9fa1208947a40cdd99eaf83f5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Extensions/MyRegex.cs b/Packages/com.jovian.inspector/Editor/Extensions/MyRegex.cs new file mode 100644 index 0000000..84a340f --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/MyRegex.cs @@ -0,0 +1,24 @@ +using System.Linq; +using System.Text.RegularExpressions; + +namespace InspectorToolkit +{ + public static class MyRegex + { + public const string WholeNumber = @"^-?\d+$"; + public const string FloatingNumber = @"^-?\d*(\.\d+)?$"; + + public const string AlphanumericWithoutSpace = @"^[a-zA-Z0-9]*$"; + public const string AlphanumericWithSpace = @"^[a-zA-Z0-9 ]*$"; + + public const string Email = @"^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})*$"; + public const string URL = @"(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)"; + + + /// + /// Replace all but matching parts of the input string + /// + public static string KeepMatching(this Regex regex, string input) => regex.Matches(input).Cast() + .Aggregate(string.Empty, (a, m) => a + m.Value); + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Extensions/MyRegex.cs.meta b/Packages/com.jovian.inspector/Editor/Extensions/MyRegex.cs.meta new file mode 100644 index 0000000..57b4c88 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Extensions/MyRegex.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1403a06229e54d928e7366105c447cb7 +timeCreated: 1623083222 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Jovian.InspectorTools.asmdef b/Packages/com.jovian.inspector/Editor/Jovian.InspectorTools.asmdef new file mode 100644 index 0000000..5e51ac7 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Jovian.InspectorTools.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Jovian.InspectorTools", + "rootNamespace": "", + "references": [ + "GUID:6c878acd8a48e2d48ad42c741bc8551d", + "GUID:b9747d4f21927cf4c9f4a1c9c0680d86" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Jovian.InspectorTools.asmdef.meta b/Packages/com.jovian.inspector/Editor/Jovian.InspectorTools.asmdef.meta new file mode 100644 index 0000000..a4714f5 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Jovian.InspectorTools.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: bdd23c84fca642c4dbfafdf2e45a581e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Tools.meta b/Packages/com.jovian.inspector/Editor/Tools.meta new file mode 100644 index 0000000..48ef34c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 23ebf63dd08d6264dab5a6368cf5f24d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Tools/AutoFindUtil.cs b/Packages/com.jovian.inspector/Editor/Tools/AutoFindUtil.cs new file mode 100644 index 0000000..ad5481a --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/AutoFindUtil.cs @@ -0,0 +1,141 @@ +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using System.Linq; +using Jovian.Utilities; +using Jovian.Utilities.Editor; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace InspectorToolkit { + public static class AutoFindUtil { + public static void Search(AutoFindAttribute autoFindAttribute, SerializedProperty property, bool autoAddIfMissing = false) { + Type propertyType = EditorSerializationUtility.GetTypeFromProperty(property); + + foreach (Object targetObject in property.serializedObject.targetObjects) { + if (IsScopeGameObject(autoFindAttribute.scope)) { + GameObject targetGameObject = ((Component)targetObject).gameObject; + + if (EditorSerializationUtility.IsPropertyAnArrayElement(property)) { + SerializedProperty arrayProperty = + EditorSerializationUtility.GetArrayPropertyWithElementProperty(property); + EditorSerializationUtility.SaveObjectProperties( + targetObject, + arrayProperty.name, + GetComponents(autoFindAttribute.scope, targetGameObject, propertyType, + autoFindAttribute.filter)); + } + else { + Component component = GetComponent(autoFindAttribute.scope, targetGameObject, propertyType, + autoFindAttribute.filter); + + if (autoAddIfMissing && !component) { + component = Undo.AddComponent(targetGameObject, propertyType); + } + + EditorSerializationUtility.SaveObjectProperties( + targetObject, + property.name, + component); + } + } + else if (autoFindAttribute.scope == AutoFindScope.Scene) { + IEnumerable componentsInHierarchy = + FilterComponents(HierarchyUtility.FindComponentsInHierarchy(propertyType, true), + autoFindAttribute.filter); + if (EditorSerializationUtility.IsPropertyAnArrayElement(property)) { + SerializedProperty arrayProperty = + EditorSerializationUtility.GetArrayPropertyWithElementProperty(property); + EditorSerializationUtility.SaveObjectProperties( + targetObject, + arrayProperty.name, + componentsInHierarchy); + } + else { + EditorSerializationUtility.SaveObjectProperties( + targetObject, + property.name, + componentsInHierarchy.FirstOrDefault()); + } + } + else if (autoFindAttribute.scope == AutoFindScope.Project) { + if (EditorSerializationUtility.IsPropertyAnArrayElement(property)) { + SerializedProperty arrayProperty = + EditorSerializationUtility.GetArrayPropertyWithElementProperty(property); + List assets = autoFindAttribute.isPrefab + ? AssetUtility.FindAllPrefabsInProject(propertyType, autoFindAttribute.filter) + : AssetUtility.FindAllAssetsInProject(propertyType, autoFindAttribute.filter); + EditorSerializationUtility.SaveObjectProperties( + targetObject, + arrayProperty.name, + assets + ); + } + else { + Object asset = autoFindAttribute.isPrefab + ? AssetUtility.FindPrefabInProject(propertyType, autoFindAttribute.filter) + : AssetUtility.FindAssetInProject(propertyType, autoFindAttribute.filter); + EditorSerializationUtility.SaveObjectProperties( + targetObject, + property.name, + asset + ); + } + } + } + } + + private static bool IsScopeGameObject(AutoFindScope scope) { + switch (scope) { + case AutoFindScope.Self: + case AutoFindScope.SelfAndChildren: + case AutoFindScope.SelfAndParent: + return true; + } + + return false; + } + + private static Component GetComponent(AutoFindScope scope, + GameObject gameObject, + Type componentType, + string filter = null) { + return GetComponents(scope, gameObject, componentType, filter).FirstOrDefault(); + } + + private static IEnumerable GetComponents(AutoFindScope scope, + GameObject gameObject, + Type componentType, + string filter = null) { + switch (scope) { + case AutoFindScope.Self: + List result = new(); + Component component = gameObject.GetComponent(componentType); + if (component != null) { + result.Add(component); + } + + return FilterComponents(result, filter); + case AutoFindScope.SelfAndChildren: + return FilterComponents(gameObject.GetComponentsInChildren(componentType, true), filter); + case AutoFindScope.SelfAndParent: + return FilterComponents(gameObject.GetComponentsInParent(componentType, true), filter); + case AutoFindScope.Scene: + case AutoFindScope.Project: + throw new NotSupportedException($"{scope} is not supported with GetComponents"); + default: + throw new ArgumentOutOfRangeException(nameof(scope), scope, null); + } + } + + private static IEnumerable FilterComponents(IEnumerable components, string filter) { + return string.IsNullOrEmpty(filter) ? components : components.Where(c => FilterMatchesName(c.name, filter)); + } + + private static bool FilterMatchesName(string name, string filter) { + return name.Contains(filter, StringComparison.InvariantCultureIgnoreCase); + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Tools/AutoFindUtil.cs.meta b/Packages/com.jovian.inspector/Editor/Tools/AutoFindUtil.cs.meta new file mode 100644 index 0000000..cd20934 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/AutoFindUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8bc5f32e6036f5d4e8f5f8f8b145548b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Tools/IActionableAttribute.cs b/Packages/com.jovian.inspector/Editor/Tools/IActionableAttribute.cs new file mode 100644 index 0000000..4044f0b --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/IActionableAttribute.cs @@ -0,0 +1,11 @@ +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit { + public interface IActionableAttribute { +#if UNITY_EDITOR + void Trigger(SerializedProperty serializedProperty); +#endif + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Tools/IActionableAttribute.cs.meta b/Packages/com.jovian.inspector/Editor/Tools/IActionableAttribute.cs.meta new file mode 100644 index 0000000..b852dfb --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/IActionableAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 581cfe2990d4d134d8ff859a2ee1377a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Tools/IPropertyCondition.cs b/Packages/com.jovian.inspector/Editor/Tools/IPropertyCondition.cs new file mode 100644 index 0000000..4273c24 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/IPropertyCondition.cs @@ -0,0 +1,11 @@ +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace InspectorToolkit { + public interface IPropertyCondition { +#if UNITY_EDITOR + bool DoesPropertyMeetCondition(SerializedProperty property, out string description); +#endif + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Tools/IPropertyCondition.cs.meta b/Packages/com.jovian.inspector/Editor/Tools/IPropertyCondition.cs.meta new file mode 100644 index 0000000..11df77d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/IPropertyCondition.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24e7483e9296c494283a1bb2f711c745 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Tools/Internal.meta b/Packages/com.jovian.inspector/Editor/Tools/Internal.meta new file mode 100644 index 0000000..77db5ac --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9edfed511fd35584aa76d99f420d8ffb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Tools/Internal/PlayModeVisibility.cs b/Packages/com.jovian.inspector/Editor/Tools/Internal/PlayModeVisibility.cs new file mode 100644 index 0000000..6f763f9 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/Internal/PlayModeVisibility.cs @@ -0,0 +1,24 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace InspectorToolkit { + public enum PlayModeVisibility { + Always, + EditMode, + PlayMode + } + + public static class PlayModeVisibilityExtensions { + public static bool IsVisible(this PlayModeVisibility visibility) { + switch(visibility) { + case PlayModeVisibility.EditMode: + return Application.isPlaying == false; + case PlayModeVisibility.PlayMode: + return Application.isPlaying; + default: + return true; + } + } + } +} \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Tools/Internal/PlayModeVisibility.cs.meta b/Packages/com.jovian.inspector/Editor/Tools/Internal/PlayModeVisibility.cs.meta new file mode 100644 index 0000000..eb01a5a --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/Internal/PlayModeVisibility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eaa053cd33e1de54da40cf9b02c51661 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/Editor/Tools/Internal/UnityObjectEditor.cs b/Packages/com.jovian.inspector/Editor/Tools/Internal/UnityObjectEditor.cs new file mode 100644 index 0000000..d772e70 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/Internal/UnityObjectEditor.cs @@ -0,0 +1,149 @@ +using UnityEditor; +using UnityEngine; + +#if UNITY_EDITOR +namespace InspectorToolkit.Internal { + + public interface IGUILayoutPropertyField { + void GUILayoutPropertyField(SerializedProperty serializedProperty, params GUILayoutOption[] options); + void GUILayoutPropertyField(SerializedProperty serializedProperty, bool includeChildren, params GUILayoutOption[] options); + void GUILayoutPropertyField(SerializedProperty serializedProperty, GUIContent label, bool includeChildren, params GUILayoutOption[] options); + } + + [CustomEditor(typeof(Object), true)] [CanEditMultipleObjects] + public class UnityObjectEditor : Editor, IGUILayoutPropertyField { + private FoldoutAttributeHandler foldout; + private ButtonMethodHandler buttonMethod; + private ShowInInspectorAttributeHandler showInInspectorHandler; + private float lastShowInInspectorTime; + + + private static ButtonMethodHandler[] previewButtonMethods; + private static Vector2 previewScroll; + private Rect previewRect; + + private void OnEnable() { + if(target == null) { + return; + } + + foldout = new FoldoutAttributeHandler(target, serializedObject, this); + buttonMethod = new ButtonMethodHandler(target); + showInInspectorHandler = new ShowInInspectorAttributeHandler(target, serializedObject); + + if (previewButtonMethods == null && target is Component component) { + previewScroll = Vector2.zero; + Component[] objectComponents = component.gameObject.GetComponents(); + previewButtonMethods = new ButtonMethodHandler[objectComponents.Length]; + for (int i = 0; i < objectComponents.Length; i++) { + previewButtonMethods[i] = new ButtonMethodHandler(objectComponents[i]); + } + } + + EditorApplication.update -= OnUpdate; + EditorApplication.update += OnUpdate; + } + + private void OnDisable() { + EditorApplication.update -= OnUpdate; + foldout?.OnDisable(); + + previewButtonMethods = null; + } + + private void OnUpdate() { + if(showInInspectorHandler?.HasValues == true) { + float newShowInInspectorTime = Time.realtimeSinceStartup % 1f; + if(newShowInInspectorTime < lastShowInInspectorTime) { + Repaint(); + } + lastShowInInspectorTime = newShowInInspectorTime; + } + } + + public override void OnInspectorGUI() { + if (buttonMethod != null && buttonMethod.HasAnyVisibleMethods()) { + buttonMethod?.OnBeforeInspectorGUI(); + EditorGUILayout.Space(); + } + + if(foldout != null) { + foldout.Update(); + if(!foldout.OverrideInspector) { + base.OnInspectorGUI(); + } + else { + foldout.OnInspectorGUI(); + } + } + + if(showInInspectorHandler != null) { + showInInspectorHandler.Update(); + if(showInInspectorHandler.HasValues) { + showInInspectorHandler.OnInspectorGUI(this); + } + } + + if(buttonMethod != null && buttonMethod.HasAnyVisibleMethods()){ + EditorGUILayout.Space(); + buttonMethod?.OnAfterInspectorGUI(); + } + } + + private static bool HasAnyVisiblePreviewMethods() { + if (previewButtonMethods == null) { + return false; + } + + foreach (ButtonMethodHandler previewButtonMethod in previewButtonMethods) { + if (previewButtonMethod.HasAnyVisibleMethods()) { + return true; + } + } + + return false; + } + + public override bool HasPreviewGUI() => targets != null && targets.Length == 1 && target != null && HasAnyVisiblePreviewMethods(); + + public override void OnPreviewGUI(Rect r, GUIStyle background) { + // Only Repaint has the true Rect size - so store it whenever we repaint. + if (Event.current.type == EventType.Repaint) { + previewRect = r; + } + GUILayout.BeginArea(previewRect); + previewScroll = GUILayout.BeginScrollView(previewScroll); + + foreach (ButtonMethodHandler previewButtonMethod in previewButtonMethods) { + if (previewButtonMethod.HasAnyVisibleMethods()) { + GUILayout.BeginVertical(EditorStyles.helpBox); + GUILayout.Label(ObjectNames.NicifyVariableName(previewButtonMethod.Target.GetType().Name), EditorStyles.boldLabel); + previewButtonMethod.OnBeforeInspectorGUI(); + previewButtonMethod.OnAfterInspectorGUI(); + GUILayout.EndVertical(); + } + } + + GUILayout.EndScrollView(); + GUILayout.EndArea(); + } + + public void GUILayoutPropertyField(SerializedProperty serializedProperty, params GUILayoutOption[] options) { + GUILayoutPropertyField(serializedProperty, true, options); + } + + public void GUILayoutPropertyField(SerializedProperty serializedProperty, bool includeChildren, params GUILayoutOption[] options) { + GUILayoutPropertyField(serializedProperty, new GUIContent(ObjectNames.NicifyVariableName(serializedProperty.name), serializedProperty.tooltip), includeChildren, options); + } + + public void GUILayoutPropertyField(SerializedProperty serializedProperty, GUIContent label, bool includeChildren, params GUILayoutOption[] options) { + if (serializedProperty.isArray) { + ArrayAttributePropertyHandler.DrawArrayProperty(serializedProperty, label, includeChildren, options); + } + else { + EditorGUILayout.PropertyField(serializedProperty, label, includeChildren, options); + } + } + } +} +#endif diff --git a/Packages/com.jovian.inspector/Editor/Tools/Internal/UnityObjectEditor.cs.meta b/Packages/com.jovian.inspector/Editor/Tools/Internal/UnityObjectEditor.cs.meta new file mode 100644 index 0000000..72c6b80 --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/Internal/UnityObjectEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e53afb9413554e64be65650721f4bb12 +timeCreated: 1576668537 \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Tools/RequiredUtil.cs b/Packages/com.jovian.inspector/Editor/Tools/RequiredUtil.cs new file mode 100644 index 0000000..40b708d --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/RequiredUtil.cs @@ -0,0 +1,44 @@ + +using UnityEngine; + +#if UNITY_EDITOR +using UnityEditor; +namespace InspectorToolkit.Internal { + + public static class RequiredUtil { + private const float ICON_X_OFFSET = 15f; + private const float ICON_SIZE = 18f; + private static Color ErrorColor = new(1f, 0.5f, 0.5f, 1f); + private static GUIContent errorIcon; + private static GUIStyle errorStyle; + + public static void LayoutRequired(ref Rect position, string tooltip, bool indent = true) { + if (indent) { + position.x += ICON_SIZE; + position.width -= ICON_SIZE; + } + } + + public static void DrawRequired(ref Rect position, string tooltip, bool indent = true) { + if(errorIcon == null || errorStyle == null) { + errorIcon = new GUIContent(EditorGUIUtility.IconContent("console.erroricon.sml").image); + errorStyle = new GUIStyle(GUI.skin.label) { + padding = new RectOffset(0, 0, 0, 0), + imagePosition = ImagePosition.ImageOnly + }; + } + + errorIcon.tooltip = tooltip; + + Rect errorIconRect = new Rect(position.x - ICON_X_OFFSET, position.y + 1, ICON_SIZE, EditorGUIUtility.singleLineHeight); + GUI.Label(errorIconRect, errorIcon, errorStyle); + GUI.color = ErrorColor; + + if (indent) { + position.x += ICON_SIZE; + position.width -= ICON_SIZE; + } + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.jovian.inspector/Editor/Tools/RequiredUtil.cs.meta b/Packages/com.jovian.inspector/Editor/Tools/RequiredUtil.cs.meta new file mode 100644 index 0000000..f8d448c --- /dev/null +++ b/Packages/com.jovian.inspector/Editor/Tools/RequiredUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 64fce657f181acc4f85facc3de64fba7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.inspector/package.json b/Packages/com.jovian.inspector/package.json new file mode 100644 index 0000000..100cf08 --- /dev/null +++ b/Packages/com.jovian.inspector/package.json @@ -0,0 +1,7 @@ +{ + "name": "com.jovian.inspector-tools", + "displayName": "Jovian Inspector Tools", + "version": "1.0.0", + "description": "Inspector enhancements. Based on MyBox", + "relatedPackages": {} +} diff --git a/Packages/com.jovian.inspector/package.json.meta b/Packages/com.jovian.inspector/package.json.meta new file mode 100644 index 0000000..8da21f4 --- /dev/null +++ b/Packages/com.jovian.inspector/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b0ecbfa3b35222d43ae94da1cf712764 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.jovian.utilties/Editor/EditorSerializationUtility.cs b/Packages/com.jovian.utilties/Editor/EditorSerializationUtility.cs new file mode 100644 index 0000000..354d912 --- /dev/null +++ b/Packages/com.jovian.utilties/Editor/EditorSerializationUtility.cs @@ -0,0 +1,428 @@ +#if UNITY_EDITOR +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text.RegularExpressions; +using UnityEngine; +using UnityEngine.Assertions; +using Debug = UnityEngine.Debug; +using Object = UnityEngine.Object; +using Type = System.Type; +using UnityEditor; + +namespace Jovian.Utilities.Editor { + /// + /// Helper class for serializing objects in the editor + /// + public static class EditorSerializationUtility { + + /// + /// Returns the property type for cases when it is needed, like in the case of trying to get the type of SerializedPropertyType.ObjectReference + /// Solution found on https://answers.unity.com/questions/929293/get-field-type-of-serializedproperty.html + /// + /// + /// + public static Type GetTypeFromProperty(SerializedProperty property) { + //gets parent type info + string[] slices = property.propertyPath.Split('.'); + Type type = property.serializedObject.targetObject.GetType(); + + for (int i = 0; i < slices.Length; i++) { + string slice = slices[i]; + if (slice == "Array") { + i++; //skips "data[x]" + if (type.IsArray) // e.g Type[] + { + type = type.GetElementType(); + } + else if (type.IsGenericType) // e.g. List + { + type = type.GetGenericArguments()[0]; + } + else { + throw new NotSupportedException("Unsupported array type. Type[] or List are only supported array types"); + } + } + else { + //gets info on field and its type + type = type.GetField(slice, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance)?.FieldType; + } + + if (type == null) { + throw new NullReferenceException( + $"Type is null, something is not working correctly. Path={property.propertyPath}, Slice={slice}"); + } + } + + //type is now the type of the property + return type; + } + + private static readonly Regex isArrayElementRegex = new Regex("Array.data\\[\\d+\\]$"); + + /// + /// Checks if the property is an array element + /// + /// + /// + public static bool IsPropertyAnArrayElement(SerializedProperty property) { + return isArrayElementRegex.IsMatch(property.propertyPath); + } + + /// + /// Returns the parent array property for the given property, which is a member of the array + /// + /// + /// + public static SerializedProperty GetArrayPropertyWithElementProperty(SerializedProperty property) { + SerializedObject serializedObject = property.serializedObject; + string propertyPath = property.propertyPath; + int arrayDataIndex = propertyPath.LastIndexOf(".Array.data", StringComparison.Ordinal); + string propertyPathWithoutArray = propertyPath.Substring(0, arrayDataIndex); + int pathDividerIndex = propertyPathWithoutArray.LastIndexOf(".", StringComparison.Ordinal); + + string parentPropertyName = propertyPathWithoutArray; + if (pathDividerIndex != -1) { + parentPropertyName = propertyPathWithoutArray.Substring(pathDividerIndex); + } + + return serializedObject.FindProperty(parentPropertyName); + } + + /// + /// Will save specifically the property args passed in the form new object[] { "propertyName1", "propertyName2", etc. } to the specified targetObject + /// + /// + /// + /// + /// + public static void SaveObjectProperties(Object targetObject, params object[] args) { + SerializedObject serializedObject = new SerializedObject(targetObject); + + for (int i = 0; i < args.Length; i += 2) { + object keyArg = args[i]; + Type keyType = keyArg.GetType(); + + if ((keyType == typeof(string)) == false) { + throw new NotSupportedException($"Key must be string. {args[i]} is {keyType}"); + } + + SerializedProperty property = serializedObject.FindProperty((string)keyArg); + object argValue = args[i + 1]; + + if (property == null) { + throw new Exception($"No property found for key {keyArg}"); + } + + if (argValue == null) { + property.objectReferenceValue = null; + } + else { + SetSerializedPropertyValue(property, argValue); + } + } + + serializedObject.ApplyModifiedProperties(); + } + + /// + /// Will auto-detect the value type and save it to the property. It only supports the serializable types that the Unity serialization system supports + /// + /// + /// + /// + public static void SetSerializedPropertyValue(SerializedProperty fromProperty, object value) { + if (fromProperty == null) { + Debug.LogError("SetSerializedPropertyValue failed, property is null"); + return; + } + + // Strings are counted as arrays but should be handled separately + if (fromProperty.isArray && fromProperty.propertyType != SerializedPropertyType.String) { + fromProperty.arraySize = 0; + var argArray = (IEnumerable)value; + var enumerator = argArray.GetEnumerator(); + + int index = 0; + while (enumerator.MoveNext()) { + fromProperty.InsertArrayElementAtIndex(index); + var arrayProperty = fromProperty.GetArrayElementAtIndex(index); + SetSerializedPropertyValue(arrayProperty, enumerator.Current); + index++; + } + } + else { + switch (fromProperty.propertyType) { + case SerializedPropertyType.AnimationCurve: + fromProperty.animationCurveValue = (AnimationCurve)value; + break; + case SerializedPropertyType.Boolean: + fromProperty.boolValue = (bool)value; + break; + case SerializedPropertyType.BoundsInt: + fromProperty.boundsIntValue = (BoundsInt)value; + break; + case SerializedPropertyType.Character: + fromProperty.intValue = (int)(char)value; + break; + case SerializedPropertyType.Color: { + if (value is Color32 color32) { + fromProperty.colorValue = (Color)color32; + } + else { + fromProperty.colorValue = (Color)value; + } + + break; + } + case SerializedPropertyType.ExposedReference: + case SerializedPropertyType.ObjectReference: + fromProperty.objectReferenceValue = (Object)value; + break; + case SerializedPropertyType.Float: + fromProperty.floatValue = (float)value; + break; + case SerializedPropertyType.Integer: + fromProperty.intValue = (int)value; + break; + case SerializedPropertyType.LayerMask: + fromProperty.intValue = ((LayerMask)value).value; + break; + case SerializedPropertyType.Quaternion: + fromProperty.quaternionValue = (Quaternion)value; + break; + case SerializedPropertyType.Rect: + fromProperty.rectValue = (Rect)value; + break; + case SerializedPropertyType.RectInt: + fromProperty.rectIntValue = (RectInt)value; + break; + case SerializedPropertyType.String: + fromProperty.stringValue = (string)value; + break; + case SerializedPropertyType.Vector2: + fromProperty.vector2Value = (Vector2)value; + break; + case SerializedPropertyType.Vector2Int: + fromProperty.vector2IntValue = (Vector2Int)value; + break; + case SerializedPropertyType.Vector3: + fromProperty.vector3Value = (Vector3)value; + break; + case SerializedPropertyType.Vector3Int: + fromProperty.vector3IntValue = (Vector3Int)value; + break; + case SerializedPropertyType.Vector4: + fromProperty.vector4Value = (Vector4)value; + break; + case SerializedPropertyType.Enum: + fromProperty.enumValueIndex = Array.IndexOf(Enum.GetValues(value.GetType()), value); + // flags??? + break; + case SerializedPropertyType.Generic: + SaveGenericProperty(fromProperty, value); + break; + default: + throw new NotSupportedException($"PropertyType {fromProperty.propertyType} is not supported - yet. Array? {fromProperty.isArray} Path: {fromProperty.propertyPath}"); + } + } + } + + /// + /// Will copy fromProperty to toProperty. It only supports the serializable types that the Unity serialization system supports + /// + /// + /// + /// + public static void CopyPropertyValue(SerializedProperty fromProperty, SerializedProperty toProperty) { + Assert.IsNotNull(fromProperty, "fromProperty == null"); + Assert.IsNotNull(toProperty, "toProperty == null"); + Assert.AreEqual(fromProperty.propertyType, toProperty.propertyType, $"Properties do not match types. fromType={fromProperty.propertyType}, toType={toProperty.propertyType}"); + switch (fromProperty.propertyType) { + case SerializedPropertyType.AnimationCurve: + toProperty.animationCurveValue = fromProperty.animationCurveValue; + break; + case SerializedPropertyType.Boolean: + toProperty.boolValue = fromProperty.boolValue; + break; + case SerializedPropertyType.BoundsInt: + toProperty.boundsIntValue = fromProperty.boundsIntValue; + break; + case SerializedPropertyType.Character: + toProperty.intValue = fromProperty.intValue; + break; + case SerializedPropertyType.Color: { + toProperty.colorValue = fromProperty.colorValue; + break; + } + case SerializedPropertyType.ExposedReference: + case SerializedPropertyType.ObjectReference: + toProperty.objectReferenceValue = fromProperty.objectReferenceValue; + break; + case SerializedPropertyType.Float: + toProperty.floatValue = fromProperty.floatValue; + break; + case SerializedPropertyType.Integer: + toProperty.intValue = fromProperty.intValue; + break; + case SerializedPropertyType.LayerMask: + toProperty.intValue = fromProperty.intValue; + break; + case SerializedPropertyType.Quaternion: + toProperty.quaternionValue = fromProperty.quaternionValue; + break; + case SerializedPropertyType.Rect: + toProperty.rectValue = fromProperty.rectValue; + break; + case SerializedPropertyType.RectInt: + toProperty.rectIntValue = fromProperty.rectIntValue; + break; + case SerializedPropertyType.String: + toProperty.stringValue = fromProperty.stringValue; + break; + case SerializedPropertyType.Vector2: + toProperty.vector2Value = fromProperty.vector2Value; + break; + case SerializedPropertyType.Vector2Int: + toProperty.vector2IntValue = fromProperty.vector2IntValue; + break; + case SerializedPropertyType.Vector3: + toProperty.vector3Value = fromProperty.vector3Value; + break; + case SerializedPropertyType.Vector3Int: + toProperty.vector3IntValue = fromProperty.vector3IntValue; + break; + case SerializedPropertyType.Vector4: + toProperty.vector4Value = fromProperty.vector4Value; + break; + case SerializedPropertyType.Enum: + toProperty.intValue = fromProperty.intValue; + break; + case SerializedPropertyType.Generic: + CopyGenericProperty(fromProperty, toProperty); + break; + case SerializedPropertyType.ManagedReference: + toProperty.managedReferenceValue = Activator.CreateInstance(fromProperty.managedReferenceValue.GetType()); + CopyGenericProperty(fromProperty, toProperty); + break; + default: + throw new NotSupportedException($"PropertyType {fromProperty.propertyType} is not supported - yet. Array? {fromProperty.isArray} Path: {fromProperty.propertyPath}"); + } + } + + private static void CopyGenericProperty(SerializedProperty fromProperty, SerializedProperty toProperty) { + IEnumerator fromPropertyEnumerator = fromProperty.GetEnumerator(); + IEnumerator toPropertyEnumerator = toProperty.GetEnumerator(); + while (toPropertyEnumerator.MoveNext() && fromPropertyEnumerator.MoveNext()) { + if (toPropertyEnumerator.Current is SerializedProperty toChildProperty && + fromPropertyEnumerator.Current is SerializedProperty fromChildProperty) { + CopyPropertyValue(fromChildProperty, toChildProperty); + } + } + } + + private static void SaveGenericProperty(SerializedProperty property, object instance) { + Type type = instance.GetType(); + IEnumerable fields = type.GetRuntimeFields(); + foreach (FieldInfo field in fields) { + if (field.IsNotSerialized || field.IsStatic) { + continue; + } + + SerializedProperty fieldProperty = property.FindPropertyRelative(field.Name); + if (fieldProperty != null) { + SetSerializedPropertyValue(fieldProperty, field.GetValue(instance)); + } + else { + Debug.Log($"SaveGenericProperty cannot field serializedProperty named '{field.Name}'"); + } + } + } + + public static bool TryGetAttribute( + this SerializedProperty serializedProperty, + out TAttribute attribute, + bool includeAttributesFromParentProperties = false, + bool includeInheritedAttributes = true + ) + where TAttribute : Attribute { + if (serializedProperty == null) { + throw new ArgumentNullException(nameof(serializedProperty)); + } + + Type targetObjectType = serializedProperty.serializedObject.targetObject.GetType(); + + if (targetObjectType == null) { + throw new ArgumentException($"Could not find the {nameof(targetObjectType)} of {nameof(serializedProperty)}"); + } + + string[] pathSegments = includeAttributesFromParentProperties ? serializedProperty.propertyPath.Split('.') : new[] { serializedProperty.propertyPath }; + + foreach (string pathSegment in pathSegments) { + FieldInfo fieldInfo = targetObjectType.GetField(pathSegment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + if (fieldInfo != null) { + attribute = fieldInfo.GetCustomAttribute(includeInheritedAttributes); + if (attribute != null) { + return true; + } + } + + PropertyInfo propertyInfo = targetObjectType.GetProperty(pathSegment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + if (propertyInfo != null) { + attribute = propertyInfo.GetCustomAttribute(includeInheritedAttributes); + if (attribute != null) { + return true; + } + } + } + + attribute = null; + return false; + } + + public static bool TryGetAttributes( + SerializedProperty serializedProperty, + out List attributes, + bool includeAttributesFromParentProperties = false, + bool includeInheritedAttributes = true + ) + where TAttribute : Attribute { + if (serializedProperty == null) { + throw new ArgumentNullException(nameof(serializedProperty)); + } + + Type targetObjectType = serializedProperty.serializedObject.targetObject.GetType(); + + if (targetObjectType == null) { + throw new ArgumentException($"Could not find the {nameof(targetObjectType)} of {nameof(serializedProperty)}"); + } + + attributes = new List(); + + string[] pathSegments = includeAttributesFromParentProperties ? serializedProperty.propertyPath.Split('.') : new[] { serializedProperty.propertyPath }; + + foreach (string pathSegment in pathSegments) { + FieldInfo fieldInfo = targetObjectType.GetField(pathSegment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + if (fieldInfo != null) { + IEnumerable foundAttributes = fieldInfo.GetCustomAttributes(includeInheritedAttributes); + foreach (TAttribute foundAttribute in foundAttributes) { + attributes.Add(foundAttribute); + } + } + + PropertyInfo propertyInfo = targetObjectType.GetProperty(pathSegment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + if (propertyInfo != null) { + IEnumerable foundAttributes = propertyInfo.GetCustomAttributes(includeInheritedAttributes); + foreach (TAttribute attribute in foundAttributes) { + attributes.Add(attribute); + } + } + } + + return attributes.Count > 0; + } + + } +} +#endif diff --git a/Packages/com.jovian.utilties/Editor/EditorSerializationUtility.cs.meta b/Packages/com.jovian.utilties/Editor/EditorSerializationUtility.cs.meta new file mode 100644 index 0000000..062c29c --- /dev/null +++ b/Packages/com.jovian.utilties/Editor/EditorSerializationUtility.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 13a6cb5c52b6ad744a90547f102f5296 \ No newline at end of file diff --git a/Packages/com.jovian.utilties/Runtime/BowserLog.cs b/Packages/com.jovian.utilties/Runtime/BowserLog.cs deleted file mode 100644 index 882ab2e..0000000 --- a/Packages/com.jovian.utilties/Runtime/BowserLog.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; -using UnityEngine; -using Debug = UnityEngine.Debug; - -namespace Jovian.Utilities { - - public static class BowserLog { - - private const string PREFIX = "Bowser:"; - - [MethodImpl(MethodImplOptions.AggressiveInlining), DebuggerHidden] - public static void Log(string log, object obj = null) { - Debug.Log(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), DebuggerHidden] - public static void LogWarning(string log, object obj = null) { - Debug.LogWarning(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), DebuggerHidden] - public static void LogError(string log, object obj = null) { - Debug.LogError(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object); - } - - [Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD"), DebuggerHidden] - public static void LogDebug(string log, object obj = null) { - Debug.Log(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object); - } - - [Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD"), DebuggerHidden] - public static void LogWarningDebug(string log, object obj = null) { - Debug.LogWarning(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object); - } - - [Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD"), DebuggerHidden] - public static void LogErrorDebug(string log, object obj = null) { - Debug.LogError(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object); - } - } -} diff --git a/Packages/com.jovian.utilties/Runtime/BowserLog.cs.meta b/Packages/com.jovian.utilties/Runtime/BowserLog.cs.meta deleted file mode 100644 index 78d6b0a..0000000 --- a/Packages/com.jovian.utilties/Runtime/BowserLog.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 79607e8a014ff1841b183019d9102aff \ No newline at end of file diff --git a/Packages/com.jovian.utilties/package.json b/Packages/com.jovian.utilties/package.json index 52b89fb..facd60a 100644 --- a/Packages/com.jovian.utilties/package.json +++ b/Packages/com.jovian.utilties/package.json @@ -1,7 +1,7 @@ { "name": "com.jovian.utilities", "displayName": "Jovian Utilities", - "version": "0.1.18", + "version": "1.0.0", "description": "General purpose utility scripts", "hideInEditor": false } diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 5a1aa12..4691290 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -12,6 +12,12 @@ "source": "embedded", "dependencies": {} }, + "com.jovian.inspector-tools": { + "version": "file:com.jovian.inspector", + "depth": 0, + "source": "embedded", + "dependencies": {} + }, "com.jovian.logger": { "version": "file:com.jovian.logger", "depth": 0,