ugui学习 - 自己实现InputField光标的显示,闪烁,左右移动

发布时间 2023-07-28 23:52:06作者: yanghui01

最终效果

 

代码

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

[DisallowMultipleComponent]
[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
public class MyInputField_Caret : MonoBehaviour, ICanvasElement
{
    private VertexHelper m_CaretVh;
    private Mesh m_CaretMesh;

    public Text m_Text;

    [Range(1, 5)]
    public int m_CaretWidth = 1; //光标宽度
    public Color m_CaretColor = Color.black; //光标颜色

    [Range(0f, 4f)]
    public float m_CaretBlinkRate = 0.85f; //光标闪烁频率, 1s闪烁多少次, 显示-隐藏, 知道下次显示前为1次闪烁
    private float m_CaretBlinkStartTime;
    private bool m_CaretVisibleFlag = true;
    private Coroutine m_BlinkCo;

    private int m_CaretPos; //光标在哪个字符处, 光标处字符指示的是光标后面那个字符

    private CanvasRenderer m_CaretRenderer;
    public CanvasRenderer caretRenderer
    {
        get
        {
            if (null == m_CaretRenderer)
                m_CaretRenderer = GetComponent<CanvasRenderer>();
            return m_CaretRenderer;
        }
    }

    void Start()
    {
        m_CaretVh = new VertexHelper();
        m_CaretMesh = new Mesh();

        var renderer = this.caretRenderer;

        //设置材质和贴图
        renderer.materialCount = 1;
        var caretMat = Graphic.defaultGraphicMaterial;
        renderer.SetMaterial(caretMat, 0);

        var caretTexture = Texture2D.whiteTexture;
        renderer.SetTexture(caretTexture);

        //设置顶点
        PopulateCaretMesh(m_CaretVh);
        m_CaretVh.FillMesh(m_CaretMesh);
        renderer.SetMesh(m_CaretMesh);

        m_BlinkCo = StartCoroutine(CaretBlinkCo());
    }

    public virtual void Rebuild(CanvasUpdate update)
    {
        switch (update)
        {
            case CanvasUpdate.LatePreRender:
                UpdateCaretVerts();
                break;
        }
    }

    public void LayoutComplete() { }
    public void GraphicUpdateComplete() { }
    public bool IsDestroyed() { return (null == this); }


    private void UpdateCaretVerts()
    {
        var renderer = this.caretRenderer;

        m_CaretVh.Clear();
        //设置顶点
        if (m_CaretVisibleFlag)
            PopulateCaretMesh(m_CaretVh);
        m_CaretVh.FillMesh(m_CaretMesh);
        renderer.SetMesh(m_CaretMesh);
    }

    private void PopulateCaretMesh(VertexHelper vh)
    {
        var textGen = m_Text.cachedTextGenerator;
        if (textGen.characterCount <= 0)
            return;

        //光标显示在字符位置处
        float minX = 0;
        if (m_CaretPos < textGen.characters.Count)
        {
            var chInfo = textGen.characters[m_CaretPos];
            minX = chInfo.cursorPos.x;
            minX /= m_Text.pixelsPerUnit;
        }

        //找出光标处字符在哪一行上
        var caretLine = textGen.lineCount - 1;
        for (int i = 1; i < textGen.lineCount; ++i)
        {
            var tLineInfo = textGen.lines[i];
            if (tLineInfo.startCharIdx > m_CaretPos)
            {
                caretLine = i - 1;
                break;
            }
        }

        var lineInfo = textGen.lines[caretLine];
        float maxY = lineInfo.topY / m_Text.pixelsPerUnit;
        float minY = maxY - lineInfo.height / m_Text.pixelsPerUnit;

        //这边添加一个四边形
        vh.AddVert(new Vector3(minX, maxY), m_CaretColor, new Vector2(0f, 1f)); //左上
        vh.AddVert(new Vector3(minX + m_CaretWidth, maxY), m_CaretColor, new Vector2(1f, 1f)); //右上
        vh.AddVert(new Vector3(minX + m_CaretWidth, minY), m_CaretColor, new Vector2(1f, 0f)); //右下
        vh.AddVert(new Vector3(minX, minY), m_CaretColor, new Vector2(0f, 0f)); //左下

        //顺时针
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(0, 2, 3);
    }

    IEnumerator CaretBlinkCo()
    {
        m_CaretVisibleFlag = true;
        m_CaretBlinkStartTime = Time.unscaledTime;
        yield return null;

        while (m_CaretBlinkRate > 0)
        {
            float blinkFullPeriod = 1 / m_CaretBlinkRate;
            float blinkVisiblePeriod = blinkFullPeriod * 0.5f;
            //比如: 2s闪烁1次, 那[0, 1)光标是显示状态, [1, 2)光标是隐藏状态
            bool visibleFlag = ((Time.unscaledTime - m_CaretBlinkStartTime) % blinkFullPeriod) < blinkVisiblePeriod;
            if (m_CaretVisibleFlag != visibleFlag)
            {
                m_CaretVisibleFlag = visibleFlag;
                UpdateCaretVerts();
            }

            yield return null;
        }

        m_BlinkCo = null;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            if (m_CaretPos > 0)
            {
                m_CaretPos--;

                //光标闪烁重置(重新开始计时)
                m_CaretVisibleFlag = true;
                m_CaretBlinkStartTime = Time.unscaledTime;
                CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); //下一帧统一更新
            }
        }
        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            if (m_CaretPos < m_Text.text.Length)
            {
                m_CaretPos++;

                //光标闪烁重置(重新开始计时)
                m_CaretVisibleFlag = true;
                m_CaretBlinkStartTime = Time.unscaledTime;
                CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); //下一帧统一更新
            }
        }
    }

}

 

ugui的InputField做了,我们这边简化掉了的:

上下箭头让光标上下移动

ctrl+右箭头,移到下一行行首

ctrl+左箭头,移到当前行首或上一行行首

Home键,移到第1个字符前

End键,移到最后1个字符后

控件有焦点的情况才响应左右箭头键