Serialization.cs 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304
  1. // Serialization // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Reflection;
  7. using System.Text;
  8. using UnityEditor;
  9. using UnityEngine;
  10. using Object = UnityEngine.Object;
  11. // Shared File Last Modified: 2021-11-23
  12. namespace Animancer.Editor
  13. // namespace InspectorGadgets.Editor
  14. // namespace UltEvents.Editor
  15. {
  16. /// <summary>The possible states for a function in a <see cref="GenericMenu"/>.</summary>
  17. public enum MenuFunctionState
  18. {
  19. /************************************************************************************************************************/
  20. /// <summary>Displayed normally.</summary>
  21. Normal,
  22. /// <summary>Has a check mark next to it to show that it is selected.</summary>
  23. Selected,
  24. /// <summary>Greyed out and unusable.</summary>
  25. Disabled,
  26. /************************************************************************************************************************/
  27. }
  28. /// <summary>[Editor-Only] Various serialization utilities.</summary>
  29. public static partial class Serialization
  30. {
  31. /************************************************************************************************************************/
  32. #region Public Static API
  33. /************************************************************************************************************************/
  34. /// <summary>The text used in a <see cref="SerializedProperty.propertyPath"/> to denote array elements.</summary>
  35. public const string
  36. ArrayDataPrefix = ".Array.data[",
  37. ArrayDataSuffix = "]";
  38. /// <summary>Bindings for Public and Non-Public Instance members.</summary>
  39. public const BindingFlags
  40. InstanceBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
  41. /************************************************************************************************************************/
  42. /// <summary>Returns a user friendly version of the <see cref="SerializedProperty.propertyPath"/>.</summary>
  43. public static string GetFriendlyPath(this SerializedProperty property)
  44. {
  45. return property.propertyPath.Replace(ArrayDataPrefix, "[");
  46. }
  47. /************************************************************************************************************************/
  48. #region Get Value
  49. /************************************************************************************************************************/
  50. /// <summary>Gets the value of the specified <see cref="SerializedProperty"/>.</summary>
  51. public static object GetValue(this SerializedProperty property, object targetObject)
  52. {
  53. if (property.hasMultipleDifferentValues &&
  54. property.serializedObject.targetObject != targetObject as Object)
  55. {
  56. property = new SerializedObject(targetObject as Object).FindProperty(property.propertyPath);
  57. }
  58. switch (property.propertyType)
  59. {
  60. case SerializedPropertyType.Boolean: return property.boolValue;
  61. case SerializedPropertyType.Float: return property.floatValue;
  62. case SerializedPropertyType.String: return property.stringValue;
  63. case SerializedPropertyType.Integer:
  64. case SerializedPropertyType.Character:
  65. case SerializedPropertyType.LayerMask:
  66. case SerializedPropertyType.ArraySize:
  67. return property.intValue;
  68. case SerializedPropertyType.Vector2: return property.vector2Value;
  69. case SerializedPropertyType.Vector3: return property.vector3Value;
  70. case SerializedPropertyType.Vector4: return property.vector4Value;
  71. case SerializedPropertyType.Quaternion: return property.quaternionValue;
  72. case SerializedPropertyType.Color: return property.colorValue;
  73. case SerializedPropertyType.AnimationCurve: return property.animationCurveValue;
  74. case SerializedPropertyType.Rect: return property.rectValue;
  75. case SerializedPropertyType.Bounds: return property.boundsValue;
  76. case SerializedPropertyType.Vector2Int: return property.vector2IntValue;
  77. case SerializedPropertyType.Vector3Int: return property.vector3IntValue;
  78. case SerializedPropertyType.RectInt: return property.rectIntValue;
  79. case SerializedPropertyType.BoundsInt: return property.boundsIntValue;
  80. case SerializedPropertyType.ObjectReference: return property.objectReferenceValue;
  81. case SerializedPropertyType.ExposedReference: return property.exposedReferenceValue;
  82. case SerializedPropertyType.FixedBufferSize: return property.fixedBufferSize;
  83. case SerializedPropertyType.Gradient: return property.GetGradientValue();
  84. case SerializedPropertyType.Enum:// Would be complex because enumValueIndex can't be cast directly.
  85. case SerializedPropertyType.Generic:
  86. default:
  87. return GetAccessor(property)?.GetValue(targetObject);
  88. }
  89. }
  90. /************************************************************************************************************************/
  91. /// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
  92. public static object GetValue(this SerializedProperty property) => GetValue(property, property.serializedObject.targetObject);
  93. /// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
  94. public static T GetValue<T>(this SerializedProperty property) => (T)GetValue(property);
  95. /// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
  96. public static void GetValue<T>(this SerializedProperty property, out T value) => value = (T)GetValue(property);
  97. /************************************************************************************************************************/
  98. /// <summary>Gets the value of the <see cref="SerializedProperty"/> for each of its target objects.</summary>
  99. public static T[] GetValues<T>(this SerializedProperty property)
  100. {
  101. try
  102. {
  103. var targetObjects = property.serializedObject.targetObjects;
  104. var values = new T[targetObjects.Length];
  105. for (int i = 0; i < values.Length; i++)
  106. {
  107. values[i] = (T)GetValue(property, targetObjects[i]);
  108. }
  109. return values;
  110. }
  111. catch
  112. {
  113. return null;
  114. }
  115. }
  116. /************************************************************************************************************************/
  117. /// <summary>Is the value of the `property` the same as the default serialized value for its type?</summary>
  118. public static bool IsDefaultValueByType(SerializedProperty property)
  119. {
  120. if (property.hasMultipleDifferentValues)
  121. return false;
  122. switch (property.propertyType)
  123. {
  124. case SerializedPropertyType.Boolean: return property.boolValue == default;
  125. case SerializedPropertyType.Float: return property.floatValue == default;
  126. case SerializedPropertyType.String: return property.stringValue == "";
  127. case SerializedPropertyType.Integer:
  128. case SerializedPropertyType.Character:
  129. case SerializedPropertyType.LayerMask:
  130. case SerializedPropertyType.ArraySize:
  131. return property.intValue == default;
  132. case SerializedPropertyType.Vector2: return property.vector2Value.Equals(default);
  133. case SerializedPropertyType.Vector3: return property.vector3Value.Equals(default);
  134. case SerializedPropertyType.Vector4: return property.vector4Value.Equals(default);
  135. case SerializedPropertyType.Quaternion: return property.quaternionValue.Equals(default);
  136. case SerializedPropertyType.Color: return property.colorValue.Equals(default);
  137. case SerializedPropertyType.AnimationCurve: return property.animationCurveValue == default;
  138. case SerializedPropertyType.Rect: return property.rectValue == default;
  139. case SerializedPropertyType.Bounds: return property.boundsValue == default;
  140. case SerializedPropertyType.Vector2Int: return property.vector2IntValue == default;
  141. case SerializedPropertyType.Vector3Int: return property.vector3IntValue == default;
  142. case SerializedPropertyType.RectInt: return property.rectIntValue.Equals(default);
  143. case SerializedPropertyType.BoundsInt: return property.boundsIntValue == default;
  144. case SerializedPropertyType.ObjectReference: return property.objectReferenceValue == default;
  145. case SerializedPropertyType.ExposedReference: return property.exposedReferenceValue == default;
  146. case SerializedPropertyType.FixedBufferSize: return property.fixedBufferSize == default;
  147. case SerializedPropertyType.Enum: return property.enumValueIndex == default;
  148. case SerializedPropertyType.Gradient:
  149. case SerializedPropertyType.Generic:
  150. default:
  151. if (property.isArray)
  152. return property.arraySize == default;
  153. var depth = property.depth;
  154. property = property.Copy();
  155. var enterChildren = true;
  156. while (property.Next(enterChildren) && property.depth > depth)
  157. {
  158. enterChildren = false;
  159. if (!IsDefaultValueByType(property))
  160. return false;
  161. }
  162. return true;
  163. }
  164. }
  165. /************************************************************************************************************************/
  166. #endregion
  167. /************************************************************************************************************************/
  168. #region Set Value
  169. /************************************************************************************************************************/
  170. /// <summary>Sets the value of the specified <see cref="SerializedProperty"/>.</summary>
  171. public static void SetValue(this SerializedProperty property, object targetObject, object value)
  172. {
  173. switch (property.propertyType)
  174. {
  175. case SerializedPropertyType.Boolean: property.boolValue = (bool)value; break;
  176. case SerializedPropertyType.Float: property.floatValue = (float)value; break;
  177. case SerializedPropertyType.String: property.stringValue = (string)value; break;
  178. case SerializedPropertyType.Integer:
  179. case SerializedPropertyType.Character:
  180. case SerializedPropertyType.LayerMask:
  181. case SerializedPropertyType.ArraySize:
  182. property.intValue = (int)value; break;
  183. case SerializedPropertyType.Vector2: property.vector2Value = (Vector2)value; break;
  184. case SerializedPropertyType.Vector3: property.vector3Value = (Vector3)value; break;
  185. case SerializedPropertyType.Vector4: property.vector4Value = (Vector4)value; break;
  186. case SerializedPropertyType.Quaternion: property.quaternionValue = (Quaternion)value; break;
  187. case SerializedPropertyType.Color: property.colorValue = (Color)value; break;
  188. case SerializedPropertyType.AnimationCurve: property.animationCurveValue = (AnimationCurve)value; break;
  189. case SerializedPropertyType.Rect: property.rectValue = (Rect)value; break;
  190. case SerializedPropertyType.Bounds: property.boundsValue = (Bounds)value; break;
  191. case SerializedPropertyType.Vector2Int: property.vector2IntValue = (Vector2Int)value; break;
  192. case SerializedPropertyType.Vector3Int: property.vector3IntValue = (Vector3Int)value; break;
  193. case SerializedPropertyType.RectInt: property.rectIntValue = (RectInt)value; break;
  194. case SerializedPropertyType.BoundsInt: property.boundsIntValue = (BoundsInt)value; break;
  195. case SerializedPropertyType.ObjectReference: property.objectReferenceValue = (Object)value; break;
  196. case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = (Object)value; break;
  197. case SerializedPropertyType.FixedBufferSize:
  198. throw new InvalidOperationException($"{nameof(SetValue)} failed:" +
  199. $" {nameof(SerializedProperty)}.{nameof(SerializedProperty.fixedBufferSize)} is read-only.");
  200. case SerializedPropertyType.Gradient: property.SetGradientValue((Gradient)value); break;
  201. case SerializedPropertyType.Enum:// Would be complex because enumValueIndex can't be cast directly.
  202. case SerializedPropertyType.Generic:
  203. default:
  204. var accessor = GetAccessor(property);
  205. if (accessor != null)
  206. accessor.SetValue(targetObject, value);
  207. break;
  208. }
  209. }
  210. /************************************************************************************************************************/
  211. /// <summary>Sets the value of the <see cref="SerializedProperty"/>.</summary>
  212. public static void SetValue(this SerializedProperty property, object value)
  213. {
  214. switch (property.propertyType)
  215. {
  216. case SerializedPropertyType.Boolean: property.boolValue = (bool)value; break;
  217. case SerializedPropertyType.Float: property.floatValue = (float)value; break;
  218. case SerializedPropertyType.Integer: property.intValue = (int)value; break;
  219. case SerializedPropertyType.String: property.stringValue = (string)value; break;
  220. case SerializedPropertyType.Vector2: property.vector2Value = (Vector2)value; break;
  221. case SerializedPropertyType.Vector3: property.vector3Value = (Vector3)value; break;
  222. case SerializedPropertyType.Vector4: property.vector4Value = (Vector4)value; break;
  223. case SerializedPropertyType.Quaternion: property.quaternionValue = (Quaternion)value; break;
  224. case SerializedPropertyType.Color: property.colorValue = (Color)value; break;
  225. case SerializedPropertyType.AnimationCurve: property.animationCurveValue = (AnimationCurve)value; break;
  226. case SerializedPropertyType.Rect: property.rectValue = (Rect)value; break;
  227. case SerializedPropertyType.Bounds: property.boundsValue = (Bounds)value; break;
  228. case SerializedPropertyType.Vector2Int: property.vector2IntValue = (Vector2Int)value; break;
  229. case SerializedPropertyType.Vector3Int: property.vector3IntValue = (Vector3Int)value; break;
  230. case SerializedPropertyType.RectInt: property.rectIntValue = (RectInt)value; break;
  231. case SerializedPropertyType.BoundsInt: property.boundsIntValue = (BoundsInt)value; break;
  232. case SerializedPropertyType.ObjectReference: property.objectReferenceValue = (Object)value; break;
  233. case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = (Object)value; break;
  234. case SerializedPropertyType.ArraySize: property.intValue = (int)value; break;
  235. case SerializedPropertyType.FixedBufferSize:
  236. throw new InvalidOperationException($"{nameof(SetValue)} failed:" +
  237. $" {nameof(SerializedProperty)}.{nameof(SerializedProperty.fixedBufferSize)} is read-only.");
  238. case SerializedPropertyType.Generic:
  239. case SerializedPropertyType.Enum:
  240. case SerializedPropertyType.LayerMask:
  241. case SerializedPropertyType.Gradient:
  242. case SerializedPropertyType.Character:
  243. default:
  244. var accessor = GetAccessor(property);
  245. if (accessor != null)
  246. {
  247. var targets = property.serializedObject.targetObjects;
  248. for (int i = 0; i < targets.Length; i++)
  249. {
  250. accessor.SetValue(targets[i], value);
  251. }
  252. }
  253. break;
  254. }
  255. }
  256. /************************************************************************************************************************/
  257. /// <summary>
  258. /// Resets the value of the <see cref="SerializedProperty"/> to the default value of its type and all its field
  259. /// types, ignoring values set by constructors or field initializers.
  260. /// </summary>
  261. /// <remarks>
  262. /// If you want to run constructors and field initializers, you can call
  263. /// <see cref="PropertyAccessor.ResetValue"/> instead.
  264. /// </remarks>
  265. public static void ResetValue(SerializedProperty property, string undoName = "Inspector")
  266. {
  267. switch (property.propertyType)
  268. {
  269. case SerializedPropertyType.Boolean: property.boolValue = default; break;
  270. case SerializedPropertyType.Float: property.floatValue = default; break;
  271. case SerializedPropertyType.String: property.stringValue = ""; break;
  272. case SerializedPropertyType.Integer:
  273. case SerializedPropertyType.Character:
  274. case SerializedPropertyType.LayerMask:
  275. case SerializedPropertyType.ArraySize:
  276. property.intValue = default;
  277. break;
  278. case SerializedPropertyType.Vector2: property.vector2Value = default; break;
  279. case SerializedPropertyType.Vector3: property.vector3Value = default; break;
  280. case SerializedPropertyType.Vector4: property.vector4Value = default; break;
  281. case SerializedPropertyType.Quaternion: property.quaternionValue = default; break;
  282. case SerializedPropertyType.Color: property.colorValue = default; break;
  283. case SerializedPropertyType.AnimationCurve: property.animationCurveValue = default; break;
  284. case SerializedPropertyType.Rect: property.rectValue = default; break;
  285. case SerializedPropertyType.Bounds: property.boundsValue = default; break;
  286. case SerializedPropertyType.Vector2Int: property.vector2IntValue = default; break;
  287. case SerializedPropertyType.Vector3Int: property.vector3IntValue = default; break;
  288. case SerializedPropertyType.RectInt: property.rectIntValue = default; break;
  289. case SerializedPropertyType.BoundsInt: property.boundsIntValue = default; break;
  290. case SerializedPropertyType.ObjectReference: property.objectReferenceValue = default; break;
  291. case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = default; break;
  292. case SerializedPropertyType.Enum: property.enumValueIndex = default; break;
  293. case SerializedPropertyType.Gradient:
  294. case SerializedPropertyType.FixedBufferSize:
  295. case SerializedPropertyType.Generic:
  296. default:
  297. if (property.isArray)
  298. {
  299. property.arraySize = default;
  300. break;
  301. }
  302. var depth = property.depth;
  303. property = property.Copy();
  304. var enterChildren = true;
  305. while (property.Next(enterChildren) && property.depth > depth)
  306. {
  307. enterChildren = false;
  308. ResetValue(property);
  309. }
  310. break;
  311. }
  312. }
  313. /************************************************************************************************************************/
  314. /// <summary>Copies the value of `from` into `to` (including all nested properties).</summary>
  315. public static float CopyValueFrom(this SerializedProperty to, SerializedProperty from)
  316. {
  317. from = from.Copy();
  318. var fromPath = from.propertyPath;
  319. var pathPrefixLength = fromPath.Length + 1;
  320. var depth = from.depth;
  321. var copyCount = 0;
  322. var totalCount = 0;
  323. StringBuilder issues = null;
  324. do
  325. {
  326. while (from.propertyType == SerializedPropertyType.Generic)
  327. if (!from.Next(true))
  328. goto LogResults;
  329. SerializedProperty toRelative;
  330. var relativePath = from.propertyPath;
  331. if (relativePath.Length <= pathPrefixLength)
  332. {
  333. toRelative = to;
  334. }
  335. else
  336. {
  337. relativePath = relativePath.Substring(pathPrefixLength, relativePath.Length - pathPrefixLength);
  338. toRelative = to.FindPropertyRelative(relativePath);
  339. }
  340. if (!from.hasMultipleDifferentValues &&
  341. toRelative != null &&
  342. toRelative.propertyType == from.propertyType &&
  343. toRelative.type == from.type)
  344. {
  345. // GetValue and SetValue currently access the underlying field for enums, but we need the stored value.
  346. if (toRelative.propertyType == SerializedPropertyType.Enum)
  347. toRelative.enumValueIndex = from.enumValueIndex;
  348. else
  349. toRelative.SetValue(from.GetValue());
  350. copyCount++;
  351. }
  352. else
  353. {
  354. if (issues == null)
  355. issues = new StringBuilder();
  356. issues.AppendLine()
  357. .Append(" - ");
  358. if (from.hasMultipleDifferentValues)
  359. {
  360. issues
  361. .Append("The selected objects have different values for '")
  362. .Append(relativePath)
  363. .Append("'.");
  364. }
  365. else if (toRelative == null)
  366. {
  367. issues
  368. .Append("No property '")
  369. .Append(relativePath)
  370. .Append("' exists relative to '")
  371. .Append(to.propertyPath)
  372. .Append("'.");
  373. }
  374. else if (toRelative.propertyType != from.propertyType)
  375. {
  376. issues
  377. .Append("The type of '")
  378. .Append(toRelative.propertyPath)
  379. .Append("' was '")
  380. .Append(toRelative.propertyType)
  381. .Append("' but should be '")
  382. .Append(from.propertyType)
  383. .Append("'.");
  384. }
  385. else if (toRelative.type != from.type)
  386. {
  387. issues
  388. .Append("The type of '")
  389. .Append(toRelative.propertyPath)
  390. .Append("' was '")
  391. .Append(toRelative.type)
  392. .Append("' but should be '")
  393. .Append(from.type)
  394. .Append("'.");
  395. }
  396. else// This should never happen.
  397. {
  398. issues
  399. .Append(" - Unknown issue with '")
  400. .Append(relativePath)
  401. .Append("'.");
  402. }
  403. }
  404. totalCount++;
  405. }
  406. while (from.Next(false) && from.depth > depth);
  407. LogResults:
  408. if (copyCount < totalCount)
  409. Debug.Log($"Copied {copyCount} / {totalCount} values from '{fromPath}' to '{to.propertyPath}': {issues}");
  410. return (float)copyCount / totalCount;
  411. }
  412. /************************************************************************************************************************/
  413. #endregion
  414. /************************************************************************************************************************/
  415. #region Gradients
  416. /************************************************************************************************************************/
  417. private static PropertyInfo _GradientValue;
  418. /// <summary><c>SerializedProperty.gradientValue</c> is internal.</summary>
  419. private static PropertyInfo GradientValue
  420. {
  421. get
  422. {
  423. if (_GradientValue == null)
  424. _GradientValue = typeof(SerializedProperty).GetProperty("gradientValue", InstanceBindings);
  425. return _GradientValue;
  426. }
  427. }
  428. /// <summary>Gets the <see cref="Gradient"/> value from a <see cref="SerializedPropertyType.Gradient"/>.</summary>
  429. public static Gradient GetGradientValue(this SerializedProperty property) => (Gradient)GradientValue.GetValue(property, null);
  430. /// <summary>Sets the <see cref="Gradient"/> value on a <see cref="SerializedPropertyType.Gradient"/>.</summary>
  431. public static void SetGradientValue(this SerializedProperty property, Gradient value) => GradientValue.SetValue(property, value, null);
  432. /************************************************************************************************************************/
  433. #endregion
  434. /************************************************************************************************************************/
  435. /// <summary>Indicates whether both properties refer to the same underlying field.</summary>
  436. public static bool AreSameProperty(SerializedProperty a, SerializedProperty b)
  437. {
  438. if (a == b)
  439. return true;
  440. if (a == null)
  441. return b == null;
  442. if (b == null)
  443. return false;
  444. if (a.propertyPath != b.propertyPath)
  445. return false;
  446. var aTargets = a.serializedObject.targetObjects;
  447. var bTargets = b.serializedObject.targetObjects;
  448. if (aTargets.Length != bTargets.Length)
  449. return false;
  450. for (int i = 0; i < aTargets.Length; i++)
  451. {
  452. if (aTargets[i] != bTargets[i])
  453. return false;
  454. }
  455. return true;
  456. }
  457. /************************************************************************************************************************/
  458. /// <summary>
  459. /// Executes the `action` once with a new <see cref="SerializedProperty"/> for each of the
  460. /// <see cref="SerializedObject.targetObjects"/>. Or if there is only one target, it uses the `property`.
  461. /// </summary>
  462. public static void ForEachTarget(this SerializedProperty property, Action<SerializedProperty> function,
  463. string undoName = "Inspector")
  464. {
  465. var targets = property.serializedObject.targetObjects;
  466. if (undoName != null)
  467. Undo.RecordObjects(targets, undoName);
  468. if (targets.Length == 1)
  469. {
  470. function(property);
  471. property.serializedObject.ApplyModifiedProperties();
  472. }
  473. else
  474. {
  475. var path = property.propertyPath;
  476. for (int i = 0; i < targets.Length; i++)
  477. {
  478. using (var serializedObject = new SerializedObject(targets[i]))
  479. {
  480. property = serializedObject.FindProperty(path);
  481. function(property);
  482. property.serializedObject.ApplyModifiedProperties();
  483. }
  484. }
  485. }
  486. }
  487. /************************************************************************************************************************/
  488. /// <summary>
  489. /// Adds a menu item to execute the specified `function` for each of the `property`s target objects.
  490. /// </summary>
  491. public static void AddFunction(this GenericMenu menu, string label, MenuFunctionState state, GenericMenu.MenuFunction function)
  492. {
  493. if (state != MenuFunctionState.Disabled)
  494. {
  495. menu.AddItem(new GUIContent(label), state == MenuFunctionState.Selected, function);
  496. }
  497. else
  498. {
  499. menu.AddDisabledItem(new GUIContent(label));
  500. }
  501. }
  502. /// <summary>
  503. /// Adds a menu item to execute the specified `function` for each of the `property`s target objects.
  504. /// </summary>
  505. public static void AddFunction(this GenericMenu menu, string label, bool enabled, GenericMenu.MenuFunction function)
  506. => AddFunction(menu, label, enabled ? MenuFunctionState.Normal : MenuFunctionState.Disabled, function);
  507. /************************************************************************************************************************/
  508. /// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
  509. public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label,
  510. MenuFunctionState state, Action<SerializedProperty> function)
  511. {
  512. if (state != MenuFunctionState.Disabled && GUI.enabled)
  513. {
  514. menu.AddItem(new GUIContent(label), state == MenuFunctionState.Selected, () =>
  515. {
  516. ForEachTarget(property, function);
  517. GUIUtility.keyboardControl = 0;
  518. GUIUtility.hotControl = 0;
  519. EditorGUIUtility.editingTextField = false;
  520. });
  521. }
  522. else
  523. {
  524. menu.AddDisabledItem(new GUIContent(label));
  525. }
  526. }
  527. /// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
  528. public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label, bool enabled,
  529. Action<SerializedProperty> function)
  530. => AddPropertyModifierFunction(menu, property, label, enabled ? MenuFunctionState.Normal : MenuFunctionState.Disabled, function);
  531. /// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
  532. public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label,
  533. Action<SerializedProperty> function)
  534. => AddPropertyModifierFunction(menu, property, label, MenuFunctionState.Normal, function);
  535. /************************************************************************************************************************/
  536. /// <summary>
  537. /// Calls the specified `method` for each of the underlying values of the `property` (in case it represents
  538. /// multiple selected objects) and records an undo step for any modifications made.
  539. /// </summary>
  540. public static void ModifyValues<T>(this SerializedProperty property, Action<T> method, string undoName = "Inspector")
  541. {
  542. RecordUndo(property, undoName);
  543. var values = GetValues<T>(property);
  544. for (int i = 0; i < values.Length; i++)
  545. method(values[i]);
  546. OnPropertyChanged(property);
  547. }
  548. /************************************************************************************************************************/
  549. /// <summary>
  550. /// Records the state of the specified `property` so it can be undone.
  551. /// </summary>
  552. public static void RecordUndo(this SerializedProperty property, string undoName = "Inspector")
  553. => Undo.RecordObjects(property.serializedObject.targetObjects, undoName);
  554. /************************************************************************************************************************/
  555. /// <summary>
  556. /// Updates the specified `property` and marks its target objects as dirty so any changes to a prefab will be saved.
  557. /// </summary>
  558. public static void OnPropertyChanged(this SerializedProperty property)
  559. {
  560. var targets = property.serializedObject.targetObjects;
  561. // If this change is made to a prefab, this makes sure that any instances in the scene will be updated.
  562. for (int i = 0; i < targets.Length; i++)
  563. {
  564. EditorUtility.SetDirty(targets[i]);
  565. }
  566. property.serializedObject.Update();
  567. }
  568. /************************************************************************************************************************/
  569. /// <summary>
  570. /// Returns the <see cref="SerializedPropertyType"/> that represents fields of the specified `type`.
  571. /// </summary>
  572. public static SerializedPropertyType GetPropertyType(Type type)
  573. {
  574. // Primitives.
  575. if (type == typeof(bool))
  576. return SerializedPropertyType.Boolean;
  577. if (type == typeof(int))
  578. return SerializedPropertyType.Integer;
  579. if (type == typeof(float))
  580. return SerializedPropertyType.Float;
  581. if (type == typeof(string))
  582. return SerializedPropertyType.String;
  583. if (type == typeof(LayerMask))
  584. return SerializedPropertyType.LayerMask;
  585. // Vectors.
  586. if (type == typeof(Vector2))
  587. return SerializedPropertyType.Vector2;
  588. if (type == typeof(Vector3))
  589. return SerializedPropertyType.Vector3;
  590. if (type == typeof(Vector4))
  591. return SerializedPropertyType.Vector4;
  592. if (type == typeof(Quaternion))
  593. return SerializedPropertyType.Quaternion;
  594. // Other.
  595. if (type == typeof(Color) || type == typeof(Color32))
  596. return SerializedPropertyType.Color;
  597. if (type == typeof(Gradient))
  598. return SerializedPropertyType.Gradient;
  599. if (type == typeof(Rect))
  600. return SerializedPropertyType.Rect;
  601. if (type == typeof(Bounds))
  602. return SerializedPropertyType.Bounds;
  603. if (type == typeof(AnimationCurve))
  604. return SerializedPropertyType.AnimationCurve;
  605. // Int Variants.
  606. if (type == typeof(Vector2Int))
  607. return SerializedPropertyType.Vector2Int;
  608. if (type == typeof(Vector3Int))
  609. return SerializedPropertyType.Vector3Int;
  610. if (type == typeof(RectInt))
  611. return SerializedPropertyType.RectInt;
  612. if (type == typeof(BoundsInt))
  613. return SerializedPropertyType.BoundsInt;
  614. // Special.
  615. if (typeof(Object).IsAssignableFrom(type))
  616. return SerializedPropertyType.ObjectReference;
  617. if (type.IsEnum)
  618. return SerializedPropertyType.Enum;
  619. return SerializedPropertyType.Generic;
  620. }
  621. /************************************************************************************************************************/
  622. /// <summary>Removes the specified array element from the `property`.</summary>
  623. /// <remarks>
  624. /// If the element is not at its default value, the first call to
  625. /// <see cref="SerializedProperty.DeleteArrayElementAtIndex"/> will only reset it, so this method will
  626. /// call it again if necessary to ensure that it actually gets removed.
  627. /// </remarks>
  628. public static void RemoveArrayElement(SerializedProperty property, int index)
  629. {
  630. var count = property.arraySize;
  631. property.DeleteArrayElementAtIndex(index);
  632. if (property.arraySize == count)
  633. property.DeleteArrayElementAtIndex(index);
  634. }
  635. /************************************************************************************************************************/
  636. #endregion
  637. /************************************************************************************************************************/
  638. #region Accessor Pool
  639. /************************************************************************************************************************/
  640. private static readonly Dictionary<Type, Dictionary<string, PropertyAccessor>>
  641. TypeToPathToAccessor = new Dictionary<Type, Dictionary<string, PropertyAccessor>>();
  642. /************************************************************************************************************************/
  643. /// <summary>
  644. /// Returns an <see cref="PropertyAccessor"/> that can be used to access the details of the specified `property`.
  645. /// </summary>
  646. public static PropertyAccessor GetAccessor(this SerializedProperty property)
  647. {
  648. var type = property.serializedObject.targetObject.GetType();
  649. return GetAccessor(property, property.propertyPath, ref type);
  650. }
  651. /************************************************************************************************************************/
  652. /// <summary>
  653. /// Returns an <see cref="PropertyAccessor"/> for a <see cref="SerializedProperty"/> with the specified `propertyPath`
  654. /// on the specified `type` of object.
  655. /// </summary>
  656. private static PropertyAccessor GetAccessor(SerializedProperty property, string propertyPath, ref Type type)
  657. {
  658. if (!TypeToPathToAccessor.TryGetValue(type, out var pathToAccessor))
  659. {
  660. pathToAccessor = new Dictionary<string, PropertyAccessor>();
  661. TypeToPathToAccessor.Add(type, pathToAccessor);
  662. }
  663. if (!pathToAccessor.TryGetValue(propertyPath, out var accessor))
  664. {
  665. var nameStartIndex = propertyPath.LastIndexOf('.');
  666. string elementName;
  667. PropertyAccessor parent;
  668. // Array.
  669. if (nameStartIndex > 6 &&
  670. nameStartIndex < propertyPath.Length - 7 &&
  671. string.Compare(propertyPath, nameStartIndex - 6, ArrayDataPrefix, 0, 12) == 0)
  672. {
  673. var index = int.Parse(propertyPath.Substring(nameStartIndex + 6, propertyPath.Length - nameStartIndex - 7));
  674. var nameEndIndex = nameStartIndex - 6;
  675. nameStartIndex = propertyPath.LastIndexOf('.', nameEndIndex - 1);
  676. elementName = propertyPath.Substring(nameStartIndex + 1, nameEndIndex - nameStartIndex - 1);
  677. FieldInfo field;
  678. if (nameStartIndex >= 0)
  679. {
  680. parent = GetAccessor(property, propertyPath.Substring(0, nameStartIndex), ref type);
  681. field = GetField(parent, property, type, elementName);
  682. }
  683. else
  684. {
  685. parent = null;
  686. field = GetField(type, elementName);
  687. }
  688. accessor = new CollectionPropertyAccessor(parent, elementName, field, index);
  689. }
  690. else// Single.
  691. {
  692. if (nameStartIndex >= 0)
  693. {
  694. elementName = propertyPath.Substring(nameStartIndex + 1);
  695. parent = GetAccessor(property, propertyPath.Substring(0, nameStartIndex), ref type);
  696. }
  697. else
  698. {
  699. elementName = propertyPath;
  700. parent = null;
  701. }
  702. var field = GetField(parent, property, type, elementName);
  703. accessor = new PropertyAccessor(parent, elementName, field);
  704. }
  705. pathToAccessor.Add(propertyPath, accessor);
  706. }
  707. if (accessor != null)
  708. {
  709. var field = accessor.GetField(property);
  710. if (field != null)
  711. {
  712. type = field.FieldType;
  713. }
  714. else
  715. {
  716. var value = accessor.GetValue(property);
  717. type = value?.GetType();
  718. }
  719. }
  720. return accessor;
  721. }
  722. /************************************************************************************************************************/
  723. /// <summary>Returns a field with the specified `name` in the `declaringType` or any of its base types.</summary>
  724. /// <remarks>Uses the <see cref="InstanceBindings"/>.</remarks>
  725. public static FieldInfo GetField(PropertyAccessor accessor, SerializedProperty property, Type declaringType, string name)
  726. {
  727. declaringType = accessor?.GetFieldElementType(property) ?? declaringType;
  728. return GetField(declaringType, name);
  729. }
  730. /// <summary>Returns a field with the specified `name` in the `declaringType` or any of its base types.</summary>
  731. /// <remarks>Uses the <see cref="InstanceBindings"/>.</remarks>
  732. public static FieldInfo GetField(Type declaringType, string name)
  733. {
  734. while (declaringType != null)
  735. {
  736. var field = declaringType.GetField(name, InstanceBindings);
  737. if (field != null)
  738. return field;
  739. declaringType = declaringType.BaseType;
  740. }
  741. return null;
  742. }
  743. /************************************************************************************************************************/
  744. #endregion
  745. /************************************************************************************************************************/
  746. #region PropertyAccessor
  747. /************************************************************************************************************************/
  748. /// <summary>[Editor-Only]
  749. /// A wrapper for accessing the underlying values and fields of a <see cref="SerializedProperty"/>.
  750. /// </summary>
  751. public class PropertyAccessor
  752. {
  753. /************************************************************************************************************************/
  754. /// <summary>The accessor for the field which this accessor is nested inside.</summary>
  755. public readonly PropertyAccessor Parent;
  756. /// <summary>The name of the field wrapped by this accessor.</summary>
  757. public readonly string Name;
  758. /// <summary>The field wrapped by this accessor.</summary>
  759. protected readonly FieldInfo Field;
  760. /// <summary>
  761. /// The type of the wrapped <see cref="Field"/>.
  762. /// Or if it's a collection, this is the type of items in the collection.
  763. /// </summary>
  764. protected readonly Type FieldElementType;
  765. /************************************************************************************************************************/
  766. /// <summary>[Internal] Creates a new <see cref="PropertyAccessor"/>.</summary>
  767. internal PropertyAccessor(PropertyAccessor parent, string name, FieldInfo field)
  768. : this(parent, name, field, field?.FieldType)
  769. { }
  770. /// <summary>Creates a new <see cref="PropertyAccessor"/>.</summary>
  771. protected PropertyAccessor(PropertyAccessor parent, string name, FieldInfo field, Type fieldElementType)
  772. {
  773. Parent = parent;
  774. Name = name;
  775. Field = field;
  776. FieldElementType = fieldElementType;
  777. }
  778. /************************************************************************************************************************/
  779. /// <summary>Returns the <see cref="Field"/> if there is one or tries to get it from the object's type.</summary>
  780. ///
  781. /// <remarks>
  782. /// If this accessor has a <see cref="Parent"/>, the `obj` must be associated with the root
  783. /// <see cref="SerializedProperty"/> and this method will change it to reference the parent field's value.
  784. /// </remarks>
  785. ///
  786. /// <example><code>
  787. /// [Serializable]
  788. /// public class InnerClass
  789. /// {
  790. /// public float value;
  791. /// }
  792. ///
  793. /// [Serializable]
  794. /// public class RootClass
  795. /// {
  796. /// public InnerClass inner;
  797. /// }
  798. ///
  799. /// public class MyBehaviour : MonoBehaviour
  800. /// {
  801. /// public RootClass root;
  802. /// }
  803. ///
  804. /// [UnityEditor.CustomEditor(typeof(MyBehaviour))]
  805. /// public class MyEditor : UnityEditor.Editor
  806. /// {
  807. /// private void OnEnable()
  808. /// {
  809. /// var serializedObject = new SerializedObject(target);
  810. /// var rootProperty = serializedObject.FindProperty("root");
  811. /// var innerProperty = rootProperty.FindPropertyRelative("inner");
  812. /// var valueProperty = innerProperty.FindPropertyRelative("value");
  813. ///
  814. /// var accessor = valueProperty.GetAccessor();
  815. ///
  816. /// object obj = target;
  817. /// var valueField = accessor.GetField(ref obj);
  818. /// // valueField is a FieldInfo referring to InnerClass.value.
  819. /// // obj now holds the ((MyBehaviour)target).root.inner.
  820. /// }
  821. /// }
  822. /// </code></example>
  823. ///
  824. public FieldInfo GetField(ref object obj)
  825. {
  826. if (Parent != null)
  827. obj = Parent.GetValue(obj);
  828. if (Field != null)
  829. return Field;
  830. if (obj is null)
  831. return null;
  832. return Serialization.GetField(obj.GetType(), Name);
  833. }
  834. /// <summary>
  835. /// Returns the <see cref="Field"/> if there is one, otherwise calls <see cref="GetField(ref object)"/>.
  836. /// </summary>
  837. public FieldInfo GetField(object obj)
  838. => Field ?? GetField(ref obj);
  839. /// <summary>
  840. /// Calls <see cref="GetField(object)"/> with the <see cref="SerializedObject.targetObject"/>.
  841. /// </summary>
  842. public FieldInfo GetField(SerializedObject serializedObject)
  843. => serializedObject != null ? GetField(serializedObject.targetObject) : null;
  844. /// <summary>
  845. /// Calls <see cref="GetField(SerializedObject)"/> with the
  846. /// <see cref="SerializedProperty.serializedObject"/>.
  847. /// </summary>
  848. public FieldInfo GetField(SerializedProperty serializedProperty)
  849. => serializedProperty != null ? GetField(serializedProperty.serializedObject) : null;
  850. /************************************************************************************************************************/
  851. /// <summary>
  852. /// Returns the <see cref="FieldElementType"/> if there is one, otherwise calls <see cref="GetField(ref object)"/>
  853. /// and returns its <see cref="FieldInfo.FieldType"/>.
  854. /// </summary>
  855. public virtual Type GetFieldElementType(object obj)
  856. => FieldElementType ?? GetField(ref obj)?.FieldType;
  857. /// <summary>
  858. /// Calls <see cref="GetFieldElementType(object)"/> with the
  859. /// <see cref="SerializedObject.targetObject"/>.
  860. /// </summary>
  861. public Type GetFieldElementType(SerializedObject serializedObject)
  862. => serializedObject != null ? GetFieldElementType(serializedObject.targetObject) : null;
  863. /// <summary>
  864. /// Calls <see cref="GetFieldElementType(SerializedObject)"/> with the
  865. /// <see cref="SerializedProperty.serializedObject"/>.
  866. /// </summary>
  867. public Type GetFieldElementType(SerializedProperty serializedProperty)
  868. => serializedProperty != null ? GetFieldElementType(serializedProperty.serializedObject) : null;
  869. /************************************************************************************************************************/
  870. /// <summary>
  871. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
  872. /// the value of the <see cref="Field"/>.
  873. /// </summary>
  874. public virtual object GetValue(object obj)
  875. {
  876. var field = GetField(ref obj);
  877. if (field is null ||
  878. (obj is null && !field.IsStatic))
  879. return null;
  880. return field.GetValue(obj);
  881. }
  882. /// <summary>
  883. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
  884. /// the value of the <see cref="Field"/>.
  885. /// </summary>
  886. public object GetValue(SerializedObject serializedObject)
  887. => serializedObject != null ? GetValue(serializedObject.targetObject) : null;
  888. /// <summary>
  889. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
  890. /// the value of the <see cref="Field"/>.
  891. /// </summary>
  892. public object GetValue(SerializedProperty serializedProperty)
  893. => serializedProperty != null ? GetValue(serializedProperty.serializedObject.targetObject) : null;
  894. /************************************************************************************************************************/
  895. /// <summary>
  896. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
  897. /// of the <see cref="Field"/>.
  898. /// </summary>
  899. public virtual void SetValue(object obj, object value)
  900. {
  901. var field = GetField(ref obj);
  902. if (field is null ||
  903. obj is null)
  904. return;
  905. field.SetValue(obj, value);
  906. }
  907. /// <summary>
  908. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
  909. /// of the <see cref="Field"/>.
  910. /// </summary>
  911. public void SetValue(SerializedObject serializedObject, object value)
  912. {
  913. if (serializedObject != null)
  914. SetValue(serializedObject.targetObject, value);
  915. }
  916. /// <summary>
  917. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
  918. /// of the <see cref="Field"/>.
  919. /// </summary>
  920. public void SetValue(SerializedProperty serializedProperty, object value)
  921. {
  922. if (serializedProperty != null)
  923. SetValue(serializedProperty.serializedObject, value);
  924. }
  925. /************************************************************************************************************************/
  926. /// <summary>
  927. /// Resets the value of the <see cref="SerializedProperty"/> to the default value of its type by executing
  928. /// its constructor and field initializers.
  929. /// </summary>
  930. /// <remarks>
  931. /// If you don't want to run constructors and field initializers, you can call
  932. /// <see cref="Serialization.ResetValue"/> instead.
  933. /// </remarks>
  934. /// <example><code>
  935. /// SerializedProperty property;
  936. /// property.GetAccessor().ResetValue(property);
  937. /// </code></example>
  938. public void ResetValue(SerializedProperty property, string undoName = "Inspector")
  939. {
  940. property.RecordUndo(undoName);
  941. property.serializedObject.ApplyModifiedProperties();
  942. var type = GetValue(property)?.GetType();
  943. var value = type != null ? Activator.CreateInstance(type) : null;
  944. SetValue(property, value);
  945. property.serializedObject.Update();
  946. }
  947. /************************************************************************************************************************/
  948. /// <summary>Returns a description of this accessor's path.</summary>
  949. public override string ToString()
  950. {
  951. if (Parent != null)
  952. return $"{Parent}.{Name}";
  953. else
  954. return Name;
  955. }
  956. /************************************************************************************************************************/
  957. /// <summary>Returns a this accessor's <see cref="SerializedProperty.propertyPath"/>.</summary>
  958. public virtual string GetPath()
  959. {
  960. if (Parent != null)
  961. return $"{Parent.GetPath()}.{Name}";
  962. else
  963. return Name;
  964. }
  965. /************************************************************************************************************************/
  966. }
  967. /************************************************************************************************************************/
  968. #endregion
  969. /************************************************************************************************************************/
  970. #region CollectionPropertyAccessor
  971. /************************************************************************************************************************/
  972. /// <summary>[Editor-Only] A <see cref="PropertyAccessor"/> for a specific element index in a collection.</summary>
  973. public class CollectionPropertyAccessor : PropertyAccessor
  974. {
  975. /************************************************************************************************************************/
  976. /// <summary>The index of the array element this accessor targets.</summary>
  977. public readonly int ElementIndex;
  978. /************************************************************************************************************************/
  979. /// <summary>[Internal] Creates a new <see cref="CollectionPropertyAccessor"/>.</summary>
  980. internal CollectionPropertyAccessor(PropertyAccessor parent, string name, FieldInfo field, int elementIndex)
  981. : base(parent, name, field, GetElementType(field?.FieldType))
  982. {
  983. ElementIndex = elementIndex;
  984. }
  985. /************************************************************************************************************************/
  986. /// <inheritdoc/>
  987. public override Type GetFieldElementType(object obj) => FieldElementType ?? GetElementType(GetField(ref obj)?.FieldType);
  988. /************************************************************************************************************************/
  989. /// <summary>Returns the type of elements in the array.</summary>
  990. public static Type GetElementType(Type fieldType)
  991. {
  992. if (fieldType == null)
  993. return null;
  994. if (fieldType.IsArray)
  995. return fieldType.GetElementType();
  996. if (fieldType.IsGenericType)
  997. return fieldType.GetGenericArguments()[0];
  998. Debug.LogWarning($"{nameof(Serialization)}.{nameof(CollectionPropertyAccessor)}:" +
  999. $" unable to determine element type for {fieldType}");
  1000. return fieldType;
  1001. }
  1002. /************************************************************************************************************************/
  1003. /// <summary>Returns the collection object targeted by this accessor.</summary>
  1004. public object GetCollection(object obj) => base.GetValue(obj);
  1005. /// <inheritdoc/>
  1006. public override object GetValue(object obj)
  1007. {
  1008. var collection = base.GetValue(obj);
  1009. if (collection == null)
  1010. return null;
  1011. var list = collection as IList;
  1012. if (list != null)
  1013. {
  1014. if (ElementIndex < list.Count)
  1015. return list[ElementIndex];
  1016. else
  1017. return null;
  1018. }
  1019. var enumerator = ((IEnumerable)collection).GetEnumerator();
  1020. for (int i = 0; i < ElementIndex; i++)
  1021. {
  1022. if (!enumerator.MoveNext())
  1023. return null;
  1024. }
  1025. return enumerator.Current;
  1026. }
  1027. /************************************************************************************************************************/
  1028. /// <summary>Sets the collection object targeted by this accessor.</summary>
  1029. public void SetCollection(object obj, object value) => base.SetValue(obj, value);
  1030. /// <inheritdoc/>
  1031. public override void SetValue(object obj, object value)
  1032. {
  1033. var collection = base.GetValue(obj);
  1034. if (collection == null)
  1035. return;
  1036. var list = collection as IList;
  1037. if (list != null)
  1038. {
  1039. if (ElementIndex < list.Count)
  1040. list[ElementIndex] = value;
  1041. return;
  1042. }
  1043. throw new InvalidOperationException($"{nameof(SetValue)} failed: field doesn't implement {nameof(IList)}.");
  1044. }
  1045. /************************************************************************************************************************/
  1046. /// <summary>Returns a description of this accessor's path.</summary>
  1047. public override string ToString() => $"{base.ToString()}[{ElementIndex}]";
  1048. /************************************************************************************************************************/
  1049. /// <summary>Returns the <see cref="SerializedProperty.propertyPath"/> of the array containing the target.</summary>
  1050. public string GetCollectionPath() => base.GetPath();
  1051. /// <summary>Returns this accessor's <see cref="SerializedProperty.propertyPath"/>.</summary>
  1052. public override string GetPath() => $"{base.GetPath()}{ArrayDataPrefix}{ElementIndex}{ArrayDataSuffix}";
  1053. /************************************************************************************************************************/
  1054. }
  1055. /************************************************************************************************************************/
  1056. #endregion
  1057. /************************************************************************************************************************/
  1058. }
  1059. }
  1060. #endif