using System; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using Module.Inspector.Editor.Utilities; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; namespace Module.Inspector.Editor { public static class SerializedPropertyExtension { public enum ECompareType : byte { False, True, Unknown }; public static SerializedProperty GetParent(this SerializedProperty sp) { string propertyPath = GetRootPath(sp.propertyPath); return !propertyPath.Equals(sp.propertyPath) ? sp.serializedObject.FindProperty(propertyPath) : null; } public static SerializedProperty GetSibling(this SerializedProperty sp, string fieldName) { string propertyPath = GetRootPath(sp.propertyPath); if (string.IsNullOrEmpty(propertyPath)) { propertyPath = fieldName; } else { if (propertyPath.EndsWith(".Array")) { propertyPath = propertyPath.Substring(0, propertyPath.Length - 6); SerializedProperty spArray = sp.serializedObject.FindProperty(propertyPath); int index = spArray.IndexOfProperty(sp); if (index != -1) propertyPath += ".Array.data[" + index + "]"; } propertyPath += "." + fieldName; } return sp.serializedObject.FindProperty(propertyPath); } public static SerializedProperty GetRelativeProperty(this SerializedProperty sp, string relativePath) { SerializedProperty rp = sp; string[] split = relativePath.Split('/'); for (var i = 0; i < split.Length; i++) { rp = split[i].Equals("PARENT") ? GetParent(rp) : GetSibling(rp, split[i]); if (rp == null) break; } return rp; } public static bool IsSiblingValue(this SerializedProperty sp, string siblingFieldName, object siblingFieldValue, bool useFieldValue) { if (string.IsNullOrEmpty(siblingFieldName)) return false; SerializedProperty spSibling = sp.GetRelativeProperty(siblingFieldName); return EditorSerializedPropertyUtility.IsValue(spSibling, siblingFieldValue, useFieldValue); } public static ECompareType IsGreaterOrEqualToSiblingValue(this SerializedProperty sp, string siblingFieldName) { if (string.IsNullOrEmpty(siblingFieldName)) return ECompareType.Unknown; SerializedProperty spSibling = sp.GetRelativeProperty(siblingFieldName); if (spSibling == null) return ECompareType.Unknown; if (spSibling.propertyType != sp.propertyType) return ECompareType.Unknown; if (sp.propertyType == SerializedPropertyType.Integer) return sp.IsGreaterOrEqualToSiblingValue(siblingFieldName, sp.intValue); if (sp.propertyType == SerializedPropertyType.Float) return sp.IsGreaterOrEqualToSiblingValue(siblingFieldName, sp.floatValue); if (sp.propertyType == SerializedPropertyType.Enum) return sp.IsGreaterOrEqualToSiblingValue(siblingFieldName, sp.intValue); return ECompareType.Unknown; } public static ECompareType IsGreaterOrEqualToSiblingValue(this SerializedProperty sp, string siblingFieldName, object siblingFieldValue) { if (string.IsNullOrEmpty(siblingFieldName)) return ECompareType.Unknown; SerializedProperty spSibling = sp.GetRelativeProperty(siblingFieldName); if (spSibling == null) return ECompareType.Unknown; if (spSibling.propertyType == SerializedPropertyType.Integer && siblingFieldValue is int i) return spSibling.intValue <= i ? ECompareType.True : ECompareType.False; if (spSibling.propertyType == SerializedPropertyType.Float && siblingFieldValue is float f) return spSibling.floatValue <= f ? ECompareType.True : ECompareType.False; if (spSibling.propertyType == SerializedPropertyType.Enum && siblingFieldValue is int e) return spSibling.intValue <= e ? ECompareType.True : ECompareType.False; return ECompareType.Unknown; } public static ECompareType IsSmallerOrEqualToSiblingValue(this SerializedProperty sp, string siblingFieldName) { if (string.IsNullOrEmpty(siblingFieldName)) return ECompareType.Unknown; SerializedProperty spSibling = sp.GetRelativeProperty(siblingFieldName); if (spSibling == null) return ECompareType.Unknown; if (spSibling.propertyType != sp.propertyType) return ECompareType.Unknown; if (sp.propertyType == SerializedPropertyType.Integer) return sp.IsSmallerOrEqualToSiblingValue(siblingFieldName, sp.intValue); if (sp.propertyType == SerializedPropertyType.Float) return sp.IsSmallerOrEqualToSiblingValue(siblingFieldName, sp.floatValue); if (sp.propertyType == SerializedPropertyType.Enum) return sp.IsSmallerOrEqualToSiblingValue(siblingFieldName, sp.intValue); return ECompareType.Unknown; } public static ECompareType IsSmallerOrEqualToSiblingValue(this SerializedProperty sp, string siblingFieldName, object siblingFieldValue) { if (string.IsNullOrEmpty(siblingFieldName)) return ECompareType.Unknown; SerializedProperty spSibling = sp.GetRelativeProperty(siblingFieldName); if (spSibling == null) return ECompareType.Unknown; if (spSibling.propertyType == SerializedPropertyType.Integer && siblingFieldValue is int i) return spSibling.intValue >= i ? ECompareType.True : ECompareType.False; if (spSibling.propertyType == SerializedPropertyType.Float && siblingFieldValue is float f) return spSibling.floatValue >= f ? ECompareType.True : ECompareType.False; if (spSibling.propertyType == SerializedPropertyType.Enum && siblingFieldValue is int e) return spSibling.intValue >= e ? ECompareType.True : ECompareType.False; return ECompareType.Unknown; } public static void SetValueTo(this SerializedProperty sp, SerializedProperty other) { if (sp.propertyType != other.propertyType) return; switch (sp.propertyType) { case SerializedPropertyType.LayerMask: case SerializedPropertyType.Integer: case SerializedPropertyType.Enum: sp.intValue = other.intValue; break; case SerializedPropertyType.Boolean: sp.boolValue = other.boolValue; break; case SerializedPropertyType.Float: sp.floatValue = other.floatValue; break; case SerializedPropertyType.String: sp.stringValue = other.stringValue; break; case SerializedPropertyType.Color: sp.colorValue = other.colorValue; break; case SerializedPropertyType.ObjectReference: sp.objectReferenceValue = other.objectReferenceValue; break; case SerializedPropertyType.Vector2: sp.vector2Value = other.vector2Value; break; case SerializedPropertyType.Vector3: sp.vector3Value = other.vector3Value; break; case SerializedPropertyType.Vector4: sp.vector4Value = other.vector4Value; break; case SerializedPropertyType.Rect: sp.rectValue = other.rectValue; break; case SerializedPropertyType.AnimationCurve: sp.animationCurveValue = other.animationCurveValue; break; case SerializedPropertyType.Bounds: sp.boundsValue = other.boundsValue; break; case SerializedPropertyType.Quaternion: sp.quaternionValue = other.quaternionValue; break; case SerializedPropertyType.Vector2Int: sp.vector2IntValue = other.vector2IntValue; break; case SerializedPropertyType.Vector3Int: sp.vector3IntValue = other.vector3IntValue; break; case SerializedPropertyType.RectInt: sp.rectIntValue = other.rectIntValue; break; case SerializedPropertyType.BoundsInt: sp.boundsIntValue = other.boundsIntValue; break; } } public static bool TrySetValueTo(this SerializedProperty sp, object value, bool allowDuplicate) { if (sp.isArray && sp.propertyType != SerializedPropertyType.String) { if (!allowDuplicate && sp.Contains(value)) return true; Type type = value.GetType(); if (!type.IsArray) { sp.InsertArrayElementAtIndex(sp.arraySize); sp = sp.GetArrayElementAtIndex(sp.arraySize - 1); } } switch (sp.propertyType) { case SerializedPropertyType.LayerMask: case SerializedPropertyType.Integer: case SerializedPropertyType.Enum: if (!(value is int i)) return false; sp.intValue = i; return true; case SerializedPropertyType.Boolean: if (!(value is bool b)) return false; sp.boolValue = b; return true; case SerializedPropertyType.Float: if (!(value is float f)) return false; sp.floatValue = f; return true; case SerializedPropertyType.String: if (!(value is string str)) return false; sp.stringValue = str; return true; case SerializedPropertyType.Color: if (!(value is Color c)) return false; sp.colorValue = c; return true; case SerializedPropertyType.ObjectReference: if (!(value is Object o)) return false; sp.objectReferenceValue = o; return true; case SerializedPropertyType.Vector2: if (!(value is Vector2 v2)) return false; sp.vector2Value = v2; return true; case SerializedPropertyType.Vector3: if (!(value is Vector3 v3)) return false; sp.vector3Value = v3; return true; case SerializedPropertyType.Vector4: if (!(value is Vector4 v4)) return false; sp.vector4Value = v4; return true; case SerializedPropertyType.Rect: if (!(value is Rect r)) return false; sp.rectValue = r; return true; case SerializedPropertyType.AnimationCurve: if (!(value is AnimationCurve ac)) return false; sp.animationCurveValue = ac; return true; case SerializedPropertyType.Bounds: if (!(value is Bounds bounds)) return false; sp.boundsValue = bounds; return true; case SerializedPropertyType.Quaternion: if (!(value is Quaternion q)) return false; sp.quaternionValue = q; return true; case SerializedPropertyType.Vector2Int: if (!(value is Vector2Int v2Int)) return false; sp.vector2IntValue = v2Int; return true; case SerializedPropertyType.Vector3Int: if (!(value is Vector3Int v3Int)) return false; sp.vector3IntValue = v3Int; return true; case SerializedPropertyType.RectInt: if (!(value is RectInt ri)) return false; sp.rectIntValue = ri; return true; case SerializedPropertyType.BoundsInt: if (!(value is BoundsInt bi)) return false; sp.boundsIntValue = bi; return true; } return false; } private static bool Contains(this SerializedProperty sp, object value) { if (sp.isArray) { for (var i = 0; i < sp.arraySize; i++) { SerializedProperty temp = sp.GetArrayElementAtIndex(i); if (temp.Contains(value)) return true; } } else { try { switch (sp.propertyType) { case SerializedPropertyType.LayerMask: case SerializedPropertyType.Integer: case SerializedPropertyType.Enum: return Convert.ToInt32(value) == sp.intValue; case SerializedPropertyType.Boolean: return Convert.ToBoolean(value) == sp.boolValue; case SerializedPropertyType.Float: return Mathf.Approximately(Convert.ToSingle(value), sp.floatValue); case SerializedPropertyType.String: return Convert.ToString(value).Equals(sp.stringValue); case SerializedPropertyType.ObjectReference: if (!(value is Object obj)) return false; return sp.objectReferenceValue == obj; } } catch (Exception e) { Debug.LogException(e); } } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string GetRootPath(string propertyPath) { int lastIndex = propertyPath.LastIndexOf('.'); return lastIndex != -1 ? propertyPath.Remove(lastIndex) : string.Empty; } public static T GetValue(this SerializedProperty property) where T : class { return property.GetValue() as T; } private static object GetValue(this SerializedProperty property) { object obj = property.serializedObject.targetObject; foreach (string path in property.propertyPath.Split('.')) { Type type = obj.GetType(); FieldInfo field = type.GetField(path); obj = field.GetValue(obj); } return obj; } public static Type GetValueType(this SerializedProperty property) { object obj = property.serializedObject.targetObject; Type type = null; foreach (string path in property.propertyPath.Split('.')) { FieldInfo field = obj.GetType().GetField(path); if (field == null) continue; obj = field.GetValue(obj); type = field.FieldType; } if (type != null && type.IsArray) type = type.GetElementType(); return type; } public static void SetArray(this SerializedProperty property, T[] array) where T : Object { property.ClearArray(); for (var i = 0; i < array.Length; i++) { property.InsertArrayElementAtIndex(i); SerializedProperty sp = property.GetArrayElementAtIndex(i); sp.objectReferenceValue = array[i]; } } public static string GetValueAsString(this SerializedProperty property) { switch (property.propertyType) { case SerializedPropertyType.Generic: break; case SerializedPropertyType.Integer: return property.intValue.ToString(); case SerializedPropertyType.Boolean: return property.boolValue.ToString(); case SerializedPropertyType.Float: return property.floatValue.ToString(CultureInfo.InvariantCulture); case SerializedPropertyType.String: return property.stringValue; case SerializedPropertyType.Color: return property.colorValue.ToString(); case SerializedPropertyType.ObjectReference: return property.objectReferenceValue != null ? property.objectReferenceValue.ToString() : "null"; case SerializedPropertyType.LayerMask: // TODO: Return as (Layer0 | Layer1) instead for all set bits return property.intValue.ToString(); case SerializedPropertyType.Enum: return property.enumNames[property.enumValueIndex]; case SerializedPropertyType.Vector2: return property.vector2Value.ToString(); case SerializedPropertyType.Vector3: return property.vector3Value.ToString(); case SerializedPropertyType.Vector4: return property.vector4Value.ToString(); case SerializedPropertyType.Rect: return property.rectValue.ToString(); case SerializedPropertyType.ArraySize: return property.arraySize.ToString(); case SerializedPropertyType.Character: return ((char)property.intValue).ToString(); case SerializedPropertyType.Bounds: return property.boundsValue.ToString(); case SerializedPropertyType.Quaternion: return property.quaternionValue.ToString(); case SerializedPropertyType.ExposedReference: return property.exposedReferenceValue != null ? property.exposedReferenceValue.ToString() : "null"; case SerializedPropertyType.FixedBufferSize: return property.fixedBufferSize.ToString(); case SerializedPropertyType.Vector2Int: return property.vector2IntValue.ToString(); case SerializedPropertyType.Vector3Int: return property.vector3IntValue.ToString(); case SerializedPropertyType.RectInt: return property.rectIntValue.ToString(); case SerializedPropertyType.BoundsInt: return property.boundsIntValue.ToString(); case SerializedPropertyType.ManagedReference: return property.objectReferenceValue != null ? property.objectReferenceValue.ToString() : "null"; } return "Unable to draw value as string"; } public static int IndexOfProperty(this SerializedProperty property, SerializedProperty element) { if (!property.isArray) return -1; for (var i = 0; i < property.arraySize; i++) { SerializedProperty e = property.GetArrayElementAtIndex(i); if (e.propertyPath.Equals(element.propertyPath)) return i; } return -1; } public static FieldInfo GetFieldInfo(this SerializedProperty property) { object obj = property.serializedObject.targetObject; FieldInfo field = null; foreach (string path in property.propertyPath.Split('.')) { field = obj.GetType().GetField(path); if (field != null) obj = field.GetValue(obj); } return field; } } }