369 lines
9.7 KiB
C#
369 lines
9.7 KiB
C#
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.UI;
|
||
|
||
/// <summary>
|
||
/// Tab键导航系统
|
||
/// 自动管理InputField之间的Tab键切换
|
||
/// </summary>
|
||
public class TabNavigationSystem : MonoBehaviour
|
||
{
|
||
[Header("导航设置")]
|
||
public List<Selectable> NavigationOrder = new List<Selectable>(); // 导航顺序
|
||
public bool AutoDetectInputFields = true; // 自动检测InputField
|
||
public bool WrapNavigation = true; // 是否循环导航
|
||
public bool EnableShiftReverse = true; // 是否支持Shift+Tab反向导航
|
||
|
||
[Header("调试")]
|
||
public bool ShowDebugInfo = false;
|
||
|
||
private int _currentIndex = -1;
|
||
private EventSystem _eventSystem;
|
||
|
||
void Start()
|
||
{
|
||
Initialize();
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
HandleTabInput();
|
||
}
|
||
|
||
private void Initialize()
|
||
{
|
||
_eventSystem = EventSystem.current;
|
||
|
||
if (AutoDetectInputFields)
|
||
{
|
||
AutoDetectNavigationElements();
|
||
}
|
||
|
||
// 设置初始焦点
|
||
if (NavigationOrder.Count > 0)
|
||
{
|
||
SetFocus(0);
|
||
}
|
||
|
||
if (ShowDebugInfo)
|
||
{
|
||
Debug.Log($"TabNavigationSystem: 初始化完成,找到 {NavigationOrder.Count} 个可导航元素");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动检测可导航元素
|
||
/// </summary>
|
||
private void AutoDetectNavigationElements()
|
||
{
|
||
NavigationOrder.Clear();
|
||
|
||
// 查找所有InputField
|
||
var inputFields = FindObjectsOfType<TMP_InputField>();
|
||
foreach (var inputField in inputFields)
|
||
{
|
||
if (inputField.interactable && inputField.gameObject.activeInHierarchy)
|
||
{
|
||
NavigationOrder.Add(inputField);
|
||
}
|
||
}
|
||
|
||
// 查找所有传统InputField
|
||
var legacyInputFields = FindObjectsOfType<InputField>();
|
||
foreach (var inputField in legacyInputFields)
|
||
{
|
||
if (inputField.interactable && inputField.gameObject.activeInHierarchy)
|
||
{
|
||
NavigationOrder.Add(inputField);
|
||
}
|
||
}
|
||
|
||
// 根据层级顺序排序
|
||
NavigationOrder.Sort((a, b) =>
|
||
{
|
||
// 先按Y坐标排序(从上到下)
|
||
var posA = a.transform.position;
|
||
var posB = b.transform.position;
|
||
|
||
if (Mathf.Abs(posA.y - posB.y) > 0.1f)
|
||
{
|
||
return posB.y.CompareTo(posA.y); // Y坐标大的在前(上面的先)
|
||
}
|
||
|
||
// Y坐标相近时按X坐标排序(从左到右)
|
||
return posA.x.CompareTo(posB.x);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理Tab键输入
|
||
/// </summary>
|
||
private void HandleTabInput()
|
||
{
|
||
if (NavigationOrder.Count == 0) return;
|
||
|
||
// 检查Tab键
|
||
if (Input.GetKeyDown(KeyCode.Tab))
|
||
{
|
||
bool reverse = EnableShiftReverse && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift));
|
||
|
||
if (reverse)
|
||
{
|
||
NavigatePrevious();
|
||
}
|
||
else
|
||
{
|
||
NavigateNext();
|
||
}
|
||
}
|
||
|
||
// 检查Enter键(移动到下一个)
|
||
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
|
||
{
|
||
// 如果当前焦点是InputField,则移动到下一个
|
||
var currentSelection = _eventSystem?.currentSelectedGameObject;
|
||
if (currentSelection != null)
|
||
{
|
||
var inputField = currentSelection.GetComponent<TMP_InputField>();
|
||
var legacyInputField = currentSelection.GetComponent<InputField>();
|
||
|
||
if (inputField != null || legacyInputField != null)
|
||
{
|
||
NavigateNext();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导航到下一个元素
|
||
/// </summary>
|
||
public void NavigateNext()
|
||
{
|
||
if (NavigationOrder.Count == 0) return;
|
||
|
||
_currentIndex++;
|
||
|
||
if (WrapNavigation)
|
||
{
|
||
_currentIndex %= NavigationOrder.Count;
|
||
}
|
||
else
|
||
{
|
||
_currentIndex = Mathf.Min(_currentIndex, NavigationOrder.Count - 1);
|
||
}
|
||
|
||
SetFocus(_currentIndex);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导航到上一个元素
|
||
/// </summary>
|
||
public void NavigatePrevious()
|
||
{
|
||
if (NavigationOrder.Count == 0) return;
|
||
|
||
_currentIndex--;
|
||
|
||
if (WrapNavigation)
|
||
{
|
||
if (_currentIndex < 0)
|
||
{
|
||
_currentIndex = NavigationOrder.Count - 1;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_currentIndex = Mathf.Max(_currentIndex, 0);
|
||
}
|
||
|
||
SetFocus(_currentIndex);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置焦点到指定索引的元素
|
||
/// </summary>
|
||
public void SetFocus(int index)
|
||
{
|
||
if (index < 0 || index >= NavigationOrder.Count) return;
|
||
if (_eventSystem == null) return;
|
||
|
||
var target = NavigationOrder[index];
|
||
if (target == null || !target.interactable || !target.gameObject.activeInHierarchy)
|
||
{
|
||
// 跳过不可用的元素
|
||
if (WrapNavigation)
|
||
{
|
||
NavigateNext();
|
||
}
|
||
return;
|
||
}
|
||
|
||
_currentIndex = index;
|
||
|
||
// 设置选中
|
||
_eventSystem.SetSelectedGameObject(target.gameObject);
|
||
|
||
// 如果是InputField,激活输入
|
||
var inputField = target.GetComponent<TMP_InputField>();
|
||
if (inputField != null)
|
||
{
|
||
inputField.Select();
|
||
inputField.ActivateInputField();
|
||
}
|
||
else
|
||
{
|
||
var legacyInputField = target.GetComponent<InputField>();
|
||
if (legacyInputField != null)
|
||
{
|
||
legacyInputField.Select();
|
||
legacyInputField.ActivateInputField();
|
||
}
|
||
}
|
||
|
||
if (ShowDebugInfo)
|
||
{
|
||
Debug.Log($"TabNavigationSystem: 焦点切换到 {target.name} (索引: {index})");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动添加导航元素
|
||
/// </summary>
|
||
public void AddNavigationElement(Selectable element)
|
||
{
|
||
if (element != null && !NavigationOrder.Contains(element))
|
||
{
|
||
NavigationOrder.Add(element);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除导航元素
|
||
/// </summary>
|
||
public void RemoveNavigationElement(Selectable element)
|
||
{
|
||
if (NavigationOrder.Contains(element))
|
||
{
|
||
int index = NavigationOrder.IndexOf(element);
|
||
NavigationOrder.Remove(element);
|
||
|
||
// 调整当前索引
|
||
if (_currentIndex >= index && _currentIndex > 0)
|
||
{
|
||
_currentIndex--;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新导航列表
|
||
/// </summary>
|
||
public void RefreshNavigation()
|
||
{
|
||
if (AutoDetectInputFields)
|
||
{
|
||
AutoDetectNavigationElements();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前焦点元素
|
||
/// </summary>
|
||
public Selectable GetCurrentElement()
|
||
{
|
||
if (_currentIndex >= 0 && _currentIndex < NavigationOrder.Count)
|
||
{
|
||
return NavigationOrder[_currentIndex];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前焦点索引
|
||
/// </summary>
|
||
public int GetCurrentIndex()
|
||
{
|
||
return _currentIndex;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 跳转到指定元素
|
||
/// </summary>
|
||
public void FocusElement(Selectable element)
|
||
{
|
||
int index = NavigationOrder.IndexOf(element);
|
||
if (index >= 0)
|
||
{
|
||
SetFocus(index);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tab导航辅助组件
|
||
/// 可以附加到单独的InputField上,提供更精细的控制
|
||
/// </summary>
|
||
public class TabNavigationHelper : MonoBehaviour
|
||
{
|
||
[Header("导航设置")]
|
||
public Selectable NextElement; // 下一个元素
|
||
public Selectable PreviousElement; // 上一个元素
|
||
public bool SkipThisElement = false; // 是否跳过此元素
|
||
|
||
private Selectable _thisSelectable;
|
||
|
||
void Start()
|
||
{
|
||
_thisSelectable = GetComponent<Selectable>();
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (SkipThisElement) return;
|
||
|
||
// 检查是否是当前选中的元素
|
||
if (EventSystem.current?.currentSelectedGameObject == gameObject)
|
||
{
|
||
if (Input.GetKeyDown(KeyCode.Tab))
|
||
{
|
||
bool reverse = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
|
||
|
||
if (reverse && PreviousElement != null)
|
||
{
|
||
FocusElement(PreviousElement);
|
||
}
|
||
else if (!reverse && NextElement != null)
|
||
{
|
||
FocusElement(NextElement);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void FocusElement(Selectable element)
|
||
{
|
||
if (element != null && element.interactable && element.gameObject.activeInHierarchy)
|
||
{
|
||
EventSystem.current?.SetSelectedGameObject(element.gameObject);
|
||
|
||
var inputField = element.GetComponent<TMP_InputField>();
|
||
if (inputField != null)
|
||
{
|
||
inputField.Select();
|
||
inputField.ActivateInputField();
|
||
}
|
||
else
|
||
{
|
||
var legacyInputField = element.GetComponent<InputField>();
|
||
if (legacyInputField != null)
|
||
{
|
||
legacyInputField.Select();
|
||
legacyInputField.ActivateInputField();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |