GU source code analysis 10: InputField

Posted by jblallement on Wed, 09 Mar 2022 16:43:59 +0100

Source code 10: InputField

InputField is the input box that provides users with input text content. It is an important interactive means. For example, it is commonly used to enter user name, password and so on.

    public class InputField
        : Selectable,
        IUpdateSelectedHandler,
        IBeginDragHandler,
        IDragHandler,
        IEndDragHandler,
        IPointerClickHandler,
        ISubmitHandler,
        ICanvasElement,
        ILayoutElement
    {
    	...
    }

The implementation of InputField class is not explained in the previous article.

       protected override void OnEnable()
        {
            base.OnEnable();
            if (m_Text == null)
                m_Text = string.Empty;
            m_DrawStart = 0;
            m_DrawEnd = m_Text.Length;

            // If we have a cached renderer then we had OnDisable called so just restore the material.
            if (m_CachedInputRenderer != null)
                m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture);

            if (m_TextComponent != null)
            {
                m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty);
                m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel);
                m_TextComponent.RegisterDirtyMaterialCallback(UpdateCaretMaterial);
                UpdateLabel();
            }
        }

OnEnable component just activated

1. Set the start and end positions of input text drawing

2. Add two RegisterDirtyVerticesCallback events, MarkGeometryAsDirty and UpdateLabel, to the text component

Registerdirtymaterialcallcallback was called and UpdateCaretMaterial callback was added

Finally, call UpdateLable.

        private void MarkGeometryAsDirty()
        {
#if UNITY_EDITOR
            if (!Application.isPlaying || UnityEditor.PrefabUtility.IsPartOfPrefabAsset(gameObject))
                return;
#endif

            CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
        }

Mark geometry asdirty registers himself in the canvas update registry to the graphics reconstruction sequence. (analysis in later articles)

The UpdateLable method is mainly used to update the display when entering

    /// </summary>
    protected void UpdateLabel()
    {
        if (m_TextComponent != null && m_TextComponent.font != null && !m_PreventFontCallback)
        {
            m_PreventFontCallback = true;
			
			//Get all displayed text strings
            //When there is IME combined string, the combined string needs to be accessed and calculated according to the cursor position 
            //https://docs.unity3d.com/cn/2018.4/ScriptReference/Input-compositionString.html
            string fullText;
            if (EventSystem.current != null && gameObject == EventSystem.current.currentSelectedGameObject && compositionString.Length > 0)
                fullText = text.Substring(0, m_CaretPosition) + compositionString + text.Substring(m_CaretPosition);
            else
                fullText = text;

			//For password type input, use * instead of input
            string processed;
            if (inputType == InputType.Password)
                processed = new string(asteriskChar, fullText.Length);
            else
                processed = fullText;
			
			//Display Placeholder without input
            bool isEmpty = string.IsNullOrEmpty(fullText);
            if (m_Placeholder != null)
                m_Placeholder.enabled = isEmpty;

            // If not currently editing the text, set the visible range to the whole text.
            // The UpdateLabel method will then truncate it to the part that fits inside the Text area.
            // We can't do this when text is being edited since it would discard the current scroll,
            // which is defined by means of the m_DrawStart and m_DrawEnd indices.
            if (!m_AllowInput)
            {
                m_DrawStart = 0;
                m_DrawEnd = m_Text.Length;
            }

            if (!isEmpty)
            {
            	//Get Text generation settings (Text component function)
                // Determine what will actually fit into the given line
                Vector2 extents = m_TextComponent.rectTransform.rect.size;
                var settings = m_TextComponent.GetGenerationSettings(extents);
                settings.generateOutOfBounds = true;
				//Generate vertex data and text string data of Mesh,
                cachedInputTextGenerator.PopulateWithErrors(processed, settings, gameObject);
				//Calculate m according to TextGenerator and caretPos (cursor position)_ Drawstart and m_DrawEnd, and take the substring of the string according to these two values, and set the cursor to be visible (using CO process flashing).
                SetDrawRangeToContainCaretPosition(caretSelectPositionInternal);
				//Gets the string that can actually be drawn and displayed
                processed = processed.Substring(m_DrawStart, Mathf.Min(m_DrawEnd, processed.Length) - m_DrawStart);
				//Set the cursor to be visible (using CO process flashing).
                SetCaretVisible();
            }
            //The processed string is registered in the graph reconstruction list to the Text component
            m_TextComponent.text = processed;
            MarkGeometryAsDirty();
            m_PreventFontCallback = false;
        }
    }
  private void UpdateCaretMaterial()
        {
            if (m_TextComponent != null && m_CachedInputRenderer != null)
                m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture);
        }
  • Call m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture); Create a default material
  • Call m_CachedInputRenderer.SetMaterial assigns the created material to the cursor and updates the material of the cursor
  public void DeactivateInputField()
    {
        // Not activated do nothing.
        if (!m_AllowInput)
            return;

        m_HasDoneFocusTransition = false;
        m_AllowInput = false;

        if (m_Placeholder != null)
            m_Placeholder.enabled = string.IsNullOrEmpty(m_Text);

        if (m_TextComponent != null && IsInteractable())
        {
            if (m_WasCanceled)
                text = m_OriginalText;

            SendOnSubmit();

            if (m_Keyboard != null)
            {
                m_Keyboard.active = false;
                m_Keyboard = null;
            }

            m_CaretPosition = m_CaretSelectPosition = 0;
            if (input != null)
                input.imeCompositionMode = IMECompositionMode.Auto;
        }

        MarkGeometryAsDirty();
    }

