IndieGame/client/Packages/com.unity.inputsystem@1.7.0/InputSystem/Editor/Internal/InputActionSerializationHelpers.cs

744 lines
36 KiB
C#
Raw Normal View History

2024-10-11 10:12:15 +08:00
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;
////TODO: resolving bindings to actions needs to take "{id}" form into account
namespace UnityEngine.InputSystem.Editor
{
// Helpers for doctoring around in InputActions using SerializedProperties.
internal static class InputActionSerializationHelpers
{
public static string GetName(SerializedProperty element)
{
using (var nameProperty = element.FindPropertyRelative("m_Name"))
{
Debug.Assert(nameProperty != null, $"Cannot find m_Name property in {element.propertyPath}");
return nameProperty.stringValue;
}
}
public static Guid GetId(SerializedProperty element)
{
using (var idProperty = element.FindPropertyRelative("m_Id"))
{
Debug.Assert(idProperty != null, $"Cannot find m_Id property in {element.propertyPath}");
return new Guid(idProperty.stringValue);
}
}
public static int GetIndex(SerializedProperty arrayProperty, Guid id)
{
Debug.Assert(arrayProperty.isArray, $"Property {arrayProperty.propertyPath} is not an array");
for (var i = 0; i < arrayProperty.arraySize; ++i)
{
using (var element = arrayProperty.GetArrayElementAtIndex(i))
if (GetId(element) == id)
return i;
}
return -1;
}
public static int GetIndex(SerializedProperty arrayProperty, SerializedProperty arrayElement)
{
return GetIndex(arrayProperty, GetId(arrayElement));
}
public static int GetIndex(SerializedProperty arrayElement)
{
var arrayProperty = arrayElement.GetArrayPropertyFromElement();
return GetIndex(arrayProperty, arrayElement);
}
/// <summary>
/// Starting with the given binding, find the composite that the binding belongs to. The given binding
/// must either be the composite or be part of a composite.
/// </summary>
public static int GetCompositeStartIndex(SerializedProperty bindingArrayProperty, int bindingIndex)
{
for (var i = bindingIndex; i >= 0; --i)
{
var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(i);
var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
if ((bindingFlags & InputBinding.Flags.Composite) != 0)
return i;
Debug.Assert((bindingFlags & InputBinding.Flags.PartOfComposite) != 0,
"Binding is neither a composite nor part of a composite");
}
return -1;
}
public static int GetCompositePartCount(SerializedProperty bindingArrayProperty, int bindingIndex)
{
var compositeStartIndex = GetCompositeStartIndex(bindingArrayProperty, bindingIndex);
if (compositeStartIndex == -1)
return 0;
var numParts = 0;
for (var i = compositeStartIndex + 1; i < bindingArrayProperty.arraySize; ++i, ++numParts)
{
var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(i);
var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
if ((bindingFlags & InputBinding.Flags.PartOfComposite) == 0)
break;
}
return numParts;
}
public static int ConvertBindingIndexOnActionToBindingIndexInArray(SerializedProperty bindingArrayProperty, string actionName,
int bindingIndexOnAction)
{
var bindingCount = bindingArrayProperty.arraySize;
var indexOnAction = -1;
var indexInArray = 0;
for (; indexInArray < bindingCount; ++indexInArray)
{
var bindingActionName = bindingArrayProperty.GetArrayElementAtIndex(indexInArray).FindPropertyRelative("m_Action")
.stringValue;
if (actionName.Equals(bindingActionName, StringComparison.InvariantCultureIgnoreCase))
{
++indexOnAction;
if (indexOnAction == bindingIndexOnAction)
return indexInArray;
}
}
return indexInArray;
}
public static SerializedProperty DuplicateElement(SerializedProperty arrayProperty, SerializedProperty toDuplicate, string name, int index, bool changeName = true)
{
var json = toDuplicate.CopyToJson(true);
var duplicatedProperty = AddElement(arrayProperty, name, index);
duplicatedProperty.RestoreFromJson(json);
if (changeName)
EnsureUniqueName(duplicatedProperty);
AssignUniqueIDs(duplicatedProperty);
return duplicatedProperty;
}
public static void DuplicateAction(SerializedProperty actionMap, SerializedProperty arrayProperty, SerializedProperty toDuplicate, string name)
{
var property = DuplicateElement(arrayProperty, toDuplicate, name, toDuplicate.GetIndexOfArrayElement() + 1);
var newName = property.FindPropertyRelative("m_Name").stringValue;
var bindingsArray = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
var bindings = bindingsArray.Where(binding => binding.FindPropertyRelative("m_Action").stringValue.Equals(name)).ToList();
var index = bindings.Select(b => b.GetIndexOfArrayElement()).Max() + 1;
foreach (var binding in bindings)
{
var newIndex = DuplicateBindingAsPartOfAction(bindingsArray, binding, newName, index);
index = newIndex;
}
}
private static SerializedProperty DuplicateComposite(SerializedProperty bindingsArray, SerializedProperty compositeToDuplicate, string name, string actionName, int index, out int newIndex, bool increaseIndex = true)
{
newIndex = index;
var bindings = GetBindingsForComposite(bindingsArray, compositeToDuplicate);
if (increaseIndex)
newIndex += GetCompositePartCount(bindingsArray, compositeToDuplicate.GetIndexOfArrayElement());
var newComposite = DuplicateElement(bindingsArray, compositeToDuplicate, name, newIndex++, false);
newComposite.FindPropertyRelative("m_Action").stringValue = actionName;
foreach (var binding in bindings)
{
var newBinding = DuplicateElement(bindingsArray, binding, binding.FindPropertyRelative("m_Name").stringValue, newIndex++, false);
newBinding.FindPropertyRelative("m_Action").stringValue = actionName;
}
return newComposite;
}
private static List<SerializedProperty> GetBindingsForComposite(SerializedProperty bindingsArray, SerializedProperty compositeToDuplicate)
{
var compositeBindings = new List<SerializedProperty>();
var compositeStartIndex = GetCompositeStartIndex(bindingsArray, compositeToDuplicate.GetIndexOfArrayElement());
if (compositeStartIndex == -1)
return compositeBindings;
for (var i = compositeStartIndex + 1; i < bindingsArray.arraySize; ++i)
{
var bindingProperty = bindingsArray.GetArrayElementAtIndex(i);
var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
if ((bindingFlags & InputBinding.Flags.PartOfComposite) == 0)
break;
compositeBindings.Add(bindingProperty);
}
return compositeBindings;
}
private static bool IsComposite(SerializedProperty property) => property.FindPropertyRelative("m_Flags").intValue == (int)InputBinding.Flags.Composite;
private static bool IsPartComposite(SerializedProperty property) => property.FindPropertyRelative("m_Flags").intValue == (int)InputBinding.Flags.PartOfComposite;
private static string PropertyName(SerializedProperty property) => property.FindPropertyRelative("m_Name").stringValue;
private static int DuplicateBindingAsPartOfAction(SerializedProperty arrayProperty, SerializedProperty toDuplicate, string newActionName, int index)
{
if (IsComposite(toDuplicate))
{
DuplicateComposite(arrayProperty, toDuplicate, PropertyName(toDuplicate), newActionName, index, out var newIndex, false);
return newIndex;
}
if (IsPartComposite(toDuplicate))
return index;
var duplicatedBinding = DuplicateElement(arrayProperty, toDuplicate, PropertyName(toDuplicate), index++, false);
duplicatedBinding.FindPropertyRelative("m_Action").stringValue = newActionName;
return index;
}
public static int DuplicateBinding(SerializedProperty arrayProperty, SerializedProperty toDuplicate, string newActionName, int index)
{
if (IsComposite(toDuplicate))
{
var newComposite = DuplicateComposite(arrayProperty, toDuplicate, PropertyName(toDuplicate), newActionName, index, out _);
index = newComposite.GetIndexOfArrayElement();
}
else
{
var duplicatedBinding = DuplicateElement(arrayProperty, toDuplicate, PropertyName(toDuplicate), index, false);
duplicatedBinding.FindPropertyRelative("m_Action").stringValue = newActionName;
}
return index;
}
public static SerializedProperty AddElement(SerializedProperty arrayProperty, string name, int index = -1)
{
var uniqueName = FindUniqueName(arrayProperty, name);
if (index < 0)
index = arrayProperty.arraySize;
arrayProperty.InsertArrayElementAtIndex(index);
var elementProperty = arrayProperty.GetArrayElementAtIndex(index);
elementProperty.ResetValuesToDefault();
elementProperty.FindPropertyRelative("m_Name").stringValue = uniqueName;
elementProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
return elementProperty;
}
public static SerializedProperty AddActionMap(SerializedObject asset, int index = -1)
{
if (!(asset.targetObject is InputActionAsset))
throw new InvalidOperationException(
$"Can only add action maps to InputActionAsset objects (actual object is {asset.targetObject}");
var mapArrayProperty = asset.FindProperty("m_ActionMaps");
var name = FindUniqueName(mapArrayProperty, "New action map");
if (index < 0)
index = mapArrayProperty.arraySize;
mapArrayProperty.InsertArrayElementAtIndex(index);
var mapProperty = mapArrayProperty.GetArrayElementAtIndex(index);
mapProperty.FindPropertyRelative("m_Name").stringValue = name;
mapProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
mapProperty.FindPropertyRelative("m_Actions").ClearArray();
mapProperty.FindPropertyRelative("m_Bindings").ClearArray();
return mapProperty;
}
public static void DeleteActionMap(SerializedObject asset, Guid id)
{
var mapArrayProperty = asset.FindProperty("m_ActionMaps");
var mapIndex = GetIndex(mapArrayProperty, id);
if (mapIndex == -1)
throw new ArgumentException($"No map with id {id} in {asset}", nameof(id));
mapArrayProperty.DeleteArrayElementAtIndex(mapIndex);
}
// Append a new action to the end of the set.
public static SerializedProperty AddAction(SerializedProperty actionMap, int index = -1)
{
var actionsArrayProperty = actionMap.FindPropertyRelative("m_Actions");
if (index < 0)
index = actionsArrayProperty.arraySize;
var actionName = FindUniqueName(actionsArrayProperty, "New action");
actionsArrayProperty.InsertArrayElementAtIndex(index);
var actionProperty = actionsArrayProperty.GetArrayElementAtIndex(index);
actionProperty.FindPropertyRelative("m_Name").stringValue = actionName;
actionProperty.FindPropertyRelative("m_Type").intValue = (int)InputActionType.Button; // Default to creating button actions.
actionProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
actionProperty.FindPropertyRelative("m_ExpectedControlType").stringValue = "Button";
actionProperty.FindPropertyRelative("m_Flags").intValue = 0;
actionProperty.FindPropertyRelative("m_Interactions").stringValue = "";
actionProperty.FindPropertyRelative("m_Processors").stringValue = "";
return actionProperty;
}
public static void DeleteActionAndBindings(SerializedProperty actionMap, Guid actionId)
{
using (var actionsArrayProperty = actionMap.FindPropertyRelative("m_Actions"))
using (var bindingsArrayProperty = actionMap.FindPropertyRelative("m_Bindings"))
{
// Find index of action.
var actionIndex = GetIndex(actionsArrayProperty, actionId);
if (actionIndex == -1)
throw new ArgumentException($"No action with ID {actionId} in {actionMap.propertyPath}",
nameof(actionId));
using (var actionsProperty = actionsArrayProperty.GetArrayElementAtIndex(actionIndex))
{
var actionName = GetName(actionsProperty);
var actionIdString = actionId.ToString();
// Delete all bindings that refer to the action by ID or name.
for (var i = 0; i < bindingsArrayProperty.arraySize; ++i)
{
using (var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i))
using (var bindingActionProperty = bindingProperty.FindPropertyRelative("m_Action"))
{
var targetAction = bindingActionProperty.stringValue;
if (targetAction.Equals(actionName, StringComparison.InvariantCultureIgnoreCase) ||
targetAction == actionIdString)
{
bindingsArrayProperty.DeleteArrayElementAtIndex(i);
--i;
}
}
}
}
actionsArrayProperty.DeleteArrayElementAtIndex(actionIndex);
}
}
// Equivalent to InputAction.AddBinding().
public static SerializedProperty AddBinding(SerializedProperty actionProperty,
SerializedProperty actionMapProperty = null, SerializedProperty afterBinding = null,
string groups = "", string path = "", string name = "",
string interactions = "", string processors = "",
InputBinding.Flags flags = InputBinding.Flags.None)
{
var bindingsArrayProperty = actionMapProperty != null
? actionMapProperty.FindPropertyRelative("m_Bindings")
: actionProperty.FindPropertyRelative("m_SingletonActionBindings");
var bindingsCount = bindingsArrayProperty.arraySize;
var actionName = actionProperty.FindPropertyRelative("m_Name").stringValue;
int bindingIndex;
if (afterBinding != null)
{
// If we're supposed to put the binding right after another binding, find the
// binding's index. Also, if it's a composite, skip past all its parts.
bindingIndex = GetIndex(bindingsArrayProperty, afterBinding);
if (IsCompositeBinding(afterBinding))
bindingIndex += GetCompositePartCount(bindingsArrayProperty, bindingIndex);
++bindingIndex; // Put it *after* the binding.
}
else
{
// Find the index of the last binding for the action in the array.
var indexOfLastBindingForAction = -1;
for (var i = 0; i < bindingsCount; ++i)
{
var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i);
var bindingActionName = bindingProperty.FindPropertyRelative("m_Action").stringValue;
if (actionName.Equals(bindingActionName, StringComparison.InvariantCultureIgnoreCase))
indexOfLastBindingForAction = i;
}
// Insert after last binding or at end of array.
bindingIndex = indexOfLastBindingForAction != -1 ? indexOfLastBindingForAction + 1 : bindingsCount;
}
////TODO: bind using {id} rather than action name
return AddBindingToBindingArray(bindingsArrayProperty,
bindingIndex: bindingIndex,
actionName: actionName,
groups: groups,
path: path,
name: name,
interactions: interactions,
processors: processors,
flags: flags);
}
public static SerializedProperty AddBindingToBindingArray(SerializedProperty bindingsArrayProperty, int bindingIndex = -1,
string actionName = "", string groups = "", string path = "", string name = "", string interactions = "", string processors = "",
InputBinding.Flags flags = InputBinding.Flags.None)
{
Debug.Assert(bindingsArrayProperty != null);
Debug.Assert(bindingsArrayProperty.isArray, "SerializedProperty is not an array of bindings");
Debug.Assert(bindingIndex == -1 || (bindingIndex >= 0 && bindingIndex <= bindingsArrayProperty.arraySize));
if (bindingIndex == -1)
bindingIndex = bindingsArrayProperty.arraySize;
bindingsArrayProperty.InsertArrayElementAtIndex(bindingIndex);
var newBindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(bindingIndex);
newBindingProperty.FindPropertyRelative("m_Path").stringValue = path;
newBindingProperty.FindPropertyRelative("m_Groups").stringValue = groups;
newBindingProperty.FindPropertyRelative("m_Interactions").stringValue = interactions;
newBindingProperty.FindPropertyRelative("m_Processors").stringValue = processors;
newBindingProperty.FindPropertyRelative("m_Flags").intValue = (int)flags;
newBindingProperty.FindPropertyRelative("m_Action").stringValue = actionName;
newBindingProperty.FindPropertyRelative("m_Name").stringValue = name;
newBindingProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
////FIXME: this likely leaves m_Bindings in the map for singleton actions unsync'd in some cases
return newBindingProperty;
}
public static void ChangeBinding(SerializedProperty bindingProperty, string path = null, string groups = null,
string interactions = null, string processors = null, string action = null)
{
// Path.
if (!string.IsNullOrEmpty(path))
{
var pathProperty = bindingProperty.FindPropertyRelative("m_Path");
pathProperty.stringValue = path;
}
// Groups.
if (!string.IsNullOrEmpty(groups))
{
var groupsProperty = bindingProperty.FindPropertyRelative("m_Groups");
groupsProperty.stringValue = groups;
}
// Interactions.
if (!string.IsNullOrEmpty(interactions))
{
var interactionsProperty = bindingProperty.FindPropertyRelative("m_Interactions");
interactionsProperty.stringValue = interactions;
}
// Processors.
if (!string.IsNullOrEmpty(processors))
{
var processorsProperty = bindingProperty.FindPropertyRelative("m_Processors");
processorsProperty.stringValue = processors;
}
// Action.
if (!string.IsNullOrEmpty(action))
{
var actionProperty = bindingProperty.FindPropertyRelative("m_Action");
actionProperty.stringValue = action;
}
}
public static void DeleteBinding(SerializedProperty binding, SerializedProperty actionMap)
{
var bindingsProperty = actionMap.FindPropertyRelative("m_Bindings");
DeleteBinding(binding, bindingsProperty, binding.GetIndexOfArrayElement());
}
private static void DeleteBinding(SerializedProperty bindingProperty, SerializedProperty bindingArrayProperty, int bindingIndex)
{
var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
var isComposite = (bindingFlags & InputBinding.Flags.Composite) != 0;
// If it's a composite, delete all its parts first.
if (isComposite)
{
for (var partIndex = bindingIndex + 1; partIndex < bindingArrayProperty.arraySize;)
{
var part = bindingArrayProperty.GetArrayElementAtIndex(partIndex);
var flags = (InputBinding.Flags)part.FindPropertyRelative("m_Flags").intValue;
if ((flags & InputBinding.Flags.PartOfComposite) == 0)
break;
bindingArrayProperty.DeleteArrayElementAtIndex(partIndex);
}
}
bindingArrayProperty.DeleteArrayElementAtIndex(bindingIndex);
}
public static void DeleteBinding(SerializedProperty bindingArrayProperty, Guid id)
{
var bindingIndex = GetIndex(bindingArrayProperty, id);
var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(bindingIndex);
DeleteBinding(bindingProperty, bindingArrayProperty, bindingIndex);
}
public static void AssignUniqueIDs(SerializedProperty element)
{
// Assign new ID to map.
AssignUniqueID(element);
//
foreach (var child in element.GetChildren())
{
if (!child.isArray)
continue;
var fieldType = child.GetFieldType();
if (fieldType == typeof(InputBinding[]) || fieldType == typeof(InputAction[]) ||
fieldType == typeof(InputActionMap))
{
////TODO: update bindings that refer to actions by {id}
for (var i = 0; i < child.arraySize; ++i)
using (var childElement = child.GetArrayElementAtIndex(i))
AssignUniqueIDs(childElement);
}
}
}
public static void AssignUniqueID(SerializedProperty property)
{
var idProperty = property.FindPropertyRelative("m_Id");
idProperty.stringValue = Guid.NewGuid().ToString();
}
public static void EnsureUniqueName(SerializedProperty arrayElement)
{
var arrayProperty = arrayElement.GetArrayPropertyFromElement();
var arrayIndexOfElement = arrayElement.GetIndexOfArrayElement();
var nameProperty = arrayElement.FindPropertyRelative("m_Name");
var baseName = nameProperty.stringValue;
nameProperty.stringValue = FindUniqueName(arrayProperty, baseName, ignoreIndex: arrayIndexOfElement);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "False positive (possibly caused by lambda expression?).")]
public static string FindUniqueName(SerializedProperty arrayProperty, string baseName, int ignoreIndex = -1)
{
return StringHelpers.MakeUniqueName(baseName,
Enumerable.Range(0, arrayProperty.arraySize),
index =>
{
if (index == ignoreIndex)
return string.Empty;
var elementProperty = arrayProperty.GetArrayElementAtIndex(index);
var nameProperty = elementProperty.FindPropertyRelative("m_Name");
if (nameProperty == null)
throw new ArgumentException($"Cannot find m_Name property in elements of array",
nameof(arrayProperty));
return nameProperty.stringValue;
});
}
public static void RenameAction(SerializedProperty actionProperty, SerializedProperty actionMapProperty, string newName)
{
// Make sure name is unique.
var actionsArrayProperty = actionMapProperty.FindPropertyRelative("m_Actions");
var uniqueName = FindUniqueName(actionsArrayProperty, newName, actionProperty.GetIndexOfArrayElement());
// Update all bindings that refer to the action.
var nameProperty = actionProperty.FindPropertyRelative("m_Name");
var oldName = nameProperty.stringValue;
var bindingsProperty = actionMapProperty.FindPropertyRelative("m_Bindings");
for (var i = 0; i < bindingsProperty.arraySize; i++)
{
var element = bindingsProperty.GetArrayElementAtIndex(i);
var actionNameProperty = element.FindPropertyRelative("m_Action");
if (actionNameProperty.stringValue.Equals(oldName, StringComparison.InvariantCultureIgnoreCase))
actionNameProperty.stringValue = uniqueName;
}
// Update name.
nameProperty.stringValue = uniqueName;
}
public static void RenameActionMap(SerializedProperty actionMapProperty, string newName)
{
// Make sure name is unique in InputActionAsset.
var assetObject = actionMapProperty.serializedObject;
var mapsArrayProperty = assetObject.FindProperty("m_ActionMaps");
var uniqueName = FindUniqueName(mapsArrayProperty, newName, actionMapProperty.GetIndexOfArrayElement());
// Assign to map.
var nameProperty = actionMapProperty.FindPropertyRelative("m_Name");
nameProperty.stringValue = uniqueName;
}
public static void RenameComposite(SerializedProperty compositeGroupProperty, string newName)
{
var nameProperty = compositeGroupProperty.FindPropertyRelative("m_Name");
nameProperty.stringValue = newName;
}
public static SerializedProperty AddCompositeBinding(SerializedProperty actionProperty, SerializedProperty actionMapProperty,
string compositeName, Type compositeType = null, string groups = "", bool addPartBindings = true)
{
var newProperty = AddBinding(actionProperty, actionMapProperty);
newProperty.FindPropertyRelative("m_Name").stringValue = ObjectNames.NicifyVariableName(compositeName);
newProperty.FindPropertyRelative("m_Path").stringValue = compositeName;
newProperty.FindPropertyRelative("m_Flags").intValue = (int)InputBinding.Flags.Composite;
if (addPartBindings)
{
var fields = compositeType.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
foreach (var field in fields)
{
// Skip fields that aren't marked with [InputControl] attribute.
if (field.GetCustomAttribute<InputControlAttribute>(false) == null)
continue;
var partProperty = AddBinding(actionProperty, actionMapProperty, groups: groups);
partProperty.FindPropertyRelative("m_Name").stringValue = field.Name;
partProperty.FindPropertyRelative("m_Flags").intValue = (int)InputBinding.Flags.PartOfComposite;
}
}
return newProperty;
}
public static bool IsCompositeBinding(SerializedProperty bindingProperty)
{
using (var flagsProperty = bindingProperty.FindPropertyRelative("m_Flags"))
{
var flags = (InputBinding.Flags)flagsProperty.intValue;
return (flags & InputBinding.Flags.Composite) != 0;
}
}
public static SerializedProperty ChangeCompositeBindingType(SerializedProperty bindingProperty,
NameAndParameters nameAndParameters)
{
var bindingsArrayProperty = bindingProperty.GetArrayPropertyFromElement();
Debug.Assert(bindingsArrayProperty != null, "SerializedProperty is not an array of bindings");
var bindingIndex = bindingProperty.GetIndexOfArrayElement();
Debug.Assert(IsCompositeBinding(bindingProperty),
$"Binding {bindingProperty.propertyPath} is not a composite");
// If the composite still has the default name, change it to the default
// one for the new composite type.
var pathProperty = bindingProperty.FindPropertyRelative("m_Path");
var nameProperty = bindingProperty.FindPropertyRelative("m_Name");
if (nameProperty.stringValue ==
ObjectNames.NicifyVariableName(NameAndParameters.Parse(pathProperty.stringValue).name))
nameProperty.stringValue = ObjectNames.NicifyVariableName(nameAndParameters.name);
pathProperty.stringValue = nameAndParameters.ToString();
// Adjust part bindings if we have information on the registered composite. If we don't have
// a type, we don't know about the parts. In that case, leave part bindings untouched.
var compositeType = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParameters.name);
if (compositeType != null)
{
var actionName = bindingProperty.FindPropertyRelative("m_Action").stringValue;
// Repurpose existing part bindings for the new composite or add any part bindings that
// we're missing.
var fields = compositeType.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
var partIndex = 0;
var partBindingsStartIndex = bindingIndex + 1;
foreach (var field in fields)
{
// Skip fields that aren't marked with [InputControl] attribute.
if (field.GetCustomAttribute<InputControlAttribute>(false) == null)
continue;
// See if we can reuse an existing part binding.
SerializedProperty partProperty = null;
if (partBindingsStartIndex + partIndex < bindingsArrayProperty.arraySize)
{
////REVIEW: this should probably look up part bindings by name rather than going sequentially
var element = bindingsArrayProperty.GetArrayElementAtIndex(partBindingsStartIndex + partIndex);
if (((InputBinding.Flags)element.FindPropertyRelative("m_Flags").intValue & InputBinding.Flags.PartOfComposite) != 0)
partProperty = element;
}
// If not, insert a new binding.
if (partProperty == null)
{
partProperty = AddBindingToBindingArray(bindingsArrayProperty, partBindingsStartIndex + partIndex,
flags: InputBinding.Flags.PartOfComposite);
}
// Initialize.
partProperty.FindPropertyRelative("m_Name").stringValue = ObjectNames.NicifyVariableName(field.Name);
partProperty.FindPropertyRelative("m_Action").stringValue = actionName;
++partIndex;
}
////REVIEW: when we allow adding the same part multiple times, we may want to do something smarter here
// Delete extraneous part bindings.
while (partBindingsStartIndex + partIndex < bindingsArrayProperty.arraySize)
{
var element = bindingsArrayProperty.GetArrayElementAtIndex(partBindingsStartIndex + partIndex);
if (((InputBinding.Flags)element.FindPropertyRelative("m_Flags").intValue & InputBinding.Flags.PartOfComposite) == 0)
break;
bindingsArrayProperty.DeleteArrayElementAtIndex(partBindingsStartIndex + partIndex);
// No incrementing of partIndex.
}
}
return bindingProperty;
}
public static void ReplaceBindingGroup(SerializedObject asset, string oldBindingGroup, string newBindingGroup, bool deleteOrphanedBindings = false)
{
var mapArrayProperty = asset.FindProperty("m_ActionMaps");
var mapCount = mapArrayProperty.arraySize;
for (var k = 0; k < mapCount; ++k)
{
var actionMapProperty = mapArrayProperty.GetArrayElementAtIndex(k);
var bindingsArrayProperty = actionMapProperty.FindPropertyRelative("m_Bindings");
var bindingsCount = bindingsArrayProperty.arraySize;
for (var i = 0; i < bindingsCount; ++i)
{
var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i);
var groupsProperty = bindingProperty.FindPropertyRelative("m_Groups");
var groups = groupsProperty.stringValue;
// Ignore bindings not belonging to any control scheme.
if (string.IsNullOrEmpty(groups))
continue;
var groupsArray = groups.Split(InputBinding.Separator);
var numGroups = groupsArray.LengthSafe();
var didRename = false;
for (var n = 0; n < numGroups; ++n)
{
if (string.Compare(groupsArray[n], oldBindingGroup, StringComparison.InvariantCultureIgnoreCase) != 0)
continue;
if (string.IsNullOrEmpty(newBindingGroup))
{
ArrayHelpers.EraseAt(ref groupsArray, n);
--n;
--numGroups;
}
else
groupsArray[n] = newBindingGroup;
didRename = true;
}
if (!didRename)
continue;
if (groupsArray != null)
groupsProperty.stringValue = string.Join(InputBinding.kSeparatorString, groupsArray);
else
{
if (deleteOrphanedBindings)
{
// Binding no long belongs to any binding group. Delete it.
bindingsArrayProperty.DeleteArrayElementAtIndex(i);
--i;
--bindingsCount;
}
else
{
groupsProperty.stringValue = string.Empty;
}
}
}
}
}
public static void RemoveUnusedBindingGroups(SerializedProperty binding, ReadOnlyArray<InputControlScheme> controlSchemes)
{
var groupsProperty = binding.FindPropertyRelative(nameof(InputBinding.m_Groups));
groupsProperty.stringValue = string.Join(InputBinding.kSeparatorString,
groupsProperty.stringValue
.Split(InputBinding.Separator)
.Where(g => controlSchemes.Any(c => c.bindingGroup.Equals(g, StringComparison.InvariantCultureIgnoreCase))));
}
}
}
#endif // UNITY_EDITOR