I needed to add tab key support to a GUI.TextArea and soon discovered it was not quite as easy as I had originally thought. I have provided two code examples below. The first example is simplified and the second example is more complex that wraps TextArea controls inside of parent control.

With these examples you can type text in a TextArea/TextField and press the tab key to insert 4 spaces, or press Shift+Tab to move the line 4 spaces to the left if the area is clear.

I have also provided a third advanced example from my UIControls library to give an example of a real word usage scenario. This third example synchronizes my TextBox control with the unity’s TextEditor. My TextBox control has similar properties as Winforms TextBox.

Simplified example

using System; 
using UnityEditor;
using UnityEngine;

public class TextAreaTabSupport : EditorWindow
{
    private int lastKBFocus = -1;
    private string textA = string.Empty;
    private string textB = string.Empty;
    private string textC = string.Empty;

    [MenuItem("Test/Text Area Tab Support")]
    public static void ShowWindow()
    {
        GetWindow<TextAreaTabSupport>().Show();
    }

    public void OnGUI()
    {
        var current = Event.current;
        GUI.SetNextControlName("testa");

        if (GUI.GetNameOfFocusedControl() == "testa" && this.lastKBFocus == GUIUtility.keyboardControl)
        {
            if (current.type == EventType.KeyDown || current.type == EventType.KeyUp)
            {
                if (current.isKey && (current.keyCode == KeyCode.Tab || current.character == '\t'))
                {
                    if (current.type == EventType.KeyUp)
                    {
                        var te = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);

                        if (!current.shift)
                        {
                            for (var i = 0; i < 4; i++)
                            {
                                te.Insert(' ');
                            }
                        }
                        else
                        {
                            var min = Math.Min(te.cursorIndex, te.selectIndex);
                            var index = min;
                            var temp = te.text;
                            for (var i = 1; i < 5; i++)
                            {
                                if ((min - i) < 0 || temp[min - i] != ' ')
                                {
                                    break;
                                }

                                index = min - i;
                            }

                            if (index < min)
                            {
                                te.selectIndex = index;
                                te.cursorIndex = min;
                                te.ReplaceSelection(string.Empty);
                            }
                        }

                        this.textA = te.text;
                    }

                    current.Use();
                }
            }
        }

        this.textA = GUI.TextArea(new Rect(0, 40, 100, 100), this.textA);

        if (GUI.GetNameOfFocusedControl() == "testa" && current.type == EventType.KeyDown || current.type == EventType.KeyUp)
        {
            this.lastKBFocus = GUIUtility.keyboardControl;
        }

        GUI.SetNextControlName("testb");
        this.textB = GUI.TextArea(new Rect(110, 40, 100, 100), this.textB);

        GUI.SetNextControlName("testc");
        this.textC = GUI.TextField(new Rect(220, 40, 100, 30), this.textC);

        if (GUI.Button(new Rect(10, 110, 50, 25), "Click"))
        {

        }
    }
}

And a more complex example

using System;
using UnityEditor;
using UnityEngine;

public class TextAreaTabSupport : EditorWindow
{
    private Vector2 scroll;
    private int lastKBFocus = -1;
    private string textA = string.Empty;
    private string textB = string.Empty;
    private string textC = string.Empty;

    [MenuItem("Test/Text Area Tab Support")]
    public static void ShowWindow()
    {
        GetWindow<TextAreaTabSupport>().Show();
    }

    public void OnGUI()
    {
        var current = Event.current;

        GUI.SetNextControlName("scroller");
        using (var scroll = new GUI.ScrollViewScope(new Rect(Vector2.zero, new Vector2(330, 150)), this.scroll, new Rect(Vector2.zero, new Vector2(330, 150))))
        {
            this.scroll = scroll.scrollPosition;

            if (GUI.GetNameOfFocusedControl() == "testa" && this.lastKBFocus == GUIUtility.keyboardControl)
            {
                if (current.type == EventType.KeyDown || current.type == EventType.KeyUp)
                {
                    if (current.isKey && (current.keyCode == KeyCode.Tab || current.character == '\t'))
                    {
                        if (current.type == EventType.KeyUp)
                        {
                            var te = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);

                            if (!current.shift)
                            {
                                for (var i = 0; i < 4; i++)
                                {
                                    te.Insert(' ');
                                }
                            }
                            else
                            {
                                var min = Math.Min(te.cursorIndex, te.selectIndex);
                                var index = min;
                                var temp = te.text;
                                for (var i = 1; i < 5; i++)
                                {
                                    if ((min - i) < 0 || temp[min - i] != ' ')
                                    {
                                        break;
                                    }

                                    index = min - i;
                                }

                                if (index < min)
                                {
                                    te.selectIndex = index;
                                    te.cursorIndex = min;
                                    te.ReplaceSelection(string.Empty);
                                }
                            }

                            this.textA = te.text;
                        }

                        current.Use();
                    }
                }
            }

            using (new GUI.GroupScope(new Rect(0, 0, 110, 110)))
            {
                GUI.SetNextControlName("testa");
                this.textA = GUI.TextArea(new Rect(0, 4, 100, 100), this.textA);
            }

            if (this.lastKBFocus != GUIUtility.keyboardControl && (current.type == EventType.KeyDown || current.type == EventType.KeyUp))
            {
                this.lastKBFocus = GUIUtility.keyboardControl;
            }

            GUI.SetNextControlName("testb");
            this.textB = GUI.TextArea(new Rect(110, 40, 100, 100), this.textB);

            GUI.SetNextControlName("testc");
            this.textC = GUI.TextField(new Rect(220, 40, 100, 30), this.textC);

            if (GUI.Button(new Rect(10, 110, 50, 25), "Click"))
            {

            }
        }
    }
}

