/// ======================================================== /// file:InlineText.cs /// brief: /// author: coding2233 /// date: /// version:v1.0 /// ======================================================== using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Text.RegularExpressions; using System.Text; using UnityEngine.EventSystems; using UnityEngine.Events; using System; [ExecuteInEditMode] public class InlineText : Text, IPointerClickHandler { // 用正则取 [图集ID#表情Tag] ID值==-1 ,表示为超链接 private static readonly Regex _InputTagRegex = new Regex(@"\[(\-{0,1}\d{0,})#(.+?)\]", RegexOptions.Singleline); //文本表情管理器 private InlineManager _InlineManager; //更新后的文本 private string _OutputText = ""; //表情位置索引信息 Dictionary _SpriteInfo = new Dictionary(); //图集ID,相关信息 Dictionary> _DrawSpriteInfo = new Dictionary>(); #region 超链接 [System.Serializable] public class HrefClickEvent : UnityEvent { } //点击事件监听 public HrefClickEvent OnHrefClick = new HrefClickEvent(); // 超链接信息列表 private readonly List _ListHrefInfos = new List(); #endregion /// /// 初始化 /// protected override void OnEnable() { // base.OnEnable(); //支持富文本 supportRichText = true; //对齐几何 alignByGeometry = true; if (!_InlineManager) _InlineManager = GetComponentInParent(); //启动的是 更新顶点 SetVerticesDirty(); } public void ActiveText() { OnEnable(); } public override void SetVerticesDirty() { base.SetVerticesDirty(); if (!_InlineManager) { _OutputText = m_Text; return; } //设置新文本 _OutputText = GetOutputText(); } readonly UIVertex[] m_TempVerts = new UIVertex[4]; protected override void OnPopulateMesh(VertexHelper toFill) { if (font == null) return; // We don't care if we the font Texture changes while we are doing our Update. // The end result of cachedTextGenerator will be valid for this instance. // Otherwise we can get issues like Case 619238. m_DisableFontTextureRebuiltCallback = true; Vector2 extents = rectTransform.rect.size; var settings = GetGenerationSettings(extents); // cachedTextGenerator.PopulateWithErrors(text, settings, gameObject); cachedTextGenerator.Populate(_OutputText, settings); // Apply the offset to the vertices IList verts = cachedTextGenerator.verts; float unitsPerPixel = 1 / pixelsPerUnit; //Last 4 verts are always a new line... (\n) int vertCount = verts.Count - 4; Vector2 roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel; roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset; toFill.Clear(); //计算quad占位的信息 CalcQuadInfo(verts); //计算包围盒 CalcBoundsInfo(verts,toFill,settings); if (roundingOffset != Vector2.zero) { for (int i = 0; i < vertCount; ++i) { int tempVertsIndex = i & 3; m_TempVerts[tempVertsIndex] = verts[i]; m_TempVerts[tempVertsIndex].position *= unitsPerPixel; m_TempVerts[tempVertsIndex].position.x += roundingOffset.x; m_TempVerts[tempVertsIndex].position.y += roundingOffset.y; if (tempVertsIndex == 3) toFill.AddUIVertexQuad(m_TempVerts); } } else { for (int i = 0; i < vertCount; ++i) { int tempVertsIndex = i & 3; m_TempVerts[tempVertsIndex] = verts[i]; m_TempVerts[tempVertsIndex].position *= unitsPerPixel; if (tempVertsIndex == 3) toFill.AddUIVertexQuad(m_TempVerts); } } m_DisableFontTextureRebuiltCallback = false; } #region 文本所占的长宽 public override float preferredWidth { get { var settings = GetGenerationSettings(Vector2.zero); return cachedTextGeneratorForLayout.GetPreferredWidth(_OutputText, settings) / pixelsPerUnit; } } public override float preferredHeight { get { var settings = GetGenerationSettings(new Vector2(rectTransform.rect.size.x, 0.0f)); return cachedTextGeneratorForLayout.GetPreferredHeight(_OutputText, settings) / pixelsPerUnit; } } #endregion #region 计算Quad占位信息 void CalcQuadInfo(IList verts) { foreach (var item in _SpriteInfo) { if ((item.Key + 4) > verts.Count) continue; for (int i = item.Key; i < item.Key + 4; i++) { //清除乱码 UIVertex tempVertex = verts[i]; tempVertex.uv0 = Vector2.zero; verts[i] = tempVertex; //计算位置 item.Value._Pos[i - item.Key] = tempVertex.position; } } //绘制表情 UpdateDrawnSprite(); } #endregion #region 绘制表情 void UpdateDrawnSprite() { _DrawSpriteInfo = new Dictionary>(); foreach (var item in _SpriteInfo) { int _id = item.Value._ID; string _tag = item.Value._Tag; //更新绘制表情的信息 List _listSpriteInfo = null; if (_DrawSpriteInfo.ContainsKey(_id)) _listSpriteInfo = _DrawSpriteInfo[_id]; else { _listSpriteInfo = new List(); _DrawSpriteInfo.Add(_id, _listSpriteInfo); } _listSpriteInfo.Add(item.Value); } foreach (var item in _DrawSpriteInfo) { _InlineManager.UpdateTextInfo(item.Key, this, item.Value); } } #endregion #region 处理超链接的包围盒 void CalcBoundsInfo(IList verts, VertexHelper toFill,TextGenerationSettings settings) { #region 包围框 // 处理超链接包围框 UIVertex vert = new UIVertex(); foreach (var hrefInfo in _ListHrefInfos) { hrefInfo.boxes.Clear(); if (hrefInfo.startIndex >= verts.Count) { continue; } // 将超链接里面的文本顶点索引坐标加入到包围框 vert = verts[hrefInfo.startIndex]; var pos = vert.position; var bounds = new Bounds(pos, Vector3.zero); for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i++) { if (i >= verts.Count) { break; } vert = verts[i]; pos = vert.position; if (pos.x < bounds.min.x) { // 换行重新添加包围框 hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size)); bounds = new Bounds(pos, Vector3.zero); } else { bounds.Encapsulate(pos); // 扩展包围框 } } //添加包围盒 hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size)); } #endregion #region 添加下划线 TextGenerator _UnderlineText = new TextGenerator(); _UnderlineText.Populate("_", settings); IList _TUT = _UnderlineText.verts; foreach (var item in _ListHrefInfos) { for (int i = 0; i < item.boxes.Count; i++) { //计算下划线的位置 Vector3[] _ulPos = new Vector3[4]; _ulPos[0] = item.boxes[i].position + new Vector2(0.0f, fontSize * 0.2f); _ulPos[1] = _ulPos[0]+new Vector3(item.boxes[i].width,0.0f); _ulPos[2] = item.boxes[i].position + new Vector2(item.boxes[i].width, 0.0f); _ulPos[3] =item.boxes[i].position; //绘制下划线 for (int j = 0; j < 4; j++) { m_TempVerts[j] = _TUT[j]; m_TempVerts[j].color = Color.blue; m_TempVerts[j].position = _ulPos[j]; if (j == 3) toFill.AddUIVertexQuad(m_TempVerts); } } } #endregion } #endregion #region 根据正则规则更新文本 private string GetOutputText() { _SpriteInfo = new Dictionary(); StringBuilder _textBuilder = new StringBuilder(); int _textIndex = 0; foreach (Match match in _InputTagRegex.Matches(text)) { int _tempID = 0; if (!string.IsNullOrEmpty(match.Groups[1].Value)) _tempID = int.Parse(match.Groups[1].Value); string _tempTag = match.Groups[2].Value; //更新超链接 if (_tempID == -1) { _textBuilder.Append(text.Substring(_textIndex, match.Index - _textIndex)); _textBuilder.Append(""); int _startIndex = _textBuilder.Length * 4; _textBuilder.Append("[" + match.Groups[2].Value + "]"); int _endIndex = _textBuilder.Length * 4 - 2; _textBuilder.Append(""); var hrefInfo = new HrefInfo { startIndex = _startIndex, // 超链接里的文本起始顶点索引 endIndex = _endIndex, name = match.Groups[2].Value }; _ListHrefInfos.Add(hrefInfo); } //更新表情 else { if (!_InlineManager._IndexSpriteInfo.ContainsKey(_tempID) || !_InlineManager._IndexSpriteInfo[_tempID].ContainsKey(_tempTag)) continue; SpriteInforGroup _tempGroup = _InlineManager._IndexSpriteInfo[_tempID][_tempTag]; _textBuilder.Append(text.Substring(_textIndex, match.Index - _textIndex)); int _tempIndex = _textBuilder.Length * 4; _textBuilder.Append(@""); SpriteTagInfo _tempSpriteTag = new SpriteTagInfo { _ID = _tempID, _Tag = _tempTag, _Size = new Vector2(_tempGroup.size * _tempGroup.width, _tempGroup.size), _Pos = new Vector3[4], _UV = _tempGroup.listSpriteInfor[0].uv }; if (!_SpriteInfo.ContainsKey(_tempIndex)) _SpriteInfo.Add(_tempIndex, _tempSpriteTag); } _textIndex = match.Index + match.Length; } _textBuilder.Append(text.Substring(_textIndex, text.Length - _textIndex)); return _textBuilder.ToString(); } #endregion #region 超链接信息类 private class HrefInfo { public int startIndex; public int endIndex; public string name; public readonly List boxes = new List(); } #endregion #region 点击事件检测是否点击到超链接文本 public void OnPointerClick(PointerEventData eventData) { Vector2 lp; RectTransformUtility.ScreenPointToLocalPointInRectangle( rectTransform, eventData.position, eventData.pressEventCamera, out lp); foreach (var hrefInfo in _ListHrefInfos) { var boxes = hrefInfo.boxes; for (var i = 0; i < boxes.Count; ++i) { if (boxes[i].Contains(lp)) { OnHrefClick.Invoke(hrefInfo.name); return; } } } } #endregion } public class SpriteTagInfo { //图集ID public int _ID; //标签标签 public string _Tag; //标签大小 public Vector2 _Size; //表情位置 public Vector3[] _Pos; //uv public Vector2[] _UV; }