UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
[System.Serializable]
|
||||
|
||||
internal class CutElement
|
||||
{
|
||||
private Guid id;
|
||||
internal Type type;
|
||||
|
||||
public CutElement(Guid id, Type type)
|
||||
{
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int GetIndexOfProperty(InputActionsEditorState state)
|
||||
{
|
||||
if (type == typeof(InputActionMap))
|
||||
{
|
||||
var actionMap = state.serializedObject
|
||||
?.FindProperty(nameof(InputActionAsset.m_ActionMaps))
|
||||
?.FirstOrDefault(s => InputActionSerializationHelpers.GetId(s).Equals(id));
|
||||
return actionMap.GetIndexOfArrayElement();
|
||||
}
|
||||
|
||||
if (type == typeof(InputAction))
|
||||
{
|
||||
var action = Selectors.GetActionMapAtIndex(state, actionMapIndex(state))?.wrappedProperty.FindPropertyRelative("m_Actions").FirstOrDefault(a => InputActionSerializationHelpers.GetId(a).Equals(id));
|
||||
return action.GetIndexOfArrayElement();
|
||||
}
|
||||
|
||||
if (type == typeof(InputBinding))
|
||||
{
|
||||
var binding = Selectors.GetBindingForId(state, id.ToString(),
|
||||
out _);
|
||||
return binding.GetIndexOfArrayElement();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int actionMapIndex(InputActionsEditorState state) => type == typeof(InputActionMap) ? GetIndexOfProperty(state) : GetActionMapIndex(state);
|
||||
|
||||
private int GetActionMapIndex(InputActionsEditorState state)
|
||||
{
|
||||
var actionMaps = state.serializedObject?.FindProperty(nameof(InputActionAsset.m_ActionMaps));
|
||||
var cutActionMapIndex = state.serializedObject
|
||||
?.FindProperty(nameof(InputActionAsset.m_ActionMaps))
|
||||
?.FirstOrDefault(s => s.FindPropertyRelative("m_Id").stringValue.Equals(id)).GetIndexOfArrayElement();
|
||||
if (type == typeof(InputBinding))
|
||||
cutActionMapIndex = actionMaps.FirstOrDefault(map => map.FindPropertyRelative("m_Bindings").Select(InputActionSerializationHelpers.GetId).Contains(id)).GetIndexOfArrayElement();
|
||||
else if (type == typeof(InputAction))
|
||||
cutActionMapIndex = actionMaps.FirstOrDefault(map => map.FindPropertyRelative("m_Actions").Select(InputActionSerializationHelpers.GetId).Contains(id)).GetIndexOfArrayElement();
|
||||
return cutActionMapIndex ?? -1;
|
||||
}
|
||||
}
|
||||
internal struct InputActionsEditorState
|
||||
{
|
||||
public int selectedActionMapIndex { get {return m_selectedActionMapIndex; } }
|
||||
public int selectedActionIndex { get {return m_selectedActionIndex; } }
|
||||
public int selectedBindingIndex { get {return m_selectedBindingIndex; } }
|
||||
public SelectionType selectionType { get {return m_selectionType; } }
|
||||
public SerializedObject serializedObject { get; } // Note that state doesn't own this disposable object
|
||||
private readonly List<CutElement> cutElements => m_CutElements;
|
||||
|
||||
// Control schemes
|
||||
public int selectedControlSchemeIndex { get { return m_selectedControlSchemeIndex; } }
|
||||
public int selectedDeviceRequirementIndex { get {return m_selectedDeviceRequirementIndex; } }
|
||||
public InputControlScheme selectedControlScheme => m_ControlScheme; // TODO Bad this either po
|
||||
|
||||
internal InputActionsEditorSessionAnalytic m_Analytics;
|
||||
|
||||
[SerializeField] int m_selectedActionMapIndex;
|
||||
[SerializeField] int m_selectedActionIndex;
|
||||
[SerializeField] int m_selectedBindingIndex;
|
||||
[SerializeField] SelectionType m_selectionType;
|
||||
[SerializeField] int m_selectedControlSchemeIndex;
|
||||
[SerializeField] int m_selectedDeviceRequirementIndex;
|
||||
private List<CutElement> m_CutElements;
|
||||
internal bool hasCutElements => m_CutElements != null && m_CutElements.Count > 0;
|
||||
|
||||
public InputActionsEditorState(
|
||||
InputActionsEditorSessionAnalytic analytics,
|
||||
SerializedObject inputActionAsset,
|
||||
int selectedActionMapIndex = 0,
|
||||
int selectedActionIndex = 0,
|
||||
int selectedBindingIndex = 0,
|
||||
SelectionType selectionType = SelectionType.Action,
|
||||
InputControlScheme selectedControlScheme = default,
|
||||
int selectedControlSchemeIndex = -1,
|
||||
int selectedDeviceRequirementIndex = -1,
|
||||
List<CutElement> cutElements = null)
|
||||
{
|
||||
Debug.Assert(inputActionAsset != null);
|
||||
|
||||
m_Analytics = analytics;
|
||||
|
||||
serializedObject = inputActionAsset;
|
||||
|
||||
m_selectedActionMapIndex = selectedActionMapIndex;
|
||||
m_selectedActionIndex = selectedActionIndex;
|
||||
m_selectedBindingIndex = selectedBindingIndex;
|
||||
m_selectionType = selectionType;
|
||||
m_ControlScheme = selectedControlScheme;
|
||||
m_selectedControlSchemeIndex = selectedControlSchemeIndex;
|
||||
m_selectedDeviceRequirementIndex = selectedDeviceRequirementIndex;
|
||||
|
||||
m_CutElements = cutElements;
|
||||
}
|
||||
|
||||
public InputActionsEditorState(InputActionsEditorState other, SerializedObject asset)
|
||||
{
|
||||
m_Analytics = other.m_Analytics;
|
||||
|
||||
// Assign serialized object, not that this might be equal to other.serializedObject,
|
||||
// a slight variation of it with any kind of changes or a completely different one.
|
||||
// Hence, we do our best here to keep any selections consistent by remapping objects
|
||||
// based on GUIDs (IDs) and when it fails, attempt to select first object and if that
|
||||
// fails revert to not having a selection. This would even be true for domain reloads
|
||||
// if the asset would be modified during domain reload.
|
||||
serializedObject = asset;
|
||||
|
||||
if (other.Equals(default(InputActionsEditorState)))
|
||||
{
|
||||
// This instance was created by default constructor and thus is missing some appropriate defaults:
|
||||
other.m_selectionType = SelectionType.Action;
|
||||
other.m_selectedControlSchemeIndex = -1;
|
||||
other.m_selectedDeviceRequirementIndex = -1;
|
||||
}
|
||||
|
||||
// Attempt to preserve action map selection by GUID, otherwise select first or last resort none
|
||||
var otherSelectedActionMap = other.GetSelectedActionMap();
|
||||
var actionMapCount = Selectors.GetActionMapCount(asset);
|
||||
m_selectedActionMapIndex = otherSelectedActionMap != null
|
||||
? Selectors.GetActionMapIndexFromId(asset,
|
||||
InputActionSerializationHelpers.GetId(otherSelectedActionMap))
|
||||
: actionMapCount > 0 ? 0 : -1;
|
||||
var selectedActionMap = m_selectedActionMapIndex >= 0
|
||||
? Selectors.GetActionMapAtIndex(asset, m_selectedActionMapIndex)?.wrappedProperty : null;
|
||||
|
||||
// Attempt to preserve action selection by GUID, otherwise select first or last resort none
|
||||
var otherSelectedAction = m_selectedActionMapIndex >= 0 ?
|
||||
Selectors.GetSelectedAction(other) : null;
|
||||
m_selectedActionIndex = selectedActionMap != null && otherSelectedAction.HasValue
|
||||
? Selectors.GetActionIndexFromId(selectedActionMap,
|
||||
InputActionSerializationHelpers.GetId(otherSelectedAction.Value.wrappedProperty))
|
||||
: Selectors.GetActionCount(selectedActionMap) > 0 ? 0 : -1;
|
||||
|
||||
// Attempt to preserve binding selection by GUID, otherwise select first or none
|
||||
m_selectedBindingIndex = -1;
|
||||
if (m_selectedActionMapIndex >= 0)
|
||||
{
|
||||
var otherSelectedBinding = Selectors.GetSelectedBinding(other);
|
||||
if (otherSelectedBinding != null)
|
||||
{
|
||||
var otherSelectedBindingId =
|
||||
InputActionSerializationHelpers.GetId(otherSelectedBinding.Value.wrappedProperty);
|
||||
var binding = Selectors.GetBindingForId(asset, otherSelectedBindingId.ToString(), out _);
|
||||
if (binding != null)
|
||||
m_selectedBindingIndex = binding.GetIndexOfArrayElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check selection type and override any previous selection if not valid given indices
|
||||
// since we have remapped GUIDs to selection indices for another asset (SerializedObject)
|
||||
if (other.m_selectionType == SelectionType.Binding && m_selectedBindingIndex < 0)
|
||||
m_selectionType = SelectionType.Action;
|
||||
else
|
||||
m_selectionType = other.m_selectionType;
|
||||
|
||||
m_selectedControlSchemeIndex = other.m_selectedControlSchemeIndex;
|
||||
m_selectedDeviceRequirementIndex = other.m_selectedDeviceRequirementIndex;
|
||||
|
||||
// Selected ControlScheme index is serialized but we have to recreated actual object after domain reload.
|
||||
// In case asset is different from from others asset the index might not even be valid range so we need
|
||||
// to reattempt to preserve selection but range adapt.
|
||||
// Note that control schemes and device requirements currently lack any GUID/ID to be uniquely identified.
|
||||
var controlSchemesArrayProperty = serializedObject.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
|
||||
if (m_selectedControlSchemeIndex >= 0 && controlSchemesArrayProperty.arraySize > 0)
|
||||
{
|
||||
if (m_selectedControlSchemeIndex >= controlSchemesArrayProperty.arraySize)
|
||||
m_selectedControlSchemeIndex = 0;
|
||||
m_ControlScheme = new InputControlScheme(
|
||||
controlSchemesArrayProperty.GetArrayElementAtIndex(other.m_selectedControlSchemeIndex));
|
||||
// TODO Preserve device requirement index
|
||||
}
|
||||
else
|
||||
{
|
||||
m_selectedControlSchemeIndex = -1;
|
||||
m_selectedDeviceRequirementIndex = -1;
|
||||
m_ControlScheme = new InputControlScheme();
|
||||
}
|
||||
|
||||
m_CutElements = other.cutElements;
|
||||
}
|
||||
|
||||
public InputActionsEditorState With(
|
||||
int? selectedActionMapIndex = null,
|
||||
int? selectedActionIndex = null,
|
||||
int? selectedBindingIndex = null,
|
||||
SelectionType? selectionType = null,
|
||||
InputControlScheme? selectedControlScheme = null,
|
||||
int? selectedControlSchemeIndex = null,
|
||||
int? selectedDeviceRequirementIndex = null,
|
||||
List<CutElement> cutElements = null)
|
||||
{
|
||||
return new InputActionsEditorState(
|
||||
m_Analytics,
|
||||
serializedObject,
|
||||
selectedActionMapIndex ?? this.selectedActionMapIndex,
|
||||
selectedActionIndex ?? this.selectedActionIndex,
|
||||
selectedBindingIndex ?? this.selectedBindingIndex,
|
||||
selectionType ?? this.selectionType,
|
||||
|
||||
// Control schemes
|
||||
selectedControlScheme ?? this.selectedControlScheme,
|
||||
selectedControlSchemeIndex ?? this.selectedControlSchemeIndex,
|
||||
selectedDeviceRequirementIndex ?? this.selectedDeviceRequirementIndex,
|
||||
|
||||
cutElements ?? m_CutElements
|
||||
);
|
||||
}
|
||||
|
||||
public InputActionsEditorState ClearCutElements()
|
||||
{
|
||||
return new InputActionsEditorState(
|
||||
m_Analytics,
|
||||
serializedObject,
|
||||
selectedActionMapIndex,
|
||||
selectedActionIndex,
|
||||
selectedBindingIndex,
|
||||
selectionType,
|
||||
selectedControlScheme,
|
||||
selectedControlSchemeIndex,
|
||||
selectedDeviceRequirementIndex,
|
||||
cutElements: null);
|
||||
}
|
||||
|
||||
public SerializedProperty GetActionMapByName(string actionMapName)
|
||||
{
|
||||
return serializedObject
|
||||
.FindProperty(nameof(InputActionAsset.m_ActionMaps))
|
||||
.FirstOrDefault(p => p.FindPropertyRelative(nameof(InputActionMap.m_Name)).stringValue == actionMapName);
|
||||
}
|
||||
|
||||
public InputActionsEditorState SelectAction(string actionName)
|
||||
{
|
||||
var actionMap = GetSelectedActionMap();
|
||||
var actions = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Actions));
|
||||
|
||||
for (var i = 0; i < actions.arraySize; i++)
|
||||
{
|
||||
if (actions.GetArrayElementAtIndex(i)
|
||||
.FindPropertyRelative(nameof(InputAction.m_Name)).stringValue != actionName) continue;
|
||||
|
||||
return With(selectedActionIndex: i, selectionType: SelectionType.Action);
|
||||
}
|
||||
|
||||
// If we cannot find the desired map we should return invalid index
|
||||
return With(selectedActionIndex: -1, selectionType: SelectionType.Action);
|
||||
}
|
||||
|
||||
public InputActionsEditorState SelectAction(SerializedProperty state)
|
||||
{
|
||||
var index = state.GetIndexOfArrayElement();
|
||||
return With(selectedActionIndex: index, selectionType: SelectionType.Action);
|
||||
}
|
||||
|
||||
public InputActionsEditorState SelectActionMap(SerializedProperty actionMap)
|
||||
{
|
||||
var index = actionMap.GetIndexOfArrayElement();
|
||||
return With(selectedBindingIndex: 0, selectedActionMapIndex: index, selectedActionIndex: 0);
|
||||
}
|
||||
|
||||
public InputActionsEditorState SelectActionMap(string actionMapName)
|
||||
{
|
||||
var actionMap = GetActionMapByName(actionMapName);
|
||||
return With(selectedBindingIndex: 0,
|
||||
selectedActionMapIndex: actionMap.GetIndexOfArrayElement(),
|
||||
selectedActionIndex: 0, selectionType: SelectionType.Action);
|
||||
}
|
||||
|
||||
public InputActionsEditorState SelectBinding(int index)
|
||||
{
|
||||
//if no binding selected (due to no bindings in list) set selection type to action
|
||||
if (index == -1)
|
||||
return With(selectedBindingIndex: index, selectionType: SelectionType.Action);
|
||||
return With(selectedBindingIndex: index, selectionType: SelectionType.Binding);
|
||||
}
|
||||
|
||||
public InputActionsEditorState SelectAction(int index)
|
||||
{
|
||||
//if no action selected (no actions available) set selection type to none
|
||||
if (index == -1)
|
||||
return With(selectedActionIndex: index, selectionType: SelectionType.None);
|
||||
return With(selectedActionIndex: index, selectionType: SelectionType.Action);
|
||||
}
|
||||
|
||||
public InputActionsEditorState SelectActionMap(int index)
|
||||
{
|
||||
if (index == -1)
|
||||
return With(selectedActionMapIndex: index, selectionType: SelectionType.None);
|
||||
return With(selectedBindingIndex: 0,
|
||||
selectedActionMapIndex: index,
|
||||
selectedActionIndex: 0, selectionType: SelectionType.Action);
|
||||
}
|
||||
|
||||
public InputActionsEditorState CutActionOrBinding()
|
||||
{
|
||||
m_CutElements = new List<CutElement>();
|
||||
var type = selectionType == SelectionType.Action ? typeof(InputAction) : typeof(InputBinding);
|
||||
var property = selectionType == SelectionType.Action ? Selectors.GetSelectedAction(this)?.wrappedProperty : Selectors.GetSelectedBinding(this)?.wrappedProperty;
|
||||
cutElements.Add(new CutElement(InputActionSerializationHelpers.GetId(property), type));
|
||||
return With(cutElements: cutElements);
|
||||
}
|
||||
|
||||
public InputActionsEditorState CutActionMaps()
|
||||
{
|
||||
m_CutElements = new List<CutElement> { new(InputActionSerializationHelpers.GetId(Selectors.GetSelectedActionMap(this)?.wrappedProperty), typeof(InputActionMap)) };
|
||||
return With(cutElements: cutElements);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDisabledActionMaps(List<string> allActionMaps)
|
||||
{
|
||||
if (cutElements == null || cutElements == null)
|
||||
return Enumerable.Empty<string>();
|
||||
var cutActionMaps = cutElements.Where(cut => cut.type == typeof(InputActionMap));
|
||||
var state = this;
|
||||
return allActionMaps.Where(actionMapName =>
|
||||
{
|
||||
return cutActionMaps.Any(am => am.GetIndexOfProperty(state) == allActionMaps.IndexOf(actionMapName));
|
||||
});
|
||||
}
|
||||
|
||||
public readonly bool IsBindingCut(int actionMapIndex, int bindingIndex)
|
||||
{
|
||||
if (cutElements == null)
|
||||
return false;
|
||||
|
||||
var state = this;
|
||||
return cutElements.Any(cutElement => cutElement.actionMapIndex(state) == actionMapIndex &&
|
||||
cutElement.GetIndexOfProperty(state) == bindingIndex &&
|
||||
cutElement.type == typeof(InputBinding));
|
||||
}
|
||||
|
||||
public readonly bool IsActionCut(int actionMapIndex, int actionIndex)
|
||||
{
|
||||
if (cutElements == null)
|
||||
return false;
|
||||
|
||||
var state = this;
|
||||
return cutElements.Any(cutElement => cutElement.actionMapIndex(state) == actionMapIndex &&
|
||||
cutElement.GetIndexOfProperty(state) == actionIndex &&
|
||||
cutElement.type == typeof(InputAction));
|
||||
}
|
||||
|
||||
public readonly bool IsActionMapCut(int actionMapIndex)
|
||||
{
|
||||
if (cutElements == null)
|
||||
return false;
|
||||
var state = this;
|
||||
return cutElements.Any(cutElement => cutElement.GetIndexOfProperty(state) == actionMapIndex && cutElement.type == typeof(InputActionMap));
|
||||
}
|
||||
|
||||
public readonly List<CutElement> GetCutElements()
|
||||
{
|
||||
return m_CutElements;
|
||||
}
|
||||
|
||||
private SerializedProperty GetSelectedActionMap()
|
||||
{
|
||||
return Selectors.GetActionMapAtIndex(serializedObject, selectedActionMapIndex)?.wrappedProperty;
|
||||
}
|
||||
|
||||
private readonly InputControlScheme m_ControlScheme;
|
||||
}
|
||||
|
||||
internal enum SelectionType
|
||||
{
|
||||
None,
|
||||
Action,
|
||||
Binding
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user