using System;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace Cielonos.Settings.UI
{
///
/// 键位绑定条目,用于 中的每一行。
///
/// 显示:[Action 名称] [当前按键文本] [重新绑定按钮]
/// 点击重新绑定按钮后进入监听模式,捕获下一个按键输入并更新绑定。
///
///
public class SettingsEntryKeyBinding : MonoBehaviour
{
[SerializeField] private TMP_Text actionNameText;
[SerializeField] private TMP_Text currentKeyText;
[SerializeField] private Button rebindButton;
[SerializeField] private TMP_Text rebindButtonText;
[Header("Visuals")]
[SerializeField] private string waitingText = "...";
[SerializeField] private GameObject waitingOverlay;
private InputAction inputAction;
private int bindingIndex;
private InputActionRebindingExtensions.RebindingOperation rebindOperation;
/// 绑定完成时触发。
public event Action OnBindingChanged;
///
/// 初始化键位绑定条目。
///
/// 要绑定的 InputAction。
///
/// 要重新绑定的 binding 索引。
/// 对于非 composite 的 action,通常为 0。
///
/// 条目显示名称(可为 null,则使用 action 名)。
public void Initialize(InputAction action, int targetBindingIndex, string displayName = null)
{
inputAction = action;
bindingIndex = targetBindingIndex;
if (actionNameText != null)
actionNameText.text = displayName ?? FormatActionName(action.name);
if (rebindButton != null)
rebindButton.onClick.AddListener(StartRebind);
RefreshDisplay();
}
///
/// 刷新当前按键的显示文本。
///
public void RefreshDisplay()
{
if (inputAction == null || currentKeyText == null) return;
string displayString = inputAction.bindings[bindingIndex].ToDisplayString(
InputBinding.DisplayStringOptions.DontUseShortDisplayNames);
currentKeyText.text = displayString;
}
///
/// 开始交互式重新绑定。
///
private void StartRebind()
{
if (inputAction == null) return;
// 显示等待提示
SetWaitingState(true);
// 先禁用 action 以允许重新绑定
inputAction.Disable();
rebindOperation = inputAction.PerformInteractiveRebinding(bindingIndex)
.WithControlsExcluding("/position")
.WithControlsExcluding("/delta")
.WithControlsExcluding("/position")
.WithControlsExcluding("/delta")
.WithCancelingThrough("/escape")
.OnMatchWaitForAnother(0.1f)
.OnComplete(operation => FinishRebind(true))
.OnCancel(operation => FinishRebind(false))
.Start();
}
///
/// 重新绑定完成或取消后的处理。
///
private void FinishRebind(bool completed)
{
rebindOperation?.Dispose();
rebindOperation = null;
inputAction.Enable();
SetWaitingState(false);
RefreshDisplay();
if (completed)
{
OnBindingChanged?.Invoke();
}
}
///
/// 重置此条目的绑定到默认值。
///
public void ResetToDefault()
{
if (inputAction == null) return;
inputAction.RemoveBindingOverride(bindingIndex);
RefreshDisplay();
OnBindingChanged?.Invoke();
}
private void SetWaitingState(bool isWaiting)
{
if (currentKeyText != null)
currentKeyText.text = isWaiting ? waitingText : "";
if (rebindButton != null)
rebindButton.interactable = !isWaiting;
if (waitingOverlay != null)
waitingOverlay.SetActive(isWaiting);
}
///
/// 将 ActionName (camelCase) 格式化为可读文本。
///
private static string FormatActionName(string actionName)
{
if (string.IsNullOrEmpty(actionName)) return actionName;
var sb = new System.Text.StringBuilder(actionName.Length + 4);
sb.Append(char.ToUpper(actionName[0]));
for (int i = 1; i < actionName.Length; i++)
{
char c = actionName[i];
if (char.IsUpper(c) && i > 0 && char.IsLower(actionName[i - 1]))
sb.Append(' ');
sb.Append(c);
}
return sb.ToString();
}
private void OnDestroy()
{
rebindOperation?.Dispose();
if (rebindButton != null)
rebindButton.onClick.RemoveListener(StartRebind);
}
}
}