// Copyright (C) 2023 Nicholas Maltbie
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using nickmaltbie.ScreenManager;
using nickmaltbie.ScreenManager.Actions;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace nickmaltbie.OpenKCC.UI.Actions
{
///
/// Rebind an individual button input action
///
public class SelectiveRebindInput : MonoBehaviour, IBindingControl
{
///
/// Prefix for input mapping for saving to player preferences
///
public const string inputMappingPlayerPrefPrefix = "Input Mapping";
///
/// Path used to cancel binding mid operation
///
public string cancelPath = "/escape";
///
/// Input action being modified
///
public InputActionReference inputAction = null;
///
/// Binding display name for showing the control button description
///
public UnityEngine.UI.Text bindingDisplayNameText = null;
///
/// Button to start rebinding for the given input action
///
public Button startRebinding = null;
///
/// Text to display when waiting for the player to press a new input action
///
public GameObject waitingForInputObject = null;
///
/// Menu controller related to this selected object
///
public MenuController menuController;
///
/// Rebinding operation action waiting for player command to change button bindings
///
public InputActionRebindingExtensions.RebindingOperation rebindingOperation { get; private set; }
///
/// Get a readable display name for a binding index
///
/// Human readable information of button for this binding index
public string GetKeyReadableName() =>
InputControlPath.ToHumanReadableString(
inputAction.action.bindings[0].effectivePath,
InputControlPath.HumanReadableStringOptions.OmitDevice);
///
/// Get the input mapping player preference key from a given index
///
public string InputMappingKey => $"{inputMappingPlayerPrefPrefix} {inputAction.action.name}";
public void Awake()
{
// Load the default mapping saved to the file
menuController = gameObject.GetComponentInParent();
string inputMapping = PlayerPrefs.GetString(InputMappingKey, string.Empty);
if (!string.IsNullOrEmpty(inputMapping))
{
inputAction.action.ApplyBindingOverride(0, inputMapping);
}
}
public void Start()
{
startRebinding.onClick.AddListener(() => StartRebinding());
bindingDisplayNameText.text = GetKeyReadableName();
}
///
/// Start the rebinding process for a given component of this composite axis.
///
public void StartRebinding()
{
startRebinding.gameObject.SetActive(false);
waitingForInputObject.SetActive(true);
menuController.allowInputChanges = false;
inputAction.action.Disable();
inputAction.action.actionMap.Disable();
rebindingOperation = inputAction.action.PerformInteractiveRebinding(0)
.WithControlsExcluding("/position") // Don't bind to mouse position
.WithControlsExcluding("/delta") // To avoid accidental input from mouse motion
.WithCancelingThrough(cancelPath)
.OnMatchWaitForAnother(0.1f)
.WithTimeout(5.0f)
.OnComplete(operation => RebindComplete())
.OnCancel(operation => RebindCancel())
.Start();
}
///
/// Cancel the rebinding process for a given component of this composite axis.
///
public void RebindCancel()
{
bindingDisplayNameText.text = GetKeyReadableName();
rebindingOperation.Dispose();
startRebinding.gameObject.SetActive(true);
waitingForInputObject.SetActive(false);
menuController.allowInputChanges = true;
inputAction.action.Enable();
inputAction.action.actionMap.Enable();
}
///
/// Finish the rebinding process for a given component of this composite axis.
///
public void RebindComplete()
{
string overridePath = inputAction.action.bindings[0].overridePath;
#if UNITY_2020_3_OR_NEWER
foreach (PlayerInput input in GameObject.FindObjectsByType(FindObjectsSortMode.None))
#else
foreach (PlayerInput input in GameObject.FindObjectsOfType())
#endif
{
input.actions.FindAction(inputAction.name)?.ApplyBindingOverride(0, overridePath);
}
bindingDisplayNameText.text = GetKeyReadableName();
rebindingOperation.Dispose();
PlayerPrefs.SetString(InputMappingKey, inputAction.action.bindings[0].overridePath);
startRebinding.gameObject.SetActive(true);
waitingForInputObject.SetActive(false);
menuController.allowInputChanges = true;
inputAction.action.Enable();
inputAction.action.actionMap.Enable();
}
public void ResetBinding()
{
PlayerPrefs.DeleteKey(InputMappingKey);
inputAction.action.RemoveAllBindingOverrides();
}
public void UpdateDisplay()
{
bindingDisplayNameText.text = GetKeyReadableName();
}
}
}