forked from Shardstone/trail-into-darkness
added more utilty packges
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jovian.Utilities;
|
||||||
|
|
||||||
namespace Nox.Game {
|
namespace Nox.Game {
|
||||||
public interface IModfiersFactory {
|
public interface IModfiersFactory {
|
||||||
IReadOnlyCollection<ModifierDefinition> GetAll();
|
IReadOnlyCollection<ModifierDefinition> GetAll();
|
||||||
ModifierDefinition GetById(ModifierIds modifierId);
|
ModifierDefinition GetById(Guid modifierId);
|
||||||
IReadOnlyCollection<ModifierDefinition> GetModifiersFor(CharacterDefinition character);
|
IReadOnlyCollection<ModifierDefinition> GetModifiersFor(CharacterDefinition character);
|
||||||
bool TryAddModifier(CharacterDefinition character, string modiferId);
|
bool TryAddModifier(CharacterDefinition character, string modiferId);
|
||||||
}
|
}
|
||||||
@@ -20,7 +21,6 @@ namespace Nox.Game {
|
|||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class ModifierDefinition {
|
public sealed class ModifierDefinition {
|
||||||
[ReadOnlyField]
|
|
||||||
public System.Guid id = Guid.NewGuid();
|
public System.Guid id = Guid.NewGuid();
|
||||||
public StatType statType;
|
public StatType statType;
|
||||||
public AttributeType attributeType;
|
public AttributeType attributeType;
|
||||||
@@ -44,7 +44,7 @@ namespace Nox.Game {
|
|||||||
public IReadOnlyCollection<ModifierDefinition> GetAll() {
|
public IReadOnlyCollection<ModifierDefinition> GetAll() {
|
||||||
return modifiersRegistry.modifiersData.modifiers;
|
return modifiersRegistry.modifiersData.modifiers;
|
||||||
}
|
}
|
||||||
public ModifierDefinition GetById(ModifierIds modifierId) {
|
public ModifierDefinition GetById(Guid modifierId) {
|
||||||
return modifiersRegistry.modifiersData.modifiers.FirstOrDefault(m => m.id == modifierId);
|
return modifiersRegistry.modifiersData.modifiers.FirstOrDefault(m => m.id == modifierId);
|
||||||
}
|
}
|
||||||
public IReadOnlyCollection<ModifierDefinition> GetModifiersFor(CharacterDefinition character) {
|
public IReadOnlyCollection<ModifierDefinition> GetModifiersFor(CharacterDefinition character) {
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ namespace Nox.Game {
|
|||||||
|
|
||||||
public interface IPerkFactory {
|
public interface IPerkFactory {
|
||||||
IReadOnlyCollection<PerkDefinition> GetAll();
|
IReadOnlyCollection<PerkDefinition> GetAll();
|
||||||
PerkDefinition GetById(PerksIds perkId);
|
PerkDefinition GetById(Guid perkId);
|
||||||
IReadOnlyCollection<PerkDefinition> GetPerksFor(CharacterDefinition character);
|
IReadOnlyCollection<PerkDefinition> GetPerksFor(CharacterDefinition character);
|
||||||
bool TryAddPerk(CharacterDefinition character, PerksIds perkId);
|
bool TryAddPerk(CharacterDefinition character, Guid perkId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class PerkDefinition {
|
public sealed class PerkDefinition {
|
||||||
public PerksIds id;
|
public Guid id;
|
||||||
public string name;
|
public string name;
|
||||||
public ModifiersData modifiers = new ();
|
public ModifiersData modifiers = new ();
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ namespace Nox.Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PerkFactory : IPerkFactory {
|
public sealed class PerkFactory : IPerkFactory {
|
||||||
private readonly Dictionary<PerksIds, PerkDefinition> perkPool = new ();
|
private readonly Dictionary<Guid, PerkDefinition> perkPool = new ();
|
||||||
|
|
||||||
public PerkFactory(PerksRegistry perksRegistry) {
|
public PerkFactory(PerksRegistry perksRegistry) {
|
||||||
if(!perksRegistry) {
|
if(!perksRegistry) {
|
||||||
@@ -40,7 +40,7 @@ namespace Nox.Game {
|
|||||||
return perkPool.Values.ToList();
|
return perkPool.Values.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PerkDefinition GetById(PerksIds perkId) {
|
public PerkDefinition GetById(Guid perkId) {
|
||||||
perkPool.TryGetValue(perkId, out var perk);
|
perkPool.TryGetValue(perkId, out var perk);
|
||||||
return perk;
|
return perk;
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ namespace Nox.Game {
|
|||||||
return perkPool.Values.Where(p => !ownedPerkIds.Contains(p.id)).ToList();
|
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) {
|
if(character == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
8
Assets/Settings/Resources.meta
Normal file
8
Assets/Settings/Resources.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 40aa0aa493d1bb745a531cdb360e7e62
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
23
Assets/Settings/Resources/logger-settings.asset
Normal file
23
Assets/Settings/Resources/logger-settings.asset
Normal file
@@ -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}
|
||||||
8
Assets/Settings/Resources/logger-settings.asset.meta
Normal file
8
Assets/Settings/Resources/logger-settings.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 97634890f80d79941abb209746593eef
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Packages/com.jovian.inspector/Editor.meta
Normal file
8
Packages/com.jovian.inspector/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 400491600b7856d4dbb01f428e9dfbfb
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Packages/com.jovian.inspector/Editor/Attributes.meta
Normal file
8
Packages/com.jovian.inspector/Editor/Attributes.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8dc5c6745ff8d4443955c7fd0c3dfb0d
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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[] { "<MISSING>" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c43d05448be2cce45a0c4a41227752aa
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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<ArrayAttribute> 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<TAttribute>(SerializedProperty serializedProperty, bool inherit, out List<TAttribute> 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<TAttribute>();
|
||||||
|
|
||||||
|
foreach(string pathSegment in serializedProperty.propertyPath.Split('.')) {
|
||||||
|
FieldInfo fieldInfo = targetObjectType.GetRuntimeField(pathSegment);
|
||||||
|
if(fieldInfo != null) {
|
||||||
|
IEnumerable<TAttribute> fieldAttributes = fieldInfo.GetCustomAttributes<TAttribute>(inherit);
|
||||||
|
foreach (TAttribute fieldAttribute in fieldAttributes) {
|
||||||
|
attributes.Add(fieldAttribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyInfo propertyInfo = targetObjectType.GetRuntimeProperty(pathSegment);
|
||||||
|
if(propertyInfo != null) {
|
||||||
|
IEnumerable<TAttribute> fieldAttributes = propertyInfo.GetCustomAttributes<TAttribute>(inherit);
|
||||||
|
foreach (TAttribute fieldAttribute in fieldAttributes) {
|
||||||
|
attributes.Add(fieldAttribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes.Count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 08ca9e3facae5974ca19daee6aa978ed
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Jovian.Utilities;
|
||||||
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all objects in the project matching <c>filter</c> string and lists alphabetically in a dropdown (if they are <c>assetType</c>)
|
||||||
|
/// </summary>
|
||||||
|
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<Object> 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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b3b5e8f6d90c14e4ca8c2af794bcf4f3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit.Internal
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public abstract class AttributeBase : PropertyAttribute
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
/// <summary>
|
||||||
|
/// Validation is called before all other methods.
|
||||||
|
/// Once in OnGUI and once in GetPropertyHeight
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ValidateProperty(UnityEditor.SerializedProperty property)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual void OnBeforeGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called once per AttributeBase group.
|
||||||
|
/// I.e. if something with higher order is drawn, later will be skipped
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>false if nothing is drawn</returns>
|
||||||
|
public virtual bool OnGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnAfterGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overriding occurs just like OnGUI. Once per group, attribute with higher priority first
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Null if not overrided</returns>
|
||||||
|
public virtual float? OverrideHeight()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 816fc9d7c08c47f8b84318dce8cecf88
|
||||||
|
timeCreated: 1582017264
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ce6a191490f7425a984c36a53cebd36d
|
||||||
|
timeCreated: 1582017102
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 51175a247ebc7d24eb3034828da2e103
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d458f9ebb11d49449bbb0acefc84ea2e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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<MethodData> 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<MemberInfo> 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 <color=brown>{member.Name}</color>.Reason: Member is not a method but has EditorButtonAttribute!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e96e2e6d8d1626c4abf4c413bc489af0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Validate a string field to only allow or disallow a set of pre-defined
|
||||||
|
/// characters on typing.
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Only characters in range will be allowed
|
||||||
|
/// </summary>
|
||||||
|
Allow,
|
||||||
|
/// <summary>
|
||||||
|
/// Characters in range will be removed from the string
|
||||||
|
/// </summary>
|
||||||
|
Disallow,
|
||||||
|
/// <summary>
|
||||||
|
/// Highlight the field if any of the specified characters were fould
|
||||||
|
/// </summary>
|
||||||
|
WarningIfAny,
|
||||||
|
/// <summary>
|
||||||
|
/// Highlight the field if some characters in string are not the characters from specified range
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: add63a4947a64f2428030472426ecba4
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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<SerializedProperty> 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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d56876fd24830a4c99f6963c8f15435
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bc29630bf159f4742a4ea87bb9eaccdc
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit {
|
||||||
|
/// <summary>
|
||||||
|
/// Conditionally Show/Hide field in inspector, based on some other field value
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class ConditionalFieldAttribute : PropertyAttribute {
|
||||||
|
public readonly string FieldToCheck;
|
||||||
|
public readonly string[] CompareValues;
|
||||||
|
public readonly bool Inverse;
|
||||||
|
|
||||||
|
/// <param name="fieldToCheck">String name of field to check value</param>
|
||||||
|
/// <param name="inverse">Inverse check result</param>
|
||||||
|
/// <param name="compareValues">On which values field will be shown in inspector</param>
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key is Associated with drawer type (the T in [CustomPropertyDrawer(typeof(T))])
|
||||||
|
/// Value is PropertyDrawer Type
|
||||||
|
/// </summary>
|
||||||
|
private static Dictionary<Type, Type> _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<Type, Type>();
|
||||||
|
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 <color=brown>" + fieldInfo.Name + "</color>";
|
||||||
|
if(fieldInfo != null && fieldInfo.DeclaringType != null)
|
||||||
|
warning += " on behaviour <color=brown>" + fieldInfo.DeclaringType.Name + "</color>";
|
||||||
|
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<FlagsAttribute>() != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the property value matches any of the values in '_compareValues'
|
||||||
|
/// </summary>
|
||||||
|
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 <color=brown>" + arrayProp.name + "</color> in object <color=brown>" + target.name + "</color> caused: ";
|
||||||
|
var warning = who + "Array fields is not supported by [ConditionalFieldAttribute]. Consider to use <color=blue>CollectionWrapper</color>";
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4f0c8c14a34cc7b4c8ee94ff351a2902
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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<MemberInfo> _constants = new List<MemberInfo>();
|
||||||
|
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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d93e4517d0ec49138d37edab20cacd2f
|
||||||
|
timeCreated: 1582028131
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create Popup with predefined values for string, int or float property
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bb25c9dc1c7ff1345ac286ba31c0af36
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Use to display inspector of property object
|
||||||
|
/// </summary>
|
||||||
|
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<Object, SerializedObject> _targets = new Dictionary<Object, SerializedObject>();
|
||||||
|
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<NonReorderableAttribute>();
|
||||||
|
#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<NonReorderableAttribute>();
|
||||||
|
#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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d0a699aecc941574e9faf6a546c0e663
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>Adds the property to the specified foldout group.</summary>
|
||||||
|
/// <param name="name">Name of the foldout group.</param>
|
||||||
|
/// <param name="foldEverything">Toggle to put all properties to the specified group</param>
|
||||||
|
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<string, CacheFoldProp> _cacheFoldouts = new Dictionary<string, CacheFoldProp>();
|
||||||
|
private readonly List<SerializedProperty> _props = new List<SerializedProperty>();
|
||||||
|
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<string> {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<string> {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<string> Types = new HashSet<string>();
|
||||||
|
public readonly List<SerializedProperty> Properties = new List<SerializedProperty>();
|
||||||
|
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<int, List<FieldInfo>> Fields = new Dictionary<int, List<FieldInfo>>(FastComparable.Default);
|
||||||
|
private static readonly Dictionary<int, List<PropertyInfo>> Properties = new Dictionary<int, List<PropertyInfo>>(FastComparable.Default);
|
||||||
|
|
||||||
|
public static int GetFields(Object target, out List<FieldInfo> 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<PropertyInfo> 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<Type> GetTypeTree(Type t)
|
||||||
|
{
|
||||||
|
var types = new List<Type>();
|
||||||
|
while (t.BaseType != null)
|
||||||
|
{
|
||||||
|
types.Add(t);
|
||||||
|
t = t.BaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class FastComparable : IEqualityComparer<int>
|
||||||
|
{
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 69f6d9b4f0e9f594dbcc767598fe91c6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 621eca357dcb59841acf613ba4b7dee5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Field will be Read-Only in Playmode
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dd5ef49202d6492bb39be27d203b0163
|
||||||
|
timeCreated: 1588577864
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8c3f94f6ba2d5454f8944e8f0f9479b2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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 <color=brown>{0}</color> in object <color=brown>{1}</color> is of wrong type. Expected: Int",
|
||||||
|
property.name, property.serializedObject.targetObject));
|
||||||
|
_checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 979d3d332ae931a4f8dc8aaf52644f0c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 65d3077f048889344952a1fd170f5810
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
|
/// <returns>true if fixed</returns>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8930225c73d74e5dbb7784e4c97eb1ef
|
||||||
|
timeCreated: 1582014887
|
||||||
@@ -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 <color=brown>" +
|
||||||
|
property.name +
|
||||||
|
"</color>. 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 <color=brown>" +
|
||||||
|
property.name +
|
||||||
|
"</color>. 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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aa6fba45efae4ac4b9830e11066fb969
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
|
|
||||||
|
/// <returns>true if fixed</returns>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7a6fc14799014e3ea82c8158bd477aca
|
||||||
|
timeCreated: 1582013139
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Jovian.Utilities;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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<UnityEngine.Object> 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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 01fff4c8e285bbc43b74bc4b6e76eb04
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 88837c4ffdcc87f4c927e0ef8d2c4ba5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 303db5223d8c8674791938a5180540e1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8a3118b2fddf33042bce8519708bf318
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set number value to 0 if less than 0
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if fixed</returns>
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9f63e31912630bf4e88f4f2e9eab2f7b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
public class ReadOnlyAttribute : ConditionalFieldAttribute
|
||||||
|
{
|
||||||
|
public ReadOnlyAttribute() : base("") { }
|
||||||
|
|
||||||
|
/// <param name="fieldToCheck">String name of field to check value</param>
|
||||||
|
/// <param name="inverse">Inverse check result</param>
|
||||||
|
/// <param name="compareValues">On which values field will be shown in inspector</param>
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 44c78368338beb948bdffa8db7179a0b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Validate a string field by regex expression
|
||||||
|
/// RegexMode.Match will keep only matching content
|
||||||
|
/// RegexMode.Replace will keep all except regex match
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Keep only parts of the string that Match the Expression
|
||||||
|
/// </summary>
|
||||||
|
Match,
|
||||||
|
/// <summary>
|
||||||
|
/// Remove from the string parts that not match the Expression
|
||||||
|
/// </summary>
|
||||||
|
Replace,
|
||||||
|
/// <summary>
|
||||||
|
/// Highlight the field if any of the parts of the string matching the Expression
|
||||||
|
/// </summary>
|
||||||
|
WarningIfMatch,
|
||||||
|
/// <summary>
|
||||||
|
/// Highlight the field if some parts of the string not matching the Expression
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 38cf7a3ca1a04872b56d3cc1d90997b6
|
||||||
|
timeCreated: 1623076361
|
||||||
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ebe88cf8a45d4df19c15769d67ccbf11
|
||||||
|
timeCreated: 1709801667
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a239ed93c95b4382a478c97d2bec7768
|
||||||
|
timeCreated: 1567174003
|
||||||
@@ -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<Component>();
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b54e6bb2f8b4c84abe977357edeb0fc
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using InspectorToolkit.EditorTools;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace InspectorToolkit {
|
||||||
|
/// <summary>
|
||||||
|
/// Does not work on objects with children - will break their UI
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1587de26320cce74981351e58d6e7b26
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class RequireTagAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Tag;
|
||||||
|
|
||||||
|
public RequireTagAttribute(string tag)
|
||||||
|
{
|
||||||
|
Tag = tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4ab3a785f0a04eeeb2b90de6430cef9e
|
||||||
|
timeCreated: 1567412854
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 20675fbb6f9e5764a9aa6e8efb4b8150
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Author: Anton
|
||||||
|
// https://github.com/antontidev
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InspectorToolkit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to pick scene from inspector.
|
||||||
|
/// Consider to use <see cref="SceneReference"/> type instead as it is more flexible
|
||||||
|
/// </summary>
|
||||||
|
[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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f2e2f0d183cdeeb4a9e0d11deb8f782b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class SearchableEnumAttribute : PropertyAttribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
namespace InspectorToolkit.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Draws the custom enum selector popup for enum fields using the
|
||||||
|
/// SearchableEnumAttribute.
|
||||||
|
/// </summary>
|
||||||
|
[CustomPropertyDrawer(typeof(SearchableEnumAttribute))]
|
||||||
|
public class SearchableEnumAttributeDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
private const string TYPE_ERROR = "SearchableEnum can only be used on enum fields.";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cache of the hash to use to resolve the ID for the drawer.
|
||||||
|
/// </summary>
|
||||||
|
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<int> onSelect = i =>
|
||||||
|
{
|
||||||
|
property.enumValueIndex = i;
|
||||||
|
property.serializedObject.ApplyModifiedProperties();
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchablePopup.Show(position, property.enumDisplayNames,
|
||||||
|
property.enumValueIndex, onSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A popup window that displays a list of options and may use a search
|
||||||
|
/// string to filter the displayed content.
|
||||||
|
/// </summary>
|
||||||
|
public class SearchablePopup : PopupWindowContent
|
||||||
|
{
|
||||||
|
#region -- Constants --------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary> Height of each element in the popup list. </summary>
|
||||||
|
private const float ROW_HEIGHT = 16.0f;
|
||||||
|
|
||||||
|
/// <summary> How far to indent list entries. </summary>
|
||||||
|
private const float ROW_INDENT = 8.0f;
|
||||||
|
|
||||||
|
/// <summary> Name to use for the text field for search. </summary>
|
||||||
|
private const string SEARCH_CONTROL_NAME = "EnumSearchText";
|
||||||
|
|
||||||
|
#endregion -- Constants -----------------------------------------------
|
||||||
|
|
||||||
|
#region -- Static Functions -------------------------------------------
|
||||||
|
|
||||||
|
/// <summary> Show a new SearchablePopup. </summary>
|
||||||
|
/// <param name="activatorRect">
|
||||||
|
/// Rectangle of the button that triggered the popup.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="options">List of strings to choose from.</param>
|
||||||
|
/// <param name="current">
|
||||||
|
/// Index of the currently selected string.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="onSelectionMade">
|
||||||
|
/// Callback to trigger when a choice is made.
|
||||||
|
/// </param>
|
||||||
|
public static void Show(Rect activatorRect, string[] options, int current, Action<int> onSelectionMade)
|
||||||
|
{
|
||||||
|
SearchablePopup win =
|
||||||
|
new SearchablePopup(options, current, onSelectionMade);
|
||||||
|
PopupWindow.Show(activatorRect, win);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Force the focused window to redraw. This can be used to make the
|
||||||
|
/// popup more responsive to mouse movement.
|
||||||
|
/// </summary>
|
||||||
|
private static void Repaint()
|
||||||
|
{
|
||||||
|
EditorWindow.focusedWindow.Repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Draw a generic box. </summary>
|
||||||
|
/// <param name="rect">Where to draw.</param>
|
||||||
|
/// <param name="tint">Color to tint the box.</param>
|
||||||
|
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 ---------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores a list of strings and can return a subset of that list that
|
||||||
|
/// matches a given filter string.
|
||||||
|
/// </summary>
|
||||||
|
private class FilteredList
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An entry in the filtered list, mapping the text to the
|
||||||
|
/// original index.
|
||||||
|
/// </summary>
|
||||||
|
public struct Entry
|
||||||
|
{
|
||||||
|
public int index;
|
||||||
|
public string text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> All possible items in the list. </summary>
|
||||||
|
private readonly string[] allItems;
|
||||||
|
|
||||||
|
/// <summary> Create a new filtered list. </summary>
|
||||||
|
/// <param name="items">All The items to filter.</param>
|
||||||
|
public FilteredList(string[] items)
|
||||||
|
{
|
||||||
|
allItems = items;
|
||||||
|
Entries = new List<Entry>();
|
||||||
|
UpdateFilter("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> The current string filtering the list. </summary>
|
||||||
|
public string Filter { get; private set; }
|
||||||
|
|
||||||
|
/// <summary> All valid entries for the current filter. </summary>
|
||||||
|
public List<Entry> Entries { get; private set; }
|
||||||
|
|
||||||
|
/// <summary> Total possible entries in the list. </summary>
|
||||||
|
public int MaxLength
|
||||||
|
{
|
||||||
|
get { return allItems.Length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a new filter string and updates the Entries that match the
|
||||||
|
/// new filter if it has changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filter">String to use to filter the list.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the filter is updated, false if newFilter is the same
|
||||||
|
/// as the current Filter and no update is necessary.
|
||||||
|
/// </returns>
|
||||||
|
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 ------------------------------------------
|
||||||
|
|
||||||
|
/// <summary> Callback to trigger when an item is selected. </summary>
|
||||||
|
private readonly Action<int> onSelectionMade;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index of the item that was selected when the list was opened.
|
||||||
|
/// </summary>
|
||||||
|
private readonly int currentIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container for all available options that does the actual string
|
||||||
|
/// filtering of the content.
|
||||||
|
/// </summary>
|
||||||
|
private readonly FilteredList list;
|
||||||
|
|
||||||
|
/// <summary> Scroll offset for the vertical scroll area. </summary>
|
||||||
|
private Vector2 scroll;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index of the item under the mouse or selected with the keyboard.
|
||||||
|
/// </summary>
|
||||||
|
private int hoverIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An item index to scroll to on the next draw.
|
||||||
|
/// </summary>
|
||||||
|
private int scrollToIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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<int> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process keyboard input to navigate the choices or make a selection.
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d4c1ac06e38adc9418191cf707dc1c63
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5b55cf9f9427edf4f94a66bc2a6b14b3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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<Member> 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<FieldInfo> 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<PropertyInfo> 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<ShowInInspectorAttribute>();
|
||||||
|
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<ShowInInspectorAttribute>();
|
||||||
|
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 = "<NULL>";
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 39458dd0676f9c0449d334871e5e1bb1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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 <color=brown>{0}</color> in object <color=brown>{1}</color> 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 <color=brown>{0}</color> in object <color=brown>{1}</color> 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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1f05661da7d39cf418051ccfb13eb5a9
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 292774cd51b5c464ca08b8bc1805e662
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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 <color=brown>{0}</color> in object <color=brown>{1}</color> is of wrong type. Expected: String",
|
||||||
|
property.name, property.serializedObject.targetObject));
|
||||||
|
_checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4112d636c944a774aae43810656bde88
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 140e2c93656440341b95fd7ae6396b7f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Packages/com.jovian.inspector/Editor/Extensions.meta
Normal file
8
Packages/com.jovian.inspector/Editor/Extensions.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6b11d21170b13bc458c03fd935af0f63
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dd2b08b6aac506f4eabedf9dff5a11cb
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HelpBox with centered text alignment
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ToolbarButtonStyle is not resizable by default
|
||||||
|
/// </summary>
|
||||||
|
public static GUIStyle ResizableToolbarButtonStyle {
|
||||||
|
get {
|
||||||
|
if(_resizableToolbarButtonStyle != null) return _resizableToolbarButtonStyle;
|
||||||
|
_resizableToolbarButtonStyle = new GUIStyle(EditorStyles.toolbarButton);
|
||||||
|
_resizableToolbarButtonStyle.fixedHeight = 0;
|
||||||
|
return _resizableToolbarButtonStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GUIStyle _resizableToolbarButtonStyle;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ToolbarButton with Border, Margin and Padding set to 0
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Style for a toggle button
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MiniButtonLeft/Middle/Right style based on array index
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <param name="collection"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw Separator within GuiLayout
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw Line within GUILayout
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw line within Rect and get Rect back with offset
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw Rect filled with Color
|
||||||
|
/// </summary>
|
||||||
|
public static void DrawColouredRect(Rect rect, Color color) {
|
||||||
|
var defaultBackgroundColor = GUI.backgroundColor;
|
||||||
|
GUI.backgroundColor = color;
|
||||||
|
GUI.Box(rect, "");
|
||||||
|
GUI.backgroundColor = defaultBackgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw background Line within GUILayout
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw background Line of height
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make a field for SerializedProperty and check if changed
|
||||||
|
/// </summary>
|
||||||
|
public static bool PropertyField(SerializedProperty property, params GUILayoutOption[] options) {
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
EditorGUILayout.PropertyField(property, options);
|
||||||
|
return EditorGUI.EndChangeCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make a field for SerializedProperty and check if changed
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move array element at index on index-1 position
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move array element at index on index+1 position
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add new array element to property and get it as SerializedProperty
|
||||||
|
/// </summary>
|
||||||
|
public static SerializedProperty NewArrayElementButton(this SerializedProperty property) {
|
||||||
|
if(PlusButton()) {
|
||||||
|
return property.NewElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove array element at index
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag-and-Drop Area to catch objects of specific type
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Asset type to catch</typeparam>
|
||||||
|
/// <param name="areaText">Label to display</param>
|
||||||
|
/// <param name="height">Height of the Drop Area</param>
|
||||||
|
/// <param name="allowExternal">Allow to drag external files and import as unity assets</param>
|
||||||
|
/// <param name="externalImportFolder">Path relative to Assets folder</param>
|
||||||
|
/// <returns>Received objects. Null if none received</returns>
|
||||||
|
public static T[] DropArea<T>(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<T> result = new List<T>();
|
||||||
|
|
||||||
|
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<string> importedFiles = new List<string>();
|
||||||
|
|
||||||
|
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<T>(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<T>(AssetDatabase.GetAssetPath(dragged));
|
||||||
|
|
||||||
|
if(validObject != null) result.Add(validObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Count > 0 ? result.OrderBy(o => o.name).ToArray() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag-and-Drop Area to get paths of received objects
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="areaText">Label to display</param>
|
||||||
|
/// <param name="height">Height of the Drop Area</param>
|
||||||
|
/// <returns>Received paths</returns>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a filepath textfield with a browse button. Opens the open file panel.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a folder path textfield with a browse button. Opens the save folder panel.
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display Button with ArrowUI
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a toolbar that is filled in from an Enum. Useful for setting tool modes.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an array foldout like in inspectors
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user