123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- #if UNITY_EDITOR
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using System.Text;
- using UnityEditor;
- using UnityEngine.InputSystem.Utilities;
-
- namespace UnityEngine.InputSystem.Editor
- {
- /// <summary>
- /// Helpers for working with <see cref="SerializedProperty"/> in the editor.
- /// </summary>
- internal static class SerializedPropertyHelpers
- {
- // Show a PropertyField with a greyed-out default text if the field is empty and not being edited.
- // This is meant to communicate the fact that filling these properties is optional and that Unity will
- // use reasonable defaults if left empty.
- public static void PropertyFieldWithDefaultText(this SerializedProperty prop, GUIContent label, string defaultText)
- {
- GUI.SetNextControlName(label.text);
- var rt = GUILayoutUtility.GetRect(label, GUI.skin.textField);
-
- EditorGUI.PropertyField(rt, prop, label);
- if (string.IsNullOrEmpty(prop.stringValue) && GUI.GetNameOfFocusedControl() != label.text && Event.current.type == EventType.Repaint)
- {
- using (new EditorGUI.DisabledScope(true))
- {
- rt.xMin += EditorGUIUtility.labelWidth;
- GUI.skin.textField.Draw(rt, new GUIContent(defaultText), false, false, false, false);
- }
- }
- }
-
- public static SerializedProperty GetParentProperty(this SerializedProperty property)
- {
- var path = property.propertyPath;
- var lastDot = path.LastIndexOf('.');
- if (lastDot == -1)
- return null;
- var parentPath = path.Substring(0, lastDot);
- return property.serializedObject.FindProperty(parentPath);
- }
-
- public static SerializedProperty GetArrayPropertyFromElement(this SerializedProperty property)
- {
- // Arrays have a structure of 'arrayName.Array.data[index]'.
- // Given property should be element and thus 'data[index]'.
- var arrayProperty = property.GetParentProperty();
- Debug.Assert(arrayProperty.name == "Array", "Expecting 'Array' property");
- return arrayProperty.GetParentProperty();
- }
-
- public static int GetIndexOfArrayElement(this SerializedProperty property)
- {
- if (property == null)
- return -1;
- var propertyPath = property.propertyPath;
- if (propertyPath[propertyPath.Length - 1] != ']')
- return -1;
- var lastIndexOfLeftBracket = propertyPath.LastIndexOf('[');
- if (int.TryParse(
- propertyPath.Substring(lastIndexOfLeftBracket + 1, propertyPath.Length - lastIndexOfLeftBracket - 2),
- out var index))
- return index;
- return -1;
- }
-
- public static Type GetArrayElementType(this SerializedProperty property)
- {
- Debug.Assert(property.isArray, $"Property {property.propertyPath} is not an array");
-
- var fieldType = property.GetFieldType();
- if (fieldType == null)
- throw new ArgumentException($"Cannot determine managed field type of {property.propertyPath}",
- nameof(property));
-
- return fieldType.GetElementType();
- }
-
- public static void ResetValuesToDefault(this SerializedProperty property)
- {
- var isString = property.propertyType == SerializedPropertyType.String;
-
- if (property.isArray && !isString)
- {
- property.ClearArray();
- }
- else if (property.hasChildren && !isString)
- {
- foreach (var child in property.GetChildren())
- ResetValuesToDefault(child);
- }
- else
- {
- switch (property.propertyType)
- {
- case SerializedPropertyType.Float:
- property.floatValue = default(float);
- break;
-
- case SerializedPropertyType.Boolean:
- property.boolValue = default(bool);
- break;
-
- case SerializedPropertyType.Enum:
- case SerializedPropertyType.Integer:
- property.intValue = default(int);
- break;
-
- case SerializedPropertyType.String:
- property.stringValue = string.Empty;
- break;
-
- case SerializedPropertyType.ObjectReference:
- property.objectReferenceValue = null;
- break;
- }
- }
- }
-
- public static string ToJson(this SerializedObject serializedObject)
- {
- return JsonUtility.ToJson(serializedObject, prettyPrint: true);
- }
-
- // The following is functionality that allows turning Unity data into text and text
- // back into Unity data. Given that this is essential functionality for any kind of
- // copypaste support, I'm not sure why the Unity editor API isn't providing this out
- // of the box. Internally, we do have support for this on a whole-object kind of level
- // but not for parts of serialized objects.
-
- /// <summary>
- ///
- /// </summary>
- /// <param name="property"></param>
- /// <returns></returns>
- /// <remarks>
- /// Converting entire objects to JSON is easy using Unity's serialization system but we cannot
- /// easily convert just a part of the serialized graph to JSON (or any text format for that matter)
- /// and then recreate the same data from text through SerializedProperties. This method helps by manually
- /// turning an arbitrary part of a graph into JSON which can then be used with <see cref="RestoreFromJson"/>
- /// to write the data back into an existing property.
- ///
- /// The primary use for this is copy-paste where serialized data needs to be stored in
- /// <see cref="EditorGUIUtility.systemCopyBuffer"/>.
- /// </remarks>
- public static string CopyToJson(this SerializedProperty property, bool ignoreObjectReferences = false)
- {
- var buffer = new StringBuilder();
- CopyToJson(property, buffer, ignoreObjectReferences);
- return buffer.ToString();
- }
-
- public static void CopyToJson(this SerializedProperty property, StringBuilder buffer, bool ignoreObjectReferences = false)
- {
- CopyToJson(property, buffer, noPropertyName: true, ignoreObjectReferences: ignoreObjectReferences);
- }
-
- private static void CopyToJson(this SerializedProperty property, StringBuilder buffer, bool noPropertyName, bool ignoreObjectReferences)
- {
- var propertyType = property.propertyType;
- if (ignoreObjectReferences && propertyType == SerializedPropertyType.ObjectReference)
- return;
-
- // Property name.
- if (!noPropertyName)
- {
- buffer.Append('"');
- buffer.Append(property.name);
- buffer.Append('"');
- buffer.Append(':');
- }
-
- // Strings are classified as arrays and have children.
- var isString = propertyType == SerializedPropertyType.String;
-
- // Property value.
- if (property.isArray && !isString)
- {
- buffer.Append('[');
- var arraySize = property.arraySize;
- var isFirst = true;
- for (var i = 0; i < arraySize; ++i)
- {
- var element = property.GetArrayElementAtIndex(i);
- if (ignoreObjectReferences && element.propertyType == SerializedPropertyType.ObjectReference)
- continue;
- if (!isFirst)
- buffer.Append(',');
- CopyToJson(element, buffer, true, ignoreObjectReferences);
- isFirst = false;
- }
- buffer.Append(']');
- }
- else if (property.hasChildren && !isString)
- {
- // Any structured data we represent as a JSON object.
-
- buffer.Append('{');
- var isFirst = true;
- foreach (var child in property.GetChildren())
- {
- if (ignoreObjectReferences && child.propertyType == SerializedPropertyType.ObjectReference)
- continue;
- if (!isFirst)
- buffer.Append(',');
- CopyToJson(child, buffer, false, ignoreObjectReferences);
- isFirst = false;
- }
- buffer.Append('}');
- }
- else
- {
- switch (propertyType)
- {
- case SerializedPropertyType.Enum:
- case SerializedPropertyType.Integer:
- buffer.Append(property.intValue);
- break;
-
- case SerializedPropertyType.Float:
- buffer.Append(property.floatValue);
- break;
-
- case SerializedPropertyType.String:
- buffer.Append('"');
- buffer.Append(property.stringValue.Escape());
- buffer.Append('"');
- break;
-
- case SerializedPropertyType.Boolean:
- if (property.boolValue)
- buffer.Append("true");
- else
- buffer.Append("false");
- break;
-
- ////TODO: other property types
- default:
- throw new NotImplementedException($"Support for {property.propertyType} property type");
- }
- }
- }
-
- public static void RestoreFromJson(this SerializedProperty property, string json)
- {
- var parser = new JsonParser(json);
- RestoreFromJson(property, ref parser);
- }
-
- public static void RestoreFromJson(this SerializedProperty property, ref JsonParser parser)
- {
- var isString = property.propertyType == SerializedPropertyType.String;
-
- if (property.isArray && !isString)
- {
- property.ClearArray();
- parser.ParseToken('[');
- while (!parser.ParseToken(']') && !parser.isAtEnd)
- {
- var index = property.arraySize;
- property.InsertArrayElementAtIndex(index);
- var elementProperty = property.GetArrayElementAtIndex(index);
- RestoreFromJson(elementProperty, ref parser);
- parser.ParseToken(',');
- }
- }
- else if (property.hasChildren && !isString)
- {
- parser.ParseToken('{');
- while (!parser.ParseToken('}') && !parser.isAtEnd)
- {
- parser.ParseStringValue(out var propertyName);
- parser.ParseToken(':');
-
- var childProperty = property.FindPropertyRelative(propertyName.ToString());
- if (childProperty == null)
- throw new ArgumentException($"Cannot find property '{propertyName}' in {property}", nameof(property));
-
- RestoreFromJson(childProperty, ref parser);
- parser.ParseToken(',');
- }
- }
- else
- {
- switch (property.propertyType)
- {
- case SerializedPropertyType.Float:
- {
- parser.ParseNumber(out var num);
- property.floatValue = (float)num.ToDouble();
- break;
- }
-
- case SerializedPropertyType.String:
- {
- parser.ParseStringValue(out var str);
- property.stringValue = str.ToString();
- break;
- }
-
- case SerializedPropertyType.Boolean:
- {
- parser.ParseBooleanValue(out var b);
- property.boolValue = b.ToBoolean();
- break;
- }
-
- case SerializedPropertyType.Enum:
- case SerializedPropertyType.Integer:
- {
- parser.ParseNumber(out var num);
- property.intValue = (int)num.ToInteger();
- break;
- }
-
- default:
- throw new NotImplementedException(
- $"Restoring property value of type {property.propertyType} (property: {property})");
- }
- }
- }
-
- public static IEnumerable<SerializedProperty> GetChildren(this SerializedProperty property)
- {
- if (!property.hasChildren)
- yield break;
-
- using (var iter = property.Copy())
- {
- var end = iter.GetEndProperty(true);
-
- // Go to first child.
- if (!iter.Next(true))
- yield break; // Shouldn't happen; we've already established we have children.
-
- // Iterate over children.
- while (!SerializedProperty.EqualContents(iter, end))
- {
- yield return iter;
- if (!iter.Next(false))
- break;
- }
- }
- }
-
- public static FieldInfo GetField(this SerializedProperty property)
- {
- var objectType = property.serializedObject.targetObject.GetType();
- var currentSerializableType = objectType;
- var pathComponents = property.propertyPath.Split('.');
-
- FieldInfo result = null;
- foreach (var component in pathComponents)
- {
- // Handle arrays. They are followed by "Array" and "data[N]" elements.
- if (result != null && currentSerializableType.IsArray)
- {
- if (component == "Array")
- continue;
-
- if (component.StartsWith("data["))
- {
- currentSerializableType = currentSerializableType.GetElementType();
- continue;
- }
- }
-
- result = currentSerializableType.GetField(component,
- BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);
- if (result == null)
- return null;
- currentSerializableType = result.FieldType;
- }
-
- return result;
- }
-
- public static Type GetFieldType(this SerializedProperty property)
- {
- return GetField(property)?.FieldType;
- }
-
- public static void SetStringValue(this SerializedProperty property, string propertyName, string value)
- {
- var propertyRelative = property?.FindPropertyRelative(propertyName);
- if (propertyRelative != null)
- propertyRelative.stringValue = value;
- }
- }
- }
- #endif // UNITY_EDITOR
|