From c02c713d375d219e05e8b144ac8d243333ea0258 Mon Sep 17 00:00:00 2001 From: Rhys van der Waerden Date: Mon, 6 Mar 2023 00:50:13 +1100 Subject: [PATCH 1/2] Organize project for UPM support - Generate meta files - Add package.json - Move samples into hidden folder, importable via package manager --- Attributes.meta | 8 ++++++ .../Spiney.SubclassSelector.Runtime.asmdef | 14 +++++++++++ ...piney.SubclassSelector.Runtime.asmdef.meta | 7 ++++++ Attributes/SubclassPathAttribute.cs.meta | 11 ++++++++ Attributes/SubclassSelectorAttribute.cs.meta | 11 ++++++++ Editor.meta | 8 ++++++ Editor/Spiney.SubclassSelector.Editor.asmdef | 24 ++++++++++++++++++ ...Spiney.SubclassSelector.Editor.asmdef.meta | 7 ++++++ ...SubclassSelectorAttributeProcessor.cs.meta | 11 ++++++++ Editor/SubclassSelectorUtilities.cs.meta | 11 ++++++++ LICENSE.meta | 7 ++++++ README.md.meta | 7 ++++++ {Examples => Samples~}/PlainClasses.cs | 0 .../SubclassSelectorExample.cs | 0 package.json | 25 +++++++++++++++++++ package.json.meta | 7 ++++++ 16 files changed, 158 insertions(+) create mode 100644 Attributes.meta create mode 100644 Attributes/Spiney.SubclassSelector.Runtime.asmdef create mode 100644 Attributes/Spiney.SubclassSelector.Runtime.asmdef.meta create mode 100644 Attributes/SubclassPathAttribute.cs.meta create mode 100644 Attributes/SubclassSelectorAttribute.cs.meta create mode 100644 Editor.meta create mode 100644 Editor/Spiney.SubclassSelector.Editor.asmdef create mode 100644 Editor/Spiney.SubclassSelector.Editor.asmdef.meta create mode 100644 Editor/SubclassSelectorAttributeProcessor.cs.meta create mode 100644 Editor/SubclassSelectorUtilities.cs.meta create mode 100644 LICENSE.meta create mode 100644 README.md.meta rename {Examples => Samples~}/PlainClasses.cs (100%) rename {Examples => Samples~}/SubclassSelectorExample.cs (100%) create mode 100644 package.json create mode 100644 package.json.meta diff --git a/Attributes.meta b/Attributes.meta new file mode 100644 index 0000000..7a012f9 --- /dev/null +++ b/Attributes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3a249b41117a9b043a88be309acc8411 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Attributes/Spiney.SubclassSelector.Runtime.asmdef b/Attributes/Spiney.SubclassSelector.Runtime.asmdef new file mode 100644 index 0000000..66eca49 --- /dev/null +++ b/Attributes/Spiney.SubclassSelector.Runtime.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Spiney.SubclassSelector.Runtime", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Attributes/Spiney.SubclassSelector.Runtime.asmdef.meta b/Attributes/Spiney.SubclassSelector.Runtime.asmdef.meta new file mode 100644 index 0000000..ba2deff --- /dev/null +++ b/Attributes/Spiney.SubclassSelector.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: faf144d3e138eac43ae4270ba99588ca +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Attributes/SubclassPathAttribute.cs.meta b/Attributes/SubclassPathAttribute.cs.meta new file mode 100644 index 0000000..1994bce --- /dev/null +++ b/Attributes/SubclassPathAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 229d895b10849c849bb2a10e53ffddd3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Attributes/SubclassSelectorAttribute.cs.meta b/Attributes/SubclassSelectorAttribute.cs.meta new file mode 100644 index 0000000..7cb0311 --- /dev/null +++ b/Attributes/SubclassSelectorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 789e008babfd6c14d8edbd8799c9ed10 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..26c3f25 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7fb88d155aec1ad4297eef3b37234b9e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Spiney.SubclassSelector.Editor.asmdef b/Editor/Spiney.SubclassSelector.Editor.asmdef new file mode 100644 index 0000000..d022bb9 --- /dev/null +++ b/Editor/Spiney.SubclassSelector.Editor.asmdef @@ -0,0 +1,24 @@ +{ + "name": "Spiney.SubclassSelector.Editor", + "rootNamespace": "", + "references": [ + "GUID:faf144d3e138eac43ae4270ba99588ca" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "Sirenix.OdinInspector.Attributes.dll", + "Sirenix.OdinInspector.Editor.dll", + "Sirenix.Utilities.dll", + "Sirenix.Utilities.Editor.dll", + "Sirenix.Serialization.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/Spiney.SubclassSelector.Editor.asmdef.meta b/Editor/Spiney.SubclassSelector.Editor.asmdef.meta new file mode 100644 index 0000000..e195fc3 --- /dev/null +++ b/Editor/Spiney.SubclassSelector.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5e8aaa8b269e7d94f871987aa970bdd6 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SubclassSelectorAttributeProcessor.cs.meta b/Editor/SubclassSelectorAttributeProcessor.cs.meta new file mode 100644 index 0000000..2e13dec --- /dev/null +++ b/Editor/SubclassSelectorAttributeProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f2d5d65571cfd24d8b61ce170f074bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SubclassSelectorUtilities.cs.meta b/Editor/SubclassSelectorUtilities.cs.meta new file mode 100644 index 0000000..38d2f75 --- /dev/null +++ b/Editor/SubclassSelectorUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cceef74008801724f81412e2a4bbe06f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE.meta b/LICENSE.meta new file mode 100644 index 0000000..b43c09f --- /dev/null +++ b/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4fce850ad394c1d4a9e3299cd2f77665 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..acc7309 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 82e47bc39d43a1449bac3b8549f0e347 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PlainClasses.cs b/Samples~/PlainClasses.cs similarity index 100% rename from Examples/PlainClasses.cs rename to Samples~/PlainClasses.cs diff --git a/Examples/SubclassSelectorExample.cs b/Samples~/SubclassSelectorExample.cs similarity index 100% rename from Examples/SubclassSelectorExample.cs rename to Samples~/SubclassSelectorExample.cs diff --git a/package.json b/package.json new file mode 100644 index 0000000..bd70bcf --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "com.spiney.odin-subclass-selector", + "version": "1.0.0", + "displayName": "Odin Subclass Selector", + "description": "", + "unity": "2020.3", + "unityRelease": "21f1", + "licensesUrl": "https://github.com/spiney199/Subclass-Selector/blob/main/LICENSE", + "author": { + "name": "", + "email": "", + "url": "" + }, + "devDependencies": { + }, + "dependencies": { + }, + "samples": [ + { + "displayName": "Sample Scripts", + "description": "Example scripts using SubclassSelector attribute", + "path": "Samples~/" + } + ] +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..22fba55 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5b1c4525179d20548b93e690e2b7211c +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 612bb118ef93c605f56e8f76eaddd45aedeacaa1 Mon Sep 17 00:00:00 2001 From: Rhys van der Waerden Date: Mon, 6 Mar 2023 00:50:48 +1100 Subject: [PATCH 2/2] Add namespace Spiney.SubclassSelector To prevent name collision in global namespace --- Attributes/SubclassPathAttribute.cs | 119 +++--- Attributes/SubclassSelectorAttribute.cs | 163 ++++---- Editor/SubclassSelectorAttributeProcessor.cs | 153 ++++---- Editor/SubclassSelectorUtilities.cs | 379 ++++++++++--------- 4 files changed, 413 insertions(+), 401 deletions(-) diff --git a/Attributes/SubclassPathAttribute.cs b/Attributes/SubclassPathAttribute.cs index abe5ae8..b789c6c 100644 --- a/Attributes/SubclassPathAttribute.cs +++ b/Attributes/SubclassPathAttribute.cs @@ -1,81 +1,84 @@ using System; -/// -/// Use this attribute on plain class types to determine their path when using . -/// -[System.Diagnostics.Conditional("UNITY_EDITOR")] -[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -public sealed class SubclassPathAttribute : Attribute +namespace Spiney.SubclassSelector { - #region Constructors - - /// - /// Empty constructor. The types names in will not be modified. - /// - public SubclassPathAttribute() { } - /// - /// Determines the path for types displayed in . - /// The default type name will be used. + /// Use this attribute on plain class types to determine their path when using . /// - /// The path for this type. - public SubclassPathAttribute(string path) + [System.Diagnostics.Conditional("UNITY_EDITOR")] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class SubclassPathAttribute : Attribute { - SubClassPath = path; - } + #region Constructors - /// - /// Determines both the path and the display name for types displayed in . - /// - /// The path for this type. - /// The display name for this type. - public SubclassPathAttribute(string path, string subclassName) - { - SubClassPath = path; - SubClassName = subclassName; - } + /// + /// Empty constructor. The types names in will not be modified. + /// + public SubclassPathAttribute() { } + + /// + /// Determines the path for types displayed in . + /// The default type name will be used. + /// + /// The path for this type. + public SubclassPathAttribute(string path) + { + SubClassPath = path; + } - #endregion + /// + /// Determines both the path and the display name for types displayed in . + /// + /// The path for this type. + /// The display name for this type. + public SubclassPathAttribute(string path, string subclassName) + { + SubClassPath = path; + SubClassName = subclassName; + } - #region Internal Members + #endregion - private string _subClassPath = string.Empty; + #region Internal Members - private string _subClassName = string.Empty; + private string _subClassPath = string.Empty; - #endregion + private string _subClassName = string.Empty; - #region Properties + #endregion - /// - /// The path for this type as used by . - /// - public string SubClassPath - { - get => _subClassPath; - set + #region Properties + + /// + /// The path for this type as used by . + /// + public string SubClassPath { - HasSubClassPath = !string.IsNullOrEmpty(value); - _subClassPath = value; + get => _subClassPath; + set + { + HasSubClassPath = !string.IsNullOrEmpty(value); + _subClassPath = value; + } } - } - /// - /// The display name for this type as used by . - /// - public string SubClassName - { - get => _subClassName; - set + /// + /// The display name for this type as used by . + /// + public string SubClassName { - HasSubClassName = !string.IsNullOrEmpty(value); - _subClassName = value; + get => _subClassName; + set + { + HasSubClassName = !string.IsNullOrEmpty(value); + _subClassName = value; + } } - } - public bool HasSubClassPath { get; private set; } = false; + public bool HasSubClassPath { get; private set; } = false; - public bool HasSubClassName { get; private set; } = false; + public bool HasSubClassName { get; private set; } = false; - #endregion + #endregion + } } \ No newline at end of file diff --git a/Attributes/SubclassSelectorAttribute.cs b/Attributes/SubclassSelectorAttribute.cs index f6c415c..4541936 100644 --- a/Attributes/SubclassSelectorAttribute.cs +++ b/Attributes/SubclassSelectorAttribute.cs @@ -1,91 +1,94 @@ using System; -/// -/// Attribute that allows you to easily select from among the subclsses of a non-Unity Object, polymorphic reference value. -/// Supports both singular fields and collections of any type that Odin supports. -/// -[System.Diagnostics.Conditional("UNITY_EDITOR")] -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] -public sealed class SubclassSelectorAttribute : Attribute +namespace Spiney.SubclassSelector { - #region Constructor - - public SubclassSelectorAttribute() { } - - #endregion - - #region Internal Members - - private string _customTypeFilter = string.Empty; - - private bool _hasCustomTypeFilter = false; - - #endregion - - #region Properties - - /// - /// If false, duplicate won't be selectable in the collection selector, or collection element dropdowns. - /// - /// - /// Only affects collections. True by default. - /// - public bool AllowDuplicates { get; set; } = true; - - /// - /// Will hide the Odin default object reference picker if true as it it has been - /// decorated with . - /// - /// - /// True by default. - /// - public bool HideReferencePicker { get; set; } = true; - /// - /// If true, dropdowns will be drawn at the top - /// of each list element. + /// Attribute that allows you to easily select from among the subclsses of a non-Unity Object, polymorphic reference value. + /// Supports both singular fields and collections of any type that Odin supports. /// - /// - /// Only affects collections. False by default. - /// - public bool DrawDropdownForListElements { get; set; } = false; - - /// - /// If true, will draw a BoxGroup-style box around each element of a collection. - /// - /// - /// False by default. Only affects collections. - /// - public bool DrawBoxForListElements { get; set; } = false; - - /// - /// If true, will hide the class' label as if it had been decorated with . - /// - /// - /// Only affects non-collection fields. True by default. - /// - public bool HideClassLabel { get; set; } = true; - - /// - /// Provide a value resolver expression to filter types after the intial collection of subtypes has been generated. - /// - /// - /// Abstract types are already filtered. - /// - public string CustomTypeFilter + [System.Diagnostics.Conditional("UNITY_EDITOR")] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class SubclassSelectorAttribute : Attribute { - get => _customTypeFilter; - set + #region Constructor + + public SubclassSelectorAttribute() { } + + #endregion + + #region Internal Members + + private string _customTypeFilter = string.Empty; + + private bool _hasCustomTypeFilter = false; + + #endregion + + #region Properties + + /// + /// If false, duplicate won't be selectable in the collection selector, or collection element dropdowns. + /// + /// + /// Only affects collections. True by default. + /// + public bool AllowDuplicates { get; set; } = true; + + /// + /// Will hide the Odin default object reference picker if true as it it has been + /// decorated with . + /// + /// + /// True by default. + /// + public bool HideReferencePicker { get; set; } = true; + + /// + /// If true, dropdowns will be drawn at the top + /// of each list element. + /// + /// + /// Only affects collections. False by default. + /// + public bool DrawDropdownForListElements { get; set; } = false; + + /// + /// If true, will draw a BoxGroup-style box around each element of a collection. + /// + /// + /// False by default. Only affects collections. + /// + public bool DrawBoxForListElements { get; set; } = false; + + /// + /// If true, will hide the class' label as if it had been decorated with . + /// + /// + /// Only affects non-collection fields. True by default. + /// + public bool HideClassLabel { get; set; } = true; + + /// + /// Provide a value resolver expression to filter types after the intial collection of subtypes has been generated. + /// + /// + /// Abstract types are already filtered. + /// + public string CustomTypeFilter { - _hasCustomTypeFilter = !string.IsNullOrEmpty(value); - _customTypeFilter = value; + get => _customTypeFilter; + set + { + _hasCustomTypeFilter = !string.IsNullOrEmpty(value); + _customTypeFilter = value; + } } - } - /// - /// Is there a custom type filter expression? - /// - public bool HasCustomTypeFilter => _hasCustomTypeFilter; + /// + /// Is there a custom type filter expression? + /// + public bool HasCustomTypeFilter => _hasCustomTypeFilter; - #endregion + #endregion + } } \ No newline at end of file diff --git a/Editor/SubclassSelectorAttributeProcessor.cs b/Editor/SubclassSelectorAttributeProcessor.cs index 2a02242..2c043a1 100644 --- a/Editor/SubclassSelectorAttributeProcessor.cs +++ b/Editor/SubclassSelectorAttributeProcessor.cs @@ -5,111 +5,114 @@ using Sirenix.OdinInspector.Editor; using Sirenix.Utilities; -internal sealed class SubclassSelectorAttributeProcessor : OdinAttributeProcessor where T : class +namespace Spiney.SubclassSelector { - #region Internal Members + internal sealed class SubclassSelectorAttributeProcessor : OdinAttributeProcessor where T : class + { + #region Internal Members - private SubclassSelectorAttribute subclassSelector; - private ICollectionResolver collectionResolver; + private SubclassSelectorAttribute subclassSelector; + private ICollectionResolver collectionResolver; - private bool isCollection = false; - private bool parentIsCollection = false; + private bool isCollection = false; + private bool parentIsCollection = false; - #endregion + #endregion - #region Processor Overrides + #region Processor Overrides - public override bool CanProcessSelfAttributes(InspectorProperty property) - { - if ((subclassSelector = property.Attributes.GetAttribute()) == null) + public override bool CanProcessSelfAttributes(InspectorProperty property) { - return false; - } - - bool isUnityObject; - collectionResolver = property.ChildResolver as ICollectionResolver; - isCollection = collectionResolver != null; + if ((subclassSelector = property.Attributes.GetAttribute()) == null) + { + return false; + } - if (isCollection) - { - Type elementType = collectionResolver.ElementType; - isUnityObject = typeof(UnityEngine.Object).IsAssignableFrom(elementType); - return !isUnityObject; - } - else - { - collectionResolver = property.ParentValueProperty?.ChildResolver as ICollectionResolver ?? null; - parentIsCollection = collectionResolver != null; - isUnityObject = typeof(UnityEngine.Object).IsAssignableFrom(property.ValueEntry.BaseValueType); + bool isUnityObject; + collectionResolver = property.ChildResolver as ICollectionResolver; + isCollection = collectionResolver != null; - if (parentIsCollection) + if (isCollection) { - return !isUnityObject && subclassSelector.DrawDropdownForListElements; + Type elementType = collectionResolver.ElementType; + isUnityObject = typeof(UnityEngine.Object).IsAssignableFrom(elementType); + return !isUnityObject; } else { - return !isUnityObject; + collectionResolver = property.ParentValueProperty?.ChildResolver as ICollectionResolver ?? null; + parentIsCollection = collectionResolver != null; + isUnityObject = typeof(UnityEngine.Object).IsAssignableFrom(property.ValueEntry.BaseValueType); + + if (parentIsCollection) + { + return !isUnityObject && subclassSelector.DrawDropdownForListElements; + } + else + { + return !isUnityObject; + } } } - } - public override void ProcessSelfAttributes(InspectorProperty property, List attributes) - { - var hideReferencePicker = property.Attributes.GetAttribute(); - if (subclassSelector.HideReferencePicker && hideReferencePicker == null) + public override void ProcessSelfAttributes(InspectorProperty property, List attributes) { - hideReferencePicker = new HideReferenceObjectPickerAttribute(); - attributes.Add(hideReferencePicker); - } - - var hideLabel = property.Attributes.GetAttribute(); - - if (subclassSelector.HideClassLabel && hideLabel == null) - { - hideLabel = new HideLabelAttribute(); - attributes.Add(hideLabel); - } - - if (isCollection) - { - var labelText = new LabelTextAttribute(property.NiceName); - attributes.Add(labelText); - - var lds = property.Attributes.GetAttribute(); - - if (lds == null) + var hideReferencePicker = property.Attributes.GetAttribute(); + if (subclassSelector.HideReferencePicker && hideReferencePicker == null) { - lds = new ListDrawerSettingsAttribute(); - attributes.Add(lds); + hideReferencePicker = new HideReferenceObjectPickerAttribute(); + attributes.Add(hideReferencePicker); } - lds.CustomAddFunction = SubclassSelectorUtilities.OpenSubclassSelectorString; + var hideLabel = property.Attributes.GetAttribute(); - if (subclassSelector.DrawBoxForListElements) + if (subclassSelector.HideClassLabel && hideLabel == null) { - lds.OnBeginListElementGUI = SubclassSelectorUtilities.OnBeginBoxSubclassElementString; - lds.OnEndListElementGUI = SubclassSelectorUtilities.OnEndBoxSubclassElementString; + hideLabel = new HideLabelAttribute(); + attributes.Add(hideLabel); } - } - else - { - var typeFilter = new TypeFilterAttribute(SubclassSelectorUtilities.TypeFilterResolverString); - if (parentIsCollection) + if (isCollection) { - typeFilter.DrawValueNormally = true; + var labelText = new LabelTextAttribute(property.NiceName); + attributes.Add(labelText); + + var lds = property.Attributes.GetAttribute(); + + if (lds == null) + { + lds = new ListDrawerSettingsAttribute(); + attributes.Add(lds); + } + + lds.CustomAddFunction = SubclassSelectorUtilities.OpenSubclassSelectorString; + + if (subclassSelector.DrawBoxForListElements) + { + lds.OnBeginListElementGUI = SubclassSelectorUtilities.OnBeginBoxSubclassElementString; + lds.OnEndListElementGUI = SubclassSelectorUtilities.OnEndBoxSubclassElementString; + } } else { - bool valueIsNull = property.ValueEntry.WeakSmartValue == null; - bool hidePicker = subclassSelector.HideReferencePicker; - typeFilter.DrawValueNormally = !valueIsNull || !hidePicker; + var typeFilter = new TypeFilterAttribute(SubclassSelectorUtilities.TypeFilterResolverString); + + if (parentIsCollection) + { + typeFilter.DrawValueNormally = true; + } + else + { + bool valueIsNull = property.ValueEntry.WeakSmartValue == null; + bool hidePicker = subclassSelector.HideReferencePicker; + typeFilter.DrawValueNormally = !valueIsNull || !hidePicker; + } + + attributes.Add(typeFilter); } - - attributes.Add(typeFilter); } - } - #endregion + #endregion + } } #endif \ No newline at end of file diff --git a/Editor/SubclassSelectorUtilities.cs b/Editor/SubclassSelectorUtilities.cs index 7e5e2c1..74f5ac7 100644 --- a/Editor/SubclassSelectorUtilities.cs +++ b/Editor/SubclassSelectorUtilities.cs @@ -10,269 +10,272 @@ using Sirenix.Utilities; using Sirenix.Utilities.Editor; -/// -/// Utility methods for use with . -/// -public static class SubclassSelectorUtilities +namespace Spiney.SubclassSelector { - #region Resolver Strings - - public static string OpenSubclassSelectorString => $"@{nameof(SubclassSelectorUtilities)}.{nameof(OpenSubclassCollectionSelector)}($property)"; + /// + /// Utility methods for use with . + /// + public static class SubclassSelectorUtilities + { + #region Resolver Strings - public static string TypeFilterResolverString => $"@{nameof(SubclassSelectorUtilities)}.{nameof(GetSubclassSelectorDropdownItems)}($property)"; + public static string OpenSubclassSelectorString => $"@{nameof(SubclassSelectorUtilities)}.{nameof(OpenSubclassCollectionSelector)}($property)"; - public static string OnBeginBoxSubclassElementString => $"@{nameof(SubclassSelectorUtilities)}.{nameof(BeginDrawBoxedSubclassElement)}($property, $index)"; + public static string TypeFilterResolverString => $"@{nameof(SubclassSelectorUtilities)}.{nameof(GetSubclassSelectorDropdownItems)}($property)"; - public static string OnEndBoxSubclassElementString => $"@{nameof(SubclassSelectorUtilities)}.{nameof(EndDrawBoxedSubclassElement)}()"; + public static string OnBeginBoxSubclassElementString => $"@{nameof(SubclassSelectorUtilities)}.{nameof(BeginDrawBoxedSubclassElement)}($property, $index)"; - #endregion + public static string OnEndBoxSubclassElementString => $"@{nameof(SubclassSelectorUtilities)}.{nameof(EndDrawBoxedSubclassElement)}()"; - #region Validation and Data Retreival + #endregion - /// - /// Gets all child types of , filtering abstract types. - /// - /// Type to get all derived/child types of. - public static IEnumerable GetTypeDerivedTypes(Type baseType, InspectorProperty inspectorProperty) - { - var subclassSelector = inspectorProperty.Attributes.GetAttribute(); - ValueResolver typeFilterResolver = null; + #region Validation and Data Retreival - if (subclassSelector.HasCustomTypeFilter) + /// + /// Gets all child types of , filtering abstract types. + /// + /// Type to get all derived/child types of. + public static IEnumerable GetTypeDerivedTypes(Type baseType, InspectorProperty inspectorProperty) { - typeFilterResolver = ValueResolver.Get(inspectorProperty, subclassSelector.CustomTypeFilter, - new NamedValue("type", typeof(Type))); + var subclassSelector = inspectorProperty.Attributes.GetAttribute(); + ValueResolver typeFilterResolver = null; - if (typeFilterResolver.HasError) + if (subclassSelector.HasCustomTypeFilter) { - Debug.LogError(typeFilterResolver.ErrorMessage); - } - } + typeFilterResolver = ValueResolver.Get(inspectorProperty, subclassSelector.CustomTypeFilter, + new NamedValue("type", typeof(Type))); - var types = TypeCache.GetTypesDerivedFrom(baseType).Where(AbstractTypeFilter); - - if (!baseType.IsAbstract && PassesCustomTypeFilter(baseType)) - { - yield return baseType; - } - - foreach (Type type in types) - { - if (PassesCustomTypeFilter(type)) - { - yield return type; + if (typeFilterResolver.HasError) + { + Debug.LogError(typeFilterResolver.ErrorMessage); + } } - } - // local methods - static bool AbstractTypeFilter(Type type) => !type.IsAbstract; + var types = TypeCache.GetTypesDerivedFrom(baseType).Where(AbstractTypeFilter); - bool PassesCustomTypeFilter(Type type) - { - if (typeFilterResolver == null || typeFilterResolver.HasError) + if (!baseType.IsAbstract && PassesCustomTypeFilter(baseType)) { - return true; + yield return baseType; } - else + + foreach (Type type in types) { - typeFilterResolver.Context.NamedValues.Set("type", type); - bool result = typeFilterResolver.GetValue(); - return result; + if (PassesCustomTypeFilter(type)) + { + yield return type; + } } - } - } - public static string GetTypeSubclassPath(Type type) - { - SubclassPathAttribute pathAttribute = type.GetAttribute(); + // local methods + static bool AbstractTypeFilter(Type type) => !type.IsAbstract; - if (pathAttribute == null || (!pathAttribute.HasSubClassPath && !pathAttribute.HasSubClassName)) - { - return type.GetNiceName(); - } - - string path = string.Empty; - if (pathAttribute.HasSubClassPath) - { - path += (pathAttribute.SubClassPath + "/"); + bool PassesCustomTypeFilter(Type type) + { + if (typeFilterResolver == null || typeFilterResolver.HasError) + { + return true; + } + else + { + typeFilterResolver.Context.NamedValues.Set("type", type); + bool result = typeFilterResolver.GetValue(); + return result; + } + } } - path += pathAttribute.HasSubClassName ? pathAttribute.SubClassName : type.Name; - - return path; - } - - /// - /// Returns whether a particular type exists with children of an . - /// - /// If true, types derived from will also count. - public static bool TypeExistsInPropertyChildren(PropertyChildren children, Type type) - { - foreach (var child in children) + public static string GetTypeSubclassPath(Type type) { - Type valueType = child.ValueEntry.TypeOfValue; - bool duplicate = (valueType == type); + SubclassPathAttribute pathAttribute = type.GetAttribute(); - if (duplicate) + if (pathAttribute == null || (!pathAttribute.HasSubClassPath && !pathAttribute.HasSubClassName)) { - return true; + return type.GetNiceName(); } - } - - return false; - } - - #endregion - #region Field Methods - - public static IEnumerable> GetSubclassSelectorDropdownItems(InspectorProperty property) - { - SubclassSelectorAttribute subclassSelector = property.Attributes.GetAttribute(); - IEnumerable types = GetTypeDerivedTypes(property.ValueEntry.BaseValueType, property); - - InspectorProperty parent = property.ParentValueProperty; - bool parentIsCollection = parent != null && parent.ChildResolver is ICollectionResolver; - - // if the parent is not a collection, - // or we don't care about duplicate values... - if (!parentIsCollection || subclassSelector.AllowDuplicates) - { - foreach (var type in types) + string path = string.Empty; + if (pathAttribute.HasSubClassPath) { - yield return GetDropdownItem(type); + path += (pathAttribute.SubClassPath + "/"); } + + path += pathAttribute.HasSubClassName ? pathAttribute.SubClassName : type.Name; + + return path; } - else + + /// + /// Returns whether a particular type exists with children of an . + /// + /// If true, types derived from will also count. + public static bool TypeExistsInPropertyChildren(PropertyChildren children, Type type) { - foreach (var type in types) + foreach (var child in children) { - // the current type should be selectable so that the dropdown - // draws the correct Subclass path - if (type == property.ValueEntry.TypeOfValue) - { - yield return GetDropdownItem(type); - continue; - } + Type valueType = child.ValueEntry.TypeOfValue; + bool duplicate = (valueType == type); - if (!TypeExistsInPropertyChildren(parent.Children, type)) + if (duplicate) { - yield return GetDropdownItem(type); + return true; } } - } - static ValueDropdownItem GetDropdownItem(Type type) - { - string name = GetTypeSubclassPath(type); - return new ValueDropdownItem(name, type); + return false; } - } - #endregion + #endregion - #region Collection Methods + #region Field Methods - public static void OpenSubclassCollectionSelector(InspectorProperty property) - { - var collectionResolver = (ICollectionResolver)property.ChildResolver; - var selectorAttribute = property.Attributes.GetAttribute(); - - Type elementType = collectionResolver.ElementType; - IEnumerable types = GetTypeDerivedTypes(collectionResolver.ElementType, property).Where(GenericSelectorTypeFilter); - - List> selectorTypes = new List>(); - foreach (Type type in types) + public static IEnumerable> GetSubclassSelectorDropdownItems(InspectorProperty property) { - string name = GetTypeSubclassPath(type); - var selectorItem = new GenericSelectorItem(name, type); + SubclassSelectorAttribute subclassSelector = property.Attributes.GetAttribute(); + IEnumerable types = GetTypeDerivedTypes(property.ValueEntry.BaseValueType, property); - selectorTypes.Add(selectorItem); - } + InspectorProperty parent = property.ParentValueProperty; + bool parentIsCollection = parent != null && parent.ChildResolver is ICollectionResolver; - GenericSelector selector = new GenericSelector(title: "Select Types", true, selectorTypes); - - foreach (var menuItem in selector.SelectionTree.EnumerateTree()) - { - // skip menu item if it has children (a dropdown) or has a null value - if (menuItem.ChildMenuItems.Count > 0 && menuItem.Value == null) + // if the parent is not a collection, + // or we don't care about duplicate values... + if (!parentIsCollection || subclassSelector.AllowDuplicates) { - continue; + foreach (var type in types) + { + yield return GetDropdownItem(type); + } } - - Type itemType = (Type)menuItem.Value; - bool hasDefaultConstructor = itemType.GetConstructor(Type.EmptyTypes) != null; - - if (!hasDefaultConstructor) + else { - menuItem.Icon = EditorIcons.AlertTriangle.Raw; - menuItem.Name = $"{menuItem.Name} - No Default Constructor"; + foreach (var type in types) + { + // the current type should be selectable so that the dropdown + // draws the correct Subclass path + if (type == property.ValueEntry.TypeOfValue) + { + yield return GetDropdownItem(type); + continue; + } + + if (!TypeExistsInPropertyChildren(parent.Children, type)) + { + yield return GetDropdownItem(type); + } + } } - else + + static ValueDropdownItem GetDropdownItem(Type type) { - menuItem.Icon = EditorIcons.File.Raw; + string name = GetTypeSubclassPath(type); + return new ValueDropdownItem(name, type); } } - selector.DrawConfirmSelectionButton = true; - selector.SelectionConfirmed += AddSelectedTypes; - selector.ShowInPopup(); + #endregion + + #region Collection Methods - // local methods - bool GenericSelectorTypeFilter(Type type) + public static void OpenSubclassCollectionSelector(InspectorProperty property) { - if (selectorAttribute.AllowDuplicates) + var collectionResolver = (ICollectionResolver)property.ChildResolver; + var selectorAttribute = property.Attributes.GetAttribute(); + + Type elementType = collectionResolver.ElementType; + IEnumerable types = GetTypeDerivedTypes(collectionResolver.ElementType, property).Where(GenericSelectorTypeFilter); + + List> selectorTypes = new List>(); + foreach (Type type in types) { - return true; + string name = GetTypeSubclassPath(type); + var selectorItem = new GenericSelectorItem(name, type); + + selectorTypes.Add(selectorItem); } - return !TypeExistsInPropertyChildren(property.Children, type); - } + GenericSelector selector = new GenericSelector(title: "Select Types", true, selectorTypes); - void AddSelectedTypes(IEnumerable selectedTypes) - { - foreach (var selection in selectedTypes) + foreach (var menuItem in selector.SelectionTree.EnumerateTree()) { - // prevent errors when selecting dropdown values - if (selection == null) + // skip menu item if it has children (a dropdown) or has a null value + if (menuItem.ChildMenuItems.Count > 0 && menuItem.Value == null) { continue; } - bool hasDefaultConstructor = selection.GetConstructor(Type.EmptyTypes) != null; - object[] values; + Type itemType = (Type)menuItem.Value; + bool hasDefaultConstructor = itemType.GetConstructor(Type.EmptyTypes) != null; - if (hasDefaultConstructor) + if (!hasDefaultConstructor) { - var instance = Activator.CreateInstance(selection); - values = new object[] { instance }; + menuItem.Icon = EditorIcons.AlertTriangle.Raw; + menuItem.Name = $"{menuItem.Name} - No Default Constructor"; } else { - var instance = Sirenix.Serialization.UnitySerializationUtility.CreateDefaultUnityInitializedObject(selection); - values = new object[] { instance }; + menuItem.Icon = EditorIcons.File.Raw; } + } + + selector.DrawConfirmSelectionButton = true; + selector.SelectionConfirmed += AddSelectedTypes; + selector.ShowInPopup(); + + // local methods + bool GenericSelectorTypeFilter(Type type) + { + if (selectorAttribute.AllowDuplicates) + { + return true; + } + + return !TypeExistsInPropertyChildren(property.Children, type); + } - collectionResolver.QueueAdd(values); - property.Children.Update(); + void AddSelectedTypes(IEnumerable selectedTypes) + { + foreach (var selection in selectedTypes) + { + // prevent errors when selecting dropdown values + if (selection == null) + { + continue; + } + + bool hasDefaultConstructor = selection.GetConstructor(Type.EmptyTypes) != null; + object[] values; + + if (hasDefaultConstructor) + { + var instance = Activator.CreateInstance(selection); + values = new object[] { instance }; + } + else + { + var instance = Sirenix.Serialization.UnitySerializationUtility.CreateDefaultUnityInitializedObject(selection); + values = new object[] { instance }; + } + + collectionResolver.QueueAdd(values); + property.Children.Update(); + } } } - } - public static void BeginDrawBoxedSubclassElement(InspectorProperty property, int index) - { - var child = property.Children[index]; - var subclassPath = child.ValueEntry.TypeOfValue.GetAttribute(); - string groupName = subclassPath?.SubClassName ?? child.ValueEntry.TypeOfValue.GetNiceName(); - groupName = groupName.Replace('.', '-'); - SirenixEditorGUI.BeginBox(groupName, centerLabel: true); - } + public static void BeginDrawBoxedSubclassElement(InspectorProperty property, int index) + { + var child = property.Children[index]; + var subclassPath = child.ValueEntry.TypeOfValue.GetAttribute(); + string groupName = subclassPath?.SubClassName ?? child.ValueEntry.TypeOfValue.GetNiceName(); + groupName = groupName.Replace('.', '-'); + SirenixEditorGUI.BeginBox(groupName, centerLabel: true); + } - public static void EndDrawBoxedSubclassElement() - { - SirenixEditorGUI.EndBox(); - } + public static void EndDrawBoxedSubclassElement() + { + SirenixEditorGUI.EndBox(); + } - #endregion + #endregion + } } #endif \ No newline at end of file