using System; using System.Linq.Expressions; using System.Reflection; using UnityEditor; namespace Unity.Cinemachine.Editor { /// /// Helpers for the editor relating to SerializedProperties /// static class SerializedPropertyHelper { /// /// This is a way to get a field name string in such a manner that the compiler will /// generate errors for invalid fields. Much better than directly using strings. /// Usage: instead of /// /// "m_MyField"; /// /// do this: /// /// MyClass myclass = null; /// SerializedPropertyHelper.PropertyName( () => myClass.m_MyField); /// /// /// Magic expression that resolves to a field: () => myClass.m_MyField /// string name of field public static MemberInfo MemberInfo(Expression> exp) { if (exp.Body is not MemberExpression body) { var ubody = (UnaryExpression)exp.Body; body = ubody.Operand as MemberExpression; } return body.Member; } /// /// This is a way to get a field name string in such a manner that the compiler will /// generate errors for invalid fields. Much better than directly using strings. /// Usage: instead of /// /// "m_MyField"; /// /// do this: /// /// MyClass myclass = null; /// SerializedPropertyHelper.PropertyName( () => myClass.m_MyField); /// /// /// Magic expression that resolves to a field: () => myClass.m_MyField /// string name of field public static string PropertyName(Expression> exp) => MemberInfo(exp).Name; /// /// This is a way to get a field tooltip string in such a manner that the compiler will /// generate errors for invalid fields. Much better than directly using strings. /// /// Magic expression that resolves to a field: () => myClass.m_MyField /// Tooltip text public static string PropertyTooltip(Expression> exp) { var attrs = MemberInfo(exp).GetCustomAttributes(typeof(UnityEngine.TooltipAttribute), false); return attrs.Length > 0 ? ((UnityEngine.TooltipAttribute)attrs[0]).tooltip : string.Empty; } /// /// This is a way to get a field name string in such a manner that the compiler will /// generate errors for invalid fields. Much better than directly using strings. /// /// Magic expression that resolves to a field: () => myClass.m_MyField /// Type of field public static Type PropertyType(Expression> exp) { var member = MemberInfo(exp); if (member.MemberType == MemberTypes.Field) return ((FieldInfo)member).FieldType; throw new ArgumentException ( "Input MemberInfo must be of type FieldInfo" ); } /// /// A compiler-assisted (non-string-based) way to call SerializedProperty.FindProperty /// /// The serialized object to search /// Magic expression that resolves to a field: () => myClass.m_MyField /// The resulting SerializedProperty, or null public static SerializedProperty FindProperty(this SerializedObject obj, Expression> exp) { return obj.FindProperty(PropertyName(exp)); } /// /// A compiler-assisted (non-string-based) way to call SerializedProperty.FindPropertyRelative /// /// The serialized object to search /// Magic expression that resolves to a field: () => myClass.m_MyField /// The resulting SerializedProperty, or null public static SerializedProperty FindPropertyRelative(this SerializedProperty obj, Expression> exp) { return obj.FindPropertyRelative(PropertyName(exp)); } /// Get the value of a property, as an object /// The property to query /// The object value of the property public static object GetPropertyValue(SerializedProperty property) { var targetObject = property.serializedObject.targetObject; var field = targetObject.GetType().GetField(property.propertyPath); if (field != null) return field.GetValue(targetObject); var paths = property.propertyPath.Split('.'); if (paths.Length > 1) { var fieldOwner = ReflectionHelpers.GetParentObject(property.propertyPath, targetObject); field = fieldOwner?.GetType().GetField(paths[paths.Length-1]); if (field != null) return field.GetValue(fieldOwner); } return null; } } }