Is there a way to input text using a Unity Editor Utility?

Hi Guy,

I’d like to display a dialog with an input text field, when clicking on my custom menu within the Editor. It seems that Unity utillity is what I need, but I see no input dialog for text. I only found this dialog → Unity - Scripting API: EditorUtility.DisplayDialog

3 Likes

you can make your new popup window and add all the text fields and buttons you want.

6 Likes

Thanks, This helped out insane!

This is a part of my code, implementing your advise:

public class ProjectManagerCreateProject : EditorWindow
    {
        public string editorWindowText = "Choose a project name: ";
        string newProjectName = ProjectManagerGlobals.defaultProjectName;
        int projectNumber = 1;

        void OnGUI()
        {
            string inputText = EditorGUILayout.TextField(editorWindowText, newProjectName);

            if (GUILayout.Button("Create new project"))
                CreateNewProject(inputText);

            if (GUILayout.Button("Abort"))
                Close();
        }

        [MenuItem("ProjectManager/Create new project")]
        static void CreateProjectCreationWindow()
        {
            ProjectManagerCreateProject window = new ProjectManagerCreateProject();
            window.ShowUtility();
        }
}
3 Likes

Here’s another example. It’s a simple version file editor I placed in the menus next to my build commands. It’s not modal but it’s good enough for now. (Visual Studio kept adding a carriage return to the line so I needed a better solution for editing this file.)

using UnityEngine;
using UnityEditor;
using System.IO;

public class VersionEditor : EditorWindow
{
    const string FILENAME = "VERSION.txt";
    static string versionFilePath;
    static string version;

    [MenuItem("MyGame/Edit " + FILENAME, false, 100)]
    static void Init()
    {
        versionFilePath = Application.dataPath + "/" + FILENAME;
        version = File.ReadAllText(versionFilePath);

        VersionEditor window = CreateInstance<VersionEditor>();
        window.position = new Rect(Screen.width / 2, Screen.height / 2, 250, 150);
        window.ShowUtility();
    }

    void OnGUI()
    {
        GUILayout.Space(10);
        EditorGUILayout.LabelField("Change the version and click Update.", EditorStyles.wordWrappedLabel);
        GUILayout.Space(10);
        version = EditorGUILayout.TextField("Version: ", version);
        GUILayout.Space(60);
        EditorGUILayout.BeginHorizontal();
            if (GUILayout.Button("Update")) {
                File.WriteAllText(versionFilePath, version);
                AssetDatabase.ImportAsset("Assets/" + FILENAME, ImportAssetOptions.ForceUpdate);
                Close();
            }
            if (GUILayout.Button("Cancel")) Close();
        EditorGUILayout.EndHorizontal();
    }
}

How do I call this to open from other scripts?

This is a pretty old thread and there are at least two Editor-only scripts posted above. It might be better to start a fresh thread with precisely your question.

Generally you open such scripts as those above by adding a MenuItemAttribute decorator above a factory method, as shown here in the docs:

https://docs.unity3d.com/ScriptReference/EditorWindow.html

They will ONLY work in the Unity Editor.

Since this thread comes as first result in Google when searching for how to input text in Unity Editor, here is a proper and simple to use solution.
It returns the string which the user entered, or null if the user pressed Cancel/Close button.

Usage:

var name = EditorInputDialog.Show( "Question", "Please enter your name", "" );
if( !string.IsNullOrEmpty(name) ) Debug.Log( "Hello " + name );

Features:

  • custom title / description / ok / cancel button texts
  • it will be shown next to mouse cursor position
  • when the popup is shown, it will focus keyboard to text input
  • it handles user pressing Return/Enter and Escape keys
using System;
using UnityEditor;
using UnityEngine;

public class EditorInputDialog : EditorWindow
{
    string  description, inputText;
    string  okButton, cancelButton;
    bool    initializedPosition = false;
    Action  onOKButton;

    bool    shouldClose = false;

