Flatkit 추가 및 설정

This commit is contained in:
2026-01-25 11:27:33 +09:00
parent 05233497e7
commit cf16910a32
1938 changed files with 408633 additions and 244 deletions

View File

@@ -0,0 +1,16 @@
using FlatKit;
using UnityEditor;
namespace ExternPropertyAttributes.Editor {
[CanEditMultipleObjects]
[CustomEditor(typeof(OutlineSettings))]
public class OutlineSettingsInspector : ExternalCustomInspector { }
[CanEditMultipleObjects]
[CustomEditor(typeof(FogSettings))]
public class FogSettingsInspector : ExternalCustomInspector { }
[CanEditMultipleObjects]
[CustomEditor(typeof(PixelationSettings))]
public class PixelationSettingsInspector : ExternalCustomInspector { }
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aa056e1a4f8d4d518b5bee373fe90fb2
timeCreated: 1701281418

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9b4cd5046c3ad3542a050976caeda97c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using UnityEngine;
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
[CustomPropertyDrawer(typeof(HorizontalLineAttribute))]
public class HorizontalLineDecoratorDrawer : DecoratorDrawer
{
public override float GetHeight()
{
HorizontalLineAttribute lineAttr = (HorizontalLineAttribute)attribute;
return EditorGUIUtility.singleLineHeight + lineAttr.Height;
}
public override void OnGUI(Rect position)
{
Rect rect = EditorGUI.IndentedRect(position);
rect.y += EditorGUIUtility.singleLineHeight / 3.0f;
HorizontalLineAttribute lineAttr = (HorizontalLineAttribute)attribute;
ExternalCustomEditorGUI.HorizontalLine(rect, lineAttr.Height, lineAttr.Color.GetColor());
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f20fca4a878d71d4da99cbb70e6cba98
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
using UnityEditor;
using UnityEngine;
namespace ExternPropertyAttributes.Editor
{
[CustomPropertyDrawer(typeof(InfoBoxAttribute))]
public class InfoBoxDecoratorDrawer : DecoratorDrawer
{
public override float GetHeight()
{
return GetHelpBoxHeight();
}
public override void OnGUI(Rect rect)
{
InfoBoxAttribute infoBoxAttribute = (InfoBoxAttribute)attribute;
float indentLength = ExternalCustomEditorGUI.GetIndentLength(rect);
Rect infoBoxRect = new Rect(
rect.x + indentLength,
rect.y,
rect.width - indentLength,
GetHelpBoxHeight());
DrawInfoBox(infoBoxRect, infoBoxAttribute.Text, infoBoxAttribute.Type);
}
private float GetHelpBoxHeight()
{
InfoBoxAttribute infoBoxAttribute = (InfoBoxAttribute)attribute;
float minHeight = EditorGUIUtility.singleLineHeight * 2.0f;
float desiredHeight = GUI.skin.box.CalcHeight(new GUIContent(infoBoxAttribute.Text), EditorGUIUtility.currentViewWidth);
float height = Mathf.Max(minHeight, desiredHeight);
return height;
}
private void DrawInfoBox(Rect rect, string infoText, EInfoBoxType infoBoxType)
{
MessageType messageType = MessageType.None;
switch (infoBoxType)
{
case EInfoBoxType.Normal:
messageType = MessageType.Info;
break;
case EInfoBoxType.Warning:
messageType = MessageType.Warning;
break;
case EInfoBoxType.Error:
messageType = MessageType.Error;
break;
}
ExternalCustomEditorGUI.HelpBox(rect, infoText, messageType);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9588cfddbd9556042863ea90a1017ac3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
{
"name": "ExternAttributes.Editor",
"rootNamespace": "",
"references": [
"GUID:15fc0a57446b3144c949da3e2b9737a9"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 625525e52d3f2a642ada60b456fe20d2
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,215 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace ExternPropertyAttributes.Editor
{
// Applied in `CustomInspectorDefinitions.cs`.
public class ExternalCustomInspector : UnityEditor.Editor
{
private List<SerializedProperty> _serializedProperties = new List<SerializedProperty>();
private IEnumerable<FieldInfo> _nonSerializedFields;
private IEnumerable<PropertyInfo> _nativeProperties;
private IEnumerable<MethodInfo> _methods;
private Dictionary<string, SavedBool> _foldouts = new Dictionary<string, SavedBool>();
private void OnEnable()
{
_nonSerializedFields = ReflectionUtility.GetAllFields(
target, f => f.GetCustomAttributes(typeof(ShowNonSerializedFieldAttribute), true).Length > 0);
_nativeProperties = ReflectionUtility.GetAllProperties(
target, p => p.GetCustomAttributes(typeof(ShowNativePropertyAttribute), true).Length > 0);
_methods = ReflectionUtility.GetAllMethods(
target, m => m.GetCustomAttributes(typeof(ButtonAttribute), true).Length > 0);
}
public override void OnInspectorGUI()
{
GetSerializedProperties(ref _serializedProperties);
bool anyExternalCustomAttribute = _serializedProperties.Any(p => PropertyUtility.GetAttribute<ICustomAttribute>(p) != null);
if (!anyExternalCustomAttribute)
{
DrawDefaultInspector();
}
else
{
DrawSerializedProperties();
}
DrawNonSerializedFields();
DrawNativeProperties();
DrawButtons();
}
private void GetSerializedProperties(ref List<SerializedProperty> outSerializedProperties)
{
outSerializedProperties.Clear();
using (var iterator = serializedObject.GetIterator())
{
if (iterator.NextVisible(true))
{
do
{
outSerializedProperties.Add(serializedObject.FindProperty(iterator.name));
}
while (iterator.NextVisible(false));
}
}
}
private void DrawSerializedProperties()
{
serializedObject.Update();
// Draw non-grouped serialized properties
foreach (var property in GetNonGroupedProperties(_serializedProperties))
{
if (property.name.Equals("m_Script", System.StringComparison.Ordinal))
{
using (new EditorGUI.DisabledScope(disabled: true))
{
EditorGUILayout.PropertyField(property);
}
}
else
{
ExternalCustomEditorGUI.PropertyField_Layout(property, includeChildren: true);
}
}
// Draw grouped serialized properties
foreach (var group in GetGroupedProperties(_serializedProperties))
{
IEnumerable<SerializedProperty> visibleProperties = group.Where(p => PropertyUtility.IsVisible(p));
if (!visibleProperties.Any())
{
continue;
}
ExternalCustomEditorGUI.BeginBoxGroup_Layout(group.Key);
foreach (var property in visibleProperties)
{
ExternalCustomEditorGUI.PropertyField_Layout(property, includeChildren: true);
}
ExternalCustomEditorGUI.EndBoxGroup_Layout();
}
// Draw foldout serialized properties
foreach (var group in GetFoldoutProperties(_serializedProperties))
{
IEnumerable<SerializedProperty> visibleProperties = group.Where(p => PropertyUtility.IsVisible(p));
if (!visibleProperties.Any())
{
continue;
}
if (!_foldouts.ContainsKey(group.Key))
{
_foldouts[group.Key] = new SavedBool($"{target.GetInstanceID()}.{group.Key}", false);
}
_foldouts[group.Key].Value = EditorGUILayout.Foldout(_foldouts[group.Key].Value, group.Key, true);
if (_foldouts[group.Key].Value)
{
foreach (var property in visibleProperties)
{
ExternalCustomEditorGUI.PropertyField_Layout(property, true);
}
}
}
serializedObject.ApplyModifiedProperties();
}
private void DrawNonSerializedFields(bool drawHeader = false)
{
if (_nonSerializedFields.Any())
{
if (drawHeader)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Non-Serialized Fields", GetHeaderGUIStyle());
ExternalCustomEditorGUI.HorizontalLine(
EditorGUILayout.GetControlRect(false), HorizontalLineAttribute.DefaultHeight, HorizontalLineAttribute.DefaultColor.GetColor());
}
foreach (var field in _nonSerializedFields)
{
ExternalCustomEditorGUI.NonSerializedField_Layout(serializedObject.targetObject, field);
}
}
}
private void DrawNativeProperties(bool drawHeader = false)
{
if (_nativeProperties.Any())
{
if (drawHeader)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Native Properties", GetHeaderGUIStyle());
ExternalCustomEditorGUI.HorizontalLine(
EditorGUILayout.GetControlRect(false), HorizontalLineAttribute.DefaultHeight, HorizontalLineAttribute.DefaultColor.GetColor());
}
foreach (var property in _nativeProperties)
{
ExternalCustomEditorGUI.NativeProperty_Layout(serializedObject.targetObject, property);
}
}
}
private void DrawButtons(bool drawHeader = false)
{
if (_methods.Any())
{
if (drawHeader)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Buttons", GetHeaderGUIStyle());
ExternalCustomEditorGUI.HorizontalLine(
EditorGUILayout.GetControlRect(false), HorizontalLineAttribute.DefaultHeight, HorizontalLineAttribute.DefaultColor.GetColor());
}
foreach (var method in _methods)
{
ExternalCustomEditorGUI.Button(serializedObject.targetObject, method);
}
}
}
private static IEnumerable<SerializedProperty> GetNonGroupedProperties(IEnumerable<SerializedProperty> properties)
{
return properties.Where(p => PropertyUtility.GetAttribute<IGroupAttribute>(p) == null);
}
private static IEnumerable<IGrouping<string, SerializedProperty>> GetGroupedProperties(IEnumerable<SerializedProperty> properties)
{
return properties
.Where(p => PropertyUtility.GetAttribute<BoxGroupAttribute>(p) != null)
.GroupBy(p => PropertyUtility.GetAttribute<BoxGroupAttribute>(p).Name);
}
private static IEnumerable<IGrouping<string, SerializedProperty>> GetFoldoutProperties(IEnumerable<SerializedProperty> properties)
{
return properties
.Where(p => PropertyUtility.GetAttribute<FoldoutAttribute>(p) != null)
.GroupBy(p => PropertyUtility.GetAttribute<FoldoutAttribute>(p).Name);
}
private static GUIStyle GetHeaderGUIStyle()
{
GUIStyle style = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
style.fontStyle = FontStyle.Bold;
style.alignment = TextAnchor.UpperCenter;
return style;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9dd8aae67f2416c428f8e81fed6f4f32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 35f155b915f0f4c4295d78f6451df390
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using UnityEngine;
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
[CustomPropertyDrawer(typeof(AllowNestingAttribute))]
public class AllowNestingPropertyDrawer : PropertyDrawerBase
{
protected override void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(rect, label, property);
EditorGUI.PropertyField(rect, property, label, true);
EditorGUI.EndProperty();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f731bdca659b2154db7e2dc33effc220
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,192 @@
using UnityEngine;
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
[CustomPropertyDrawer(typeof(ExpandableAttribute))]
public class ExpandablePropertyDrawer : PropertyDrawerBase
{
protected override float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label)
{
System.Type propertyType = PropertyUtility.GetPropertyType(property);
if (typeof(ScriptableObject).IsAssignableFrom(propertyType))
{
ScriptableObject scriptableObject = property.objectReferenceValue as ScriptableObject;
if (scriptableObject == null)
{
return GetPropertyHeight(property);
}
if (property.isExpanded)
{
using (SerializedObject serializedObject = new SerializedObject(scriptableObject))
{
float totalHeight = EditorGUIUtility.singleLineHeight;
using (var iterator = serializedObject.GetIterator())
{
if (iterator.NextVisible(true))
{
do
{
SerializedProperty childProperty = serializedObject.FindProperty(iterator.name);
if (childProperty.name.Equals("m_Script", System.StringComparison.Ordinal))
{
continue;
}
bool visible = PropertyUtility.IsVisible(childProperty);
if (!visible)
{
continue;
}
float height = GetPropertyHeight(childProperty);
totalHeight += height;
}
while (iterator.NextVisible(false));
}
}
totalHeight += EditorGUIUtility.standardVerticalSpacing;
return totalHeight;
}
}
else
{
return GetPropertyHeight(property);
}
}
else
{
return GetPropertyHeight(property) + GetHelpBoxHeight();
}
}
protected override void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(rect, label, property);
System.Type propertyType = PropertyUtility.GetPropertyType(property);
if (typeof(ScriptableObject).IsAssignableFrom(propertyType))
{
ScriptableObject scriptableObject = property.objectReferenceValue as ScriptableObject;
if (scriptableObject == null)
{
EditorGUI.PropertyField(rect, property, label, false);
}
else
{
// Draw a foldout
Rect foldoutRect = new Rect()
{
x = rect.x,
y = rect.y,
width = EditorGUIUtility.labelWidth,
height = EditorGUIUtility.singleLineHeight
};
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label, toggleOnLabelClick: true);
// Draw the scriptable object field
float indentLength = ExternalCustomEditorGUI.GetIndentLength(rect);
float labelWidth = EditorGUIUtility.labelWidth - indentLength + ExternalCustomEditorGUI.HorizontalSpacing;
Rect propertyRect = new Rect()
{
x = rect.x + labelWidth,
y = rect.y,
width = rect.width - labelWidth,
height = EditorGUIUtility.singleLineHeight
};
EditorGUI.BeginChangeCheck();
property.objectReferenceValue = EditorGUI.ObjectField(propertyRect, GUIContent.none, property.objectReferenceValue, propertyType, false);
if (EditorGUI.EndChangeCheck())
{
property.serializedObject.ApplyModifiedProperties();
}
// Draw the child properties
if (property.isExpanded)
{
DrawChildProperties(rect, property);
}
}
}
else
{
string message = $"{typeof(ExpandableAttribute).Name} can only be used on scriptable objects";
DrawDefaultPropertyAndHelpBox(rect, property, message, MessageType.Warning);
}
EditorGUI.EndProperty();
}
private void DrawChildProperties(Rect rect, SerializedProperty property)
{
ScriptableObject scriptableObject = property.objectReferenceValue as ScriptableObject;
if (scriptableObject == null)
{
return;
}
Rect boxRect = new Rect()
{
x = 0.0f,
y = rect.y + EditorGUIUtility.singleLineHeight,
width = rect.width * 2.0f,
height = rect.height - EditorGUIUtility.singleLineHeight
};
GUI.Box(boxRect, GUIContent.none);
using (new EditorGUI.IndentLevelScope())
{
EditorGUI.BeginChangeCheck();
SerializedObject serializedObject = new SerializedObject(scriptableObject);
using (var iterator = serializedObject.GetIterator())
{
float yOffset = EditorGUIUtility.singleLineHeight;
if (iterator.NextVisible(true))
{
do
{
SerializedProperty childProperty = serializedObject.FindProperty(iterator.name);
if (childProperty.name.Equals("m_Script", System.StringComparison.Ordinal))
{
continue;
}
bool visible = PropertyUtility.IsVisible(childProperty);
if (!visible)
{
continue;
}
float childHeight = GetPropertyHeight(childProperty);
Rect childRect = new Rect()
{
x = rect.x,
y = rect.y + yOffset,
width = rect.width,
height = childHeight
};
ExternalCustomEditorGUI.PropertyField(childRect, childProperty, true);
yOffset += childHeight;
}
while (iterator.NextVisible(false));
}
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c1918a04454ee14a91bce2534050896
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,87 @@
using UnityEditor;
using UnityEngine;
namespace ExternPropertyAttributes.Editor
{
[CustomPropertyDrawer(typeof(MinMaxSliderAttribute))]
public class MinMaxSliderPropertyDrawer : PropertyDrawerBase
{
protected override float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label)
{
return (property.propertyType == SerializedPropertyType.Vector2)
? GetPropertyHeight(property)
: GetPropertyHeight(property) + GetHelpBoxHeight();
}
protected override void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(rect, label, property);
MinMaxSliderAttribute minMaxSliderAttribute = (MinMaxSliderAttribute)attribute;
if (property.propertyType == SerializedPropertyType.Vector2)
{
EditorGUI.BeginProperty(rect, label, property);
float indentLength = ExternalCustomEditorGUI.GetIndentLength(rect);
float labelWidth = EditorGUIUtility.labelWidth + ExternalCustomEditorGUI.HorizontalSpacing;
float floatFieldWidth = EditorGUIUtility.fieldWidth;
float sliderWidth = rect.width - labelWidth - 2.0f * floatFieldWidth;
float sliderPadding = 5.0f;
Rect labelRect = new Rect(
rect.x,
rect.y,
labelWidth,
rect.height);
Rect sliderRect = new Rect(
rect.x + labelWidth + floatFieldWidth + sliderPadding - indentLength,
rect.y,
sliderWidth - 2.0f * sliderPadding + indentLength,
rect.height);
Rect minFloatFieldRect = new Rect(
rect.x + labelWidth - indentLength,
rect.y,
floatFieldWidth + indentLength,
rect.height);
Rect maxFloatFieldRect = new Rect(
rect.x + labelWidth + floatFieldWidth + sliderWidth - indentLength,
rect.y,
floatFieldWidth + indentLength,
rect.height);
// Draw the label
EditorGUI.LabelField(labelRect, label.text);
// Draw the slider
EditorGUI.BeginChangeCheck();
Vector2 sliderValue = property.vector2Value;
EditorGUI.MinMaxSlider(sliderRect, ref sliderValue.x, ref sliderValue.y, minMaxSliderAttribute.MinValue, minMaxSliderAttribute.MaxValue);
sliderValue.x = EditorGUI.FloatField(minFloatFieldRect, sliderValue.x);
sliderValue.x = Mathf.Clamp(sliderValue.x, minMaxSliderAttribute.MinValue, Mathf.Min(minMaxSliderAttribute.MaxValue, sliderValue.y));
sliderValue.y = EditorGUI.FloatField(maxFloatFieldRect, sliderValue.y);
sliderValue.y = Mathf.Clamp(sliderValue.y, Mathf.Max(minMaxSliderAttribute.MinValue, sliderValue.x), minMaxSliderAttribute.MaxValue);
if (EditorGUI.EndChangeCheck())
{
property.vector2Value = sliderValue;
}
EditorGUI.EndProperty();
}
else
{
string message = minMaxSliderAttribute.GetType().Name + " can be used only on Vector2 fields";
DrawDefaultPropertyAndHelpBox(rect, property, message, MessageType.Warning);
}
EditorGUI.EndProperty();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6706a615c0046cf4aa4f6f4ffae4f823
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,94 @@
using UnityEditor;
using UnityEngine;
namespace ExternPropertyAttributes.Editor
{
public abstract class PropertyDrawerBase : PropertyDrawer
{
public sealed override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
{
// Check if visible
bool visible = PropertyUtility.IsVisible(property);
if (!visible)
{
return;
}
// Validate
ValidatorAttribute[] validatorAttributes = PropertyUtility.GetAttributes<ValidatorAttribute>(property);
foreach (var validatorAttribute in validatorAttributes)
{
validatorAttribute.GetValidator().ValidateProperty(property);
}
// Check if enabled and draw
EditorGUI.BeginChangeCheck();
bool enabled = PropertyUtility.IsEnabled(property);
using (new EditorGUI.DisabledScope(disabled: !enabled))
{
OnGUI_Internal(rect, property, PropertyUtility.GetLabel(property));
}
// Call OnValueChanged callbacks
if (EditorGUI.EndChangeCheck())
{
PropertyUtility.CallOnValueChangedCallbacks(property);
}
}
protected abstract void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label);
sealed override public float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
bool visible = PropertyUtility.IsVisible(property);
if (!visible)
{
return 0.0f;
}
return GetPropertyHeight_Internal(property, label);
}
protected virtual float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, includeChildren: true);
}
protected float GetPropertyHeight(SerializedProperty property)
{
SpecialCaseDrawerAttribute specialCaseAttribute = PropertyUtility.GetAttribute<SpecialCaseDrawerAttribute>(property);
if (specialCaseAttribute != null)
{
return specialCaseAttribute.GetDrawer().GetPropertyHeight(property);
}
return EditorGUI.GetPropertyHeight(property, includeChildren: true);
}
public virtual float GetHelpBoxHeight()
{
return EditorGUIUtility.singleLineHeight * 2.0f;
}
public void DrawDefaultPropertyAndHelpBox(Rect rect, SerializedProperty property, string message, MessageType messageType)
{
float indentLength = ExternalCustomEditorGUI.GetIndentLength(rect);
Rect helpBoxRect = new Rect(
rect.x + indentLength,
rect.y,
rect.width - indentLength,
GetHelpBoxHeight());
ExternalCustomEditorGUI.HelpBox(helpBoxRect, message, MessageType.Warning, context: property.serializedObject.targetObject);
Rect propertyRect = new Rect(
rect.x,
rect.y + GetHelpBoxHeight(),
rect.width,
GetPropertyHeight(property));
EditorGUI.PropertyField(propertyRect, property, true);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15bad6a367324ff438e9de75919e4b60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using UnityEngine;
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyPropertyDrawer : PropertyDrawerBase
{
protected override float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label)
{
return GetPropertyHeight(property);
}
protected override void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(rect, label, property);
using (new EditorGUI.DisabledScope(disabled: true))
{
EditorGUI.PropertyField(rect, property, label, true);
}
EditorGUI.EndProperty();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 158ee71d6827f134595af325a6ec857a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,110 @@
using UnityEngine;
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
[CustomPropertyDrawer(typeof(ShowAssetPreviewAttribute))]
public class ShowAssetPreviewPropertyDrawer : PropertyDrawerBase
{
protected override float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
Texture2D previewTexture = GetAssetPreview(property);
if (previewTexture != null)
{
return GetPropertyHeight(property) + GetAssetPreviewSize(property).y;
}
else
{
return GetPropertyHeight(property);
}
}
else
{
return GetPropertyHeight(property) + GetHelpBoxHeight();
}
}
protected override void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(rect, label, property);
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
Rect propertyRect = new Rect()
{
x = rect.x,
y = rect.y,
width = rect.width,
height = EditorGUIUtility.singleLineHeight
};
EditorGUI.PropertyField(propertyRect, property, label);
Texture2D previewTexture = GetAssetPreview(property);
if (previewTexture != null)
{
Rect previewRect = new Rect()
{
x = rect.x + ExternalCustomEditorGUI.GetIndentLength(rect),
y = rect.y + EditorGUIUtility.singleLineHeight,
width = rect.width,
height = GetAssetPreviewSize(property).y
};
GUI.Label(previewRect, previewTexture);
}
}
else
{
string message = property.name + " doesn't have an asset preview";
DrawDefaultPropertyAndHelpBox(rect, property, message, MessageType.Warning);
}
EditorGUI.EndProperty();
}
private Texture2D GetAssetPreview(SerializedProperty property)
{
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
if (property.objectReferenceValue != null)
{
Texture2D previewTexture = AssetPreview.GetAssetPreview(property.objectReferenceValue);
return previewTexture;
}
return null;
}
return null;
}
private Vector2 GetAssetPreviewSize(SerializedProperty property)
{
Texture2D previewTexture = GetAssetPreview(property);
if (previewTexture == null)
{
return Vector2.zero;
}
else
{
int targetWidth = ShowAssetPreviewAttribute.DefaultWidth;
int targetHeight = ShowAssetPreviewAttribute.DefaultHeight;
ShowAssetPreviewAttribute showAssetPreviewAttribute = PropertyUtility.GetAttribute<ShowAssetPreviewAttribute>(property);
if (showAssetPreviewAttribute != null)
{
targetWidth = showAssetPreviewAttribute.Width;
targetHeight = showAssetPreviewAttribute.Height;
}
int width = Mathf.Clamp(targetWidth, 0, previewTexture.width);
int height = Mathf.Clamp(targetHeight, 0, previewTexture.height);
return new Vector2(width, height);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91ee7a4a8e531d841b7d36be94da45d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b64bba7951f431b498ee0aafcfcf0434
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace ExternPropertyAttributes.Editor
{
public abstract class SpecialCasePropertyDrawerBase
{
public void OnGUI(Rect rect, SerializedProperty property)
{
// Check if visible
bool visible = PropertyUtility.IsVisible(property);
if (!visible)
{
return;
}
// Validate
ValidatorAttribute[] validatorAttributes = PropertyUtility.GetAttributes<ValidatorAttribute>(property);
foreach (var validatorAttribute in validatorAttributes)
{
validatorAttribute.GetValidator().ValidateProperty(property);
}
// Check if enabled and draw
EditorGUI.BeginChangeCheck();
bool enabled = PropertyUtility.IsEnabled(property);
using (new EditorGUI.DisabledScope(disabled: !enabled))
{
OnGUI_Internal(rect, property, PropertyUtility.GetLabel(property));
}
// Call OnValueChanged callbacks
if (EditorGUI.EndChangeCheck())
{
PropertyUtility.CallOnValueChangedCallbacks(property);
}
}
public float GetPropertyHeight(SerializedProperty property)
{
return GetPropertyHeight_Internal(property);
}
protected abstract void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label);
protected abstract float GetPropertyHeight_Internal(SerializedProperty property);
}
public static class SpecialCaseDrawerAttributeExtensions
{
private static Dictionary<Type, SpecialCasePropertyDrawerBase> _drawersByAttributeType;
static SpecialCaseDrawerAttributeExtensions()
{
_drawersByAttributeType = new Dictionary<Type, SpecialCasePropertyDrawerBase>();
}
public static SpecialCasePropertyDrawerBase GetDrawer(this SpecialCaseDrawerAttribute attr)
{
SpecialCasePropertyDrawerBase drawer;
if (_drawersByAttributeType.TryGetValue(attr.GetType(), out drawer))
{
return drawer;
}
else
{
return null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85c8b6ac240d4464186d8e511aef80b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c2e1224e0b40c3b43bf5a15040b09574
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using UnityEngine;
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
public class MaxValuePropertyValidator : PropertyValidatorBase
{
public override void ValidateProperty(SerializedProperty property)
{
MaxValueAttribute maxValueAttribute = PropertyUtility.GetAttribute<MaxValueAttribute>(property);
if (property.propertyType == SerializedPropertyType.Integer)
{
if (property.intValue > maxValueAttribute.MaxValue)
{
property.intValue = (int)maxValueAttribute.MaxValue;
}
}
else if (property.propertyType == SerializedPropertyType.Float)
{
if (property.floatValue > maxValueAttribute.MaxValue)
{
property.floatValue = maxValueAttribute.MaxValue;
}
}
else
{
string warning = maxValueAttribute.GetType().Name + " can be used only on int or float fields";
Debug.LogWarning(warning, property.serializedObject.targetObject);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ada97406b0d1ffe43abdd9895a06ceb6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using UnityEngine;
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
public class MinValuePropertyValidator : PropertyValidatorBase
{
public override void ValidateProperty(SerializedProperty property)
{
MinValueAttribute minValueAttribute = PropertyUtility.GetAttribute<MinValueAttribute>(property);
if (property.propertyType == SerializedPropertyType.Integer)
{
if (property.intValue < minValueAttribute.MinValue)
{
property.intValue = (int)minValueAttribute.MinValue;
}
}
else if (property.propertyType == SerializedPropertyType.Float)
{
if (property.floatValue < minValueAttribute.MinValue)
{
property.floatValue = minValueAttribute.MinValue;
}
}
else
{
string warning = minValueAttribute.GetType().Name + " can be used only on int or float fields";
Debug.LogWarning(warning, property.serializedObject.targetObject);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 433476848077969448c2020cf8ff7ba1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
public abstract class PropertyValidatorBase
{
public abstract void ValidateProperty(SerializedProperty property);
}
public static class ValidatorAttributeExtensions
{
private static Dictionary<Type, PropertyValidatorBase> _validatorsByAttributeType;
static ValidatorAttributeExtensions()
{
_validatorsByAttributeType = new Dictionary<Type, PropertyValidatorBase>();
_validatorsByAttributeType[typeof(MinValueAttribute)] = new MinValuePropertyValidator();
_validatorsByAttributeType[typeof(MaxValueAttribute)] = new MaxValuePropertyValidator();
_validatorsByAttributeType[typeof(RequiredAttribute)] = new RequiredPropertyValidator();
_validatorsByAttributeType[typeof(ValidateInputAttribute)] = new ValidateInputPropertyValidator();
}
public static PropertyValidatorBase GetValidator(this ValidatorAttribute attr)
{
PropertyValidatorBase validator;
if (_validatorsByAttributeType.TryGetValue(attr.GetType(), out validator))
{
return validator;
}
else
{
return null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f40a67826127b8445ba252aa14309b10
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
public class RequiredPropertyValidator : PropertyValidatorBase
{
public override void ValidateProperty(SerializedProperty property)
{
RequiredAttribute requiredAttribute = PropertyUtility.GetAttribute<RequiredAttribute>(property);
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
if (property.objectReferenceValue == null)
{
string errorMessage = property.name + " is required";
if (!string.IsNullOrEmpty(requiredAttribute.Message))
{
errorMessage = requiredAttribute.Message;
}
ExternalCustomEditorGUI.HelpBox_Layout(errorMessage, MessageType.Error, context: property.serializedObject.targetObject);
}
}
else
{
string warning = requiredAttribute.GetType().Name + " works only on reference types";
ExternalCustomEditorGUI.HelpBox_Layout(warning, MessageType.Warning, context: property.serializedObject.targetObject);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d75fb4e5b4f194a4682daa881007e48c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,75 @@
using UnityEditor;
using System.Reflection;
using System;
namespace ExternPropertyAttributes.Editor
{
public class ValidateInputPropertyValidator : PropertyValidatorBase
{
public override void ValidateProperty(SerializedProperty property)
{
ValidateInputAttribute validateInputAttribute = PropertyUtility.GetAttribute<ValidateInputAttribute>(property);
object target = PropertyUtility.GetTargetObjectWithProperty(property);
MethodInfo validationCallback = ReflectionUtility.GetMethod(target, validateInputAttribute.CallbackName);
if (validationCallback != null &&
validationCallback.ReturnType == typeof(bool))
{
ParameterInfo[] callbackParameters = validationCallback.GetParameters();
if (callbackParameters.Length == 0) {
if (!(bool)validationCallback.Invoke(target, null))
{
if (string.IsNullOrEmpty(validateInputAttribute.Message))
{
ExternalCustomEditorGUI.HelpBox_Layout(
property.name + " is not valid", MessageType.Error, context: property.serializedObject.targetObject);
}
else
{
ExternalCustomEditorGUI.HelpBox_Layout(
validateInputAttribute.Message, MessageType.Error, context: property.serializedObject.targetObject);
}
}
}
else if (callbackParameters.Length == 1)
{
FieldInfo fieldInfo = ReflectionUtility.GetField(target, property.name);
Type fieldType = fieldInfo.FieldType;
Type parameterType = callbackParameters[0].ParameterType;
if (fieldType == parameterType)
{
if (!(bool)validationCallback.Invoke(target, new object[] { fieldInfo.GetValue(target) }))
{
if (string.IsNullOrEmpty(validateInputAttribute.Message))
{
ExternalCustomEditorGUI.HelpBox_Layout(
property.name + " is not valid", MessageType.Error, context: property.serializedObject.targetObject);
}
else
{
ExternalCustomEditorGUI.HelpBox_Layout(
validateInputAttribute.Message, MessageType.Error, context: property.serializedObject.targetObject);
}
}
}
else
{
string warning = "The field type is not the same as the callback's parameter type";
ExternalCustomEditorGUI.HelpBox_Layout(warning, MessageType.Warning, context: property.serializedObject.targetObject);
}
}
else
{
string warning =
validateInputAttribute.GetType().Name +
" needs a callback with boolean return type and an optional single parameter of the same type as the field";
ExternalCustomEditorGUI.HelpBox_Layout(warning, MessageType.Warning, context: property.serializedObject.targetObject);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 688cdce682fd92c44a5c4348d6d54fe6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0b197d1a7f4cecf4387fb92c31ea51e6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,55 @@
using UnityEngine;
using System.Reflection;
using System.Collections.Generic;
namespace ExternPropertyAttributes.Editor
{
public static class ButtonUtility
{
public static bool IsEnabled(Object target, MethodInfo method)
{
EnableIfAttributeBase enableIfAttribute = method.GetCustomAttribute<EnableIfAttributeBase>();
if (enableIfAttribute == null)
{
return true;
}
List<bool> conditionValues = PropertyUtility.GetConditionValues(target, enableIfAttribute.Conditions);
if (conditionValues.Count > 0)
{
bool enabled = PropertyUtility.GetConditionsFlag(conditionValues, enableIfAttribute.ConditionOperator, enableIfAttribute.Inverted);
return enabled;
}
else
{
string message = enableIfAttribute.GetType().Name + " needs a valid boolean condition field, property or method name to work";
Debug.LogWarning(message, target);
return false;
}
}
public static bool IsVisible(Object target, MethodInfo method)
{
ShowIfAttributeBase showIfAttribute = method.GetCustomAttribute<ShowIfAttributeBase>();
if (showIfAttribute == null)
{
return true;
}
List<bool> conditionValues = PropertyUtility.GetConditionValues(target, showIfAttribute.Conditions);
if (conditionValues.Count > 0)
{
bool enabled = PropertyUtility.GetConditionsFlag(conditionValues, showIfAttribute.ConditionOperator, showIfAttribute.Inverted);
return enabled;
}
else
{
string message = showIfAttribute.GetType().Name + " needs a valid boolean condition field, property or method name to work";
Debug.LogWarning(message, target);
return false;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d422e684ed7e0fc43b5ebe4dd060a982
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,370 @@
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace ExternPropertyAttributes.Editor
{
public static class ExternalCustomEditorGUI
{
public const float IndentLength = 15.0f;
public const float HorizontalSpacing = 2.0f;
private static GUIStyle _buttonStyle = new GUIStyle(GUI.skin.button) { richText = true };
private delegate void PropertyFieldFunction(Rect rect, SerializedProperty property, GUIContent label, bool includeChildren);
public static void PropertyField(Rect rect, SerializedProperty property, bool includeChildren)
{
PropertyField_Implementation(rect, property, includeChildren, DrawPropertyField);
}
public static void PropertyField_Layout(SerializedProperty property, bool includeChildren)
{
Rect dummyRect = new Rect();
PropertyField_Implementation(dummyRect, property, includeChildren, DrawPropertyField_Layout);
}
private static void DrawPropertyField(Rect rect, SerializedProperty property, GUIContent label, bool includeChildren) {
EditorGUI.PropertyField(rect, property, label, includeChildren);
}
private static void DrawPropertyField_Layout(Rect rect, SerializedProperty property, GUIContent label, bool includeChildren)
{
var originalRichText = EditorStyles.label.richText;
EditorStyles.label.richText = true;
EditorGUILayout.PropertyField(property, label, includeChildren);
EditorStyles.label.richText = originalRichText;
}
private static void PropertyField_Implementation(Rect rect, SerializedProperty property, bool includeChildren, PropertyFieldFunction propertyFieldFunction)
{
var originalRichText = EditorStyles.label.richText;
EditorStyles.label.richText = true;
SpecialCaseDrawerAttribute specialCaseAttribute = PropertyUtility.GetAttribute<SpecialCaseDrawerAttribute>(property);
if (specialCaseAttribute != null)
{
specialCaseAttribute.GetDrawer().OnGUI(rect, property);
}
else
{
GUIContent label = PropertyUtility.GetLabel(property);
bool anyDrawerAttribute = PropertyUtility.GetAttributes<DrawerAttribute>(property).Any();
if (!anyDrawerAttribute)
{
// Drawer attributes check for visibility, enableability and validator themselves,
// so if a property doesn't have a DrawerAttribute we need to check for these explicitly
// Check if visible
bool visible = PropertyUtility.IsVisible(property);
if (!visible)
{
return;
}
// Validate
ValidatorAttribute[] validatorAttributes = PropertyUtility.GetAttributes<ValidatorAttribute>(property);
foreach (var validatorAttribute in validatorAttributes)
{
validatorAttribute.GetValidator().ValidateProperty(property);
}
// Check if enabled and draw
EditorGUI.BeginChangeCheck();
bool enabled = PropertyUtility.IsEnabled(property);
using (new EditorGUI.DisabledScope(disabled: !enabled))
{
propertyFieldFunction.Invoke(rect, property, label, includeChildren);
}
// Call OnValueChanged callbacks
if (EditorGUI.EndChangeCheck())
{
PropertyUtility.CallOnValueChangedCallbacks(property);
}
}
else
{
// We don't need to check for enableIfAttribute
propertyFieldFunction.Invoke(rect, property, label, includeChildren);
}
}
EditorStyles.label.richText = originalRichText;
}
public static float GetIndentLength(Rect sourceRect)
{
Rect indentRect = EditorGUI.IndentedRect(sourceRect);
float indentLength = indentRect.x - sourceRect.x;
return indentLength;
}
public static void BeginBoxGroup_Layout(string label = "")
{
EditorGUILayout.BeginVertical(GUI.skin.box);
if (!string.IsNullOrEmpty(label))
{
EditorGUILayout.LabelField(label, EditorStyles.boldLabel);
}
}
public static void EndBoxGroup_Layout()
{
EditorGUILayout.EndVertical();
}
/// <summary>
/// Creates a dropdown
/// </summary>
/// <param name="rect">The rect the defines the position and size of the dropdown in the inspector</param>
/// <param name="serializedObject">The serialized object that is being updated</param>
/// <param name="target">The target object that contains the dropdown</param>
/// <param name="dropdownField">The field of the target object that holds the currently selected dropdown value</param>
/// <param name="label">The label of the dropdown</param>
/// <param name="selectedValueIndex">The index of the value from the values array</param>
/// <param name="values">The values of the dropdown</param>
/// <param name="displayOptions">The display options for the values</param>
public static void Dropdown(
Rect rect, SerializedObject serializedObject, object target, FieldInfo dropdownField,
string label, int selectedValueIndex, object[] values, string[] displayOptions)
{
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUI.Popup(rect, label, selectedValueIndex, displayOptions);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(serializedObject.targetObject, "Dropdown");
// Problem with structs, because they are value type.
// The solution is to make boxing/unboxing but unfortunately I don't know the compile time type of the target object
dropdownField.SetValue(target, values[newIndex]);
}
}
public static void Button(UnityEngine.Object target, MethodInfo methodInfo)
{
bool visible = ButtonUtility.IsVisible(target, methodInfo);
if (!visible)
{
return;
}
if (methodInfo.GetParameters().All(p => p.IsOptional))
{
ButtonAttribute buttonAttribute = (ButtonAttribute)methodInfo.GetCustomAttributes(typeof(ButtonAttribute), true)[0];
string buttonText = string.IsNullOrEmpty(buttonAttribute.Text) ? ObjectNames.NicifyVariableName(methodInfo.Name) : buttonAttribute.Text;
bool buttonEnabled = ButtonUtility.IsEnabled(target, methodInfo);
EButtonEnableMode mode = buttonAttribute.SelectedEnableMode;
buttonEnabled &=
mode == EButtonEnableMode.Always ||
mode == EButtonEnableMode.Editor && !Application.isPlaying ||
mode == EButtonEnableMode.Playmode && Application.isPlaying;
bool methodIsCoroutine = methodInfo.ReturnType == typeof(IEnumerator);
if (methodIsCoroutine)
{
buttonEnabled &= (Application.isPlaying ? true : false);
}
EditorGUI.BeginDisabledGroup(!buttonEnabled);
if (GUILayout.Button(buttonText, _buttonStyle))
{
object[] defaultParams = methodInfo.GetParameters().Select(p => p.DefaultValue).ToArray();
IEnumerator methodResult = methodInfo.Invoke(target, defaultParams) as IEnumerator;
if (!Application.isPlaying)
{
// Set target object and scene dirty to serialize changes to disk
EditorUtility.SetDirty(target);
PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
if (stage != null)
{
// Prefab mode
EditorSceneManager.MarkSceneDirty(stage.scene);
}
else
{
// Normal scene
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}
}
else if (methodResult != null && target is MonoBehaviour behaviour)
{
behaviour.StartCoroutine(methodResult);
}
}
EditorGUI.EndDisabledGroup();
}
else
{
string warning = typeof(ButtonAttribute).Name + " works only on methods with no parameters";
HelpBox_Layout(warning, MessageType.Warning, context: target, logToConsole: true);
}
}
public static void NativeProperty_Layout(UnityEngine.Object target, PropertyInfo property)
{
object value = property.GetValue(target, null);
if (value == null)
{
string warning = string.Format("{0} is null. {1} doesn't support reference types with null value", property.Name, typeof(ShowNativePropertyAttribute).Name);
HelpBox_Layout(warning, MessageType.Warning, context: target);
}
else if (!Field_Layout(value, property.Name))
{
string warning = string.Format("{0} doesn't support {1} types", typeof(ShowNativePropertyAttribute).Name, property.PropertyType.Name);
HelpBox_Layout(warning, MessageType.Warning, context: target);
}
}
public static void NonSerializedField_Layout(UnityEngine.Object target, FieldInfo field)
{
object value = field.GetValue(target);
if (value == null)
{
string warning = string.Format("{0} is null. {1} doesn't support reference types with null value", field.Name, typeof(ShowNonSerializedFieldAttribute).Name);
HelpBox_Layout(warning, MessageType.Warning, context: target);
}
else if (!Field_Layout(value, field.Name))
{
string warning = string.Format("{0} doesn't support {1} types", typeof(ShowNonSerializedFieldAttribute).Name, field.FieldType.Name);
HelpBox_Layout(warning, MessageType.Warning, context: target);
}
}
public static void HorizontalLine(Rect rect, float height, Color color)
{
rect.height = height;
EditorGUI.DrawRect(rect, color);
}
public static void HelpBox(Rect rect, string message, MessageType type, UnityEngine.Object context = null, bool logToConsole = false)
{
EditorGUI.HelpBox(rect, message, type);
if (logToConsole)
{
DebugLogMessage(message, type, context);
}
}
public static void HelpBox_Layout(string message, MessageType type, UnityEngine.Object context = null, bool logToConsole = false)
{
EditorGUILayout.HelpBox(message, type);
if (logToConsole)
{
DebugLogMessage(message, type, context);
}
}
public static bool Field_Layout(object value, string label)
{
using (new EditorGUI.DisabledScope(disabled: true))
{
bool isDrawn = true;
Type valueType = value.GetType();
if (valueType == typeof(bool))
{
EditorGUILayout.Toggle(label, (bool)value);
}
else if (valueType == typeof(int))
{
EditorGUILayout.IntField(label, (int)value);
}
else if (valueType == typeof(long))
{
EditorGUILayout.LongField(label, (long)value);
}
else if (valueType == typeof(float))
{
EditorGUILayout.FloatField(label, (float)value);
}
else if (valueType == typeof(double))
{
EditorGUILayout.DoubleField(label, (double)value);
}
else if (valueType == typeof(string))
{
EditorGUILayout.TextField(label, (string)value);
}
else if (valueType == typeof(Vector2))
{
EditorGUILayout.Vector2Field(label, (Vector2)value);
}
else if (valueType == typeof(Vector3))
{
EditorGUILayout.Vector3Field(label, (Vector3)value);
}
else if (valueType == typeof(Vector4))
{
EditorGUILayout.Vector4Field(label, (Vector4)value);
}
else if (valueType == typeof(Color))
{
EditorGUILayout.ColorField(label, (Color)value);
}
else if (valueType == typeof(Bounds))
{
EditorGUILayout.BoundsField(label, (Bounds)value);
}
else if (valueType == typeof(Rect))
{
EditorGUILayout.RectField(label, (Rect)value);
}
else if (typeof(UnityEngine.Object).IsAssignableFrom(valueType))
{
EditorGUILayout.ObjectField(label, (UnityEngine.Object)value, valueType, true);
}
else if (valueType.BaseType == typeof(Enum))
{
EditorGUILayout.EnumPopup(label, (Enum)value);
}
else if (valueType.BaseType == typeof(System.Reflection.TypeInfo))
{
EditorGUILayout.TextField(label, value.ToString());
}
else
{
isDrawn = false;
}
return isDrawn;
}
}
private static void DebugLogMessage(string message, MessageType type, UnityEngine.Object context)
{
switch (type)
{
case MessageType.None:
case MessageType.Info:
Debug.Log(message, context);
break;
case MessageType.Warning:
Debug.LogWarning(message, context);
break;
case MessageType.Error:
Debug.LogError(message, context);
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 55f83b82084f6214f8ddd26c59447b4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,299 @@
using UnityEditor;
using System.Reflection;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ExternPropertyAttributes.Editor
{
public static class PropertyUtility
{
public static T GetAttribute<T>(SerializedProperty property) where T : class
{
T[] attributes = GetAttributes<T>(property);
return (attributes.Length > 0) ? attributes[0] : null;
}
public static T[] GetAttributes<T>(SerializedProperty property) where T : class
{
FieldInfo fieldInfo = ReflectionUtility.GetField(GetTargetObjectWithProperty(property), property.name);
if (fieldInfo == null)
{
return new T[] { };
}
return (T[])fieldInfo.GetCustomAttributes(typeof(T), true);
}
public static GUIContent GetLabel(SerializedProperty property)
{
LabelAttribute labelAttribute = GetAttribute<LabelAttribute>(property);
string labelText = (labelAttribute == null)
? property.displayName
: labelAttribute.Label;
GUIContent label = new GUIContent(labelText);
return label;
}
public static void CallOnValueChangedCallbacks(SerializedProperty property)
{
OnValueChangedAttribute[] onValueChangedAttributes = GetAttributes<OnValueChangedAttribute>(property);
if (onValueChangedAttributes.Length == 0)
{
return;
}
object target = GetTargetObjectWithProperty(property);
property.serializedObject.ApplyModifiedProperties(); // We must apply modifications so that the new value is updated in the serialized object
foreach (var onValueChangedAttribute in onValueChangedAttributes)
{
MethodInfo callbackMethod = ReflectionUtility.GetMethod(target, onValueChangedAttribute.CallbackName);
if (callbackMethod != null &&
callbackMethod.ReturnType == typeof(void) &&
callbackMethod.GetParameters().Length == 0)
{
callbackMethod.Invoke(target, new object[] { });
}
else
{
string warning = string.Format(
"{0} can invoke only methods with 'void' return type and 0 parameters",
onValueChangedAttribute.GetType().Name);
Debug.LogWarning(warning, property.serializedObject.targetObject);
}
}
}
public static bool IsEnabled(SerializedProperty property)
{
EnableIfAttributeBase enableIfAttribute = GetAttribute<EnableIfAttributeBase>(property);
if (enableIfAttribute == null)
{
return true;
}
object target = GetTargetObjectWithProperty(property);
List<bool> conditionValues = GetConditionValues(target, enableIfAttribute.Conditions);
if (conditionValues.Count > 0)
{
bool enabled = GetConditionsFlag(conditionValues, enableIfAttribute.ConditionOperator, enableIfAttribute.Inverted);
return enabled;
}
else
{
string message = enableIfAttribute.GetType().Name + " needs a valid boolean condition field, property or method name to work";
Debug.LogWarning(message, property.serializedObject.targetObject);
return false;
}
}
public static bool IsVisible(SerializedProperty property)
{
ShowIfAttributeBase showIfAttribute = GetAttribute<ShowIfAttributeBase>(property);
if (showIfAttribute == null)
{
return true;
}
object target = GetTargetObjectWithProperty(property);
List<bool> conditionValues = GetConditionValues(target, showIfAttribute.Conditions);
if (conditionValues.Count > 0)
{
bool enabled = GetConditionsFlag(conditionValues, showIfAttribute.ConditionOperator, showIfAttribute.Inverted);
return enabled;
}
else
{
string message = showIfAttribute.GetType().Name + " needs a valid boolean condition field, property or method name to work";
Debug.LogWarning(message, property.serializedObject.targetObject);
return false;
}
}
internal static List<bool> GetConditionValues(object target, string[] conditions)
{
List<bool> conditionValues = new List<bool>();
foreach (var condition in conditions)
{
FieldInfo conditionField = ReflectionUtility.GetField(target, condition);
if (conditionField != null &&
conditionField.FieldType == typeof(bool))
{
conditionValues.Add((bool)conditionField.GetValue(target));
}
PropertyInfo conditionProperty = ReflectionUtility.GetProperty(target, condition);
if (conditionProperty != null &&
conditionProperty.PropertyType == typeof(bool))
{
conditionValues.Add((bool)conditionProperty.GetValue(target));
}
MethodInfo conditionMethod = ReflectionUtility.GetMethod(target, condition);
if (conditionMethod != null &&
conditionMethod.ReturnType == typeof(bool) &&
conditionMethod.GetParameters().Length == 0)
{
conditionValues.Add((bool)conditionMethod.Invoke(target, null));
}
}
return conditionValues;
}
internal static bool GetConditionsFlag(List<bool> conditionValues, EConditionOperator conditionOperator, bool invert)
{
bool flag;
if (conditionOperator == EConditionOperator.And)
{
flag = true;
foreach (var value in conditionValues)
{
flag = flag && value;
}
}
else
{
flag = false;
foreach (var value in conditionValues)
{
flag = flag || value;
}
}
if (invert)
{
flag = !flag;
}
return flag;
}
public static Type GetPropertyType(SerializedProperty property)
{
Type parentType = GetTargetObjectWithProperty(property).GetType();
FieldInfo fieldInfo = parentType.GetField(property.name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
return fieldInfo.FieldType;
}
/// <summary>
/// Gets the object the property represents.
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
public static object GetTargetObjectOfProperty(SerializedProperty property)
{
if (property == null)
{
return null;
}
string path = property.propertyPath.Replace(".Array.data[", "[");
object obj = property.serializedObject.targetObject;
string[] elements = path.Split('.');
foreach (var element in elements)
{
if (element.Contains("["))
{
string elementName = element.Substring(0, element.IndexOf("["));
int index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
obj = GetValue_Imp(obj, elementName, index);
}
else
{
obj = GetValue_Imp(obj, element);
}
}
return obj;
}
/// <summary>
/// Gets the object that the property is a member of
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
public static object GetTargetObjectWithProperty(SerializedProperty property)
{
string path = property.propertyPath.Replace(".Array.data[", "[");
object obj = property.serializedObject.targetObject;
string[] elements = path.Split('.');
for (int i = 0; i < elements.Length - 1; i++)
{
string element = elements[i];
if (element.Contains("["))
{
string elementName = element.Substring(0, element.IndexOf("["));
int index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
obj = GetValue_Imp(obj, elementName, index);
}
else
{
obj = GetValue_Imp(obj, element);
}
}
return obj;
}
private static object GetValue_Imp(object source, string name)
{
if (source == null)
{
return null;
}
Type type = source.GetType();
while (type != null)
{
FieldInfo field = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (field != null)
{
return field.GetValue(source);
}
PropertyInfo property = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property != null)
{
return property.GetValue(source, null);
}
type = type.BaseType;
}
return null;
}
private static object GetValue_Imp(object source, string name, int index)
{
IEnumerable enumerable = GetValue_Imp(source, name) as IEnumerable;
if (enumerable == null)
{
return null;
}
IEnumerator enumerator = enumerable.GetEnumerator();
for (int i = 0; i <= index; i++)
{
if (!enumerator.MoveNext())
{
return null;
}
}
return enumerator.Current;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2718d2e5830b4fd4d9df0918e13e139c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace ExternPropertyAttributes.Editor
{
public static class ReflectionUtility
{
public static IEnumerable<FieldInfo> GetAllFields(object target, Func<FieldInfo, bool> predicate)
{
if (target == null)
{
// Debug.LogError("The target object is null. Check for missing scripts.");
yield break;
}
List<Type> types = new List<Type>()
{
target.GetType()
};
while (types.Last().BaseType != null)
{
types.Add(types.Last().BaseType);
}
for (int i = types.Count - 1; i >= 0; i--)
{
IEnumerable<FieldInfo> fieldInfos = types[i]
.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(predicate);
foreach (var fieldInfo in fieldInfos)
{
yield return fieldInfo;
}
}
}
public static IEnumerable<PropertyInfo> GetAllProperties(object target, Func<PropertyInfo, bool> predicate)
{
if (target == null)
{
Debug.LogError("The target object is null. Check for missing scripts.");
yield break;
}
List<Type> types = new List<Type>()
{
target.GetType()
};
while (types.Last().BaseType != null)
{
types.Add(types.Last().BaseType);
}
for (int i = types.Count - 1; i >= 0; i--)
{
IEnumerable<PropertyInfo> propertyInfos = types[i]
.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(predicate);
foreach (var propertyInfo in propertyInfos)
{
yield return propertyInfo;
}
}
}
public static IEnumerable<MethodInfo> GetAllMethods(object target, Func<MethodInfo, bool> predicate)
{
if (target == null)
{
Debug.LogError("The target object is null. Check for missing scripts.");
return null;
}
IEnumerable<MethodInfo> methodInfos = target.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
.Where(predicate);
return methodInfos;
}
public static FieldInfo GetField(object target, string fieldName)
{
return GetAllFields(target, f => f.Name.Equals(fieldName, StringComparison.InvariantCulture)).FirstOrDefault();
}
public static PropertyInfo GetProperty(object target, string propertyName)
{
return GetAllProperties(target, p => p.Name.Equals(propertyName, StringComparison.InvariantCulture)).FirstOrDefault();
}
public static MethodInfo GetMethod(object target, string methodName)
{
return GetAllMethods(target, m => m.Name.Equals(methodName, StringComparison.InvariantCulture)).FirstOrDefault();
}
public static Type GetListElementType(Type listType)
{
if (listType.IsGenericType)
{
return listType.GetGenericArguments()[0];
}
else
{
return listType.GetElementType();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6281ad38a9a903f4a89403d32cbbcb7f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using UnityEditor;
namespace ExternPropertyAttributes.Editor
{
internal class SavedBool
{
private bool _value;
private string _name;
public bool Value
{
get
{
return _value;
}
set
{
if (_value == value)
{
return;
}
_value = value;
EditorPrefs.SetBool("Ext_" + _name, value);
}
}
public SavedBool(string name, bool value)
{
_name = name;
_value = EditorPrefs.GetBool("Ext_" + name, value);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b611b2bf7ad6614fa6659a9742a0b5e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: