InspectorDisplayDrawer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. using System;
  2. using System.Reflection;
  3. using UnityEngine;
  4. using System.Text.RegularExpressions;
  5. using System.Collections;
  6. using System.Linq;
  7. #if UNITY_EDITOR
  8. using UnityEditor;
  9. #endif
  10. namespace UniRx
  11. {
  12. [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
  13. public class InspectorDisplayAttribute : PropertyAttribute
  14. {
  15. public string FieldName { get; private set; }
  16. public bool NotifyPropertyChanged { get; private set; }
  17. public InspectorDisplayAttribute(string fieldName = "value", bool notifyPropertyChanged = true)
  18. {
  19. FieldName = fieldName;
  20. NotifyPropertyChanged = notifyPropertyChanged;
  21. }
  22. }
  23. /// <summary>
  24. /// Enables multiline input field for StringReactiveProperty. Default line is 3.
  25. /// </summary>
  26. [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
  27. public class MultilineReactivePropertyAttribute : PropertyAttribute
  28. {
  29. public int Lines { get; private set; }
  30. public MultilineReactivePropertyAttribute()
  31. {
  32. Lines = 3;
  33. }
  34. public MultilineReactivePropertyAttribute(int lines)
  35. {
  36. this.Lines = lines;
  37. }
  38. }
  39. /// <summary>
  40. /// Enables range input field for Int/FloatReactiveProperty.
  41. /// </summary>
  42. [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
  43. public class RangeReactivePropertyAttribute : PropertyAttribute
  44. {
  45. public float Min { get; private set; }
  46. public float Max { get; private set; }
  47. public RangeReactivePropertyAttribute(float min, float max)
  48. {
  49. this.Min = min;
  50. this.Max = max;
  51. }
  52. }
  53. #if UNITY_EDITOR
  54. // InspectorDisplay and for Specialized ReactiveProperty
  55. // If you want to customize other specialized ReactiveProperty
  56. // [UnityEditor.CustomPropertyDrawer(typeof(YourSpecializedReactiveProperty))]
  57. // public class ExtendInspectorDisplayDrawer : InspectorDisplayDrawer { }
  58. [UnityEditor.CustomPropertyDrawer(typeof(InspectorDisplayAttribute))]
  59. [UnityEditor.CustomPropertyDrawer(typeof(IntReactiveProperty))]
  60. [UnityEditor.CustomPropertyDrawer(typeof(LongReactiveProperty))]
  61. [UnityEditor.CustomPropertyDrawer(typeof(ByteReactiveProperty))]
  62. [UnityEditor.CustomPropertyDrawer(typeof(FloatReactiveProperty))]
  63. [UnityEditor.CustomPropertyDrawer(typeof(DoubleReactiveProperty))]
  64. [UnityEditor.CustomPropertyDrawer(typeof(StringReactiveProperty))]
  65. [UnityEditor.CustomPropertyDrawer(typeof(BoolReactiveProperty))]
  66. [UnityEditor.CustomPropertyDrawer(typeof(Vector2ReactiveProperty))]
  67. [UnityEditor.CustomPropertyDrawer(typeof(Vector3ReactiveProperty))]
  68. [UnityEditor.CustomPropertyDrawer(typeof(Vector4ReactiveProperty))]
  69. [UnityEditor.CustomPropertyDrawer(typeof(ColorReactiveProperty))]
  70. [UnityEditor.CustomPropertyDrawer(typeof(RectReactiveProperty))]
  71. [UnityEditor.CustomPropertyDrawer(typeof(AnimationCurveReactiveProperty))]
  72. [UnityEditor.CustomPropertyDrawer(typeof(BoundsReactiveProperty))]
  73. [UnityEditor.CustomPropertyDrawer(typeof(QuaternionReactiveProperty))]
  74. public class InspectorDisplayDrawer : UnityEditor.PropertyDrawer
  75. {
  76. public override void OnGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label)
  77. {
  78. string fieldName;
  79. bool notifyPropertyChanged;
  80. {
  81. var attr = this.attribute as InspectorDisplayAttribute;
  82. fieldName = (attr == null) ? "value" : attr.FieldName;
  83. notifyPropertyChanged = (attr == null) ? true : attr.NotifyPropertyChanged;
  84. }
  85. if (notifyPropertyChanged)
  86. {
  87. EditorGUI.BeginChangeCheck();
  88. }
  89. var targetSerializedProperty = property.FindPropertyRelative(fieldName);
  90. if (targetSerializedProperty == null)
  91. {
  92. UnityEditor.EditorGUI.LabelField(position, label, new GUIContent() { text = "InspectorDisplay can't find target:" + fieldName });
  93. if (notifyPropertyChanged)
  94. {
  95. EditorGUI.EndChangeCheck();
  96. }
  97. return;
  98. }
  99. else
  100. {
  101. EmitPropertyField(position, targetSerializedProperty, label);
  102. }
  103. if (notifyPropertyChanged)
  104. {
  105. if (EditorGUI.EndChangeCheck())
  106. {
  107. property.serializedObject.ApplyModifiedProperties(); // deserialize to field
  108. var paths = property.propertyPath.Split('.'); // X.Y.Z...
  109. var attachedComponent = property.serializedObject.targetObject;
  110. var targetProp = (paths.Length == 1)
  111. ? fieldInfo.GetValue(attachedComponent)
  112. : GetValueRecursive(attachedComponent, 0, paths);
  113. if (targetProp == null) return;
  114. var propInfo = targetProp.GetType().GetProperty(fieldName, BindingFlags.IgnoreCase | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  115. var modifiedValue = propInfo.GetValue(targetProp, null); // retrieve new value
  116. var methodInfo = targetProp.GetType().GetMethod("SetValueAndForceNotify", BindingFlags.IgnoreCase | BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  117. if (methodInfo != null)
  118. {
  119. methodInfo.Invoke(targetProp, new object[] { modifiedValue });
  120. }
  121. }
  122. else
  123. {
  124. property.serializedObject.ApplyModifiedProperties();
  125. }
  126. }
  127. }
  128. object GetValueRecursive(object obj, int index, string[] paths)
  129. {
  130. var path = paths[index];
  131. FieldInfo fldInfo = null;
  132. var type = obj.GetType();
  133. while (fldInfo == null)
  134. {
  135. // attempt to get information about the field
  136. fldInfo = type.GetField(path, BindingFlags.IgnoreCase | BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  137. if (fldInfo != null ||
  138. type.BaseType == null ||
  139. type.BaseType.IsSubclassOf(typeof(ReactiveProperty<>))) break;
  140. // if the field information is missing, it may be in the base class
  141. type = type.BaseType;
  142. }
  143. // If array, path = Array.data[index]
  144. if (fldInfo == null && path == "Array")
  145. {
  146. try
  147. {
  148. path = paths[++index];
  149. var m = Regex.Match(path, @"(.+)\[([0-9]+)*\]");
  150. var arrayIndex = int.Parse(m.Groups[2].Value);
  151. var arrayValue = (obj as System.Collections.IList)[arrayIndex];
  152. if (index < paths.Length - 1)
  153. {
  154. return GetValueRecursive(arrayValue, ++index, paths);
  155. }
  156. else
  157. {
  158. return arrayValue;
  159. }
  160. }
  161. catch
  162. {
  163. Debug.Log("InspectorDisplayDrawer Exception, objType:" + obj.GetType().Name + " path:" + string.Join(", ", paths));
  164. throw;
  165. }
  166. }
  167. else if (fldInfo == null)
  168. {
  169. throw new Exception("Can't decode path, please report to UniRx's GitHub issues:" + string.Join(", ", paths));
  170. }
  171. var v = fldInfo.GetValue(obj);
  172. if (index < paths.Length - 1)
  173. {
  174. return GetValueRecursive(v, ++index, paths);
  175. }
  176. return v;
  177. }
  178. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  179. {
  180. var attr = this.attribute as InspectorDisplayAttribute;
  181. var fieldName = (attr == null) ? "value" : attr.FieldName;
  182. var height = base.GetPropertyHeight(property, label);
  183. var valueProperty = property.FindPropertyRelative(fieldName);
  184. if (valueProperty == null)
  185. {
  186. return height;
  187. }
  188. if (valueProperty.propertyType == SerializedPropertyType.Rect)
  189. {
  190. return height * 2;
  191. }
  192. if (valueProperty.propertyType == SerializedPropertyType.Bounds)
  193. {
  194. return height * 3;
  195. }
  196. if (valueProperty.propertyType == SerializedPropertyType.String)
  197. {
  198. var multilineAttr = GetMultilineAttribute();
  199. if (multilineAttr != null)
  200. {
  201. return ((!EditorGUIUtility.wideMode) ? 16f : 0f) + 16f + (float)((multilineAttr.Lines - 1) * 13);
  202. };
  203. }
  204. if (valueProperty.isExpanded)
  205. {
  206. var count = 0;
  207. var e = valueProperty.GetEnumerator();
  208. while (e.MoveNext()) count++;
  209. return ((height + 4) * count) + 6; // (Line = 20 + Padding) ?
  210. }
  211. return height;
  212. }
  213. protected virtual void EmitPropertyField(Rect position, UnityEditor.SerializedProperty targetSerializedProperty, GUIContent label)
  214. {
  215. var multiline = GetMultilineAttribute();
  216. if (multiline == null)
  217. {
  218. var range = GetRangeAttribute();
  219. if (range == null)
  220. {
  221. UnityEditor.EditorGUI.PropertyField(position, targetSerializedProperty, label, includeChildren: true);
  222. }
  223. else
  224. {
  225. if (targetSerializedProperty.propertyType == SerializedPropertyType.Float)
  226. {
  227. EditorGUI.Slider(position, targetSerializedProperty, range.Min, range.Max, label);
  228. }
  229. else if (targetSerializedProperty.propertyType == SerializedPropertyType.Integer)
  230. {
  231. EditorGUI.IntSlider(position, targetSerializedProperty, (int)range.Min, (int)range.Max, label);
  232. }
  233. else
  234. {
  235. EditorGUI.LabelField(position, label.text, "Use Range with float or int.");
  236. }
  237. }
  238. }
  239. else
  240. {
  241. var property = targetSerializedProperty;
  242. label = EditorGUI.BeginProperty(position, label, property);
  243. var method = typeof(EditorGUI).GetMethod("MultiFieldPrefixLabel", BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.NonPublic);
  244. position = (Rect)method.Invoke(null, new object[] { position, 0, label, 1 });
  245. EditorGUI.BeginChangeCheck();
  246. int indentLevel = EditorGUI.indentLevel;
  247. EditorGUI.indentLevel = 0;
  248. var stringValue = EditorGUI.TextArea(position, property.stringValue);
  249. EditorGUI.indentLevel = indentLevel;
  250. if (EditorGUI.EndChangeCheck())
  251. {
  252. property.stringValue = stringValue;
  253. }
  254. EditorGUI.EndProperty();
  255. }
  256. }
  257. MultilineReactivePropertyAttribute GetMultilineAttribute()
  258. {
  259. var fi = this.fieldInfo;
  260. if (fi == null) return null;
  261. return fi.GetCustomAttributes(false).OfType<MultilineReactivePropertyAttribute>().FirstOrDefault();
  262. }
  263. RangeReactivePropertyAttribute GetRangeAttribute()
  264. {
  265. var fi = this.fieldInfo;
  266. if (fi == null) return null;
  267. return fi.GetCustomAttributes(false).OfType<RangeReactivePropertyAttribute>().FirstOrDefault();
  268. }
  269. }
  270. #endif
  271. }