Advanced example

namespace Codefarts.UIControls.Renderers
{
#if UNITY_5
    using System;                      

    using UnityEngine;

    /// <summary>
    /// Provides a renderer implementation for the <see cref="TextBox"/> control.
    /// </summary>
    [ControlRenderer(typeof(TextBox))]
    public class TextBoxRenderer : BaseRenderer
    {
        /// <summary>
        /// Implemented by inheritors to draw the actual control.
        /// </summary>
        /// <param name="args">The rendering argument information.</param>
        /// <exception cref="System.ArgumentNullException">control</exception>
        public override void DrawControl(ControlRenderingArgs args)
        {
            var textBox = (TextBox)args.Control;

            // unity gui does not like null strings
            var text = textBox.Text == null ? string.Empty : textBox.Text;

            var maxLength = textBox.MaxLength == 0 ? int.MaxValue : textBox.MaxLength;

            var rect = new Rect(textBox.Location + args.Offset, textBox.Size);
            var hsbVisibility = textBox.HorizontalScrollBarVisibility;
            var vsbVisibility = textBox.VerticalScrollBarVisibility;
            var alwaysShowHorizontal = hsbVisibility == ScrollBarVisibility.Visible;
            var alwaysShowVertical = vsbVisibility == ScrollBarVisibility.Visible;

            KeyCode keyCode;
            bool isDown;
            bool isUp;
            string controlName;
            this.GetKeyInfoAndSetControlName(textBox, out keyCode, out isDown, out isUp, out controlName, false);

            // get style and sync it up
            var style = textBox.GetStyle(Control.ControlStyle, true, GUI.skin.textArea);
            if (textBox.Font != null)
            {
                style.SetFontStyle(textBox.Font);
            }

            var current = Event.current;
            int lastKBFocus;
            textBox.Properties.TryGetValueCast(ControlDrawingHelpers.LastKeyboardControlID, out lastKBFocus, -1);
            var selectionStart = textBox.SelectionStart;
            var selectionLength = textBox.SelectionLength;

            if (GUI.GetNameOfFocusedControl() == controlName && lastKBFocus == GUIUtility.keyboardControl)
            {
                var te = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);

                // sync textbox & texteditor selections
                this.SyncTextBoxSelection(TextBox.TextBoxSelectionStartChanged, te, textBox);
                this.SyncTextBoxSelection(TextBox.TextBoxSelectionLengthChanged, te, textBox);

                // process tab key if nessary
                text = this.HandleTabKeyPress(ref selectionStart, te, current, textBox, text);

                selectionLength = Math.Abs(te.cursorIndex - te.selectIndex);
            }

            // draw the text area/field control
            textBox.Text = this.DrawActualTextControl(textBox, text, rect, alwaysShowHorizontal, alwaysShowVertical, style, maxLength, controlName);

            // check if we need to record last keyboard control id
            if (GUI.GetNameOfFocusedControl() == controlName && lastKBFocus != GUIUtility.keyboardControl)
            {
                textBox.Properties[ControlDrawingHelpers.LastKeyboardControlID] = GUIUtility.keyboardControl;
            }

            // check is text selection changed and sync if nessary
            if (textBox.SelectionStart != selectionStart)
            {
                textBox.SelectionStart = selectionStart;
            }
            if (textBox.SelectionLength != selectionLength)
            {
                textBox.SelectionLength = selectionLength;
            }

            // Handle key events
            this.HandleKeyEventsAfterControlDrawn(controlName, keyCode, isDown, textBox, isUp);

            // handle mouse enter & leave events
            this.HandleMouseEvents(textBox);
        }

        private string HandleTabKeyPress(ref int selectionStart, TextEditor te, Event current, TextBox textBox, string text)
        {
            selectionStart = Math.Min(te.cursorIndex, te.selectIndex);
            if (current.type == EventType.KeyDown || current.type == EventType.KeyUp)
            {
                if (current.isKey && (current.keyCode == KeyCode.Tab || current.character == '\t'))
                {
                    // consume the tab key event before drawing the control
                    if (current.type == EventType.KeyUp && textBox.AcceptsTab)
                    {
                        if (!current.shift)
                        {
                            for (var i = 0; i < 4; i++)
                            {
                                te.Insert(' ');
                            }
                        }
                        else
                        {
                            var min = selectionStart;
                            var index = min;
                            var temp = te.text;
                            for (var i = 1; i < 5; i++)
                            {
                                if ((min - i) < 0 || temp[min - i] != ' ')
                                {
                                    break;
                                }

                                index = min - i;
                            }

                            if (index < min)
                            {
                                te.selectIndex = index;
                                te.cursorIndex = min;
                                te.ReplaceSelection(string.Empty);
                            }
                        }

                        selectionStart = Math.Min(te.cursorIndex, te.selectIndex);

                        text = te.text;
                    }

                    current.Use();
                }
            }

            return text;
        }

