354 lines
12 KiB
C#
354 lines
12 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.EventSystems;
|
|
using TMPro;
|
|
|
|
public class FocusPage : MonoBehaviour
|
|
{
|
|
[Header("页面焦点元素")]
|
|
public List<Selectable> focusElements = new List<Selectable>();
|
|
|
|
//有些元素要提前录入
|
|
public List<Selectable> focusElementStart = new List<Selectable>();
|
|
|
|
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;
|
|
|
|
public bool isNeedStartShow = false;
|
|
|
|
void OnEnable()
|
|
{
|
|
|
|
FocusManager.Instance?.SetCurrentPage(this);
|
|
|
|
// 清除失效的引用
|
|
focusElements.RemoveAll(e => e == null);
|
|
ValidateFocusElements();
|
|
StartCoroutine(DelayedFindCustomComponents());
|
|
}
|
|
|
|
|
|
public void UpdateFouceByClick(Selectable selectable)
|
|
{
|
|
|
|
int index= focusElements.IndexOf(selectable);
|
|
SetFocusImmediate(index);
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
if (FocusManager.Instance?.GetCurrentPage() == this) ClearFocus();
|
|
}
|
|
|
|
|
|
private IEnumerator DelayedFindCustomComponents()
|
|
{
|
|
// 等一帧,保证子对象 Awake / OnEnable 执行完
|
|
yield return null;
|
|
|
|
FindCustomComponents();
|
|
}
|
|
|
|
private void FindCustomComponents()
|
|
{
|
|
focusElements.Clear();
|
|
|
|
if (focusElementStart.Count != 0)
|
|
{
|
|
foreach (var item in focusElementStart)
|
|
{
|
|
focusElements.Add(item);
|
|
}
|
|
}
|
|
|
|
// 获取所有 Selectable (包括隐藏)
|
|
Selectable[] allSelectables = GetComponentsInChildren<Selectable>(true);
|
|
|
|
foreach (var sel in allSelectables)
|
|
{
|
|
if (sel == null) continue;
|
|
|
|
// 必须是明确的自定义控件
|
|
bool isCustomInput = sel.GetComponent<CustomTMPInputField>() != null;
|
|
bool isCustomDropdown = sel.GetComponent<CustomTMPDropdown>() != null;
|
|
|
|
if (isCustomInput || isCustomDropdown)
|
|
focusElements.Add(sel);
|
|
}
|
|
|
|
|
|
if (FocusCount > 0&& isNeedStartShow) SetFocusImmediate(0);
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
CheckDropdownState();
|
|
}
|
|
|
|
|
|
public bool focusDisabled = false;
|
|
private GameObject lastFocusedObj = null;
|
|
|
|
public void DisableFocus()
|
|
{
|
|
focusDisabled = true;
|
|
|
|
// 取消 EventSystem 选中
|
|
EventSystem.current.SetSelectedGameObject(null);
|
|
|
|
// 清除所有高亮
|
|
foreach (var sel in focusElements)
|
|
{
|
|
var ol = sel ? sel.GetComponent<Outline>() : null;
|
|
if (ol != null) ol.enabled = false;
|
|
}
|
|
}
|
|
|
|
public void EnableFocus()
|
|
{
|
|
focusDisabled = false;
|
|
|
|
if (lastFocusedObj != null)
|
|
{
|
|
EventSystem.current.SetSelectedGameObject(lastFocusedObj);
|
|
|
|
var ol = lastFocusedObj.GetComponent<Outline>();
|
|
if (ol != null) ol.enabled = true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#region 焦点切换
|
|
public void FocusNext()
|
|
{
|
|
if (focusDisabled) return;
|
|
|
|
Debug.Log(CurrentFocusIndex+ focusDisabled.ToString());
|
|
if (isSwitchingFocus) return;
|
|
if (isDropdownExpanded) { DropdownSelectNext(); return; }
|
|
if (currentFocusIndex + 1 < focusElements.Count) SetFocusDelayed(currentFocusIndex + 1);
|
|
}
|
|
|
|
public void FocusPrevious()
|
|
{
|
|
if (focusDisabled) return;
|
|
Debug.Log(CurrentFocusIndex + focusDisabled.ToString());
|
|
if (isSwitchingFocus) return;
|
|
if (isDropdownExpanded) { DropdownSelectPrevious(); return; }
|
|
if (currentFocusIndex - 1 >= 0) SetFocusDelayed(currentFocusIndex - 1);
|
|
}
|
|
|
|
public void FocusFirst() { if (focusDisabled) return; if (!isSwitchingFocus && FocusCount > 0) SetFocusDelayed(0); }
|
|
public void FocusLast() { if (focusDisabled) return; 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();
|
|
currentCustomTMPDropdown?.FocusedSelectNext();
|
|
}
|
|
public void DropdownSelectPrevious() {
|
|
//if (isDropdownExpanded) currentCustomTMPDropdown?.SelectPrevious();
|
|
currentCustomTMPDropdown?.FocusedSelectPrevious();
|
|
}
|
|
public void DropdownSubmit() { if (focusDisabled) return; 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];
|
|
lastFocusedObj = focusElements[index].gameObject;
|
|
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 (isSwitchingFocus)
|
|
{
|
|
isSwitchingFocus = false;
|
|
if (focusCoroutine != null) StopCoroutine(focusCoroutine);
|
|
}
|
|
//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;
|
|
|
|
// 关闭所有高亮
|
|
foreach (var elem in focusElements)
|
|
{
|
|
if (elem == null) continue; // 防止 MissingReference
|
|
var ol = elem.GetComponent<Outline>();
|
|
if (ol == null) continue; // Outline 已经被销毁或不存在
|
|
ol.enabled = false;
|
|
}
|
|
}
|
|
|
|
private void CloseDropdown()
|
|
{
|
|
if (currentCustomTMPDropdown != null)
|
|
{
|
|
currentCustomTMPDropdown.CloseDropdown();
|
|
isDropdownExpanded = false;
|
|
}
|
|
}
|
|
|
|
private void HandleSpecialFocus(Selectable element, bool autoExpand)
|
|
{
|
|
// 先关闭其他高亮
|
|
foreach (var elem in focusElements)
|
|
{
|
|
if (elem != element)
|
|
{
|
|
var ol = elem.GetComponent<Outline>();
|
|
if (ol != null) ol.enabled = false;
|
|
}
|
|
}
|
|
|
|
switch (element)
|
|
{
|
|
case CustomTMPInputField customInputField:
|
|
currentCustomInputField = customInputField;
|
|
currentCustomTMPDropdown = null;
|
|
isDropdownExpanded = false;
|
|
customInputField.Activate();
|
|
SetHighlight(customInputField, true);
|
|
if (!customInputField.GetComponent<Outline>().enabled)
|
|
SetHighlight(customInputField, true);
|
|
break;
|
|
case CustomTMPDropdown customTMPDropdown:
|
|
currentCustomTMPDropdown = customTMPDropdown;
|
|
currentCustomInputField = null;
|
|
isDropdownExpanded = false; // 不自动展开
|
|
SetHighlight(customTMPDropdown, true); // 高亮
|
|
if(!customTMPDropdown.GetComponent<Outline>().enabled)
|
|
SetHighlight(customTMPDropdown, true);
|
|
Debug.Log("打开物品高亮");
|
|
break;
|
|
default:
|
|
currentCustomInputField = null;
|
|
currentCustomTMPDropdown = null;
|
|
isDropdownExpanded = false;
|
|
break;
|
|
}
|
|
FocusManager.Instance?.ForceUpdateButtonStates();
|
|
}
|
|
|
|
private void SetHighlight(Selectable element, bool highlight)
|
|
{
|
|
Outline ol = element.GetComponent<Outline>();
|
|
if (ol != null) ol.enabled = highlight;
|
|
}
|
|
|
|
private void CheckDropdownState()
|
|
{
|
|
bool wasExpanded = isDropdownExpanded;
|
|
if (currentCustomTMPDropdown != null)
|
|
{
|
|
var dropdownField = typeof(TMP_Dropdown).GetField("m_Dropdown", System.Reflection.BindingFlags.NonPublic | System.Reflection.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()
|
|
{
|
|
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
|
|
}
|