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();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|