1304 lines
60 KiB
C#
1304 lines
60 KiB
C#
|
#if ! LITE || LITE_DLL
|
||
|
|
||
|
// Serialization // Copyright 2021 Kybernetik //
|
||
|
|
||
|
#if UNITY_EDITOR
|
||
|
|
||
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Reflection;
|
||
|
using System.Text;
|
||
|
using UnityEditor;
|
||
|
using UnityEngine;
|
||
|
using Object = UnityEngine.Object;
|
||
|
|
||
|
// Shared File Last Modified: 2021-08-14
|
||
|
// namespace Animancer.Editor
|
||
|
// namespace InspectorGadgets.Editor
|
||
|
namespace UltEvents.Editor
|
||
|
{
|
||
|
/// <summary>The possible states for a function in a <see cref="GenericMenu"/>.</summary>
|
||
|
public enum MenuFunctionState
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Displayed normally.</summary>
|
||
|
Normal,
|
||
|
|
||
|
/// <summary>Has a check mark next to it to show that it is selected.</summary>
|
||
|
Selected,
|
||
|
|
||
|
/// <summary>Greyed out and unusable.</summary>
|
||
|
Disabled,
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/// <summary>[Editor-Only] Various serialization utilities.</summary>
|
||
|
public static partial class Serialization
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
#region Public Static API
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The text used in a <see cref="SerializedProperty.propertyPath"/> to denote array elements.</summary>
|
||
|
public const string
|
||
|
ArrayDataPrefix = ".Array.data[";
|
||
|
/************************************************************************************************************************/
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The text used in a <see cref="SerializedProperty.propertyPath"/> to denote array elements.</summary>
|
||
|
public const string
|
||
|
ArrayDataSuffix = "]";
|
||
|
|
||
|
/// <summary>Bindings for Public and Non-Public Instance members.</summary>
|
||
|
public const BindingFlags
|
||
|
InstanceBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns a user friendly version of the <see cref="SerializedProperty.propertyPath"/>.</summary>
|
||
|
public static string GetFriendlyPath(this SerializedProperty property)
|
||
|
{
|
||
|
return property.propertyPath.Replace(ArrayDataPrefix, "[");
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#region Get Value
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Gets the value of the specified <see cref="SerializedProperty"/>.</summary>
|
||
|
public static object GetValue(this SerializedProperty property, object targetObject)
|
||
|
{
|
||
|
if (property.hasMultipleDifferentValues &&
|
||
|
property.serializedObject.targetObject != targetObject as Object)
|
||
|
{
|
||
|
property = new SerializedObject(targetObject as Object).FindProperty(property.propertyPath);
|
||
|
}
|
||
|
|
||
|
switch (property.propertyType)
|
||
|
{
|
||
|
case SerializedPropertyType.Boolean: return property.boolValue;
|
||
|
case SerializedPropertyType.Float: return property.floatValue;
|
||
|
case SerializedPropertyType.String: return property.stringValue;
|
||
|
|
||
|
case SerializedPropertyType.Integer:
|
||
|
case SerializedPropertyType.Character:
|
||
|
case SerializedPropertyType.LayerMask:
|
||
|
case SerializedPropertyType.ArraySize:
|
||
|
return property.intValue;
|
||
|
|
||
|
case SerializedPropertyType.Vector2: return property.vector2Value;
|
||
|
case SerializedPropertyType.Vector3: return property.vector3Value;
|
||
|
case SerializedPropertyType.Vector4: return property.vector4Value;
|
||
|
|
||
|
case SerializedPropertyType.Quaternion: return property.quaternionValue;
|
||
|
case SerializedPropertyType.Color: return property.colorValue;
|
||
|
case SerializedPropertyType.AnimationCurve: return property.animationCurveValue;
|
||
|
|
||
|
case SerializedPropertyType.Rect: return property.rectValue;
|
||
|
case SerializedPropertyType.Bounds: return property.boundsValue;
|
||
|
|
||
|
case SerializedPropertyType.Vector2Int: return property.vector2IntValue;
|
||
|
case SerializedPropertyType.Vector3Int: return property.vector3IntValue;
|
||
|
case SerializedPropertyType.RectInt: return property.rectIntValue;
|
||
|
case SerializedPropertyType.BoundsInt: return property.boundsIntValue;
|
||
|
|
||
|
case SerializedPropertyType.ObjectReference: return property.objectReferenceValue;
|
||
|
case SerializedPropertyType.ExposedReference: return property.exposedReferenceValue;
|
||
|
|
||
|
case SerializedPropertyType.FixedBufferSize: return property.fixedBufferSize;
|
||
|
|
||
|
case SerializedPropertyType.Gradient: return property.GetGradientValue();
|
||
|
|
||
|
case SerializedPropertyType.Enum:// Would be complex because enumValueIndex can't be cast directly.
|
||
|
case SerializedPropertyType.Generic:
|
||
|
default:
|
||
|
return GetAccessor(property)?.GetValue(targetObject);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
|
||
|
public static object GetValue(this SerializedProperty property) => GetValue(property, property.serializedObject.targetObject);
|
||
|
|
||
|
/// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
|
||
|
public static T GetValue<T>(this SerializedProperty property) => (T)GetValue(property);
|
||
|
|
||
|
/// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
|
||
|
public static void GetValue<T>(this SerializedProperty property, out T value) => value = (T)GetValue(property);
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Gets the value of the <see cref="SerializedProperty"/> for each of its target objects.</summary>
|
||
|
public static T[] GetValues<T>(this SerializedProperty property)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
var targetObjects = property.serializedObject.targetObjects;
|
||
|
var values = new T[targetObjects.Length];
|
||
|
for (int i = 0; i < values.Length; i++)
|
||
|
{
|
||
|
values[i] = (T)GetValue(property, targetObjects[i]);
|
||
|
}
|
||
|
|
||
|
return values;
|
||
|
}
|
||
|
catch
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Is the value of the `property` the same as the default serialized value for its type?</summary>
|
||
|
public static bool IsDefaultValueByType(SerializedProperty property)
|
||
|
{
|
||
|
if (property.hasMultipleDifferentValues)
|
||
|
return false;
|
||
|
|
||
|
switch (property.propertyType)
|
||
|
{
|
||
|
case SerializedPropertyType.Boolean: return property.boolValue == default;
|
||
|
case SerializedPropertyType.Float: return property.floatValue == default;
|
||
|
case SerializedPropertyType.String: return property.stringValue == "";
|
||
|
|
||
|
case SerializedPropertyType.Integer:
|
||
|
case SerializedPropertyType.Character:
|
||
|
case SerializedPropertyType.LayerMask:
|
||
|
case SerializedPropertyType.ArraySize:
|
||
|
return property.intValue == default;
|
||
|
|
||
|
case SerializedPropertyType.Vector2: return property.vector2Value == default;
|
||
|
case SerializedPropertyType.Vector3: return property.vector3Value == default;
|
||
|
case SerializedPropertyType.Vector4: return property.vector4Value == default;
|
||
|
|
||
|
case SerializedPropertyType.Quaternion: return property.quaternionValue == default;
|
||
|
case SerializedPropertyType.Color: return property.colorValue == default;
|
||
|
case SerializedPropertyType.AnimationCurve: return property.animationCurveValue == default;
|
||
|
|
||
|
case SerializedPropertyType.Rect: return property.rectValue == default;
|
||
|
case SerializedPropertyType.Bounds: return property.boundsValue == default;
|
||
|
|
||
|
case SerializedPropertyType.Vector2Int: return property.vector2IntValue == default;
|
||
|
case SerializedPropertyType.Vector3Int: return property.vector3IntValue == default;
|
||
|
case SerializedPropertyType.RectInt: return property.rectIntValue.Equals(default);
|
||
|
case SerializedPropertyType.BoundsInt: return property.boundsIntValue == default;
|
||
|
|
||
|
case SerializedPropertyType.ObjectReference: return property.objectReferenceValue == default;
|
||
|
case SerializedPropertyType.ExposedReference: return property.exposedReferenceValue == default;
|
||
|
|
||
|
case SerializedPropertyType.FixedBufferSize: return property.fixedBufferSize == default;
|
||
|
|
||
|
case SerializedPropertyType.Enum: return property.enumValueIndex == default;
|
||
|
|
||
|
case SerializedPropertyType.Gradient:
|
||
|
case SerializedPropertyType.Generic:
|
||
|
default:
|
||
|
if (property.isArray)
|
||
|
return property.arraySize == default;
|
||
|
|
||
|
var depth = property.depth;
|
||
|
property = property.Copy();
|
||
|
var enterChildren = true;
|
||
|
while (property.Next(enterChildren) && property.depth > depth)
|
||
|
{
|
||
|
enterChildren = false;
|
||
|
if (!IsDefaultValueByType(property))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Set Value
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Sets the value of the specified <see cref="SerializedProperty"/>.</summary>
|
||
|
public static void SetValue(this SerializedProperty property, object targetObject, object value)
|
||
|
{
|
||
|
switch (property.propertyType)
|
||
|
{
|
||
|
case SerializedPropertyType.Boolean: property.boolValue = (bool)value; break;
|
||
|
case SerializedPropertyType.Float: property.floatValue = (float)value; break;
|
||
|
case SerializedPropertyType.String: property.stringValue = (string)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Integer:
|
||
|
case SerializedPropertyType.Character:
|
||
|
case SerializedPropertyType.LayerMask:
|
||
|
case SerializedPropertyType.ArraySize:
|
||
|
property.intValue = (int)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Vector2: property.vector2Value = (Vector2)value; break;
|
||
|
case SerializedPropertyType.Vector3: property.vector3Value = (Vector3)value; break;
|
||
|
case SerializedPropertyType.Vector4: property.vector4Value = (Vector4)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Quaternion: property.quaternionValue = (Quaternion)value; break;
|
||
|
case SerializedPropertyType.Color: property.colorValue = (Color)value; break;
|
||
|
case SerializedPropertyType.AnimationCurve: property.animationCurveValue = (AnimationCurve)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Rect: property.rectValue = (Rect)value; break;
|
||
|
case SerializedPropertyType.Bounds: property.boundsValue = (Bounds)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Vector2Int: property.vector2IntValue = (Vector2Int)value; break;
|
||
|
case SerializedPropertyType.Vector3Int: property.vector3IntValue = (Vector3Int)value; break;
|
||
|
case SerializedPropertyType.RectInt: property.rectIntValue = (RectInt)value; break;
|
||
|
case SerializedPropertyType.BoundsInt: property.boundsIntValue = (BoundsInt)value; break;
|
||
|
|
||
|
case SerializedPropertyType.ObjectReference: property.objectReferenceValue = (Object)value; break;
|
||
|
case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = (Object)value; break;
|
||
|
|
||
|
case SerializedPropertyType.FixedBufferSize:
|
||
|
throw new InvalidOperationException($"{nameof(SetValue)} failed:" +
|
||
|
$" {nameof(SerializedProperty)}.{nameof(SerializedProperty.fixedBufferSize)} is read-only.");
|
||
|
|
||
|
case SerializedPropertyType.Gradient: property.SetGradientValue((Gradient)value); break;
|
||
|
|
||
|
case SerializedPropertyType.Enum:// Would be complex because enumValueIndex can't be cast directly.
|
||
|
case SerializedPropertyType.Generic:
|
||
|
default:
|
||
|
var accessor = GetAccessor(property);
|
||
|
if (accessor != null)
|
||
|
accessor.SetValue(targetObject, value);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Sets the value of the <see cref="SerializedProperty"/>.</summary>
|
||
|
public static void SetValue(this SerializedProperty property, object value)
|
||
|
{
|
||
|
switch (property.propertyType)
|
||
|
{
|
||
|
case SerializedPropertyType.Boolean: property.boolValue = (bool)value; break;
|
||
|
case SerializedPropertyType.Float: property.floatValue = (float)value; break;
|
||
|
case SerializedPropertyType.Integer: property.intValue = (int)value; break;
|
||
|
case SerializedPropertyType.String: property.stringValue = (string)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Vector2: property.vector2Value = (Vector2)value; break;
|
||
|
case SerializedPropertyType.Vector3: property.vector3Value = (Vector3)value; break;
|
||
|
case SerializedPropertyType.Vector4: property.vector4Value = (Vector4)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Quaternion: property.quaternionValue = (Quaternion)value; break;
|
||
|
case SerializedPropertyType.Color: property.colorValue = (Color)value; break;
|
||
|
case SerializedPropertyType.AnimationCurve: property.animationCurveValue = (AnimationCurve)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Rect: property.rectValue = (Rect)value; break;
|
||
|
case SerializedPropertyType.Bounds: property.boundsValue = (Bounds)value; break;
|
||
|
|
||
|
case SerializedPropertyType.Vector2Int: property.vector2IntValue = (Vector2Int)value; break;
|
||
|
case SerializedPropertyType.Vector3Int: property.vector3IntValue = (Vector3Int)value; break;
|
||
|
case SerializedPropertyType.RectInt: property.rectIntValue = (RectInt)value; break;
|
||
|
case SerializedPropertyType.BoundsInt: property.boundsIntValue = (BoundsInt)value; break;
|
||
|
|
||
|
case SerializedPropertyType.ObjectReference: property.objectReferenceValue = (Object)value; break;
|
||
|
case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = (Object)value; break;
|
||
|
|
||
|
case SerializedPropertyType.ArraySize: property.intValue = (int)value; break;
|
||
|
|
||
|
case SerializedPropertyType.FixedBufferSize:
|
||
|
throw new InvalidOperationException($"{nameof(SetValue)} failed:" +
|
||
|
$" {nameof(SerializedProperty)}.{nameof(SerializedProperty.fixedBufferSize)} is read-only.");
|
||
|
|
||
|
case SerializedPropertyType.Generic:
|
||
|
case SerializedPropertyType.Enum:
|
||
|
case SerializedPropertyType.LayerMask:
|
||
|
case SerializedPropertyType.Gradient:
|
||
|
case SerializedPropertyType.Character:
|
||
|
default:
|
||
|
var accessor = GetAccessor(property);
|
||
|
if (accessor != null)
|
||
|
{
|
||
|
var targets = property.serializedObject.targetObjects;
|
||
|
for (int i = 0; i < targets.Length; i++)
|
||
|
{
|
||
|
accessor.SetValue(targets[i], value);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Resets the value of the <see cref="SerializedProperty"/> to the default value of its type and all its field
|
||
|
/// types, ignoring values set by constructors or field initializers.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// If you want to run constructors and field initializers, you can call
|
||
|
/// <see cref="PropertyAccessor.ResetValue"/> instead.
|
||
|
/// </remarks>
|
||
|
public static void ResetValue(SerializedProperty property, string undoName = "Inspector")
|
||
|
{
|
||
|
switch (property.propertyType)
|
||
|
{
|
||
|
case SerializedPropertyType.Boolean: property.boolValue = default; break;
|
||
|
case SerializedPropertyType.Float: property.floatValue = default; break;
|
||
|
case SerializedPropertyType.String: property.stringValue = ""; break;
|
||
|
|
||
|
case SerializedPropertyType.Integer:
|
||
|
case SerializedPropertyType.Character:
|
||
|
case SerializedPropertyType.LayerMask:
|
||
|
case SerializedPropertyType.ArraySize:
|
||
|
property.intValue = default;
|
||
|
break;
|
||
|
|
||
|
case SerializedPropertyType.Vector2: property.vector2Value = default; break;
|
||
|
case SerializedPropertyType.Vector3: property.vector3Value = default; break;
|
||
|
case SerializedPropertyType.Vector4: property.vector4Value = default; break;
|
||
|
|
||
|
case SerializedPropertyType.Quaternion: property.quaternionValue = default; break;
|
||
|
case SerializedPropertyType.Color: property.colorValue = default; break;
|
||
|
case SerializedPropertyType.AnimationCurve: property.animationCurveValue = default; break;
|
||
|
|
||
|
case SerializedPropertyType.Rect: property.rectValue = default; break;
|
||
|
case SerializedPropertyType.Bounds: property.boundsValue = default; break;
|
||
|
|
||
|
case SerializedPropertyType.Vector2Int: property.vector2IntValue = default; break;
|
||
|
case SerializedPropertyType.Vector3Int: property.vector3IntValue = default; break;
|
||
|
case SerializedPropertyType.RectInt: property.rectIntValue = default; break;
|
||
|
case SerializedPropertyType.BoundsInt: property.boundsIntValue = default; break;
|
||
|
|
||
|
case SerializedPropertyType.ObjectReference: property.objectReferenceValue = default; break;
|
||
|
case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = default; break;
|
||
|
|
||
|
case SerializedPropertyType.Enum: property.enumValueIndex = default; break;
|
||
|
|
||
|
case SerializedPropertyType.Gradient:
|
||
|
case SerializedPropertyType.FixedBufferSize:
|
||
|
case SerializedPropertyType.Generic:
|
||
|
default:
|
||
|
if (property.isArray)
|
||
|
{
|
||
|
property.arraySize = default;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
var depth = property.depth;
|
||
|
property = property.Copy();
|
||
|
var enterChildren = true;
|
||
|
while (property.Next(enterChildren) && property.depth > depth)
|
||
|
{
|
||
|
enterChildren = false;
|
||
|
ResetValue(property);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Copies the value of `from` into `to` (including all nested properties).</summary>
|
||
|
public static float CopyValueFrom(this SerializedProperty to, SerializedProperty from)
|
||
|
{
|
||
|
from = from.Copy();
|
||
|
var fromPath = from.propertyPath;
|
||
|
var pathPrefixLength = fromPath.Length + 1;
|
||
|
var depth = from.depth;
|
||
|
|
||
|
var copyCount = 0;
|
||
|
var totalCount = 0;
|
||
|
StringBuilder issues = null;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
while (from.propertyType == SerializedPropertyType.Generic)
|
||
|
if (!from.Next(true))
|
||
|
goto LogResults;
|
||
|
|
||
|
SerializedProperty toRelative;
|
||
|
|
||
|
var relativePath = from.propertyPath;
|
||
|
if (relativePath.Length <= pathPrefixLength)
|
||
|
{
|
||
|
toRelative = to;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
relativePath = relativePath.Substring(pathPrefixLength, relativePath.Length - pathPrefixLength);
|
||
|
|
||
|
toRelative = to.FindPropertyRelative(relativePath);
|
||
|
}
|
||
|
|
||
|
if (!from.hasMultipleDifferentValues &&
|
||
|
toRelative != null &&
|
||
|
toRelative.propertyType == from.propertyType &&
|
||
|
toRelative.type == from.type)
|
||
|
{
|
||
|
// GetValue and SetValue currently access the underlying field for enums, but we need the stored value.
|
||
|
if (toRelative.propertyType == SerializedPropertyType.Enum)
|
||
|
toRelative.enumValueIndex = from.enumValueIndex;
|
||
|
else
|
||
|
toRelative.SetValue(from.GetValue());
|
||
|
|
||
|
copyCount++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (issues == null)
|
||
|
issues = new StringBuilder();
|
||
|
|
||
|
issues.AppendLine()
|
||
|
.Append(" - ");
|
||
|
|
||
|
if (from.hasMultipleDifferentValues)
|
||
|
{
|
||
|
issues
|
||
|
.Append("The selected objects have different values for '")
|
||
|
.Append(relativePath)
|
||
|
.Append("'.");
|
||
|
}
|
||
|
else if (toRelative == null)
|
||
|
{
|
||
|
issues
|
||
|
.Append("No property '")
|
||
|
.Append(relativePath)
|
||
|
.Append("' exists relative to '")
|
||
|
.Append(to.propertyPath)
|
||
|
.Append("'.");
|
||
|
}
|
||
|
else if (toRelative.propertyType != from.propertyType)
|
||
|
{
|
||
|
issues
|
||
|
.Append("The type of '")
|
||
|
.Append(toRelative.propertyPath)
|
||
|
.Append("' was '")
|
||
|
.Append(toRelative.propertyType)
|
||
|
.Append("' but should be '")
|
||
|
.Append(from.propertyType)
|
||
|
.Append("'.");
|
||
|
}
|
||
|
else if (toRelative.type != from.type)
|
||
|
{
|
||
|
issues
|
||
|
.Append("The type of '")
|
||
|
.Append(toRelative.propertyPath)
|
||
|
.Append("' was '")
|
||
|
.Append(toRelative.type)
|
||
|
.Append("' but should be '")
|
||
|
.Append(from.type)
|
||
|
.Append("'.");
|
||
|
}
|
||
|
else// This should never happen.
|
||
|
{
|
||
|
issues
|
||
|
.Append(" - Unknown issue with '")
|
||
|
.Append(relativePath)
|
||
|
.Append("'.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
totalCount++;
|
||
|
}
|
||
|
while (from.Next(false) && from.depth > depth);
|
||
|
|
||
|
LogResults:
|
||
|
if (copyCount < totalCount)
|
||
|
Debug.Log($"Copied {copyCount} / {totalCount} values from '{fromPath}' to '{to.propertyPath}': {issues}");
|
||
|
|
||
|
return (float)copyCount / totalCount;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Gradients
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private static PropertyInfo _GradientValue;
|
||
|
|
||
|
/// <summary><c>SerializedProperty.gradientValue</c> is internal.</summary>
|
||
|
private static PropertyInfo GradientValue
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_GradientValue == null)
|
||
|
_GradientValue = typeof(SerializedProperty).GetProperty("gradientValue", InstanceBindings);
|
||
|
|
||
|
return _GradientValue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Gets the <see cref="Gradient"/> value from a <see cref="SerializedPropertyType.Gradient"/>.</summary>
|
||
|
public static Gradient GetGradientValue(this SerializedProperty property) => (Gradient)GradientValue.GetValue(property, null);
|
||
|
|
||
|
/// <summary>Sets the <see cref="Gradient"/> value on a <see cref="SerializedPropertyType.Gradient"/>.</summary>
|
||
|
public static void SetGradientValue(this SerializedProperty property, Gradient value) => GradientValue.SetValue(property, value, null);
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Indicates whether both properties refer to the same underlying field.</summary>
|
||
|
public static bool AreSameProperty(SerializedProperty a, SerializedProperty b)
|
||
|
{
|
||
|
if (a == b)
|
||
|
return true;
|
||
|
|
||
|
if (a == null)
|
||
|
return b == null;
|
||
|
|
||
|
if (b == null)
|
||
|
return false;
|
||
|
|
||
|
if (a.propertyPath != b.propertyPath)
|
||
|
return false;
|
||
|
|
||
|
var aTargets = a.serializedObject.targetObjects;
|
||
|
var bTargets = b.serializedObject.targetObjects;
|
||
|
if (aTargets.Length != bTargets.Length)
|
||
|
return false;
|
||
|
|
||
|
for (int i = 0; i < aTargets.Length; i++)
|
||
|
{
|
||
|
if (aTargets[i] != bTargets[i])
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Executes the `action` once with a new <see cref="SerializedProperty"/> for each of the
|
||
|
/// <see cref="SerializedObject.targetObjects"/>. Or if there is only one target, it uses the `property`.
|
||
|
/// </summary>
|
||
|
public static void ForEachTarget(this SerializedProperty property, Action<SerializedProperty> function,
|
||
|
string undoName = "Inspector")
|
||
|
{
|
||
|
var targets = property.serializedObject.targetObjects;
|
||
|
|
||
|
if (undoName != null)
|
||
|
Undo.RecordObjects(targets, undoName);
|
||
|
|
||
|
if (targets.Length == 1)
|
||
|
{
|
||
|
function(property);
|
||
|
property.serializedObject.ApplyModifiedProperties();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var path = property.propertyPath;
|
||
|
for (int i = 0; i < targets.Length; i++)
|
||
|
{
|
||
|
using (var serializedObject = new SerializedObject(targets[i]))
|
||
|
{
|
||
|
property = serializedObject.FindProperty(path);
|
||
|
function(property);
|
||
|
property.serializedObject.ApplyModifiedProperties();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adds a menu item to execute the specified `function` for each of the `property`s target objects.
|
||
|
/// </summary>
|
||
|
public static void AddFunction(this GenericMenu menu, string label, MenuFunctionState state, GenericMenu.MenuFunction function)
|
||
|
{
|
||
|
if (state != MenuFunctionState.Disabled)
|
||
|
{
|
||
|
menu.AddItem(new GUIContent(label), state == MenuFunctionState.Selected, function);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
menu.AddDisabledItem(new GUIContent(label));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adds a menu item to execute the specified `function` for each of the `property`s target objects.
|
||
|
/// </summary>
|
||
|
public static void AddFunction(this GenericMenu menu, string label, bool enabled, GenericMenu.MenuFunction function)
|
||
|
=> AddFunction(menu, label, enabled ? MenuFunctionState.Normal : MenuFunctionState.Disabled, function);
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
|
||
|
public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label,
|
||
|
MenuFunctionState state, Action<SerializedProperty> function)
|
||
|
{
|
||
|
if (state != MenuFunctionState.Disabled && GUI.enabled)
|
||
|
{
|
||
|
menu.AddItem(new GUIContent(label), state == MenuFunctionState.Selected, () =>
|
||
|
{
|
||
|
ForEachTarget(property, function);
|
||
|
GUIUtility.keyboardControl = 0;
|
||
|
GUIUtility.hotControl = 0;
|
||
|
EditorGUIUtility.editingTextField = false;
|
||
|
});
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
menu.AddDisabledItem(new GUIContent(label));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
|
||
|
public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label, bool enabled,
|
||
|
Action<SerializedProperty> function)
|
||
|
=> AddPropertyModifierFunction(menu, property, label, enabled ? MenuFunctionState.Normal : MenuFunctionState.Disabled, function);
|
||
|
|
||
|
/// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
|
||
|
public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label,
|
||
|
Action<SerializedProperty> function)
|
||
|
=> AddPropertyModifierFunction(menu, property, label, MenuFunctionState.Normal, function);
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calls the specified `method` for each of the underlying values of the `property` (in case it represents
|
||
|
/// multiple selected objects) and records an undo step for any modifications made.
|
||
|
/// </summary>
|
||
|
public static void ModifyValues<T>(this SerializedProperty property, Action<T> method, string undoName = "Inspector")
|
||
|
{
|
||
|
RecordUndo(property, undoName);
|
||
|
|
||
|
var values = GetValues<T>(property);
|
||
|
for (int i = 0; i < values.Length; i++)
|
||
|
method(values[i]);
|
||
|
|
||
|
OnPropertyChanged(property);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Records the state of the specified `property` so it can be undone.
|
||
|
/// </summary>
|
||
|
public static void RecordUndo(this SerializedProperty property, string undoName = "Inspector")
|
||
|
=> Undo.RecordObjects(property.serializedObject.targetObjects, undoName);
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Updates the specified `property` and marks its target objects as dirty so any changes to a prefab will be saved.
|
||
|
/// </summary>
|
||
|
public static void OnPropertyChanged(this SerializedProperty property)
|
||
|
{
|
||
|
var targets = property.serializedObject.targetObjects;
|
||
|
|
||
|
// If this change is made to a prefab, this makes sure that any instances in the scene will be updated.
|
||
|
for (int i = 0; i < targets.Length; i++)
|
||
|
{
|
||
|
EditorUtility.SetDirty(targets[i]);
|
||
|
}
|
||
|
|
||
|
property.serializedObject.Update();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the <see cref="SerializedPropertyType"/> that represents fields of the specified `type`.
|
||
|
/// </summary>
|
||
|
public static SerializedPropertyType GetPropertyType(Type type)
|
||
|
{
|
||
|
// Primitives.
|
||
|
|
||
|
if (type == typeof(bool))
|
||
|
return SerializedPropertyType.Boolean;
|
||
|
|
||
|
if (type == typeof(int))
|
||
|
return SerializedPropertyType.Integer;
|
||
|
|
||
|
if (type == typeof(float))
|
||
|
return SerializedPropertyType.Float;
|
||
|
|
||
|
if (type == typeof(string))
|
||
|
return SerializedPropertyType.String;
|
||
|
|
||
|
if (type == typeof(LayerMask))
|
||
|
return SerializedPropertyType.LayerMask;
|
||
|
|
||
|
// Vectors.
|
||
|
|
||
|
if (type == typeof(Vector2))
|
||
|
return SerializedPropertyType.Vector2;
|
||
|
if (type == typeof(Vector3))
|
||
|
return SerializedPropertyType.Vector3;
|
||
|
if (type == typeof(Vector4))
|
||
|
return SerializedPropertyType.Vector4;
|
||
|
|
||
|
if (type == typeof(Quaternion))
|
||
|
return SerializedPropertyType.Quaternion;
|
||
|
|
||
|
// Other.
|
||
|
|
||
|
if (type == typeof(Color) || type == typeof(Color32))
|
||
|
return SerializedPropertyType.Color;
|
||
|
if (type == typeof(Gradient))
|
||
|
return SerializedPropertyType.Gradient;
|
||
|
|
||
|
if (type == typeof(Rect))
|
||
|
return SerializedPropertyType.Rect;
|
||
|
if (type == typeof(Bounds))
|
||
|
return SerializedPropertyType.Bounds;
|
||
|
|
||
|
if (type == typeof(AnimationCurve))
|
||
|
return SerializedPropertyType.AnimationCurve;
|
||
|
|
||
|
// Int Variants.
|
||
|
|
||
|
if (type == typeof(Vector2Int))
|
||
|
return SerializedPropertyType.Vector2Int;
|
||
|
if (type == typeof(Vector3Int))
|
||
|
return SerializedPropertyType.Vector3Int;
|
||
|
if (type == typeof(RectInt))
|
||
|
return SerializedPropertyType.RectInt;
|
||
|
if (type == typeof(BoundsInt))
|
||
|
return SerializedPropertyType.BoundsInt;
|
||
|
|
||
|
// Special.
|
||
|
|
||
|
if (typeof(Object).IsAssignableFrom(type))
|
||
|
return SerializedPropertyType.ObjectReference;
|
||
|
|
||
|
if (type.IsEnum)
|
||
|
return SerializedPropertyType.Enum;
|
||
|
|
||
|
return SerializedPropertyType.Generic;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Removes the specified array element from the `property`.</summary>
|
||
|
/// <remarks>
|
||
|
/// If the element is not at its default value, the first call to
|
||
|
/// <see cref="SerializedProperty.DeleteArrayElementAtIndex"/> will only reset it, so this method will
|
||
|
/// call it again if necessary to ensure that it actually gets removed.
|
||
|
/// </remarks>
|
||
|
public static void RemoveArrayElement(SerializedProperty property, int index)
|
||
|
{
|
||
|
var count = property.arraySize;
|
||
|
property.DeleteArrayElementAtIndex(index);
|
||
|
if (property.arraySize == count)
|
||
|
property.DeleteArrayElementAtIndex(index);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Accessor Pool
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private static readonly Dictionary<Type, Dictionary<string, PropertyAccessor>>
|
||
|
TypeToPathToAccessor = new Dictionary<Type, Dictionary<string, PropertyAccessor>>();
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns an <see cref="PropertyAccessor"/> that can be used to access the details of the specified `property`.
|
||
|
/// </summary>
|
||
|
public static PropertyAccessor GetAccessor(this SerializedProperty property)
|
||
|
{
|
||
|
var type = property.serializedObject.targetObject.GetType();
|
||
|
return GetAccessor(property, property.propertyPath, ref type);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns an <see cref="PropertyAccessor"/> for a <see cref="SerializedProperty"/> with the specified `propertyPath`
|
||
|
/// on the specified `type` of object.
|
||
|
/// </summary>
|
||
|
private static PropertyAccessor GetAccessor(SerializedProperty property, string propertyPath, ref Type type)
|
||
|
{
|
||
|
if (!TypeToPathToAccessor.TryGetValue(type, out var pathToAccessor))
|
||
|
{
|
||
|
pathToAccessor = new Dictionary<string, PropertyAccessor>();
|
||
|
TypeToPathToAccessor.Add(type, pathToAccessor);
|
||
|
}
|
||
|
|
||
|
if (!pathToAccessor.TryGetValue(propertyPath, out var accessor))
|
||
|
{
|
||
|
var nameStartIndex = propertyPath.LastIndexOf('.');
|
||
|
string elementName;
|
||
|
PropertyAccessor parent;
|
||
|
|
||
|
// Array.
|
||
|
if (nameStartIndex > 6 &&
|
||
|
nameStartIndex < propertyPath.Length - 7 &&
|
||
|
string.Compare(propertyPath, nameStartIndex - 6, ArrayDataPrefix, 0, 12) == 0)
|
||
|
{
|
||
|
var index = int.Parse(propertyPath.Substring(nameStartIndex + 6, propertyPath.Length - nameStartIndex - 7));
|
||
|
|
||
|
var nameEndIndex = nameStartIndex - 6;
|
||
|
nameStartIndex = propertyPath.LastIndexOf('.', nameEndIndex - 1);
|
||
|
|
||
|
elementName = propertyPath.Substring(nameStartIndex + 1, nameEndIndex - nameStartIndex - 1);
|
||
|
|
||
|
FieldInfo field;
|
||
|
if (nameStartIndex >= 0)
|
||
|
{
|
||
|
parent = GetAccessor(property, propertyPath.Substring(0, nameStartIndex), ref type);
|
||
|
field = GetField(parent, property, type, elementName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
parent = null;
|
||
|
field = GetField(type, elementName);
|
||
|
}
|
||
|
|
||
|
accessor = new CollectionPropertyAccessor(parent, elementName, field, index);
|
||
|
}
|
||
|
else// Single.
|
||
|
{
|
||
|
if (nameStartIndex >= 0)
|
||
|
{
|
||
|
elementName = propertyPath.Substring(nameStartIndex + 1);
|
||
|
parent = GetAccessor(property, propertyPath.Substring(0, nameStartIndex), ref type);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
elementName = propertyPath;
|
||
|
parent = null;
|
||
|
}
|
||
|
|
||
|
var field = GetField(parent, property, type, elementName);
|
||
|
|
||
|
accessor = new PropertyAccessor(parent, elementName, field);
|
||
|
}
|
||
|
|
||
|
pathToAccessor.Add(propertyPath, accessor);
|
||
|
}
|
||
|
|
||
|
if (accessor != null)
|
||
|
{
|
||
|
var field = accessor.GetField(property);
|
||
|
if (field != null)
|
||
|
{
|
||
|
type = field.FieldType;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var value = accessor.GetValue(property);
|
||
|
type = value?.GetType();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return accessor;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns a field with the specified `name` in the `declaringType` or any of its base types.</summary>
|
||
|
/// <remarks>Uses the <see cref="InstanceBindings"/>.</remarks>
|
||
|
public static FieldInfo GetField(PropertyAccessor accessor, SerializedProperty property, Type declaringType, string name)
|
||
|
{
|
||
|
declaringType = accessor?.GetFieldElementType(property) ?? declaringType;
|
||
|
return GetField(declaringType, name);
|
||
|
}
|
||
|
|
||
|
/// <summary>Returns a field with the specified `name` in the `declaringType` or any of its base types.</summary>
|
||
|
/// <remarks>Uses the <see cref="InstanceBindings"/>.</remarks>
|
||
|
public static FieldInfo GetField(Type declaringType, string name)
|
||
|
{
|
||
|
while (declaringType != null)
|
||
|
{
|
||
|
var field = declaringType.GetField(name, InstanceBindings);
|
||
|
if (field != null)
|
||
|
return field;
|
||
|
|
||
|
declaringType = declaringType.BaseType;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region PropertyAccessor
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Editor-Only]
|
||
|
/// A wrapper for accessing the underlying values and fields of a <see cref="SerializedProperty"/>.
|
||
|
/// </summary>
|
||
|
public class PropertyAccessor
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The accessor for the field which this accessor is nested inside.</summary>
|
||
|
public readonly PropertyAccessor Parent;
|
||
|
|
||
|
/// <summary>The name of the field wrapped by this accessor.</summary>
|
||
|
public readonly string Name;
|
||
|
|
||
|
/// <summary>The field wrapped by this accessor.</summary>
|
||
|
protected readonly FieldInfo Field;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The type of the wrapped <see cref="Field"/>.
|
||
|
/// Or if it's a collection, this is the type of items in the collection.
|
||
|
/// </summary>
|
||
|
protected readonly Type FieldElementType;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Internal] Creates a new <see cref="PropertyAccessor"/>.</summary>
|
||
|
internal PropertyAccessor(PropertyAccessor parent, string name, FieldInfo field)
|
||
|
: this(parent, name, field, field?.FieldType)
|
||
|
{ }
|
||
|
|
||
|
/// <summary>Creates a new <see cref="PropertyAccessor"/>.</summary>
|
||
|
protected PropertyAccessor(PropertyAccessor parent, string name, FieldInfo field, Type fieldElementType)
|
||
|
{
|
||
|
Parent = parent;
|
||
|
Name = name;
|
||
|
Field = field;
|
||
|
FieldElementType = fieldElementType;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns the <see cref="Field"/> if there is one or tries to get it from the object's type.</summary>
|
||
|
///
|
||
|
/// <remarks>
|
||
|
/// If this accessor has a <see cref="Parent"/>, the `obj` must be associated with the root
|
||
|
/// <see cref="SerializedProperty"/> and this method will change it to reference the parent field's value.
|
||
|
/// </remarks>
|
||
|
///
|
||
|
/// <example><code>
|
||
|
/// [Serializable]
|
||
|
/// public class InnerClass
|
||
|
/// {
|
||
|
/// public float value;
|
||
|
/// }
|
||
|
///
|
||
|
/// [Serializable]
|
||
|
/// public class RootClass
|
||
|
/// {
|
||
|
/// public InnerClass inner;
|
||
|
/// }
|
||
|
///
|
||
|
/// public class MyBehaviour : MonoBehaviour
|
||
|
/// {
|
||
|
/// public RootClass root;
|
||
|
/// }
|
||
|
///
|
||
|
/// [UnityEditor.CustomEditor(typeof(MyBehaviour))]
|
||
|
/// public class MyEditor : UnityEditor.Editor
|
||
|
/// {
|
||
|
/// private void OnEnable()
|
||
|
/// {
|
||
|
/// var serializedObject = new SerializedObject(target);
|
||
|
/// var rootProperty = serializedObject.FindProperty("root");
|
||
|
/// var innerProperty = rootProperty.FindPropertyRelative("inner");
|
||
|
/// var valueProperty = innerProperty.FindPropertyRelative("value");
|
||
|
///
|
||
|
/// var accessor = valueProperty.GetAccessor();
|
||
|
///
|
||
|
/// object obj = target;
|
||
|
/// var valueField = accessor.GetField(ref obj);
|
||
|
/// // valueField is a FieldInfo referring to InnerClass.value.
|
||
|
/// // obj now holds the ((MyBehaviour)target).root.inner.
|
||
|
/// }
|
||
|
/// }
|
||
|
/// </code></example>
|
||
|
///
|
||
|
public FieldInfo GetField(ref object obj)
|
||
|
{
|
||
|
if (Parent != null)
|
||
|
obj = Parent.GetValue(obj);
|
||
|
|
||
|
if (Field != null)
|
||
|
return Field;
|
||
|
|
||
|
if (obj is null)
|
||
|
return null;
|
||
|
|
||
|
return Serialization.GetField(obj.GetType(), Name);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the <see cref="Field"/> if there is one, otherwise calls <see cref="GetField(ref object)"/>.
|
||
|
/// </summary>
|
||
|
public FieldInfo GetField(object obj) => Field ?? GetField(ref obj);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calls <see cref="GetField(object)"/> with the <see cref="SerializedObject.targetObject"/>.
|
||
|
/// </summary>
|
||
|
public FieldInfo GetField(SerializedObject serializedObject)
|
||
|
=> serializedObject != null ? GetField(serializedObject.targetObject) : null;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calls <see cref="GetField(SerializedObject)"/> with the
|
||
|
/// <see cref="SerializedProperty.serializedObject"/>.
|
||
|
/// </summary>
|
||
|
public FieldInfo GetField(SerializedProperty serializedProperty)
|
||
|
=> serializedProperty != null ? GetField(serializedProperty.serializedObject) : null;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the <see cref="FieldElementType"/> if there is one, otherwise calls <see cref="GetField(ref object)"/>
|
||
|
/// and returns its <see cref="FieldInfo.FieldType"/>.
|
||
|
/// </summary>
|
||
|
public virtual Type GetFieldElementType(object obj) => FieldElementType ?? GetField(ref obj)?.FieldType;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calls <see cref="GetFieldElementType(object)"/> with the
|
||
|
/// <see cref="SerializedObject.targetObject"/>.
|
||
|
/// </summary>
|
||
|
public Type GetFieldElementType(SerializedObject serializedObject)
|
||
|
=> serializedObject != null ? GetFieldElementType(serializedObject.targetObject) : null;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calls <see cref="GetFieldElementType(SerializedObject)"/> with the
|
||
|
/// <see cref="SerializedProperty.serializedObject"/>.
|
||
|
/// </summary>
|
||
|
public Type GetFieldElementType(SerializedProperty serializedProperty)
|
||
|
=> serializedProperty != null ? GetFieldElementType(serializedProperty.serializedObject) : null;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
|
||
|
/// the value of the <see cref="Field"/>.
|
||
|
/// </summary>
|
||
|
public virtual object GetValue(object obj)
|
||
|
=> GetField(ref obj)?.GetValue(obj);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
|
||
|
/// the value of the <see cref="Field"/>.
|
||
|
/// </summary>
|
||
|
public object GetValue(SerializedObject serializedObject)
|
||
|
=> serializedObject != null ? GetValue(serializedObject.targetObject) : null;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
|
||
|
/// the value of the <see cref="Field"/>.
|
||
|
/// </summary>
|
||
|
public object GetValue(SerializedProperty serializedProperty)
|
||
|
=> serializedProperty != null ? GetValue(serializedProperty.serializedObject) : null;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
|
||
|
/// of the <see cref="Field"/>.
|
||
|
/// </summary>
|
||
|
public virtual void SetValue(object obj, object value)
|
||
|
{
|
||
|
var field = GetField(ref obj);
|
||
|
|
||
|
if (field is null ||
|
||
|
obj is null)
|
||
|
return;
|
||
|
|
||
|
field.SetValue(obj, value);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
|
||
|
/// of the <see cref="Field"/>.
|
||
|
/// </summary>
|
||
|
public void SetValue(SerializedObject serializedObject, object value)
|
||
|
{
|
||
|
if (serializedObject != null)
|
||
|
SetValue(serializedObject.targetObject, value);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
|
||
|
/// of the <see cref="Field"/>.
|
||
|
/// </summary>
|
||
|
public void SetValue(SerializedProperty serializedProperty, object value)
|
||
|
{
|
||
|
if (serializedProperty != null)
|
||
|
SetValue(serializedProperty.serializedObject, value);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Resets the value of the <see cref="SerializedProperty"/> to the default value of its type by executing
|
||
|
/// its constructor and field initializers.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// If you don't want to run constructors and field initializers, you can call
|
||
|
/// <see cref="Serialization.ResetValue"/> instead.
|
||
|
/// </remarks>
|
||
|
/// <example><code>
|
||
|
/// SerializedProperty property;
|
||
|
/// property.GetAccessor().ResetValue(property);
|
||
|
/// </code></example>
|
||
|
public void ResetValue(SerializedProperty property, string undoName = "Inspector")
|
||
|
{
|
||
|
property.RecordUndo(undoName);
|
||
|
property.serializedObject.ApplyModifiedProperties();
|
||
|
|
||
|
var type = GetValue(property)?.GetType();
|
||
|
var value = type != null ? Activator.CreateInstance(type) : null;
|
||
|
SetValue(property, value);
|
||
|
|
||
|
property.serializedObject.Update();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns a description of this accessor's path.</summary>
|
||
|
public override string ToString()
|
||
|
{
|
||
|
if (Parent != null)
|
||
|
return $"{Parent}.{Name}";
|
||
|
else
|
||
|
return Name;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns a this accessor's <see cref="SerializedProperty.propertyPath"/>.</summary>
|
||
|
public virtual string GetPath()
|
||
|
{
|
||
|
if (Parent != null)
|
||
|
return $"{Parent.GetPath()}.{Name}";
|
||
|
else
|
||
|
return Name;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region CollectionPropertyAccessor
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Editor-Only] A <see cref="PropertyAccessor"/> for a specific element index in a collection.</summary>
|
||
|
public class CollectionPropertyAccessor : PropertyAccessor
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The index of the array element this accessor targets.</summary>
|
||
|
public readonly int ElementIndex;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Internal] Creates a new <see cref="CollectionPropertyAccessor"/>.</summary>
|
||
|
internal CollectionPropertyAccessor(PropertyAccessor parent, string name, FieldInfo field, int elementIndex)
|
||
|
: base(parent, name, field, GetElementType(field?.FieldType))
|
||
|
{
|
||
|
ElementIndex = elementIndex;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <inheritdoc/>
|
||
|
public override Type GetFieldElementType(object obj) => FieldElementType ?? GetElementType(GetField(ref obj)?.FieldType);
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns the type of elements in the array.</summary>
|
||
|
public static Type GetElementType(Type fieldType)
|
||
|
{
|
||
|
if (fieldType == null)
|
||
|
return null;
|
||
|
|
||
|
if (fieldType.IsArray)
|
||
|
return fieldType.GetElementType();
|
||
|
|
||
|
if (fieldType.IsGenericType)
|
||
|
return fieldType.GetGenericArguments()[0];
|
||
|
|
||
|
Debug.LogWarning($"{nameof(Serialization)}.{nameof(CollectionPropertyAccessor)}:" +
|
||
|
$" unable to determine element type for {fieldType}");
|
||
|
return fieldType;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns the collection object targeted by this accessor.</summary>
|
||
|
public object GetCollection(object obj) => base.GetValue(obj);
|
||
|
|
||
|
/// <inheritdoc/>
|
||
|
public override object GetValue(object obj)
|
||
|
{
|
||
|
var collection = base.GetValue(obj);
|
||
|
if (collection == null)
|
||
|
return null;
|
||
|
|
||
|
var list = collection as IList;
|
||
|
if (list != null)
|
||
|
{
|
||
|
if (ElementIndex < list.Count)
|
||
|
return list[ElementIndex];
|
||
|
else
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
var enumerator = ((IEnumerable)collection).GetEnumerator();
|
||
|
|
||
|
for (int i = 0; i < ElementIndex; i++)
|
||
|
{
|
||
|
if (!enumerator.MoveNext())
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return enumerator.Current;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Sets the collection object targeted by this accessor.</summary>
|
||
|
public void SetCollection(object obj, object value) => base.SetValue(obj, value);
|
||
|
|
||
|
/// <inheritdoc/>
|
||
|
public override void SetValue(object obj, object value)
|
||
|
{
|
||
|
var collection = base.GetValue(obj);
|
||
|
if (collection == null)
|
||
|
return;
|
||
|
|
||
|
var list = collection as IList;
|
||
|
if (list != null)
|
||
|
{
|
||
|
if (ElementIndex < list.Count)
|
||
|
list[ElementIndex] = value;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
throw new InvalidOperationException($"{nameof(SetValue)} failed: field doesn't implement {nameof(IList)}.");
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns a description of this accessor's path.</summary>
|
||
|
public override string ToString() => $"{base.ToString()}[{ElementIndex}]";
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns the <see cref="SerializedProperty.propertyPath"/> of the array containing the target.</summary>
|
||
|
public string GetCollectionPath() => base.GetPath();
|
||
|
|
||
|
/// <summary>Returns this accessor's <see cref="SerializedProperty.propertyPath"/>.</summary>
|
||
|
public override string GetPath() => $"{base.GetPath()}{ArrayDataPrefix}{ElementIndex}{ArrayDataSuffix}";
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
#endif
|