    #region OnGUI()
    void OnGUI()
    {
        // Check if Esc/Return have been pressed
        var e = Event.current;
        if( e.type == EventType.KeyDown )
        {
            switch( e.keyCode )
            {
                // Escape pressed
                case KeyCode.Escape:
                    shouldClose = true;
                    break;

                // Enter pressed
                case KeyCode.Return:
                case KeyCode.KeypadEnter:
                    onOKButton?.Invoke();
                    shouldClose = true;
                    break;
            }
        }

        if( shouldClose ) {  // Close this dialog
            Close();
            //return;
        }

        // Draw our control
        var rect = EditorGUILayout.BeginVertical();

        EditorGUILayout.Space( 12 );
        EditorGUILayout.LabelField( description );

        EditorGUILayout.Space( 8 );
        GUI.SetNextControlName( "inText" );
        inputText = EditorGUILayout.TextField( "", inputText );
        GUI.FocusControl( "inText" );   // Focus text field
        EditorGUILayout.Space( 12 );

        // Draw OK / Cancel buttons
        var r = EditorGUILayout.GetControlRect();
        r.width /= 2;
        if( GUI.Button( r, okButton ) ) {
            onOKButton?.Invoke();
            shouldClose = true;
        }

        r.x += r.width;
        if( GUI.Button( r, cancelButton ) ) {
            inputText = null;   // Cancel - delete inputText
            shouldClose = true;
        }

        EditorGUILayout.Space( 8 );
        EditorGUILayout.EndVertical();

        // Force change size of the window
        if( rect.width != 0 && minSize != rect.size ) {
            minSize = maxSize = rect.size;
        }

        // Set dialog position next to mouse position
        if( !initializedPosition ) {
            var mousePos = GUIUtility.GUIToScreenPoint( Event.current.mousePosition );
            position = new Rect( mousePos.x + 32, mousePos.y, position.width, position.height );
            initializedPosition = true;
        }
    }
    #endregion OnGUI()

    #region Show()
    /// <summary>
    /// Returns text player entered, or null if player cancelled the dialog.
    /// </summary>
    /// <param name="title"></param>
    /// <param name="description"></param>
    /// <param name="inputText"></param>
    /// <param name="okButton"></param>
    /// <param name="cancelButton"></param>
    /// <returns></returns>
    public static string Show( string title, string description, string inputText, string okButton = "OK", string cancelButton = "Cancel" )
    {
        string ret = null;
        //var window = EditorWindow.GetWindow<InputDialog>();
        var window = CreateInstance<EditorInputDialog>();
        window.titleContent = new GUIContent( title );
        window.description = description;
        window.inputText = inputText;
        window.okButton = okButton;
        window.cancelButton = cancelButton;
        window.onOKButton += () => ret = window.inputText;
        window.ShowModal();

        return ret;
    }
    #endregion Show()
}
16 Likes

:hushed: Now that was some hella fantastic timing.

Looked for the same thing, got here through Google, was all ready to ask if there was a more up-to-date solution, and look at that. Not even 24 hours ago.

Thank you for that! <3

2 Likes

Updated my previous version of EditorInputDialog - only change is that it makes sure that input dialog is always shown visible on the screen. Previous version always showed the dialog to the right of the current mouse position, which could position the dialog outside the screen, if mouse was near right edge of the monitor.

using System;
using UnityEditor;
using UnityEngine;

public class EditorInputDialog : EditorWindow
{
    string  description, inputText;
    string  okButton, cancelButton;
    bool    initializedPosition = false;
    Action  onOKButton;

    bool    shouldClose = false;
    Vector2 maxScreenPos;

