using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using TMPro; using System.Reflection; public class FocusPage : MonoBehaviour { [Header("页面焦点元素")] public List focusElements = new List(); private int currentFocusIndex = -1; private Coroutine focusCoroutine; private bool isSwitchingFocus = false; private CustomTMPInputField currentCustomInputField; private CustomTMPDropdown currentCustomTMPDropdown; private bool isDropdownExpanded = false; public int FocusCount => focusElements.Count; public int CurrentFocusIndex => currentFocusIndex; public Selectable CurrentFocus => GetCurrentFocusElement(); public bool HasPreviousFocus => currentFocusIndex > 0; public bool HasNextFocus => currentFocusIndex < FocusCount - 1; void OnEnable() { FocusManager.Instance?.SetCurrentPage(this); FindCustomComponents(); ValidateFocusElements(); if (FocusCount > 0) SetFocusImmediate(0); } private void FindCustomComponents() { focusElements.Clear(); // 获取所有Selectable,包括隐藏的 Selectable[] allSelectables = GetComponentsInChildren(true); foreach (var sel in allSelectables) { if (sel != null && sel.interactable) { focusElements.Add(sel); } } } void OnDisable() { if (FocusManager.Instance?.GetCurrentPage() == this) ClearFocus(); } void Update() { CheckDropdownState(); } #region 焦点切换 public void FocusNext() { if (isSwitchingFocus) return; if (isDropdownExpanded) { DropdownSelectNext(); return; } if (currentFocusIndex + 1 < focusElements.Count) SetFocusDelayed(currentFocusIndex + 1); } public void FocusPrevious() { if (isSwitchingFocus) return; if (isDropdownExpanded) { DropdownSelectPrevious(); return; } if (currentFocusIndex - 1 >= 0) SetFocusDelayed(currentFocusIndex - 1); } public void FocusFirst() { if (!isSwitchingFocus && FocusCount > 0) SetFocusDelayed(0); } public void FocusLast() { if (!isSwitchingFocus && FocusCount > 0) SetFocusDelayed(FocusCount - 1); } public bool TryMoveToNext() { if (isSwitchingFocus || currentFocusIndex + 1 >= FocusCount) return false; SetFocusDelayed(currentFocusIndex + 1); return true; } public bool TryMoveToPrevious() { if (isSwitchingFocus || currentFocusIndex - 1 < 0) return false; SetFocusDelayed(currentFocusIndex - 1); return true; } #endregion #region 自定义组件访问 public CustomTMPInputField GetCurrentCustomInputField() => currentCustomInputField; public void ActivateCurrentInputField() { currentCustomInputField?.Activate(); } public void DeactivateCurrentInputField() { currentCustomInputField?.Deactivate(); } public bool HasActiveInputField() => currentCustomInputField != null && currentCustomInputField.HasFocus(); #endregion #region Dropdown public void DropdownSelectNext() { if (isDropdownExpanded) currentCustomTMPDropdown?.SelectNext(); } public void DropdownSelectPrevious() { if (isDropdownExpanded) currentCustomTMPDropdown?.SelectPrevious(); } public void DropdownSubmit() { if (isDropdownExpanded) currentCustomTMPDropdown?.SubmitSelection(); } public void DropdownToggle() { if (currentCustomTMPDropdown != null) { currentCustomTMPDropdown.ToggleDropdown(); StartCoroutine(UpdateDropdownNextFrame()); } } private IEnumerator UpdateDropdownNextFrame() { yield return null; CheckDropdownState(); FocusManager.Instance?.ForceUpdateButtonStates(); } public bool IsDropdownExpanded() => isDropdownExpanded; public bool IsOnDropdown() => currentCustomTMPDropdown != null; public string GetCurrentDropdownValue() => currentCustomTMPDropdown != null && currentCustomTMPDropdown.options.Count > 0 ? currentCustomTMPDropdown.options[currentCustomTMPDropdown.value].text : ""; #endregion #region 内部焦点处理 private void SetFocusImmediate(int index) { if (index < 0 || index >= focusElements.Count) return; ClearCurrentFocus(); currentFocusIndex = index; Selectable target = focusElements[index]; if (EventSystem.current != null) { EventSystem.current.SetSelectedGameObject(target.gameObject); HandleSpecialFocus(target, false); } FocusManager.Instance?.ForceUpdateButtonStates(); } private void SetFocusDelayed(int index, float delay = 0.05f) { if (focusCoroutine != null) StopCoroutine(focusCoroutine); focusCoroutine = StartCoroutine(SetFocusCoroutine(index, delay)); } private IEnumerator SetFocusCoroutine(int index, float delay) { if (isSwitchingFocus) yield break; isSwitchingFocus = true; yield return new WaitForSecondsRealtime(delay); if (index >= 0 && index < focusElements.Count) { ClearCurrentFocus(); currentFocusIndex = index; Selectable target = focusElements[index]; if (EventSystem.current != null && EventSystem.current.currentSelectedGameObject != target.gameObject) EventSystem.current.SetSelectedGameObject(target.gameObject); HandleSpecialFocus(target, true); } isSwitchingFocus = false; FocusManager.Instance?.ForceUpdateButtonStates(); } private void ClearCurrentFocus() { currentCustomInputField?.Deactivate(); CloseDropdown(); currentCustomInputField = null; currentCustomTMPDropdown = null; isDropdownExpanded = false; } private void CloseDropdown() { if (currentCustomTMPDropdown != null) { currentCustomTMPDropdown.CloseDropdown(); isDropdownExpanded = false; currentCustomTMPDropdown = null; } } private void HandleSpecialFocus(Selectable element, bool autoExpand) { switch (element) { case CustomTMPInputField customInputField: currentCustomInputField = customInputField; currentCustomTMPDropdown = null; isDropdownExpanded = false; customInputField.Activate(); break; case CustomTMPDropdown customTMPDropdown: currentCustomTMPDropdown = customTMPDropdown; currentCustomInputField = null; if (autoExpand) StartCoroutine(DelayedExpandDropdown()); break; default: currentCustomInputField = null; currentCustomTMPDropdown = null; isDropdownExpanded = false; break; } FocusManager.Instance?.ForceUpdateButtonStates(); } private IEnumerator DelayedExpandDropdown() { yield return new WaitForSecondsRealtime(0.05f); currentCustomTMPDropdown?.OpenDropdown(); isDropdownExpanded = true; } private void CheckDropdownState() { bool wasExpanded = isDropdownExpanded; if (currentCustomTMPDropdown != null) { var dropdownField = typeof(TMP_Dropdown).GetField("m_Dropdown", BindingFlags.NonPublic | BindingFlags.Instance); if (dropdownField != null) { var dropdownGO = dropdownField.GetValue(currentCustomTMPDropdown) as GameObject; isDropdownExpanded = dropdownGO != null && dropdownGO.activeInHierarchy; } else isDropdownExpanded = false; } else isDropdownExpanded = false; if (wasExpanded != isDropdownExpanded) FocusManager.Instance?.ForceUpdateButtonStates(); } private Selectable GetCurrentFocusElement() => currentFocusIndex >= 0 && currentFocusIndex < focusElements.Count ? focusElements[currentFocusIndex] : null; public void ClearFocus() { focusElements.Clear(); ClearCurrentFocus(); currentFocusIndex = -1; if (EventSystem.current != null) EventSystem.current.SetSelectedGameObject(null); FocusManager.Instance?.ForceUpdateButtonStates(); } private void ValidateFocusElements() => focusElements.RemoveAll(e => e == null || !e.interactable); #endregion }