| using System; |
| using System.Collections.Generic; |
| using UnityEngine.Events; |
| using UnityEngine.UI; |
|
|
| |
|
|
| |
|
|
| namespace UnityEngine.InputSystem.Samples.RebindUI |
| { |
| |
| |
| |
| public class RebindActionUI : MonoBehaviour |
| { |
| |
| |
| |
| public InputActionReference actionReference |
| { |
| get => m_Action; |
| set |
| { |
| m_Action = value; |
| UpdateActionLabel(); |
| UpdateBindingDisplay(); |
| } |
| } |
|
|
| |
| |
| |
| |
| public string bindingId |
| { |
| get => m_BindingId; |
| set |
| { |
| m_BindingId = value; |
| UpdateBindingDisplay(); |
| } |
| } |
|
|
| public InputBinding.DisplayStringOptions displayStringOptions |
| { |
| get => m_DisplayStringOptions; |
| set |
| { |
| m_DisplayStringOptions = value; |
| UpdateBindingDisplay(); |
| } |
| } |
|
|
| |
| |
| |
| public Text actionLabel |
| { |
| get => m_ActionLabel; |
| set |
| { |
| m_ActionLabel = value; |
| UpdateActionLabel(); |
| } |
| } |
|
|
| |
| |
| |
| |
| public Text bindingText |
| { |
| get => m_BindingText; |
| set |
| { |
| m_BindingText = value; |
| UpdateBindingDisplay(); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| public Text rebindPrompt |
| { |
| get => m_RebindText; |
| set => m_RebindText = value; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public GameObject rebindOverlay |
| { |
| get => m_RebindOverlay; |
| set => m_RebindOverlay = value; |
| } |
|
|
| |
| |
| |
| |
| public UpdateBindingUIEvent updateBindingUIEvent |
| { |
| get |
| { |
| if (m_UpdateBindingUIEvent == null) |
| m_UpdateBindingUIEvent = new UpdateBindingUIEvent(); |
| return m_UpdateBindingUIEvent; |
| } |
| } |
|
|
| |
| |
| |
| public InteractiveRebindEvent startRebindEvent |
| { |
| get |
| { |
| if (m_RebindStartEvent == null) |
| m_RebindStartEvent = new InteractiveRebindEvent(); |
| return m_RebindStartEvent; |
| } |
| } |
|
|
| |
| |
| |
| public InteractiveRebindEvent stopRebindEvent |
| { |
| get |
| { |
| if (m_RebindStopEvent == null) |
| m_RebindStopEvent = new InteractiveRebindEvent(); |
| return m_RebindStopEvent; |
| } |
| } |
|
|
| |
| |
| |
| |
| public InputActionRebindingExtensions.RebindingOperation ongoingRebind => m_RebindOperation; |
|
|
| |
| |
| |
| |
| |
| |
| |
| public bool ResolveActionAndBinding(out InputAction action, out int bindingIndex) |
| { |
| bindingIndex = -1; |
|
|
| action = m_Action?.action; |
| if (action == null) |
| return false; |
|
|
| if (string.IsNullOrEmpty(m_BindingId)) |
| return false; |
|
|
| |
| var bindingId = new Guid(m_BindingId); |
| bindingIndex = action.bindings.IndexOf(x => x.id == bindingId); |
| if (bindingIndex == -1) |
| { |
| Debug.LogError($"Cannot find binding with ID '{bindingId}' on '{action}'", this); |
| return false; |
| } |
|
|
| return true; |
| } |
|
|
| |
| |
| |
| public void UpdateBindingDisplay() |
| { |
| var displayString = string.Empty; |
| var deviceLayoutName = default(string); |
| var controlPath = default(string); |
|
|
| |
| var action = m_Action?.action; |
| if (action != null) |
| { |
| var bindingIndex = action.bindings.IndexOf(x => x.id.ToString() == m_BindingId); |
| if (bindingIndex != -1) |
| displayString = action.GetBindingDisplayString(bindingIndex, out deviceLayoutName, out controlPath, displayStringOptions); |
| } |
|
|
| |
| if (m_BindingText != null) |
| m_BindingText.text = displayString; |
|
|
| |
| m_UpdateBindingUIEvent?.Invoke(this, displayString, deviceLayoutName, controlPath); |
| } |
|
|
| |
| |
| |
| public void ResetToDefault() |
| { |
| if (!ResolveActionAndBinding(out var action, out var bindingIndex)) |
| return; |
|
|
| if (action.bindings[bindingIndex].isComposite) |
| { |
| |
| for (var i = bindingIndex + 1; i < action.bindings.Count && action.bindings[i].isPartOfComposite; ++i) |
| action.RemoveBindingOverride(i); |
| } |
| else |
| { |
| action.RemoveBindingOverride(bindingIndex); |
| } |
| UpdateBindingDisplay(); |
| } |
|
|
| |
| |
| |
| |
| public void StartInteractiveRebind() |
| { |
| if (!ResolveActionAndBinding(out var action, out var bindingIndex)) |
| return; |
|
|
| |
| if (action.bindings[bindingIndex].isComposite) |
| { |
| var firstPartIndex = bindingIndex + 1; |
| if (firstPartIndex < action.bindings.Count && action.bindings[firstPartIndex].isPartOfComposite) |
| PerformInteractiveRebind(action, firstPartIndex, allCompositeParts: true); |
| } |
| else |
| { |
| PerformInteractiveRebind(action, bindingIndex); |
| } |
| } |
|
|
| private void PerformInteractiveRebind(InputAction action, int bindingIndex, bool allCompositeParts = false) |
| { |
| m_RebindOperation?.Cancel(); |
|
|
| void CleanUp() |
| { |
| m_RebindOperation?.Dispose(); |
| m_RebindOperation = null; |
| } |
|
|
| |
| m_RebindOperation = action.PerformInteractiveRebinding(bindingIndex) |
| .OnCancel( |
| operation => |
| { |
| m_RebindStopEvent?.Invoke(this, operation); |
| m_RebindOverlay?.SetActive(false); |
| UpdateBindingDisplay(); |
| CleanUp(); |
| }) |
| .OnComplete( |
| operation => |
| { |
| m_RebindOverlay?.SetActive(false); |
| m_RebindStopEvent?.Invoke(this, operation); |
| UpdateBindingDisplay(); |
| CleanUp(); |
|
|
| |
| |
| if (allCompositeParts) |
| { |
| var nextBindingIndex = bindingIndex + 1; |
| if (nextBindingIndex < action.bindings.Count && action.bindings[nextBindingIndex].isPartOfComposite) |
| PerformInteractiveRebind(action, nextBindingIndex, true); |
| } |
| }); |
|
|
| |
| var partName = default(string); |
| if (action.bindings[bindingIndex].isPartOfComposite) |
| partName = $"Binding '{action.bindings[bindingIndex].name}'. "; |
|
|
| |
| m_RebindOverlay?.SetActive(true); |
| if (m_RebindText != null) |
| { |
| var text = !string.IsNullOrEmpty(m_RebindOperation.expectedControlType) |
| ? $"{partName}Waiting for {m_RebindOperation.expectedControlType} input..." |
| : $"{partName}Waiting for input..."; |
| m_RebindText.text = text; |
| } |
|
|
| |
| |
| if (m_RebindOverlay == null && m_RebindText == null && m_RebindStartEvent == null && m_BindingText != null) |
| m_BindingText.text = "<Waiting...>"; |
|
|
| |
| m_RebindStartEvent?.Invoke(this, m_RebindOperation); |
|
|
| m_RebindOperation.Start(); |
| } |
|
|
| protected void OnEnable() |
| { |
| if (s_RebindActionUIs == null) |
| s_RebindActionUIs = new List<RebindActionUI>(); |
| s_RebindActionUIs.Add(this); |
| if (s_RebindActionUIs.Count == 1) |
| InputSystem.onActionChange += OnActionChange; |
| } |
|
|
| protected void OnDisable() |
| { |
| m_RebindOperation?.Dispose(); |
| m_RebindOperation = null; |
|
|
| s_RebindActionUIs.Remove(this); |
| if (s_RebindActionUIs.Count == 0) |
| { |
| s_RebindActionUIs = null; |
| InputSystem.onActionChange -= OnActionChange; |
| } |
| } |
|
|
| |
| |
| |
| |
| private static void OnActionChange(object obj, InputActionChange change) |
| { |
| if (change != InputActionChange.BoundControlsChanged) |
| return; |
|
|
| var action = obj as InputAction; |
| var actionMap = action?.actionMap ?? obj as InputActionMap; |
| var actionAsset = actionMap?.asset ?? obj as InputActionAsset; |
|
|
| for (var i = 0; i < s_RebindActionUIs.Count; ++i) |
| { |
| var component = s_RebindActionUIs[i]; |
| var referencedAction = component.actionReference?.action; |
| if (referencedAction == null) |
| continue; |
|
|
| if (referencedAction == action || |
| referencedAction.actionMap == actionMap || |
| referencedAction.actionMap?.asset == actionAsset) |
| component.UpdateBindingDisplay(); |
| } |
| } |
|
|
| [Tooltip("Reference to action that is to be rebound from the UI.")] |
| [SerializeField] |
| private InputActionReference m_Action; |
|
|
| [SerializeField] |
| private string m_BindingId; |
|
|
| [SerializeField] |
| private InputBinding.DisplayStringOptions m_DisplayStringOptions; |
|
|
| [Tooltip("Text label that will receive the name of the action. Optional. Set to None to have the " |
| + "rebind UI not show a label for the action.")] |
| [SerializeField] |
| private Text m_ActionLabel; |
|
|
| [Tooltip("Text label that will receive the current, formatted binding string.")] |
| [SerializeField] |
| private Text m_BindingText; |
|
|
| [Tooltip("Optional UI that will be shown while a rebind is in progress.")] |
| [SerializeField] |
| private GameObject m_RebindOverlay; |
|
|
| [Tooltip("Optional text label that will be updated with prompt for user input.")] |
| [SerializeField] |
| private Text m_RebindText; |
|
|
| [Tooltip("Event that is triggered when the way the binding is display should be updated. This allows displaying " |
| + "bindings in custom ways, e.g. using images instead of text.")] |
| [SerializeField] |
| private UpdateBindingUIEvent m_UpdateBindingUIEvent; |
|
|
| [Tooltip("Event that is triggered when an interactive rebind is being initiated. This can be used, for example, " |
| + "to implement custom UI behavior while a rebind is in progress. It can also be used to further " |
| + "customize the rebind.")] |
| [SerializeField] |
| private InteractiveRebindEvent m_RebindStartEvent; |
|
|
| [Tooltip("Event that is triggered when an interactive rebind is complete or has been aborted.")] |
| [SerializeField] |
| private InteractiveRebindEvent m_RebindStopEvent; |
|
|
| private InputActionRebindingExtensions.RebindingOperation m_RebindOperation; |
|
|
| private static List<RebindActionUI> s_RebindActionUIs; |
|
|
| |
| |
| #if UNITY_EDITOR |
| protected void OnValidate() |
| { |
| UpdateActionLabel(); |
| UpdateBindingDisplay(); |
| } |
|
|
| #endif |
|
|
| private void UpdateActionLabel() |
| { |
| if (m_ActionLabel != null) |
| { |
| var action = m_Action?.action; |
| m_ActionLabel.text = action != null ? action.name : string.Empty; |
| } |
| } |
|
|
| [Serializable] |
| public class UpdateBindingUIEvent : UnityEvent<RebindActionUI, string, string, string> |
| { |
| } |
|
|
| [Serializable] |
| public class InteractiveRebindEvent : UnityEvent<RebindActionUI, InputActionRebindingExtensions.RebindingOperation> |
| { |
| } |
| } |
| } |
|
|