Files
XericLibrary-Publish/Runtime/UI/TmpText/TMPHyperlinkManager.cs
2025-11-10 11:53:03 +08:00

272 lines
9.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Sirenix.OdinInspector;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using XericLibrary.Runtime.MacroLibrary;
using XericLibrary.Runtime.Type;
namespace Deconstruction.UI.TmpText
{
/// <summary>
/// TMP超链接点击管理组件
/// </summary>
public class TMPHyperlinkManager : MonoBehaviour
{
/// <summary>
/// 画布组件
/// </summary>
#if ODIN_INSPECTOR
[LabelText("画布*")]
#endif
[Tooltip("组件可为空,将自动向上查找")]
[SerializeField]
public Canvas canvas;
#if ODIN_INSPECTOR
[LabelText("点击超链接时回调")]
#endif
[SerializeField]
public UnityEvent<string> onClickLink = new UnityEvent<string>();
/// <summary>
/// 当前摄像机
/// </summary>
private Camera _camera;
/// <summary>
/// 存储链接ID与对应回调函数的映射
/// </summary>
private Dictionary<string, Action> _linkCallbacks = new Dictionary<string, Action>();
#if UNITY_EDITOR
protected void OnValidate()
{
canvas = GetComponentInParent<Canvas>();
}
#endif
protected void Awake()
{
if (canvas == null)
canvas = transform.GetParents().Startup(transform).GetComponent<Canvas>();
if (canvas == null)
{
Debug.LogError("TMP文本对象必须在Canvas下才能使用超链接功能");
return;
}
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
_camera = null;
else
_camera = canvas.worldCamera;
}
/// <summary>
/// 注册链接回调函数
/// </summary>
public void RegisterLinkCallback(string linkID, Action callback)
{
if (_linkCallbacks.ContainsKey(linkID))
{
Debug.LogWarning($"链接ID '{linkID}' 已存在,将覆盖之前的回调函数");
_linkCallbacks[linkID] = callback;
}
else
{
_linkCallbacks.Add(linkID, callback);
}
}
/// <summary>
/// 注销链接回调函数
/// </summary>
public void UnregisterLinkCallback(string linkID)
{
if (_linkCallbacks.ContainsKey(linkID))
{
_linkCallbacks.Remove(linkID);
}
}
/// <summary>
/// 处理链接点击事件
/// </summary>
public void HandleLinkClick(string linkID)
{
if (_linkCallbacks.TryGetValue(linkID, out Action callback))
{
callback?.Invoke();
Debug.Log($"执行链接 '{linkID}' 的回调函数");
}
else
{
Debug.LogWarning($"未找到链接ID '{linkID}' 的回调函数");
}
}
/// <summary>
/// 检查坐标下的超链接索引
/// </summary>
/// <param name="text"></param>
/// <param name="position"></param>
/// <returns></returns>
internal int CheckLinkIndex(TMP_Text text, Vector3 position)
{
return TMP_TextUtilities.FindIntersectingLink(text, position, _camera);
}
/// <summary>
/// 检查坐标下的超链接id信息
/// </summary>
/// <param name="text"></param>
/// <param name="position"></param>
/// <param name="linkInfo"></param>
/// <returns></returns>
internal bool CheckLinkInfo(TMP_Text text, Vector3 position, out TMP_LinkInfo linkInfo)
{
var index = CheckLinkIndex(text, position);
if (index != -1)
{
linkInfo = text.textInfo.linkInfo[index];
return true;
}
linkInfo = default;
return false;
}
/// <summary>
/// 检查坐标下的超链接id
/// </summary>
/// <param name="text"></param>
/// <param name="position"></param>
/// <param name="linkID"></param>
/// <returns></returns>
internal bool CheckLinkID(TMP_Text text, Vector3 position, out string linkID)
{
if (CheckLinkInfo(text, position, out var info))
{
linkID = info.GetLinkID();
return true;
}
linkID = string.Empty;
return false;
}
/// <summary>
/// 当任意tmp对象超链接点击时调用从这里发送事件
/// </summary>
/// <param name="linkID"></param>
internal void OnAnyLinkClick(string linkID)
{
onClickLink?.Invoke(linkID);
// Debug.Log($"click link{linkID}");
// 这里不需要处理报错,应当由发送消息的成员处理
}
// todo 主动查找所有的tmp组件或是等组件唤醒后区管理它们用来获得所有的带有标记的超链接组件
/// <summary>
/// 主动查找所有具有link属性的tmp组件
/// </summary>
/// <param name="requirementComponent">自动查找</param>
/// <param name="forceRefesh"></param>
public void FindAllID(bool requirementComponent = true, bool forceRefesh = false)
{
transform.GetChildrenBFS().GetComponentsOTON<TMPHyperlinkReceiver>();
}
private List<TMPHyperlinkReceiver> _tempGetChildrenHyperlink = null;
/// <summary>
/// 获取所有链接子项
/// </summary>
/// <param name="forceRefesh">强制刷新,如果刷新过一次,之后不论设定如何都将直接从缓存中返回对象,置位将跳过这个缓存</param>
/// <param name="includeRepeatedMarking">包括重复标记(也就是是否包含另一个超链接管理器作用域下的超链接对象)</param>
public List<TMPHyperlinkReceiver> GetChildrenHyperlink(bool forceRefesh = false,
bool includeRepeatedMarking = false)
{
if (!forceRefesh && _tempGetChildrenHyperlink != null && _tempGetChildrenHyperlink.Count > 0)
return _tempGetChildrenHyperlink;
_tempGetChildrenHyperlink = transform.GetChildrenBFS()
.GetComponentsOTON<TMPHyperlinkReceiver>().ToList();
return _tempGetChildrenHyperlink;
}
private List<TMPHyperlinkReceiver> _tempGetChildrenTmpText2Hyperlink = null;
/// <summary>
/// 获取所有tmp_text然后根据规则返回链接
/// </summary>
/// <param name="forceRefesh">强制刷新,如果刷新过一次,之后不论设定如何都将直接从缓存中返回对象,置位将跳过这个缓存</param>
/// <param name="requirementHyperlink">强制给带有link标记的对象加上超链接组件</param>
/// <param name="includeRepeatedMarking">是否包含重复引用的链接(是否包含当前超链接管理器下其他超链接管理器作用域下的超链接对象)</param>
/// <returns></returns>
/// <remarks>
/// 这个操作会扫描所有对象,然后在具有超链接特征的文本组件上生成超链接管理器。
/// 超链接响应的前提是开启 Raycast Target 选项。
/// </remarks>
public List<TMPHyperlinkReceiver> GetChildrenTmpText2Hyperlink(
bool forceRefesh = false,
bool requirementHyperlink = true,
bool includeRepeatedMarking = false)
{
if (!forceRefesh &&
_tempGetChildrenTmpText2Hyperlink != null &&
_tempGetChildrenTmpText2Hyperlink.Count > 0)
return _tempGetChildrenTmpText2Hyperlink;
var children = includeRepeatedMarking
? transform.GetChildrenBFS()
: transform.GetChildrenBFS<TMPHyperlinkManager>();
var result = new List<TMPHyperlinkReceiver>();
var hyperlinkType = typeof(TMPHyperlinkReceiver);
var tmpTextType = typeof(TMP_Text);
// 预先分配足够的容量,减少扩容
result.Capacity = Mathf.Min(children.Count(), 10);
foreach (var child in children)
{
// 尝试从缓存获取组件
var hyperlink = child.GetComponent(hyperlinkType) as TMPHyperlinkReceiver;
if (hyperlink != null)
{
result.Add(hyperlink);
continue;
}
if (!requirementHyperlink)
continue;
// 尝试获取TMP_Text组件
var tmp = child.GetComponent(tmpTextType) as TMP_Text;
if (tmp != null && tmp.text.MatchRichTextLinkID())
{
result.Add(child.gameObject.AddComponent<TMPHyperlinkReceiver>());
}
}
return result;
}
/// <summary>
/// 直接刷新所有超链接
/// </summary>
#if ODIN_INSPECTOR
[Button]
#endif
public void RefreshHyperLink()
=> GetChildrenTmpText2Hyperlink(true);
}
}