392 lines
18 KiB
C#
392 lines
18 KiB
C#
#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
|