You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
12 KiB
306 lines
12 KiB
11 months ago
|
using System;
|
||
|
using System.Reflection;
|
||
|
using UnityEngine;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using System.Collections;
|
||
|
using System.Linq;
|
||
|
|
||
|
#if UNITY_EDITOR
|
||
|
using UnityEditor;
|
||
|
#endif
|
||
|
|
||
|
namespace UniRx
|
||
|
{
|
||
|
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
|
||
|
public class InspectorDisplayAttribute : PropertyAttribute
|
||
|
{
|
||
|
public string FieldName { get; private set; }
|
||
|
public bool NotifyPropertyChanged { get; private set; }
|
||
|
|
||
|
public InspectorDisplayAttribute(string fieldName = "value", bool notifyPropertyChanged = true)
|
||
|
{
|
||
|
FieldName = fieldName;
|
||
|
NotifyPropertyChanged = notifyPropertyChanged;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Enables multiline input field for StringReactiveProperty. Default line is 3.
|
||
|
/// </summary>
|
||
|
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
|
||
|
public class MultilineReactivePropertyAttribute : PropertyAttribute
|
||
|
{
|
||
|
public int Lines { get; private set; }
|
||
|
|
||
|
public MultilineReactivePropertyAttribute()
|
||
|
{
|
||
|
Lines = 3;
|
||
|
}
|
||
|
|
||
|
public MultilineReactivePropertyAttribute(int lines)
|
||
|
{
|
||
|
this.Lines = lines;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Enables range input field for Int/FloatReactiveProperty.
|
||
|
/// </summary>
|
||
|
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
|
||
|
public class RangeReactivePropertyAttribute : PropertyAttribute
|
||
|
{
|
||
|
public float Min { get; private set; }
|
||
|
public float Max { get; private set; }
|
||
|
|
||
|
public RangeReactivePropertyAttribute(float min, float max)
|
||
|
{
|
||
|
this.Min = min;
|
||
|
this.Max = max;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if UNITY_EDITOR
|
||
|
|
||
|
|
||
|
// InspectorDisplay and for Specialized ReactiveProperty
|
||
|
// If you want to customize other specialized ReactiveProperty
|
||
|
// [UnityEditor.CustomPropertyDrawer(typeof(YourSpecializedReactiveProperty))]
|
||
|
// public class ExtendInspectorDisplayDrawer : InspectorDisplayDrawer { }
|
||
|
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(InspectorDisplayAttribute))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(IntReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(LongReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(ByteReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(FloatReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(DoubleReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(StringReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(BoolReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(Vector2ReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(Vector3ReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(Vector4ReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(ColorReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(RectReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(AnimationCurveReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(BoundsReactiveProperty))]
|
||
|
[UnityEditor.CustomPropertyDrawer(typeof(QuaternionReactiveProperty))]
|
||
|
public class InspectorDisplayDrawer : UnityEditor.PropertyDrawer
|
||
|
{
|
||
|
public override void OnGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label)
|
||
|
{
|
||
|
string fieldName;
|
||
|
bool notifyPropertyChanged;
|
||
|
{
|
||
|
var attr = this.attribute as InspectorDisplayAttribute;
|
||
|
fieldName = (attr == null) ? "value" : attr.FieldName;
|
||
|
notifyPropertyChanged = (attr == null) ? true : attr.NotifyPropertyChanged;
|
||
|
}
|
||
|
|
||
|
if (notifyPropertyChanged)
|
||
|
{
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
}
|
||
|
var targetSerializedProperty = property.FindPropertyRelative(fieldName);
|
||
|
if (targetSerializedProperty == null)
|
||
|
{
|
||
|
UnityEditor.EditorGUI.LabelField(position, label, new GUIContent() { text = "InspectorDisplay can't find target:" + fieldName });
|
||
|
if (notifyPropertyChanged)
|
||
|
{
|
||
|
EditorGUI.EndChangeCheck();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
EmitPropertyField(position, targetSerializedProperty, label);
|
||
|
}
|
||
|
|
||
|
if (notifyPropertyChanged)
|
||
|
{
|
||
|
if (EditorGUI.EndChangeCheck())
|
||
|
{
|
||
|
property.serializedObject.ApplyModifiedProperties(); // deserialize to field
|
||
|
|
||
|
var paths = property.propertyPath.Split('.'); // X.Y.Z...
|
||
|
var attachedComponent = property.serializedObject.targetObject;
|
||
|
|
||
|
var targetProp = (paths.Length == 1)
|
||
|
? fieldInfo.GetValue(attachedComponent)
|
||
|
: GetValueRecursive(attachedComponent, 0, paths);
|
||
|
if (targetProp == null) return;
|
||
|
var propInfo = targetProp.GetType().GetProperty(fieldName, BindingFlags.IgnoreCase | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
|
var modifiedValue = propInfo.GetValue(targetProp, null); // retrieve new value
|
||
|
|
||
|
var methodInfo = targetProp.GetType().GetMethod("SetValueAndForceNotify", BindingFlags.IgnoreCase | BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
|
if (methodInfo != null)
|
||
|
{
|
||
|
methodInfo.Invoke(targetProp, new object[] { modifiedValue });
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
property.serializedObject.ApplyModifiedProperties();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
object GetValueRecursive(object obj, int index, string[] paths)
|
||
|
{
|
||
|
var path = paths[index];
|
||
|
|
||
|
FieldInfo fldInfo = null;
|
||
|
var type = obj.GetType();
|
||
|
while (fldInfo == null)
|
||
|
{
|
||
|
// attempt to get information about the field
|
||
|
fldInfo = type.GetField(path, BindingFlags.IgnoreCase | BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
|
|
||
|
if (fldInfo != null ||
|
||
|
type.BaseType == null ||
|
||
|
type.BaseType.IsSubclassOf(typeof(ReactiveProperty<>))) break;
|
||
|
|
||
|
// if the field information is missing, it may be in the base class
|
||
|
type = type.BaseType;
|
||
|
}
|
||
|
|
||
|
// If array, path = Array.data[index]
|
||
|
if (fldInfo == null && path == "Array")
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
path = paths[++index];
|
||
|
var m = Regex.Match(path, @"(.+)\[([0-9]+)*\]");
|
||
|
var arrayIndex = int.Parse(m.Groups[2].Value);
|
||
|
var arrayValue = (obj as System.Collections.IList)[arrayIndex];
|
||
|
if (index < paths.Length - 1)
|
||
|
{
|
||
|
return GetValueRecursive(arrayValue, ++index, paths);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return arrayValue;
|
||
|
}
|
||
|
}
|
||
|
catch
|
||
|
{
|
||
|
Debug.Log("InspectorDisplayDrawer Exception, objType:" + obj.GetType().Name + " path:" + string.Join(", ", paths));
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
else if (fldInfo == null)
|
||
|
{
|
||
|
throw new Exception("Can't decode path, please report to UniRx's GitHub issues:" + string.Join(", ", paths));
|
||
|
}
|
||
|
|
||
|
var v = fldInfo.GetValue(obj);
|
||
|
if (index < paths.Length - 1)
|
||
|
{
|
||
|
return GetValueRecursive(v, ++index, paths);
|
||
|
}
|
||
|
|
||
|
return v;
|
||
|
}
|
||
|
|
||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||
|
{
|
||
|
var attr = this.attribute as InspectorDisplayAttribute;
|
||
|
var fieldName = (attr == null) ? "value" : attr.FieldName;
|
||
|
|
||
|
var height = base.GetPropertyHeight(property, label);
|
||
|
var valueProperty = property.FindPropertyRelative(fieldName);
|
||
|
if (valueProperty == null)
|
||
|
{
|
||
|
return height;
|
||
|
}
|
||
|
|
||
|
if (valueProperty.propertyType == SerializedPropertyType.Rect)
|
||
|
{
|
||
|
return height * 2;
|
||
|
}
|
||
|
if (valueProperty.propertyType == SerializedPropertyType.Bounds)
|
||
|
{
|
||
|
return height * 3;
|
||
|
}
|
||
|
if (valueProperty.propertyType == SerializedPropertyType.String)
|
||
|
{
|
||
|
var multilineAttr = GetMultilineAttribute();
|
||
|
if (multilineAttr != null)
|
||
|
{
|
||
|
return ((!EditorGUIUtility.wideMode) ? 16f : 0f) + 16f + (float)((multilineAttr.Lines - 1) * 13);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (valueProperty.isExpanded)
|
||
|
{
|
||
|
var count = 0;
|
||
|
var e = valueProperty.GetEnumerator();
|
||
|
while (e.MoveNext()) count++;
|
||
|
return ((height + 4) * count) + 6; // (Line = 20 + Padding) ?
|
||
|
}
|
||
|
|
||
|
return height;
|
||
|
}
|
||
|
|
||
|
protected virtual void EmitPropertyField(Rect position, UnityEditor.SerializedProperty targetSerializedProperty, GUIContent label)
|
||
|
{
|
||
|
var multiline = GetMultilineAttribute();
|
||
|
if (multiline == null)
|
||
|
{
|
||
|
var range = GetRangeAttribute();
|
||
|
if (range == null)
|
||
|
{
|
||
|
UnityEditor.EditorGUI.PropertyField(position, targetSerializedProperty, label, includeChildren: true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (targetSerializedProperty.propertyType == SerializedPropertyType.Float)
|
||
|
{
|
||
|
EditorGUI.Slider(position, targetSerializedProperty, range.Min, range.Max, label);
|
||
|
}
|
||
|
else if (targetSerializedProperty.propertyType == SerializedPropertyType.Integer)
|
||
|
{
|
||
|
EditorGUI.IntSlider(position, targetSerializedProperty, (int)range.Min, (int)range.Max, label);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
EditorGUI.LabelField(position, label.text, "Use Range with float or int.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var property = targetSerializedProperty;
|
||
|
|
||
|
label = EditorGUI.BeginProperty(position, label, property);
|
||
|
var method = typeof(EditorGUI).GetMethod("MultiFieldPrefixLabel", BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.NonPublic);
|
||
|
position = (Rect)method.Invoke(null, new object[] { position, 0, label, 1 });
|
||
|
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
int indentLevel = EditorGUI.indentLevel;
|
||
|
EditorGUI.indentLevel = 0;
|
||
|
var stringValue = EditorGUI.TextArea(position, property.stringValue);
|
||
|
EditorGUI.indentLevel = indentLevel;
|
||
|
if (EditorGUI.EndChangeCheck())
|
||
|
{
|
||
|
property.stringValue = stringValue;
|
||
|
}
|
||
|
EditorGUI.EndProperty();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
MultilineReactivePropertyAttribute GetMultilineAttribute()
|
||
|
{
|
||
|
var fi = this.fieldInfo;
|
||
|
if (fi == null) return null;
|
||
|
return fi.GetCustomAttributes(false).OfType<MultilineReactivePropertyAttribute>().FirstOrDefault();
|
||
|
}
|
||
|
|
||
|
RangeReactivePropertyAttribute GetRangeAttribute()
|
||
|
{
|
||
|
var fi = this.fieldInfo;
|
||
|
if (fi == null) return null;
|
||
|
return fi.GetCustomAttributes(false).OfType<RangeReactivePropertyAttribute>().FirstOrDefault();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|