The disable Input event is usually called when Input ends or Focus disable is lost

  • Set m_Keyboard.active is false,
  • Set the cursor position to 0,
  • If IsInteractable is true, call SendOnSubmit and send onEndEdit event, onEndEdit Invoke(m_Text),
  • Finally, call MarkGeometryAsDirty to wait for reconstruction.
  public void ActivateInputField()
        {
            if (m_TextComponent == null || m_TextComponent.font == null || !IsActive() || !IsInteractable())
                return;

            if (isFocused)
            {
                if (m_Keyboard != null && !m_Keyboard.active)
                {
                    m_Keyboard.active = true;
                    m_Keyboard.text = m_Text;
                }
            }

            m_ShouldActivateNextUpdate = true;
        }

Activate input, usually activate the keyboard for input when clicking

   public override void OnSelect(BaseEventData eventData)
        {
            base.OnSelect(eventData);

            if (shouldActivateOnSelect)
                ActivateInputField();
        }

Override OnSelect in Selectable to activate keyboard input

    public virtual void OnPointerClick(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        ActivateInputField();
    }

As above, activate keyboard input when clicked

 public override void OnDeselect(BaseEventData eventData)
    {
        DeactivateInputField();
        base.OnDeselect(eventData);
    }

Uncheck disable input

    public virtual void OnUpdateSelected(BaseEventData eventData)
    {
        if (!isFocused)
            return;

        bool consumedEvent = false;
        while (Event.PopEvent(m_ProcessingEvent))
        {
            if (m_ProcessingEvent.rawType == EventType.KeyDown)
            {
                consumedEvent = true;
                var shouldContinue = KeyPressed(m_ProcessingEvent);
                if (shouldContinue == EditState.Finish)
                {
                    DeactivateInputField();
                    break;
                }
            }

            switch (m_ProcessingEvent.type)
            {
                case EventType.ValidateCommand:
                case EventType.ExecuteCommand:
                    switch (m_ProcessingEvent.commandName)
                    {
                        case "SelectAll":
                            SelectAll();
                            consumedEvent = true;
                            break;
                    }
                    break;
            }
        }

        if (consumedEvent)
            UpdateLabel();

        eventData.Use();
    }

The method of implementing IUpdateSelectedHandler is that the StandaloneInputModule detects whether there is a selected object to send events every frame

This is mainly used to handle a series of keyboard input operations, such as backspace key, copy and paste (Ctrl+ c Ctrl+v).

    private void OnFillVBO(Mesh vbo)
    {
        using (var helper = new VertexHelper())
        {
            if (!isFocused)
            {
                helper.FillMesh(vbo);
                return;
            }

            Vector2 roundingOffset = m_TextComponent.PixelAdjustPoint(Vector2.zero);
            if (!hasSelection)
                GenerateCaret(helper, roundingOffset);
            else
                GenerateHighlight(helper, roundingOffset);

            helper.FillMesh(vbo);
        }
    }

If no area is selected, call GenerateCaret to generate the Mesh of the cursor; otherwise, call GenerateHightlight to generate the Mesh of the selected area. Then call VertexHelper.. Fillmesh, fill Mesh.

summary

Inputfile source code involves a lot of things related to displaying keyboard operation, which really looks messy. However, it contains many features and functions of UGUI, which is worth pondering carefully

Reference article: https://zhuanlan.zhihu.com/p/340600190

Later, we will start to analyze and compare the important content, and the display core of Graphic UGUI

Topics: Unity Unity3d