347 lines
11 KiB
C#
347 lines
11 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
using System.Collections;
|
|
using System.Runtime.InteropServices;
|
|
|
|
public class FocusManager : MonoBehaviour
|
|
{
|
|
// 单例实例
|
|
public static FocusManager Instance { get; private set; }
|
|
|
|
[Header("导航按钮")]
|
|
public Button nextFocusButton;
|
|
public Button previousFocusButton;
|
|
public Button firstFocusButton;
|
|
public Button lastFocusButton;
|
|
|
|
[Header("下拉菜单控制按钮")]
|
|
public Button dropdownToggleButton;
|
|
public Button dropdownNextButton;
|
|
public Button dropdownPreviousButton;
|
|
public Button dropdownSubmitButton;
|
|
|
|
[Header("状态显示")]
|
|
public TextMeshProUGUI dropdownStatusText;
|
|
public TextMeshProUGUI pageStatusText;
|
|
public TextMeshProUGUI inputStatusText;
|
|
|
|
[Header("调试")]
|
|
public TextMeshProUGUI debugText;
|
|
|
|
private FocusPage currentPage;
|
|
private bool isProcessingInput = false; // 防止重复输入
|
|
|
|
void Awake()
|
|
{
|
|
if (Instance == null)
|
|
{
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
}
|
|
else
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
|
|
SetupButtonListeners();
|
|
Debug.Log("FocusManager 初始化完成");
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
ForceUpdateButtonStates();
|
|
UpdateStatusDisplay();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
HandleKeyboardNavigation();
|
|
UpdateStatusDisplay();
|
|
UpdateDebugInfo();
|
|
}
|
|
|
|
#region 公共方法
|
|
public void ForceUpdateButtonStates()
|
|
{
|
|
UpdateButtonStates();
|
|
}
|
|
#endregion
|
|
|
|
#region 页面管理
|
|
public void SetCurrentPage(FocusPage page)
|
|
{
|
|
if (page == null) return;
|
|
|
|
if (currentPage != null)
|
|
{
|
|
currentPage.ClearFocus();
|
|
}
|
|
|
|
currentPage = page;
|
|
Debug.Log($"当前焦点页面更新为: {page.gameObject.name}");
|
|
|
|
ForceUpdateButtonStates();
|
|
UpdateStatusDisplay();
|
|
}
|
|
|
|
public FocusPage GetCurrentPage()
|
|
{
|
|
return currentPage;
|
|
}
|
|
#endregion
|
|
|
|
#region 焦点导航
|
|
public void FocusNext()
|
|
{
|
|
if (currentPage == null || isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage.FocusNext()));
|
|
}
|
|
|
|
public void FocusPrevious()
|
|
{
|
|
if (currentPage == null || isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage.FocusPrevious()));
|
|
}
|
|
|
|
public void FocusFirst()
|
|
{
|
|
if (currentPage == null || isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage.FocusFirst()));
|
|
}
|
|
|
|
public void FocusLast()
|
|
{
|
|
if (currentPage == null || isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage.FocusLast()));
|
|
}
|
|
|
|
public void SafeFocusNext()
|
|
{
|
|
if (currentPage == null || isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage.TryMoveToNext()));
|
|
}
|
|
|
|
public void SafeFocusPrevious()
|
|
{
|
|
if (currentPage == null || isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage.TryMoveToPrevious()));
|
|
}
|
|
|
|
private IEnumerator ProcessInputCoroutine(System.Action action)
|
|
{
|
|
if (isProcessingInput) yield break;
|
|
|
|
isProcessingInput = true;
|
|
action?.Invoke();
|
|
yield return new WaitForEndOfFrame();
|
|
isProcessingInput = false;
|
|
ForceUpdateButtonStates();
|
|
}
|
|
#endregion
|
|
|
|
#region 自定义组件控制
|
|
public void ActivateCurrentInput()
|
|
{
|
|
currentPage?.ActivateCurrentInputField();
|
|
}
|
|
|
|
public void DeactivateCurrentInput()
|
|
{
|
|
currentPage?.DeactivateCurrentInputField();
|
|
}
|
|
|
|
public void InputText(string text)
|
|
{
|
|
var inputField = currentPage?.GetCurrentCustomInputField();
|
|
if (inputField != null)
|
|
{
|
|
TMPInputSimulator.InsertText(inputField, text);
|
|
}
|
|
}
|
|
|
|
public void DeleteText()
|
|
{
|
|
var inputField = currentPage?.GetCurrentCustomInputField();
|
|
if (inputField != null)
|
|
{
|
|
TMPInputSimulator.Backspace(inputField);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 下拉菜单控制
|
|
public void DropdownToggle()
|
|
{
|
|
if (isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage?.DropdownToggle()));
|
|
}
|
|
|
|
public void DropdownSelectNext()
|
|
{
|
|
if (isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage?.DropdownSelectNext()));
|
|
}
|
|
|
|
public void DropdownSelectPrevious()
|
|
{
|
|
if (isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage?.DropdownSelectPrevious()));
|
|
}
|
|
|
|
public void DropdownSubmit()
|
|
{
|
|
if (isProcessingInput) return;
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage?.DropdownSubmit()));
|
|
}
|
|
#endregion
|
|
|
|
#region 按钮管理和状态显示
|
|
private void SetupButtonListeners()
|
|
{
|
|
if (nextFocusButton != null)
|
|
nextFocusButton.onClick.AddListener(FocusNext);
|
|
if (previousFocusButton != null)
|
|
previousFocusButton.onClick.AddListener(FocusPrevious);
|
|
if (firstFocusButton != null)
|
|
firstFocusButton.onClick.AddListener(FocusFirst);
|
|
if (lastFocusButton != null)
|
|
lastFocusButton.onClick.AddListener(FocusLast);
|
|
|
|
if (dropdownToggleButton != null)
|
|
dropdownToggleButton.onClick.AddListener(DropdownToggle);
|
|
if (dropdownNextButton != null)
|
|
dropdownNextButton.onClick.AddListener(DropdownSelectNext);
|
|
if (dropdownPreviousButton != null)
|
|
dropdownPreviousButton.onClick.AddListener(DropdownSelectPrevious);
|
|
if (dropdownSubmitButton != null)
|
|
dropdownSubmitButton.onClick.AddListener(DropdownSubmit);
|
|
}
|
|
|
|
private void UpdateButtonStates()
|
|
{
|
|
if (currentPage == null)
|
|
{
|
|
SetAllButtonsInteractable(false);
|
|
return;
|
|
}
|
|
|
|
bool isDropdownExpanded = currentPage.IsDropdownExpanded();
|
|
bool isOnDropdown = currentPage.IsOnDropdown();
|
|
bool hasInputFocus = currentPage.HasActiveInputField();
|
|
|
|
if (previousFocusButton != null)
|
|
previousFocusButton.interactable = currentPage.HasPreviousFocus && !isDropdownExpanded && !hasInputFocus && !isProcessingInput;
|
|
if (nextFocusButton != null)
|
|
nextFocusButton.interactable = currentPage.HasNextFocus && !isDropdownExpanded && !hasInputFocus && !isProcessingInput;
|
|
|
|
bool hasElements = currentPage.FocusCount > 0 && !isDropdownExpanded && !hasInputFocus && !isProcessingInput;
|
|
if (firstFocusButton != null) firstFocusButton.interactable = hasElements;
|
|
if (lastFocusButton != null) lastFocusButton.interactable = hasElements;
|
|
|
|
bool dropdownButtonsEnabled = isDropdownExpanded && isOnDropdown && !hasInputFocus && !isProcessingInput;
|
|
|
|
if (dropdownToggleButton != null) dropdownToggleButton.interactable = isOnDropdown && !hasInputFocus && !isProcessingInput;
|
|
if (dropdownNextButton != null) dropdownNextButton.interactable = dropdownButtonsEnabled;
|
|
if (dropdownPreviousButton != null) dropdownPreviousButton.interactable = dropdownButtonsEnabled;
|
|
if (dropdownSubmitButton != null) dropdownSubmitButton.interactable = dropdownButtonsEnabled;
|
|
}
|
|
|
|
private void UpdateStatusDisplay()
|
|
{
|
|
if (currentPage == null)
|
|
{
|
|
if (dropdownStatusText != null) dropdownStatusText.text = "无活动页面";
|
|
if (pageStatusText != null) pageStatusText.text = "";
|
|
if (inputStatusText != null) inputStatusText.text = "";
|
|
return;
|
|
}
|
|
|
|
if (pageStatusText != null)
|
|
pageStatusText.text = $"焦点: {currentPage.CurrentFocusIndex + 1}/{currentPage.FocusCount}";
|
|
|
|
if (inputStatusText != null)
|
|
inputStatusText.text = currentPage.HasActiveInputField() ? "输入模式" : "";
|
|
|
|
if (dropdownStatusText != null)
|
|
{
|
|
if (currentPage.IsDropdownExpanded())
|
|
{
|
|
dropdownStatusText.text = $"选择中: {currentPage.GetCurrentDropdownValue()}";
|
|
}
|
|
else if (currentPage.IsOnDropdown())
|
|
{
|
|
dropdownStatusText.text = "按[打开菜单]开始选择";
|
|
}
|
|
else
|
|
{
|
|
dropdownStatusText.text = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateDebugInfo()
|
|
{
|
|
if (debugText != null && currentPage != null)
|
|
{
|
|
debugText.text = $"处理中: {isProcessingInput}\n" +
|
|
$"输入焦点: {currentPage.HasActiveInputField()}\n" +
|
|
$"下拉展开: {currentPage.IsDropdownExpanded()}";
|
|
}
|
|
}
|
|
|
|
private void SetAllButtonsInteractable(bool interactable)
|
|
{
|
|
if (previousFocusButton != null) previousFocusButton.interactable = interactable;
|
|
if (nextFocusButton != null) nextFocusButton.interactable = interactable;
|
|
if (firstFocusButton != null) firstFocusButton.interactable = interactable;
|
|
if (lastFocusButton != null) lastFocusButton.interactable = interactable;
|
|
if (dropdownToggleButton != null) dropdownToggleButton.interactable = interactable;
|
|
if (dropdownNextButton != null) dropdownNextButton.interactable = interactable;
|
|
if (dropdownPreviousButton != null) dropdownPreviousButton.interactable = interactable;
|
|
if (dropdownSubmitButton != null) dropdownSubmitButton.interactable = interactable;
|
|
}
|
|
#endregion
|
|
|
|
#region 键盘输入处理
|
|
private void HandleKeyboardNavigation()
|
|
{
|
|
if (currentPage == null || isProcessingInput) return;
|
|
|
|
bool isDropdownExpanded = currentPage.IsDropdownExpanded();
|
|
bool hasInputFocus = currentPage.HasActiveInputField();
|
|
|
|
if (hasInputFocus)
|
|
{
|
|
if (Input.GetKeyDown(KeyCode.Escape))
|
|
{
|
|
StartCoroutine(ProcessInputCoroutine(() => currentPage.DeactivateCurrentInputField()));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (isDropdownExpanded)
|
|
{
|
|
if (Input.GetKeyDown(KeyCode.UpArrow)) DropdownSelectPrevious();
|
|
if (Input.GetKeyDown(KeyCode.DownArrow)) DropdownSelectNext();
|
|
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter)) DropdownSubmit();
|
|
if (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.Escape)) DropdownToggle();
|
|
}
|
|
else
|
|
{
|
|
if (Input.GetKeyDown(KeyCode.Tab))
|
|
{
|
|
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
|
|
SafeFocusPrevious();
|
|
else
|
|
SafeFocusNext();
|
|
}
|
|
|
|
if (Input.GetKeyDown(KeyCode.Home)) FocusFirst();
|
|
if (Input.GetKeyDown(KeyCode.End)) FocusLast();
|
|
}
|
|
}
|
|
#endregion
|
|
}
|