【Unity】【エディタ拡張】テキスト入力用ポップアップを実装する

Unityのエディタ拡張でテキスト入力用ポップアップを実装する方法です。

Unity2019.2.18

やりたいこと

いま、EditorWindowからボタンを押すと下記のようなテキスト入力ポップアップが開く仕組みを作るとします。

f:id:halya_11:20200210211211g:plain

UnityではUnityEditor.EditorUtility.DisplayDialog()とかでシステムダイアログは出せるのですが、
これは拡張性に乏しく入力などはできないので今回の目的には即していません。

そこで今回はPopupWindowContentを使うことでこれを実現します。
PopupWindowContentの基本的な使い方は以下の記事にまとめていますので、
こちらを先に読むとよりわかりやすいかと思います。

light11.hatenadiary.com

ソースコード

早速ですがソースコードです。

using UnityEngine;
using UnityEditor;
using System;

public class TextFieldPopup : PopupWindowContent
{
    private const float WINDOW_PADDING = 8.0f;

    private string _text;
    private string _message;
    private float _width;
    private Action<string> _changed;
    private Action _closed;
    private GUIStyle _messageLabelStyle;
    private Vector2 _windowSize;
    private bool _didFocus = false;

    public static void Show(Vector2 position, string text, Action<string> changed, Action closed, string message = null, float width = 300)
    {
        var rect = new Rect(position, Vector2.zero);
        var content = new TextFieldPopup(text, changed, closed, message, width);
        PopupWindow.Show(rect, content);
    }

    private TextFieldPopup(string text, Action<string> changed, Action closed, string message = null, float width = 300)
    {
        _message = message;
        _text = text;
        _width = width;
        _changed = changed;
        _closed = closed;

        _messageLabelStyle = new GUIStyle(EditorStyles.boldLabel);
        _messageLabelStyle.wordWrap = true;

        // ウィンドウサイズを計算する
        var labelWidth = width - (WINDOW_PADDING * 2);
        _windowSize = Vector2.zero;
        _windowSize.x = width;
        _windowSize.y += WINDOW_PADDING; // Space
        _windowSize.y += _messageLabelStyle.CalcHeight(new GUIContent(message), labelWidth); // Message
        _windowSize.y += EditorGUIUtility.standardVerticalSpacing; // Space
        _windowSize.y += EditorGUIUtility.singleLineHeight; // TextField
        _windowSize.y += WINDOW_PADDING; // Space
    }

    public override void OnGUI(Rect rect)
    {
        // Enterで閉じる
        if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Return)
        {
            editorWindow.Close();
        }

        var textFieldName = $"{GetType().Name}{nameof(_text)}";
        using (new EditorGUILayout.HorizontalScope())
        {
            GUILayout.Space(WINDOW_PADDING);
            using (new EditorGUILayout.VerticalScope())
            {
                // タイトルを描画
                EditorGUILayout.LabelField(_message, _messageLabelStyle);
                // TextFieldを描画
                using (var ccs = new EditorGUI.ChangeCheckScope())
                {
                    GUI.SetNextControlName(textFieldName);
                    _text = EditorGUILayout.TextField(_text);
                    if (ccs.changed)
                    {
                        _changed?.Invoke(_text);
                    }
                }
            }
            GUILayout.Space(WINDOW_PADDING);
        }
        // 最初の一回だけ自動的にフォーカスする
        if (!_didFocus)
        {
            GUI.FocusControl(textFieldName);
            _didFocus = true;
        }
    }

    public override void OnClose()
    {
        _closed?.Invoke();
        base.OnClose();
    }

    public override Vector2 GetWindowSize() => _windowSize;
}

特に難しいことはしていないのでコメントを読んでコードを追っていただければと思います。
比較的汎用的に作っています。

使い方

これを使うには以下のように書きます。

    private static void Show()
    {
        TextFieldPopup.Show(new Vector2(0, 0), "example text", x => Debug.Log(x), () => Debug.Log("Closed"), "文字を入力", 300);
    }

以下のようなポップアップが表示されることが確認できました。

f:id:halya_11:20200210211211g:plain

関連

light11.hatenadiary.com