840 lines
27 KiB
C#
840 lines
27 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using UnityGameFramework.Runtime;
|
|
|
|
namespace SepCore.InputModule.Runtime
|
|
{
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("Input Module")]
|
|
public sealed class InputModuleComponent : GameFrameworkComponent
|
|
{
|
|
private const string DefaultBindingOverrideSettingKey = "SepCore.InputModule.BindingOverrides";
|
|
|
|
[SerializeField] private InputActionAsset _inputActionsAsset = null;
|
|
[SerializeField] private InputContextId _startupContext = InputContextId.None;
|
|
[SerializeField] private bool _loadBindingOverridesOnInit = true;
|
|
[SerializeField] private bool _saveBindingOverridesOnDestroy = false;
|
|
[SerializeField] private string _bindingOverrideSettingKey = DefaultBindingOverrideSettingKey;
|
|
|
|
private readonly Dictionary<InputActionId, Action<InputCommand>> _listeners =
|
|
new Dictionary<InputActionId, Action<InputCommand>>();
|
|
|
|
private readonly Dictionary<InputContextId, InputActionMap> _actionMaps =
|
|
new Dictionary<InputContextId, InputActionMap>();
|
|
|
|
private readonly InputContextStack _contextStack = new InputContextStack();
|
|
|
|
private InputActionAsset _runtimeActions = null;
|
|
private SettingComponent _settingComponent = null;
|
|
private bool _isInitialized = false;
|
|
private bool _onInitCalled = false;
|
|
private bool _ownsRuntimeActions = false;
|
|
private InputActionRebindingExtensions.RebindingOperation _activeRebindOperation;
|
|
private bool _rebindCanceledByUser;
|
|
private IInputPromptMap _promptMap;
|
|
|
|
public event Action<InputCommand> CommandTriggered;
|
|
|
|
public event Action<InputDeviceKind> DeviceKindChanged;
|
|
|
|
public event Action<InputContextId> ContextChanged;
|
|
|
|
public event Action<RebindResult> RebindCompleted;
|
|
|
|
public InputDeviceKind CurrentDeviceKind { get; private set; } = InputDeviceKind.Unknown;
|
|
|
|
public InputContextId CurrentContext => _contextStack.Current;
|
|
|
|
public bool IsInitialized => _isInitialized;
|
|
|
|
public bool IsRebinding => _activeRebindOperation != null;
|
|
|
|
public IInputPromptMap PromptMap
|
|
{
|
|
get
|
|
{
|
|
if (_promptMap == null)
|
|
{
|
|
_promptMap = new InputModuleDefaultPromptMap(_runtimeActions);
|
|
}
|
|
|
|
return _promptMap;
|
|
}
|
|
|
|
set => _promptMap = value;
|
|
}
|
|
|
|
public InputActionAsset RuntimeActions
|
|
{
|
|
get
|
|
{
|
|
EnsureInitialized();
|
|
return _runtimeActions;
|
|
}
|
|
}
|
|
|
|
protected override void Awake()
|
|
{
|
|
base.Awake();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (_isInitialized)
|
|
{
|
|
ApplyContextState();
|
|
SubscribeToGlobalDeviceTracking();
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
UnsubscribeFromGlobalDeviceTracking();
|
|
_runtimeActions?.Disable();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (!_isInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CancelRebinding();
|
|
|
|
if (_saveBindingOverridesOnDestroy)
|
|
{
|
|
SaveBindingOverrides();
|
|
}
|
|
|
|
UnsubscribeFromGlobalDeviceTracking();
|
|
UnsubscribeFromActions();
|
|
|
|
if (_ownsRuntimeActions && _runtimeActions != null)
|
|
{
|
|
if (Application.isPlaying)
|
|
{
|
|
Destroy(_runtimeActions);
|
|
}
|
|
else
|
|
{
|
|
DestroyImmediate(_runtimeActions);
|
|
}
|
|
}
|
|
|
|
_runtimeActions = null;
|
|
_actionMaps.Clear();
|
|
_listeners.Clear();
|
|
_isInitialized = false;
|
|
_onInitCalled = false;
|
|
}
|
|
|
|
public void RegisterListener(InputActionId actionId, Action<InputCommand> listener)
|
|
{
|
|
if (listener == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnsureInitialized();
|
|
_listeners[actionId] = _listeners.TryGetValue(actionId, out Action<InputCommand> existing)
|
|
? existing + listener
|
|
: listener;
|
|
}
|
|
|
|
public bool TryGetPrompt(InputActionId actionId, out InputPrompt prompt)
|
|
{
|
|
return PromptMap.TryGetPrompt(actionId, CurrentDeviceKind, out prompt);
|
|
}
|
|
|
|
public void InjectCommand(InputCommand command)
|
|
{
|
|
if (!_isInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CommandTriggered?.Invoke(command);
|
|
|
|
if (_listeners.TryGetValue(command.ActionId, out Action<InputCommand> listener))
|
|
{
|
|
listener?.Invoke(command);
|
|
}
|
|
}
|
|
|
|
public void ForceDeviceKind(InputDeviceKind deviceKind)
|
|
{
|
|
if (deviceKind == InputDeviceKind.Unknown || deviceKind == CurrentDeviceKind)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InputDeviceKind previous = CurrentDeviceKind;
|
|
CurrentDeviceKind = deviceKind;
|
|
Log.Debug("[InputModule] Device changed (forced): {0} -> {1}", previous, deviceKind);
|
|
DeviceKindChanged?.Invoke(CurrentDeviceKind);
|
|
}
|
|
|
|
public void UnregisterListener(InputActionId actionId, Action<InputCommand> listener)
|
|
{
|
|
if (listener == null || !_listeners.TryGetValue(actionId, out Action<InputCommand> existing))
|
|
{
|
|
return;
|
|
}
|
|
|
|
existing -= listener;
|
|
if (existing == null)
|
|
{
|
|
_listeners.Remove(actionId);
|
|
return;
|
|
}
|
|
|
|
_listeners[actionId] = existing;
|
|
}
|
|
|
|
public void OnInit()
|
|
{
|
|
if (_isInitialized)
|
|
{
|
|
Log.Debug("[InputModule] OnInit skipped - already initialized.");
|
|
return;
|
|
}
|
|
|
|
_onInitCalled = true;
|
|
EnsureInitialized();
|
|
ApplyContextState();
|
|
}
|
|
|
|
public void SetContext(InputContextId context)
|
|
{
|
|
EnsureInitialized();
|
|
if (_contextStack.Set(context))
|
|
{
|
|
ApplyContextState();
|
|
NotifyContextChanged();
|
|
Log.Debug("[InputModule] SetContext -> {0}", context);
|
|
}
|
|
}
|
|
|
|
public void SetUIContext()
|
|
{
|
|
SetContext(InputContextId.UI);
|
|
}
|
|
|
|
public void SetGameplayExploreContext()
|
|
{
|
|
SetContext(InputContextId.GameplayExplore);
|
|
}
|
|
|
|
public void PushContext(InputContextId context)
|
|
{
|
|
EnsureInitialized();
|
|
if (_contextStack.Push(context))
|
|
{
|
|
ApplyContextState();
|
|
NotifyContextChanged();
|
|
Log.Debug("[InputModule] PushContext -> {0} (stack depth: {1})", context, _contextStack.Count);
|
|
}
|
|
}
|
|
|
|
public bool PopContext()
|
|
{
|
|
EnsureInitialized();
|
|
if (!_contextStack.Pop(out _))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ApplyContextState();
|
|
NotifyContextChanged();
|
|
Log.Debug("[InputModule] PopContext -> {0} (stack depth: {1})", _contextStack.Current, _contextStack.Count);
|
|
return true;
|
|
}
|
|
|
|
public void ClearContexts()
|
|
{
|
|
EnsureInitialized();
|
|
if (_contextStack.Clear())
|
|
{
|
|
ApplyContextState();
|
|
NotifyContextChanged();
|
|
Log.Debug("[InputModule] ClearContexts");
|
|
}
|
|
}
|
|
|
|
public bool StartRebinding(
|
|
InputContextId contextId,
|
|
InputActionId actionId,
|
|
int bindingIndex,
|
|
float timeoutSeconds = 5f,
|
|
InputDeviceKind expectedDeviceKind = InputDeviceKind.Unknown)
|
|
{
|
|
EnsureInitialized();
|
|
|
|
if (_activeRebindOperation != null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!TryGetAction(contextId, actionId, out InputAction action))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (bindingIndex < 0 || bindingIndex >= action.bindings.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
InputBinding binding = action.bindings[bindingIndex];
|
|
string previousEffectivePath = binding.effectivePath;
|
|
string previousOverridePath = binding.overridePath;
|
|
_rebindCanceledByUser = false;
|
|
|
|
PushContext(InputContextId.Rebind);
|
|
|
|
try
|
|
{
|
|
DisableActionForRebind(action);
|
|
_activeRebindOperation = CreateRebindOperation(
|
|
action,
|
|
contextId,
|
|
actionId,
|
|
bindingIndex,
|
|
timeoutSeconds,
|
|
expectedDeviceKind,
|
|
previousEffectivePath,
|
|
previousOverridePath);
|
|
|
|
_activeRebindOperation.Start();
|
|
}
|
|
catch
|
|
{
|
|
_activeRebindOperation?.Dispose();
|
|
_activeRebindOperation = null;
|
|
|
|
if (_contextStack.Current == InputContextId.Rebind)
|
|
{
|
|
PopContext();
|
|
}
|
|
|
|
throw;
|
|
}
|
|
|
|
RebindCompleted?.Invoke(new RebindResult(RebindStatus.Started, contextId, actionId, bindingIndex, previousEffectivePath, previousEffectivePath));
|
|
return true;
|
|
}
|
|
|
|
public void CancelRebinding()
|
|
{
|
|
if (_activeRebindOperation == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_rebindCanceledByUser = true;
|
|
_activeRebindOperation.Cancel();
|
|
}
|
|
|
|
public void ResetBindingToDefault(InputContextId contextId, InputActionId actionId, int bindingIndex, bool saveAfterReset = false)
|
|
{
|
|
EnsureInitialized();
|
|
if (!TryGetAction(contextId, actionId, out InputAction action))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bindingIndex < 0 || bindingIndex >= action.bindings.Count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
action.RemoveBindingOverride(bindingIndex);
|
|
|
|
if (saveAfterReset)
|
|
{
|
|
SaveBindingOverrides();
|
|
}
|
|
}
|
|
|
|
public void ResetActionToDefaults(InputContextId contextId, InputActionId actionId, bool saveAfterReset = false)
|
|
{
|
|
EnsureInitialized();
|
|
if (!TryGetAction(contextId, actionId, out InputAction action))
|
|
{
|
|
return;
|
|
}
|
|
|
|
action.RemoveAllBindingOverrides();
|
|
|
|
if (saveAfterReset)
|
|
{
|
|
SaveBindingOverrides();
|
|
}
|
|
}
|
|
|
|
public void ResetAllBindingsToDefaults(bool saveAfterReset = false)
|
|
{
|
|
EnsureInitialized();
|
|
_runtimeActions.RemoveAllBindingOverrides();
|
|
|
|
if (saveAfterReset)
|
|
{
|
|
SaveBindingOverrides();
|
|
}
|
|
}
|
|
|
|
public InputBindingSnapshot SaveBindingOverrides()
|
|
{
|
|
EnsureInitialized();
|
|
|
|
string overridesJson = _runtimeActions.SaveBindingOverridesAsJson();
|
|
string storedData = BindingOverridePersistence.Serialize(overridesJson);
|
|
SettingComponent settingComponent = GetSettingComponent();
|
|
if (settingComponent != null)
|
|
{
|
|
settingComponent.SetString(_bindingOverrideSettingKey, storedData);
|
|
}
|
|
|
|
return new InputBindingSnapshot(overridesJson);
|
|
}
|
|
|
|
public void LoadBindingOverrides()
|
|
{
|
|
EnsureInitialized();
|
|
LoadBindingOverridesCore();
|
|
}
|
|
|
|
private void LoadBindingOverridesCore()
|
|
{
|
|
if (_runtimeActions == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SettingComponent settingComponent = GetSettingComponent();
|
|
if (settingComponent == null || !settingComponent.HasSetting(_bindingOverrideSettingKey))
|
|
{
|
|
return;
|
|
}
|
|
|
|
string storedData = settingComponent.GetString(_bindingOverrideSettingKey);
|
|
if (!BindingOverridePersistence.TryDeserialize(storedData, out string overridesJson))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(overridesJson))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_runtimeActions.LoadBindingOverridesFromJson(overridesJson);
|
|
}
|
|
|
|
public void ApplyBindingSnapshot(InputBindingSnapshot snapshot, bool saveAfterApply = false)
|
|
{
|
|
EnsureInitialized();
|
|
_runtimeActions.LoadBindingOverridesFromJson(snapshot.OverridesJson);
|
|
|
|
if (saveAfterApply)
|
|
{
|
|
SaveBindingOverrides();
|
|
}
|
|
}
|
|
|
|
private void EnsureInitialized()
|
|
{
|
|
if (_isInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_runtimeActions = _inputActionsAsset != null
|
|
? Instantiate(_inputActionsAsset)
|
|
: InputModuleDefaultActions.Create();
|
|
_ownsRuntimeActions = true;
|
|
|
|
CacheActionMaps();
|
|
SubscribeToActions();
|
|
_isInitialized = true;
|
|
SubscribeToGlobalDeviceTracking();
|
|
|
|
if (_promptMap is InputModuleDefaultPromptMap)
|
|
{
|
|
_promptMap = null;
|
|
}
|
|
|
|
int mapCount = _actionMaps.Count;
|
|
if (mapCount == 0)
|
|
{
|
|
Log.Warning("[InputModule] Initialized but no action maps were cached. Check that action map names match InputContextId enum values.");
|
|
}
|
|
else
|
|
{
|
|
Log.Info("[InputModule] Initialized. Source: {0}, Cached maps: {1}, Startup context: {2}",
|
|
_inputActionsAsset != null ? "Serialized asset" : "Default actions",
|
|
mapCount,
|
|
_startupContext);
|
|
}
|
|
|
|
if (_loadBindingOverridesOnInit)
|
|
{
|
|
LoadBindingOverridesCore();
|
|
}
|
|
|
|
_contextStack.Set(_startupContext);
|
|
|
|
if (!_onInitCalled)
|
|
{
|
|
Log.Warning("[InputModule] Auto-initialized before OnInit() was called. Action maps will not be enabled until OnInit() is invoked.");
|
|
}
|
|
}
|
|
|
|
private void CacheActionMaps()
|
|
{
|
|
_actionMaps.Clear();
|
|
foreach (InputActionMap map in _runtimeActions.actionMaps)
|
|
{
|
|
if (TryParseContextId(map.name, out InputContextId contextId))
|
|
{
|
|
_actionMaps[contextId] = map;
|
|
}
|
|
else
|
|
{
|
|
Log.Warning("[InputModule] ActionMap '{0}' does not match any InputContextId and will be ignored.", map.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SubscribeToActions()
|
|
{
|
|
foreach (InputAction action in _runtimeActions)
|
|
{
|
|
action.started += OnActionTriggered;
|
|
action.performed += OnActionTriggered;
|
|
action.canceled += OnActionTriggered;
|
|
}
|
|
}
|
|
|
|
private void UnsubscribeFromActions()
|
|
{
|
|
if (_runtimeActions == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (InputAction action in _runtimeActions)
|
|
{
|
|
action.started -= OnActionTriggered;
|
|
action.performed -= OnActionTriggered;
|
|
action.canceled -= OnActionTriggered;
|
|
}
|
|
}
|
|
|
|
private void SubscribeToGlobalDeviceTracking()
|
|
{
|
|
InputSystem.onActionChange -= OnGlobalActionChange;
|
|
InputSystem.onActionChange += OnGlobalActionChange;
|
|
}
|
|
|
|
private void UnsubscribeFromGlobalDeviceTracking()
|
|
{
|
|
InputSystem.onActionChange -= OnGlobalActionChange;
|
|
}
|
|
|
|
private void OnGlobalActionChange(object obj, InputActionChange change)
|
|
{
|
|
if (change == InputActionChange.ActionPerformed && obj is InputAction.CallbackContext context)
|
|
{
|
|
UpdateDeviceKind(context);
|
|
}
|
|
}
|
|
|
|
// Disables all action maps, then enables Global + the topmost context in the stack.
|
|
// Only called from OnInit() (explicit activation) and OnEnable() (re-enable after disable).
|
|
// The stack remembers context history for Pop restoration; only the current (topmost)
|
|
// context is active at any time, preventing overlapping bindings from stacked maps.
|
|
private void ApplyContextState()
|
|
{
|
|
if (_runtimeActions == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_runtimeActions.Disable();
|
|
|
|
if (_actionMaps.TryGetValue(InputContextId.Global, out InputActionMap globalMap))
|
|
{
|
|
globalMap.Enable();
|
|
}
|
|
|
|
InputContextId current = _contextStack.Current;
|
|
if (current != InputContextId.None && _actionMaps.TryGetValue(current, out InputActionMap currentMap))
|
|
{
|
|
currentMap.Enable();
|
|
}
|
|
}
|
|
|
|
private void NotifyContextChanged()
|
|
{
|
|
ContextChanged?.Invoke(CurrentContext);
|
|
}
|
|
|
|
private void OnActionTriggered(InputAction.CallbackContext context)
|
|
{
|
|
if (_activeRebindOperation != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InputActionId actionId = ParseActionId(context);
|
|
InputContextId contextId = ParseContextId(context);
|
|
InputDeviceKind deviceKind = UpdateDeviceKind(context);
|
|
|
|
InputCommand command = CreateCommand(actionId, contextId, deviceKind, context);
|
|
|
|
CommandTriggered?.Invoke(command);
|
|
|
|
if (_listeners.TryGetValue(actionId, out Action<InputCommand> listener))
|
|
{
|
|
listener?.Invoke(command);
|
|
}
|
|
}
|
|
|
|
private InputDeviceKind UpdateDeviceKind(InputAction.CallbackContext context)
|
|
{
|
|
InputDeviceKind deviceKind = InputDeviceKindUtility.FromDevice(context.control?.device);
|
|
if (deviceKind != InputDeviceKind.Unknown && deviceKind != CurrentDeviceKind)
|
|
{
|
|
InputDeviceKind previous = CurrentDeviceKind;
|
|
CurrentDeviceKind = deviceKind;
|
|
Log.Debug("[InputModule] Device changed: {0} -> {1}", previous, deviceKind);
|
|
DeviceKindChanged?.Invoke(CurrentDeviceKind);
|
|
}
|
|
|
|
return CurrentDeviceKind;
|
|
}
|
|
|
|
private static InputCommand CreateCommand(
|
|
InputActionId actionId,
|
|
InputContextId contextId,
|
|
InputDeviceKind deviceKind,
|
|
InputAction.CallbackContext context)
|
|
{
|
|
Vector2 vector2Value = Vector2.zero;
|
|
float scalarValue = 0f;
|
|
|
|
if (context.action.expectedControlType == "Vector2")
|
|
{
|
|
vector2Value = context.ReadValue<Vector2>();
|
|
scalarValue = vector2Value.magnitude;
|
|
}
|
|
else
|
|
{
|
|
scalarValue = context.ReadValue<float>();
|
|
}
|
|
|
|
return new InputCommand(
|
|
actionId,
|
|
contextId,
|
|
ConvertPhase(context.phase),
|
|
deviceKind,
|
|
vector2Value,
|
|
scalarValue,
|
|
context.time);
|
|
}
|
|
|
|
private SettingComponent GetSettingComponent()
|
|
{
|
|
if (_settingComponent == null)
|
|
{
|
|
_settingComponent = UnityGameFramework.Runtime.GameEntry.GetComponent<SettingComponent>();
|
|
}
|
|
|
|
return _settingComponent;
|
|
}
|
|
|
|
private static InputActionId ParseActionId(InputAction.CallbackContext context)
|
|
{
|
|
return Enum.TryParse(context.action.name, out InputActionId actionId)
|
|
? actionId
|
|
: InputActionId.Unknown;
|
|
}
|
|
|
|
private static InputContextId ParseContextId(InputAction.CallbackContext context)
|
|
{
|
|
return TryParseContextId(context.action.actionMap.name, out InputContextId contextId)
|
|
? contextId
|
|
: InputContextId.None;
|
|
}
|
|
|
|
private static bool TryParseContextId(string rawContextId, out InputContextId contextId)
|
|
{
|
|
return Enum.TryParse(rawContextId, out contextId);
|
|
}
|
|
|
|
private static InputCommandPhase ConvertPhase(InputActionPhase phase)
|
|
{
|
|
switch (phase)
|
|
{
|
|
case InputActionPhase.Started:
|
|
return InputCommandPhase.Started;
|
|
case InputActionPhase.Canceled:
|
|
return InputCommandPhase.Canceled;
|
|
default:
|
|
return InputCommandPhase.Performed;
|
|
}
|
|
}
|
|
|
|
private bool TryGetAction(InputContextId contextId, InputActionId actionId, out InputAction action)
|
|
{
|
|
action = null;
|
|
if (!_actionMaps.TryGetValue(contextId, out InputActionMap map))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
action = map.FindAction(actionId.ToString());
|
|
return action != null;
|
|
}
|
|
|
|
private bool DetectConflict(InputAction action, int bindingIndex, string newPath)
|
|
{
|
|
if (string.IsNullOrEmpty(newPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
InputActionMap targetMap = action.actionMap;
|
|
InputBinding targetBinding = action.bindings[bindingIndex];
|
|
|
|
foreach (InputActionMap map in _runtimeActions.actionMaps)
|
|
{
|
|
if (map != targetMap && map.name != nameof(InputContextId.Global))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (InputBinding binding in map.bindings)
|
|
{
|
|
if (map == targetMap && binding.id == targetBinding.id)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (string.Equals(binding.effectivePath, newPath, System.StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private InputActionRebindingExtensions.RebindingOperation CreateRebindOperation(
|
|
InputAction action,
|
|
InputContextId contextId,
|
|
InputActionId actionId,
|
|
int bindingIndex,
|
|
float timeoutSeconds,
|
|
InputDeviceKind expectedDeviceKind,
|
|
string previousEffectivePath,
|
|
string previousOverridePath)
|
|
{
|
|
InputActionRebindingExtensions.RebindingOperation operation = new InputActionRebindingExtensions.RebindingOperation()
|
|
.WithAction(action)
|
|
.WithTargetBinding(bindingIndex)
|
|
.WithTimeout(timeoutSeconds)
|
|
.WithControlsExcluding("<Pointer>/delta")
|
|
.WithControlsExcluding("<Pointer>/position")
|
|
.WithControlsExcluding("<Touchscreen>/touch*/position")
|
|
.WithControlsExcluding("<Touchscreen>/touch*/delta")
|
|
.WithControlsExcluding("<Mouse>/clickCount")
|
|
.WithMatchingEventsBeingSuppressed()
|
|
.OnCancel(_ =>
|
|
{
|
|
RebindStatus status = _rebindCanceledByUser ? RebindStatus.Canceled : RebindStatus.Timeout;
|
|
CompleteRebind(new RebindResult(status, contextId, actionId, bindingIndex, action.bindings[bindingIndex].effectivePath, previousEffectivePath));
|
|
})
|
|
.OnComplete(rebindOperation =>
|
|
{
|
|
InputDeviceKind actualDeviceKind = InputDeviceKindUtility.FromDevice(rebindOperation.selectedControl?.device);
|
|
if (expectedDeviceKind != InputDeviceKind.Unknown && actualDeviceKind != expectedDeviceKind)
|
|
{
|
|
RevertBinding(action, bindingIndex, previousOverridePath);
|
|
CompleteRebind(new RebindResult(RebindStatus.Canceled, contextId, actionId, bindingIndex, action.bindings[bindingIndex].effectivePath, previousEffectivePath));
|
|
return;
|
|
}
|
|
|
|
string newPath = action.bindings[bindingIndex].effectivePath;
|
|
if (DetectConflict(action, bindingIndex, newPath))
|
|
{
|
|
RevertBinding(action, bindingIndex, previousOverridePath);
|
|
CompleteRebind(new RebindResult(RebindStatus.ConflictDetected, contextId, actionId, bindingIndex, newPath, previousEffectivePath));
|
|
return;
|
|
}
|
|
|
|
CompleteRebind(new RebindResult(RebindStatus.Succeeded, contextId, actionId, bindingIndex, newPath, previousEffectivePath));
|
|
});
|
|
|
|
if (operation.expectedControlType != "Button")
|
|
{
|
|
operation.WithCancelingThrough("<Keyboard>/escape");
|
|
}
|
|
|
|
return operation;
|
|
}
|
|
|
|
private void CompleteRebind(RebindResult result)
|
|
{
|
|
_activeRebindOperation?.Dispose();
|
|
_activeRebindOperation = null;
|
|
|
|
if (_contextStack.Current == InputContextId.Rebind)
|
|
{
|
|
PopContext();
|
|
}
|
|
|
|
RebindCompleted?.Invoke(result);
|
|
}
|
|
|
|
private static void RevertBinding(InputAction action, int bindingIndex, string previousOverridePath)
|
|
{
|
|
if (string.IsNullOrEmpty(previousOverridePath))
|
|
{
|
|
action.RemoveBindingOverride(bindingIndex);
|
|
}
|
|
else
|
|
{
|
|
action.ApplyBindingOverride(bindingIndex, previousOverridePath);
|
|
}
|
|
}
|
|
|
|
private static void DisableActionForRebind(InputAction action)
|
|
{
|
|
InputActionMap map = action.actionMap;
|
|
if (map == null)
|
|
return;
|
|
|
|
if (map.enabled)
|
|
{
|
|
map.Disable();
|
|
return;
|
|
}
|
|
|
|
// Map is already disabled but action.enabled may still report true
|
|
// because map.Disable() bails early when m_Enabled is already false.
|
|
// Force-sync by briefly enabling and re-disabling the map.
|
|
if (action.enabled)
|
|
{
|
|
map.Enable();
|
|
map.Disable();
|
|
}
|
|
}
|
|
}
|
|
}
|