        private void SyncTextBoxSelection(string name, TextEditor editor, TextBox tb)
        {
            var props = tb.Properties;
            if (props != null)
            {
                bool changed;
                if (props.TryGetValueCast(name, out changed, false) && changed)
                {
                    props[name] = false;
                    this.SetTextEditorSelection(editor, tb.SelectionStart, tb.SelectionLength);
                }
            }
        }

        private void SetTextEditorSelection(TextEditor editor, int start, int length)
        {
            if (editor.cursorIndex < editor.selectIndex)
            {
                editor.cursorIndex = start;
                editor.selectIndex = start + length;
            }
            else
            {
                editor.selectIndex = start;
                editor.cursorIndex = start + length;
            }
        }

        protected virtual string DrawActualTextControl(TextBox textBox, string text, Rect rect, bool alwaysShowHorizontal, bool alwaysShowVertical,
            GUIStyle style, int maxLength, string controlName)
        {
            var scrollPosition = new Vector2(-textBox.HorizontalOffset, -textBox.VerticalOffset);
            if (textBox.AcceptsReturn)
            {
                var textSize = GUI.skin.textArea.CalcSize(new GUIContent(text));
                var viewRect = new Rect(Vector2.zero, textSize);
                viewRect.width = Math.Max(textSize.x, rect.width);
                viewRect.height = Math.Max(textSize.y, rect.height);

                var drawHorizScroll = viewRect.width > rect.width || alwaysShowHorizontal;
                var drawVertScroll = viewRect.height > rect.height || alwaysShowVertical;

                var horizRect = new Rect(
                    0,
                    rect.height - GUI.skin.horizontalScrollbar.fixedHeight,
                    rect.width - (drawVertScroll ? GUI.skin.verticalScrollbar.fixedWidth : 0),
                    GUI.skin.horizontalScrollbar.fixedHeight);
                var vertRect = new Rect(
                    rect.width - GUI.skin.verticalScrollbar.fixedWidth,
                    0,
                    GUI.skin.verticalScrollbar.fixedWidth,
                    rect.height - (drawHorizScroll ? GUI.skin.horizontalScrollbar.fixedHeight : 0));
                horizRect.position += rect.position;
                vertRect.position += rect.position;

                scrollPosition.x = drawHorizScroll ? scrollPosition.x : 0;
                scrollPosition.y = drawVertScroll ? scrollPosition.y : 0;

                viewRect.position = scrollPosition;
                var grpRect = new Rect(
                    rect.x,
                    rect.y,
                    rect.width - (drawVertScroll ? GUI.skin.verticalScrollbar.fixedWidth : 0),
                    rect.height - (drawHorizScroll ? GUI.skin.horizontalScrollbar.fixedHeight : 0));

                using (new GUI.GroupScope(grpRect))
                {
                    // draw background
                    var brush = textBox.Background;
                    if (brush != null)
                    {
                        BrushExtensions.Draw(brush, new Rect(Vector2.zero, grpRect.size));
                    }

                    GUI.SetNextControlName(controlName);
                    // as of unity v5.3 there is a bug that prevent me from specifying a maxlength
                    //BUG: see details here -> https://fogbugz.unity3d.com/default.asp?768436_vikdrmh7ernh03ls
                    text = GUI.TextArea(viewRect, text, style);
                }

                if (drawHorizScroll)
                {
                    textBox.HorizontalOffset = GUI.HorizontalScrollbar(
                        horizRect,
                        textBox.HorizontalOffset,
                        Math.Min(viewRect.width, rect.width),
                        0,
                        viewRect.width + (drawVertScroll ? GUI.skin.verticalScrollbar.fixedWidth : 0));
                }

                if (drawVertScroll)
                {
                    textBox.VerticalOffset = GUI.VerticalScrollbar(
                        vertRect,
                        textBox.VerticalOffset,
                        Math.Min(viewRect.height, rect.height),
                        0,
                        viewRect.height + (drawHorizScroll ? GUI.skin.horizontalScrollbar.fixedHeight : 0));
                }
            }
            else
            {
                // draw background
                var brush = textBox.Background;
                if (brush != null)
                {
                    BrushExtensions.Draw(brush, rect);
                }
                GUI.SetNextControlName(controlName);
                text = GUI.TextField(rect, text, maxLength, style);
            }

            return text;
        }
    }
#endif      
}

Add comment



biuquote
  • Comment
  • Preview
Loading






Created by: X

Just another personal website in this crazy online world

Name of author Dean Lunz (aka Created by: X)
Computer programming nerd, and tech geek.
About Me -- Resume