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;
}
}
///
/// Enables multiline input field for StringReactiveProperty. Default line is 3.
///
[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;
}
}
///
/// Enables range input field for Int/FloatReactiveProperty.
///
[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().FirstOrDefault();
}
RangeReactivePropertyAttribute GetRangeAttribute()
{
var fi = this.fieldInfo;
if (fi == null) return null;
return fi.GetCustomAttributes(false).OfType().FirstOrDefault();
}
}
#endif
}