Files
Cielonos/Assets/Scripts/Settings/UI/SettingsEntryKeyBinding.cs
SoulliesOfficial 6d7ebc5825 Passion & UI
2026-06-12 17:11:39 -04:00

167 lines
5.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace Cielonos.Settings.UI
{
/// <summary>
/// 键位绑定条目,用于 <see cref="KeyBindingPage"/> 中的每一行。
/// <para>
/// 显示:[Action 名称] [当前按键文本] [重新绑定按钮]
/// 点击重新绑定按钮后进入监听模式,捕获下一个按键输入并更新绑定。
/// </para>
/// </summary>
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;
/// <summary>绑定完成时触发。</summary>
public event Action OnBindingChanged;
/// <summary>
/// 初始化键位绑定条目。
/// </summary>
/// <param name="action">要绑定的 InputAction。</param>
/// <param name="targetBindingIndex">
/// 要重新绑定的 binding 索引。
/// 对于非 composite 的 action通常为 0。
/// </param>
/// <param name="displayName">条目显示名称(可为 null则使用 action 名)。</param>
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();
}
/// <summary>
/// 刷新当前按键的显示文本。
/// </summary>
public void RefreshDisplay()
{
if (inputAction == null || currentKeyText == null) return;
string displayString = inputAction.bindings[bindingIndex].ToDisplayString(
InputBinding.DisplayStringOptions.DontUseShortDisplayNames);
currentKeyText.text = displayString;
}
/// <summary>
/// 开始交互式重新绑定。
/// </summary>
private void StartRebind()
{
if (inputAction == null) return;
// 显示等待提示
SetWaitingState(true);
// 先禁用 action 以允许重新绑定
inputAction.Disable();
rebindOperation = inputAction.PerformInteractiveRebinding(bindingIndex)
.WithControlsExcluding("<Mouse>/position")
.WithControlsExcluding("<Mouse>/delta")
.WithControlsExcluding("<Pointer>/position")
.WithControlsExcluding("<Pointer>/delta")
.WithCancelingThrough("<Keyboard>/escape")
.OnMatchWaitForAnother(0.1f)
.OnComplete(operation => FinishRebind(true))
.OnCancel(operation => FinishRebind(false))
.Start();
}
/// <summary>
/// 重新绑定完成或取消后的处理。
/// </summary>
private void FinishRebind(bool completed)
{
rebindOperation?.Dispose();
rebindOperation = null;
inputAction.Enable();
SetWaitingState(false);
RefreshDisplay();
if (completed)
{
OnBindingChanged?.Invoke();
}
}
/// <summary>
/// 重置此条目的绑定到默认值。
/// </summary>
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);
}
/// <summary>
/// 将 ActionName (camelCase) 格式化为可读文本。
/// </summary>
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);
}
}
}