Files
MMORPG/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/CopyPasteHelper.cs
cooney ce83f21c93 UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
2026-01-25 01:31:34 +09:00

518 lines
25 KiB
C#

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
namespace UnityEngine.InputSystem.Editor
{
// TODO Make buffers an optional argument and only allocate if not already passed, reuse a common buffer
internal static class CopyPasteHelper
{
private const string k_CopyPasteMarker = "INPUTASSET ";
private const string k_StartOfText = "\u0002";
private const string k_EndOfTransmission = "\u0004";
private const string k_BindingData = "bindingData";
private const string k_EndOfBinding = "+++";
private static readonly Dictionary<Type, string> k_TypeMarker = new Dictionary<Type, string>
{
{typeof(InputActionMap), "InputActionMap"},
{typeof(InputAction), "InputAction"},
{typeof(InputBinding), "InputBinding"},
};
private static SerializedProperty s_lastAddedElement;
private static InputActionsEditorState s_State;
private static bool s_lastClipboardActionWasCut = false;
private static bool IsComposite(SerializedProperty property) => property.FindPropertyRelative("m_Flags").intValue == (int)InputBinding.Flags.Composite;
private static bool IsPartOfComposite(SerializedProperty property) => property.FindPropertyRelative("m_Flags").intValue == (int)InputBinding.Flags.PartOfComposite;
private static string PropertyName(SerializedProperty property) => property.FindPropertyRelative("m_Name").stringValue;
#region Cut
public static void CutActionMap(InputActionsEditorState state)
{
CopyActionMap(state);
s_lastClipboardActionWasCut = true;
}
public static void Cut(InputActionsEditorState state)
{
Copy(state);
s_lastClipboardActionWasCut = true;
}
#endregion
#region Copy
public static void CopyActionMap(InputActionsEditorState state)
{
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
var selectedObject = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
CopySelectedTreeViewItemsToClipboard(new List<SerializedProperty> {selectedObject}, typeof(InputActionMap), actionMap);
}
public static void Copy(InputActionsEditorState state)
{
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
var selectedObject = Selectors.GetSelectedAction(state)?.wrappedProperty;
var type = typeof(InputAction);
if (state.selectionType == SelectionType.Binding)
{
selectedObject = Selectors.GetSelectedBinding(state)?.wrappedProperty;
type = typeof(InputBinding);
}
CopySelectedTreeViewItemsToClipboard(new List<SerializedProperty> {selectedObject}, type, actionMap);
}
private static void CopySelectedTreeViewItemsToClipboard(List<SerializedProperty> items, Type type, SerializedProperty actionMap = null)
{
var copyBuffer = new StringBuilder();
CopyItems(items, copyBuffer, type, actionMap);
EditorHelpers.SetSystemCopyBufferContents(copyBuffer.ToString());
s_lastClipboardActionWasCut = false;
}
internal static void CopyItems(List<SerializedProperty> items, StringBuilder buffer, Type type, SerializedProperty actionMap)
{
buffer.Append(k_CopyPasteMarker);
buffer.Append(k_TypeMarker[type]);
foreach (var item in items)
{
CopyItemData(item, buffer, type, actionMap);
buffer.Append(k_EndOfTransmission);
}
}
private static void CopyItemData(SerializedProperty item, StringBuilder buffer, Type type, SerializedProperty actionMap)
{
if (item == null)
return;
buffer.Append(k_StartOfText);
buffer.Append(item.CopyToJson(true));
if (type == typeof(InputAction))
AppendBindingDataForAction(buffer, actionMap, item);
if (type == typeof(InputBinding) && IsComposite(item))
AppendBindingDataForComposite(buffer, actionMap, item);
}
private static void AppendBindingDataForAction(StringBuilder buffer, SerializedProperty actionMap, SerializedProperty item)
{
buffer.Append(k_BindingData);
foreach (var binding in GetBindingsForActionInMap(actionMap, item))
{
buffer.Append(binding.CopyToJson(true));
buffer.Append(k_EndOfBinding);
}
}
private static void AppendBindingDataForComposite(StringBuilder buffer, SerializedProperty actionMap, SerializedProperty item)
{
var bindingsArray = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
buffer.Append(k_BindingData);
foreach (var binding in GetBindingsForComposite(bindingsArray, item.GetIndexOfArrayElement()))
{
buffer.Append(binding.CopyToJson(true));
buffer.Append(k_EndOfBinding);
}
}
private static IEnumerable<SerializedProperty> GetBindingsForActionInMap(SerializedProperty actionMap, SerializedProperty action)
{
var actionName = PropertyName(action);
var bindingsArray = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
var bindings = bindingsArray.Where(binding => binding.FindPropertyRelative("m_Action").stringValue.Equals(actionName));
return bindings;
}
#endregion
#region PasteChecks
public static bool HasPastableClipboardData(Type selectedType)
{
var clipboard = EditorHelpers.GetSystemCopyBufferContents();
if (clipboard.Length < k_CopyPasteMarker.Length)
return false;
var isInputAssetData = clipboard.StartsWith(k_CopyPasteMarker);
return isInputAssetData && IsMatchingType(selectedType, GetCopiedClipboardType());
}
private static bool IsMatchingType(Type selectedType, Type copiedType)
{
if (selectedType == typeof(InputActionMap))
return copiedType == typeof(InputActionMap) || copiedType == typeof(InputAction);
if (selectedType == typeof(InputAction))
return copiedType == typeof(InputAction) || copiedType == typeof(InputBinding);
//bindings and composites
return copiedType == typeof(InputBinding);
}
public static Type GetCopiedType(string buffer)
{
if (!buffer.StartsWith(k_CopyPasteMarker))
return null;
foreach (var typePair in k_TypeMarker)
{
if (buffer.Substring(k_CopyPasteMarker.Length).StartsWith(typePair.Value))
return typePair.Key;
}
return null;
}
public static Type GetCopiedClipboardType()
{
return GetCopiedType(EditorHelpers.GetSystemCopyBufferContents());
}
#endregion
#region Paste
public static SerializedProperty PasteActionMapsFromClipboard(InputActionsEditorState state)
{
s_lastAddedElement = null;
var typeOfCopiedData = GetCopiedClipboardType();
if (typeOfCopiedData != typeof(InputActionMap)) return null;
s_State = state;
var actionMapArray = state.serializedObject.FindProperty(nameof(InputActionAsset.m_ActionMaps));
PasteData(EditorHelpers.GetSystemCopyBufferContents(), new[] {state.selectedActionMapIndex}, actionMapArray);
// Don't want to be able to paste repeatedly after a cut - ISX-1821
if (s_lastAddedElement != null && s_lastClipboardActionWasCut)
EditorHelpers.SetSystemCopyBufferContents(string.Empty);
return s_lastAddedElement;
}
public static SerializedProperty PasteActionsOrBindingsFromClipboard(InputActionsEditorState state, bool addLast = false, int mapIndex = -1)
{
s_lastAddedElement = null;
s_State = state;
var typeOfCopiedData = GetCopiedClipboardType();
if (typeOfCopiedData == typeof(InputAction))
PasteActionsFromClipboard(state, addLast, mapIndex);
if (typeOfCopiedData == typeof(InputBinding))
PasteBindingsFromClipboard(state);
// Don't want to be able to paste repeatedly after a cut - ISX-1821
if (s_lastAddedElement != null && s_lastClipboardActionWasCut)
EditorHelpers.SetSystemCopyBufferContents(string.Empty);
return s_lastAddedElement;
}
private static void PasteActionsFromClipboard(InputActionsEditorState state, bool addLast, int mapIndex)
{
var actionMap = mapIndex >= 0 ? Selectors.GetActionMapAtIndex(state, mapIndex)?.wrappedProperty
: Selectors.GetSelectedActionMap(state)?.wrappedProperty;
var actionArray = actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Actions));
if (actionArray == null) return;
var index = state.selectedActionIndex;
if (addLast)
index = actionArray.arraySize - 1;
PasteData(EditorHelpers.GetSystemCopyBufferContents(), new[] {index}, actionArray);
}
private static void PasteBindingsFromClipboard(InputActionsEditorState state)
{
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
var bindingsArray = actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
if (bindingsArray == null) return;
int newBindingIndex;
if (state.selectionType == SelectionType.Action)
newBindingIndex = Selectors.GetLastBindingIndexForSelectedAction(state);
else
newBindingIndex = state.selectedBindingIndex;
PasteData(EditorHelpers.GetSystemCopyBufferContents(), new[] { newBindingIndex }, bindingsArray);
}
private static void PasteData(string copyBufferString, int[] indicesToInsert, SerializedProperty arrayToInsertInto)
{
if (!copyBufferString.StartsWith(k_CopyPasteMarker))
return;
PasteItems(copyBufferString, indicesToInsert, arrayToInsertInto);
}
internal static void PasteItems(string copyBufferString, int[] indicesToInsert, SerializedProperty arrayToInsertInto)
{
// Split buffer into transmissions and then into transmission blocks
var copiedType = GetCopiedType(copyBufferString);
int indexOffset = 0;
// If the array is empty, make sure we insert at index 0
if (arrayToInsertInto.arraySize == 0)
indexOffset = -1;
foreach (var transmission in copyBufferString.Substring(k_CopyPasteMarker.Length + k_TypeMarker[copiedType].Length)
.Split(new[] {k_EndOfTransmission}, StringSplitOptions.RemoveEmptyEntries))
{
indexOffset++;
foreach (var index in indicesToInsert)
PasteBlocks(transmission, index + indexOffset, arrayToInsertInto, copiedType);
}
}
private static void PasteBlocks(string transmission, int indexToInsert, SerializedProperty arrayToInsertInto, Type copiedType)
{
var block = transmission.Substring(transmission.IndexOf(k_StartOfText, StringComparison.Ordinal) + 1);
if (copiedType == typeof(InputActionMap))
PasteElement(arrayToInsertInto, block, indexToInsert, out _);
else if (copiedType == typeof(InputAction))
PasteAction(arrayToInsertInto, block, indexToInsert);
else
{
var actionName = Selectors.GetSelectedBinding(s_State)?.wrappedProperty.FindPropertyRelative("m_Action")
.stringValue;
if (s_State.selectionType == SelectionType.Action)
{
SerializedProperty property = Selectors.GetSelectedAction(s_State)?.wrappedProperty;
if (property == null)
return;
actionName = PropertyName(property);
}
if (actionName == null)
return;
PasteBindingOrComposite(arrayToInsertInto, block, indexToInsert, actionName);
}
}
private static SerializedProperty PasteElement(SerializedProperty arrayProperty, string json, int index, out string oldId, string name = "newElement", bool changeName = true, bool assignUniqueIDs = true)
{
var duplicatedProperty = AddElement(arrayProperty, name, index);
duplicatedProperty.RestoreFromJson(json);
oldId = duplicatedProperty.FindPropertyRelative("m_Id").stringValue;
if (changeName)
InputActionSerializationHelpers.EnsureUniqueName(duplicatedProperty);
if (assignUniqueIDs)
InputActionSerializationHelpers.AssignUniqueIDs(duplicatedProperty);
s_lastAddedElement = duplicatedProperty;
return duplicatedProperty;
}
private static void PasteAction(SerializedProperty arrayProperty, string jsonToInsert, int indexToInsert)
{
var json = jsonToInsert.Split(k_BindingData, StringSplitOptions.RemoveEmptyEntries);
var bindingJsons = new string[] {};
if (json.Length > 1)
bindingJsons = json[1].Split(k_EndOfBinding, StringSplitOptions.RemoveEmptyEntries);
var property = PasteElement(arrayProperty, json[0], indexToInsert, out _, "");
var newName = PropertyName(property);
var newId = property.FindPropertyRelative("m_Id").stringValue;
var actionMapTo = Selectors.GetActionMapForAction(s_State, newId);
var bindingArrayToInsertTo = actionMapTo.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
var index = Mathf.Clamp(Selectors.GetBindingIndexBeforeAction(arrayProperty, indexToInsert, bindingArrayToInsertTo), 0, bindingArrayToInsertTo.arraySize);
foreach (var bindingJson in bindingJsons)
{
var newIndex = PasteBindingOrComposite(bindingArrayToInsertTo, bindingJson, index, newName, false);
index = newIndex;
}
s_lastAddedElement = property;
}
private static int PasteBindingOrComposite(SerializedProperty arrayProperty, string json, int index, string actionName, bool createCompositeParts = true)
{
var pastePartOfComposite = IsPartOfComposite(json);
bool currentPartOfComposite = false;
bool currentIsComposite = false;
if (arrayProperty.arraySize == 0)
index = 0;
if (index > 0)
{
var currentProperty = arrayProperty.GetArrayElementAtIndex(index - 1);
currentPartOfComposite = IsPartOfComposite(currentProperty);
currentIsComposite = IsComposite(currentProperty) || currentPartOfComposite;
if (pastePartOfComposite && !currentIsComposite) //prevent pasting part of composite into non-composite
return index;
}
// Update the target index for special cases when pasting a Binding
if (s_State.selectionType != SelectionType.Action && createCompositeParts)
{
// - Pasting into a Composite with CompositePart not the target, i.e. Composite "root" selected, paste at the end of the composite
// - Pasting a non-CompositePart, i.e. regular Binding, needs to skip all the CompositeParts (if any)
if ((pastePartOfComposite && !currentPartOfComposite) || !pastePartOfComposite)
index = Selectors.GetSelectedBindingIndexAfterCompositeBindings(s_State) + 1;
}
if (json.Contains(k_BindingData)) //copied data is composite with bindings - only true for directly copied composites, not for composites from copied actions
return PasteCompositeFromJson(arrayProperty, json, index, actionName);
var property = PasteElement(arrayProperty, json, index, out var oldId, "", false);
if (IsComposite(property))
return PasteComposite(arrayProperty, property, PropertyName(property), actionName, index, oldId, createCompositeParts); //Paste composites copied with actions
property.FindPropertyRelative("m_Action").stringValue = actionName;
return index + 1;
}
private static int PasteComposite(SerializedProperty bindingsArray, SerializedProperty duplicatedComposite, string name, string actionName, int index, string oldId, bool createCompositeParts)
{
duplicatedComposite.FindPropertyRelative("m_Name").stringValue = name;
duplicatedComposite.FindPropertyRelative("m_Action").stringValue = actionName;
if (createCompositeParts)
{
var composite = Selectors.GetBindingForId(s_State, oldId, out var bindingsFrom);
var bindings = GetBindingsForComposite(bindingsFrom, composite.GetIndexOfArrayElement());
PastePartsOfComposite(bindingsArray, bindings, ++index, actionName);
}
return index + 1;
}
private static int PastePartsOfComposite(SerializedProperty bindingsToInsertTo, List<SerializedProperty> bindingsOfComposite, int index, string actionName)
{
foreach (var binding in bindingsOfComposite)
{
var newBinding = DuplicateElement(bindingsToInsertTo, binding, PropertyName(binding), index++, false);
newBinding.FindPropertyRelative("m_Action").stringValue = actionName;
}
return index;
}
private static int PasteCompositeFromJson(SerializedProperty arrayProperty, string json, int index, string actionName)
{
var jsons = json.Split(k_BindingData, StringSplitOptions.RemoveEmptyEntries);
var property = PasteElement(arrayProperty, jsons[0], index, out _, "", false);
var bindingJsons = jsons[1].Split(k_EndOfBinding, StringSplitOptions.RemoveEmptyEntries);
property.FindPropertyRelative("m_Action").stringValue = actionName;
foreach (var bindingJson in bindingJsons)
PasteBindingOrComposite(arrayProperty, bindingJson, ++index, actionName, false);
return index + 1;
}
private static bool IsPartOfComposite(string json)
{
if (!json.Contains("m_Flags") || json.Contains(k_BindingData))
return false;
var ob = JsonUtility.FromJson<InputBinding>(json);
return ob.m_Flags == InputBinding.Flags.PartOfComposite;
}
private static SerializedProperty AddElement(SerializedProperty arrayProperty, string name, int index = -1)
{
var uniqueName = InputActionSerializationHelpers.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 int DeleteCutElements(InputActionsEditorState state)
{
if (!state.hasCutElements)
return -1;
var cutElements = state.GetCutElements();
var index = state.selectedActionMapIndex;
if (cutElements[0].type == typeof(InputAction))
index = state.selectedActionIndex;
else if (cutElements[0].type == typeof(InputBinding))
index = state.selectionType == SelectionType.Binding ? state.selectedBindingIndex : state.selectedActionIndex;
foreach (var cutElement in cutElements)
{
var cutIndex = cutElement.GetIndexOfProperty(state);
var actionMapIndex = cutElement.actionMapIndex(state);
var actionMap = Selectors.GetActionMapAtIndex(state, actionMapIndex)?.wrappedProperty;
var isInsertBindingIntoAction = cutElement.type == typeof(InputBinding) && state.selectionType == SelectionType.Action;
if (cutElement.type == typeof(InputBinding) || cutElement.type == typeof(InputAction))
{
if (cutElement.type == typeof(InputAction))
{
var action = Selectors.GetActionForIndex(actionMap, cutIndex);
var id = InputActionSerializationHelpers.GetId(action);
InputActionSerializationHelpers.DeleteActionAndBindings(actionMap, id);
}
else
{
var binding = Selectors.GetCompositeOrBindingInMap(actionMap, cutIndex).wrappedProperty;
if (binding.FindPropertyRelative("m_Flags").intValue == (int)InputBinding.Flags.Composite && !isInsertBindingIntoAction)
index -= InputActionSerializationHelpers.GetCompositePartCount(Selectors.GetSelectedActionMap(state)?.wrappedProperty.FindPropertyRelative(nameof(InputActionMap.m_Bindings)), cutIndex);
InputActionSerializationHelpers.DeleteBinding(binding, actionMap);
}
if (cutIndex <= index && actionMapIndex == state.selectedActionMapIndex && !isInsertBindingIntoAction)
index--;
}
else if (cutElement.type == typeof(InputActionMap))
{
InputActionSerializationHelpers.DeleteActionMap(state.serializedObject, InputActionSerializationHelpers.GetId(actionMap));
if (cutIndex <= index)
index--;
}
}
return index;
}
#endregion
#region Duplicate
public static void DuplicateAction(SerializedProperty arrayProperty, SerializedProperty toDuplicate, SerializedProperty actionMap, InputActionsEditorState state)
{
s_State = state;
var buffer = new StringBuilder();
buffer.Append(toDuplicate.CopyToJson(true));
AppendBindingDataForAction(buffer, actionMap, toDuplicate);
PasteAction(arrayProperty, buffer.ToString(), toDuplicate.GetIndexOfArrayElement() + 1);
}
public static int DuplicateBinding(SerializedProperty arrayProperty, SerializedProperty toDuplicate, string newActionName, int index)
{
if (IsComposite(toDuplicate))
return DuplicateComposite(arrayProperty, toDuplicate, PropertyName(toDuplicate), newActionName, index, out _).GetIndexOfArrayElement();
var binding = DuplicateElement(arrayProperty, toDuplicate, newActionName, index, false);
binding.FindPropertyRelative("m_Action").stringValue = newActionName;
return index;
}
private static SerializedProperty DuplicateComposite(SerializedProperty bindingsArray, SerializedProperty compositeToDuplicate, string name, string actionName, int index, out int newIndex, bool increaseIndex = true)
{
if (increaseIndex)
index += InputActionSerializationHelpers.GetCompositePartCount(bindingsArray, compositeToDuplicate.GetIndexOfArrayElement());
var newComposite = DuplicateElement(bindingsArray, compositeToDuplicate, name, index++, false);
newComposite.FindPropertyRelative("m_Action").stringValue = actionName;
var bindings = GetBindingsForComposite(bindingsArray, compositeToDuplicate.GetIndexOfArrayElement());
newIndex = PastePartsOfComposite(bindingsArray, bindings, index, actionName);
return newComposite;
}
public static SerializedProperty DuplicateElement(SerializedProperty arrayProperty, SerializedProperty toDuplicate, string name, int index, bool changeName = true)
{
var json = toDuplicate.CopyToJson(true);
return PasteElement(arrayProperty, json, index, out _, name, changeName);
}
#endregion
internal static List<SerializedProperty> GetBindingsForComposite(SerializedProperty bindingsArray, int indexOfComposite)
{
var compositeBindings = new List<SerializedProperty>();
var compositeStartIndex = InputActionSerializationHelpers.GetCompositeStartIndex(bindingsArray, indexOfComposite);
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;
}
}
}
#endif