【Unity】【UI Toolkit】フォーカスイベントを使ってTextFieldにPlaceholderを実装する

UnityのUI Toolkitのフォーカスイベントを使ってTextFieldにPlaceholderを実装する方法です。

Unity2021.3.16f1

フォーカスイベント

UI Toolkitでは、FocusInEventFocusOutEventによりそのUI要素がフォーカスされたときとそのUI要素からフォーカスが外れた時の処理をハンドリングできます。
これらのイベントは他のイベントと同じくRegisterCallbackメソッドで登録できます。

textField.RegisterCallback<FocusInEvent>(OnFocusIn);
textField.RegisterCallback<FocusOutEvent>(OnFocusOut);

UI Toolkitにおけるイベントの詳細については以下の記事を参照してください。

light11.hatenadiary.com

Placeholderを表示する

さてそれではこのフォーカスイベントを使ってTextFieldPlaceholderを表示するスクリプトを書いてみます。

using UnityEditor;
using UnityEngine.UIElements;

public sealed class PlaceholderExample : EditorWindow
{
    private const string PlaceHolderText = "This is placeholder";

    private bool _isPlaceholderVisible;

    public void CreateGUI()
    {
        var textField = new TextField();
        rootVisualElement.Add(textField);

        // フォーカスイベントのコールバックを登録
        textField.RegisterCallback<FocusInEvent>(OnFocusIn);
        textField.RegisterCallback<FocusOutEvent>(OnFocusOut);

        // 最初はTextFieldが空なのでPlaceholderを表示しておく
        ShowPlaceHolder(textField);
    }

    // フォーカスが当たった時の処理
    // もしPlaceholderが表示されていたら非表示にする
    private void OnFocusIn(FocusInEvent evt)
    {
        var textField = (TextField)evt.target;
        if (!_isPlaceholderVisible)
            return;

        HidePlaceHolder(textField);
    }

    // フォーカスが外れた時の処理
    // もしTextFieldが空ならPlaceholderを表示する
    private void OnFocusOut(FocusOutEvent evt)
    {
        var textField = (TextField)evt.target;
        var shouldShowPlaceholder = string.IsNullOrEmpty(textField.text);
        if (!shouldShowPlaceholder)
            return;

        ShowPlaceHolder(textField);
    }

    private void ShowPlaceHolder(TextField textField)
    {
        textField.SetValueWithoutNotify(PlaceHolderText);
        _isPlaceholderVisible = true;
    }
    
    private void HidePlaceHolder(TextField textField)
    {
        textField.value = string.Empty;
        _isPlaceholderVisible = false;
    }

    [MenuItem("Window/PlaceholderExample")]
    public static void ShowWindow()
    {
        GetWindow<PlaceholderExample>("PlaceholderExample");
    }
}

ポイントはコメントに書いた通りです。
難しいことはしておらず、フォーカスが外れたときにTextFieldが空だったらPlaceholderを設定しています。

実行結果は以下のとおりです。

実行結果

Placeholderにスタイルを設定する

次に、Placeholderの色は大体グレーだったりするので、スタイルを設定するための対応を行います。

まずスタイルシートを以下のように作ります。

.unity-text-field__placeholder > .unity-base-text-field__input {
    color: #888888;
}

次に先ほどのコードに、Placeholderが表示されているときだけこれを適用する処理を追加します。

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

public sealed class PlaceholderExample : EditorWindow
{
    private const string PlaceHolderText = "This is placeholder";

    private bool _isPlaceholderVisible;
    private string _placeholderClassName;

    public void CreateGUI()
    {
        _placeholderClassName = TextField.ussClassName + "__placeholder";
        rootVisualElement.styleSheets.Add(Resources.Load<StyleSheet>("style"));

        
        var textField = new TextField();
        rootVisualElement.Add(textField);

        textField.RegisterCallback<FocusInEvent>(OnFocusIn);
        textField.RegisterCallback<FocusOutEvent>(OnFocusOut);
        
        ShowPlaceHolder(textField);
    }

    private void OnFocusIn(FocusInEvent evt)
    {
        var textField = (TextField)evt.target;
        if (!_isPlaceholderVisible)
            return;

        HidePlaceHolder(textField);
    }

    private void OnFocusOut(FocusOutEvent evt)
    {
        var textField = (TextField)evt.target;
        var shouldShowPlaceholder = string.IsNullOrEmpty(textField.text);
        if (!shouldShowPlaceholder)
            return;

        ShowPlaceHolder(textField);
    }

    private void ShowPlaceHolder(TextField textField)
    {
        // スタイルを適用
        textField.AddToClassList(_placeholderClassName);
        
        textField.SetValueWithoutNotify(PlaceHolderText);
        _isPlaceholderVisible = true;
    }
    
    private void HidePlaceHolder(TextField textField)
    {
        // スタイルを解除
        textField.RemoveFromClassList(_placeholderClassName);
        
        textField.value = string.Empty;
        _isPlaceholderVisible = false;
    }

    [MenuItem("Window/PlaceholderExample")]
    public static void ShowWindow()
    {
        GetWindow<PlaceholderExample>("PlaceholderExample");
    }
}

これでPlaceholderのスタイルが適用されました。

スタイルが変更

Unity2023からはPlaceholder実装されてる

ちなみにUnity2023からはPlaceholderが普通に実装されているようです(バックポートの予定はなさそう)。

forum.unity.com

関連

light11.hatenadiary.com

参考

docs.unity3d.com

forum.unity.com