    #region OnGUI()
    void OnGUI()
    {
        // Check if Esc/Return have been pressed
        var e = Event.current;
        if( e.type == EventType.KeyDown )
        {
            switch( e.keyCode )
            {
                // Escape pressed
                case KeyCode.Escape:
                    shouldClose = true;
                    e.Use();
                    break;

                // Enter pressed
                case KeyCode.Return:
                case KeyCode.KeypadEnter:
                    onOKButton?.Invoke();
                    shouldClose = true;
                    e.Use();
                    break;
            }
        }

        if( shouldClose ) {  // Close this dialog
            Close();
            //return;
        }

        // Draw our control
        var rect = EditorGUILayout.BeginVertical();

        EditorGUILayout.Space( 12 );
        EditorGUILayout.LabelField( description );

        EditorGUILayout.Space( 8 );
        GUI.SetNextControlName( "inText" );
        inputText = EditorGUILayout.TextField( "", inputText );
        GUI.FocusControl( "inText" );   // Focus text field
        EditorGUILayout.Space( 12 );

        // Draw OK / Cancel buttons
        var r = EditorGUILayout.GetControlRect();
        r.width /= 2;
        if( GUI.Button( r, okButton ) ) {
            onOKButton?.Invoke();
            shouldClose = true;
        }

        r.x += r.width;
        if( GUI.Button( r, cancelButton ) ) {
            inputText = null;   // Cancel - delete inputText
            shouldClose = true;
        }

        EditorGUILayout.Space( 8 );
        EditorGUILayout.EndVertical();

        // Force change size of the window
        if( rect.width != 0 && minSize != rect.size ) {
            minSize = maxSize = rect.size;
        }

        // Set dialog position next to mouse position
        if( !initializedPosition && e.type == EventType.Layout )
        {
            initializedPosition = true;

            // Move window to a new position. Make sure we're inside visible window
            var mousePos = GUIUtility.GUIToScreenPoint( Event.current.mousePosition );
            mousePos.x += 32;
            if( mousePos.x + position.width > maxScreenPos.x ) mousePos.x -= position.width + 64; // Display on left side of mouse
            if( mousePos.y + position.height > maxScreenPos.y ) mousePos.y = maxScreenPos.y - position.height;

            position = new Rect( mousePos.x, mousePos.y, position.width, position.height );

            // Focus current window
            Focus();
        }
    }
    #endregion OnGUI()

    #region Show()
    /// <summary>
    /// Returns text player entered, or null if player cancelled the dialog.
    /// </summary>
    /// <param name="title"></param>
    /// <param name="description"></param>
    /// <param name="inputText"></param>
    /// <param name="okButton"></param>
    /// <param name="cancelButton"></param>
    /// <returns></returns>
    public static string Show( string title, string description, string inputText, string okButton = "OK", string cancelButton = "Cancel" )
    {
        // Make sure our popup is always inside parent window, and never offscreen
        // So get caller's window size
        var maxPos = GUIUtility.GUIToScreenPoint( new Vector2( Screen.width, Screen.height ) );

        string ret = null;
        //var window = EditorWindow.GetWindow<InputDialog>();
        var window = CreateInstance<EditorInputDialog>();
        window.maxScreenPos = maxPos;
        window.titleContent = new GUIContent( title );
        window.description = description;
        window.inputText = inputText;
        window.okButton = okButton;
        window.cancelButton = cancelButton;
        window.onOKButton += () => ret = window.inputText;
        //window.ShowPopup();
        window.ShowModal();

        return ret;
    }
    #endregion Show()
}
19 Likes

And before anyone with a controller for testing even tries to think about it…

No, it does not seem like Unity will handle Controller input inside a custom editor window. It’ll handle other input just fine but I have tried with a PS4 Controller, two XBOX One Controllers, a bad Bootleg SNES controller for USB and a cheap USB PS4 controller, all of them worked fine in play mode, none of them work in Editor Mode.

It is a big shame, but that’s fine.

Thank you @Vedran_M !

1 Like

Thank you @Vedran_M !

1 Like

Thank you @Vedran_M !

1 Like

Love this. Getting some editor leaking memory spam in 2021.3.9f1, exactly as shown. Attributed to the line with the ShowModal() call, and only shows up when the modal closes. I tried window.DestroyImmedate() and unsubscribing the onOKButton delegate, but these didn’t change anything. I don’t have a good setup to dig into this further, just wondered if anyone else was getting it?

You haven’t said what script your using there are several different scripts and different versions of the same script. Though based on the ShowModal I guess you talk about Vedran_M’s latest version?

What does that actually mean? So you get a log in the console? I can not reproduce this, though my test project uses an older Unity version (2019.4.19f1). It may also matter from where you actually use the Show method.

ps: You can actually get rid of that internal onOKButton delegate since ShowModal would block the execution of the main thread until the window is closed. So you can directly grab window.inputText for the return value and remove that “ret” variable and the delegate. Such a delegate makes sense, if the user can provide that delegate from the outside. Though since it’s only used internally, it’s kinda pointless anyways.

When I made such a dialog in the past, I used a bool return value, indicating ok / cancel and have the text as a ref argument. That way we can easily pass a certain variable that should be edited, but when aborted the variable simply keeps its original value. That’s usually the most common usecase. Though I would make seperate methods like Edit() which takes a ref argument and maybe a QueryString() which directly returns the string or null. The Edit would have an initial value while QueryString would not. Adding optional delegates may come in handy in certain situations, though usually that’s not necessary with modal dialogs.

Another feature that existed for ages but almost nobody used it is the ScriptableWizard. It’s essentially an EditorWindow, however it automatically shows input fields for the serializable variables you define in the class. It also shows two buttons and has 2 events associated with those buttons. It’s actually quite versatile. It’s not modal as it’s generally meant to be used as a “create wizard”. So you set would set and configure certain values and when you press “create” the wizard closes and does something with that data. Usually creating an asset or something.

Yes, @Vedran_M 's version. I stopped getting the memory leak messages when I started putting in some constraints on the layout size. It was only leaking 37 bytes, but giving like 5 console messages every time. I think Unity got confused with the auto-layout algorithm or something.

For my purposes with multiple strings, I also ended up ditching the delegate and went for an inner class including a bool ok flag.

seeing as how this is still top of the google search, I modified @Vedran_M 's code to a modern version for UIToolkit

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;


public class EditorInputDialog : EditorWindow
{
    public static string Show(string pTitle, string pDescription, string pText, string pOkButton = "Ok", string pCancelButton = "Cancel")
    {
        string r = null;
        var window = CreateInstance<EditorInputDialog>();
        window.titleContent = new GUIContent(pTitle);
        window.rootVisualElement.style.height = new Length(100, LengthUnit.Percent);
        window.rootVisualElement.style.justifyContent = new StyleEnum<Justify>(Justify.SpaceAround);

        var label = new Label(pDescription);
        window.rootVisualElement.Add(label);

        var inputText = new TextField();
        inputText.value = pText;
        window.rootVisualElement.Add(inputText);

        var okButton = new Button(() => { r = inputText.value; window.Close(); }) { text = pOkButton };
        okButton.style.flexGrow = 1;
        var cancelButton = new Button(() => { r = null; window.Close(); }) { text = pCancelButton };
        cancelButton.style.flexGrow = 1;

        var buttonContainer = new VisualElement();
        buttonContainer.style.flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row);
        buttonContainer.style.justifyContent = new StyleEnum<Justify>(Justify.SpaceAround);
        buttonContainer.Add(okButton);
        buttonContainer.Add(cancelButton);
        window.rootVisualElement.Add(buttonContainer);

        window.rootVisualElement.RegisterCallback<KeyUpEvent>(e =>
        {
            if (e.keyCode == KeyCode.Escape)
            {
                r = null;
                window.Close();
            }

            if (e.keyCode == KeyCode.Return || e.keyCode == KeyCode.KeypadEnter)
            {
                r = inputText.value;
                window.Close();
            }
        });

        // Move window to a new position. Make sure we're inside visible window
        var mousePos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);
        var maxPos = GUIUtility.GUIToScreenPoint(new Vector2(Screen.width, Screen.height));
        mousePos.x += 32;
        if (mousePos.x + window.position.width > maxPos.x) mousePos.x -= window.position.width + 64; // Display on left side of mouse
        if (mousePos.y + window.position.height > maxPos.y) mousePos.y = maxPos.y - window.position.height;

        window.position = new Rect(mousePos.x, mousePos.y, window.position.width, window.position.height);

        window.rootVisualElement.schedule.Execute(() =>
        {
            inputText.Focus();
        }).ExecuteLater(10);

        window.ShowModal();
        return r;
    }
}
4 Likes

While we usually do not like necro posts, we certainly have not enough UIToolkit examples in the forum and this looks well done. So since it’s on topic and an updated version, i think that necro is ok :slight_smile:

1 Like