【Unity】UI Toolkitにおけるマウスのキャプチャの仕組みを理解する

UnityのUI Toolkitにおけるマウスのキャプチャの仕組みについてまとめます。

Unity 2021.3.16f1

マウスのキャプチャとは

いま、UI要素の上でマウスをクリックし、ドラッグして離すことを考えます。
このようなマウスイベントは以下のイベントのコールバックを登録することで受け取ることができます。

  • マウスの押下: MouseDownEvent
  • マウス移動: MouseMoveEvent
  • マウスを押下を解除: MouseUpEvent

これらの挙動を試すために以下のようにUIを配置したEditorWindowを作成します。

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

public sealed class CaptureExample : EditorWindow
{
    private bool _isCapturing;

    public void CreateGUI()
    {
        // レイアウト
        // rootの中央にchildを配置
        var root = rootVisualElement;
        root.style.paddingTop = 100;
        root.style.paddingBottom = 100;
        root.style.paddingLeft = 100;
        root.style.paddingRight = 100;
        root.style.backgroundColor = new StyleColor(new Color(0.8f, 0.8f, 0.8f));

        var child = new VisualElement
        {
            style =
            {
                height = new StyleLength(Length.Percent(100)),
                borderBottomLeftRadius = 12,
                borderBottomRightRadius = 12,
                borderTopLeftRadius = 12,
                borderTopRightRadius = 12,
                backgroundColor = new StyleColor(new Color(0.3f, 0.3f, 0.3f))
            }
        };

        root.Add(child);

        // childに対してイベントを登録
        child.RegisterCallback<MouseUpEvent>(_ => Debug.Log("MouseUpEvent"));
        child.RegisterCallback<MouseDownEvent>(_ => Debug.Log("MouseDownEvent"));
        child.RegisterCallback<MouseMoveEvent>(_ => Debug.Log("MouseMoveEvent"));
    }

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

これを実行すると以下のようなウィンドウが表示されます。
中央の角丸四角形(child)の中でマウスをドラッグすると、上記で登録したそれぞれのイベントが発行されることを確認できます。

ウィンドウ

次にこのchildの中でマウスを押下、押下したままchildの外側までマウスを持っていきます。
すると、角丸四角形の外側ではMouseMoveEventMouseUpEventが呼ばれないことがわかります。
このようにマウスイベントは、コールバックを登録したUI要素の上にマウスが乗っている時にだけ呼ばれる仕様になっています。

ここでchildのUI要素にマウスをキャプチャさせると、キャプチャが解除されるまではマウスイベントがこのUI要素に対して発行されるようになります。
つまり、マウスをキャプチャしている間はchildの外側でもMouseMoveEventMouseUpEventが呼ばれます。

マウスキャプチャは明示的にリリースするか、他の要素をクリックするとキャプチャが解放されます。

マウスキャプチャの挙動を確認する

さてそれでは実際にマウスキャプチャの挙動を確認してみます。
上記のEditorWindowにキャプチャの処理を追記したものが以下のスクリプトです。

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

public sealed class CaptureExample : EditorWindow
{
    private bool _isCapturing;

    public void CreateGUI()
    {
        // レイアウト
        // rootの中央にchildを配置
        var root = rootVisualElement;
        root.style.paddingTop = 100;
        root.style.paddingBottom = 100;
        root.style.paddingLeft = 100;
        root.style.paddingRight = 100;
        root.style.backgroundColor = new StyleColor(new Color(0.8f, 0.8f, 0.8f));

        var child = new VisualElement
        {
            style =
            {
                height = new StyleLength(Length.Percent(100)),
                borderBottomLeftRadius = 12,
                borderBottomRightRadius = 12,
                borderTopLeftRadius = 12,
                borderTopRightRadius = 12
            }
        };

        root.Add(child);

        // childに対してイベントを登録
        // childがマウスをキャプチャしていると、これらのイベントがChild以外の場所でも反応する
        child.RegisterCallback<MouseUpEvent>(_ => Debug.Log("MouseUpEvent"));
        child.RegisterCallback<MouseDownEvent>(_ => Debug.Log("MouseDownEvent"));
        child.RegisterCallback<MouseMoveEvent>(_ => Debug.Log("MouseMoveEvent"));

        // マウスキャプチャ状態に応じて色を変える
        void SetChildColor()
        {
            child.style.backgroundColor = _isCapturing
                ? new StyleColor(new Color(0.3f, 0.8f, 0.3f))
                : new StyleColor(new Color(0.3f, 0.3f, 0.3f));
        }

        child.RegisterCallback<MouseDownEvent>(evt =>
        {
            // クリックされたらマウスをキャプチャ/リリースする
            if (_isCapturing)
            {
                child.ReleaseMouse();
                _isCapturing = false;
            }
            else
            {
                child.CaptureMouse();
                // これでも可能
                //MouseCaptureController.ReleaseMouse();
                _isCapturing = true;
            }

            SetChildColor();
        });
        SetChildColor();
    }

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

このウィンドウを表示し、childをクリックするとマウスがキャプチャされ、childが緑色に変わります。
マウスがキャプチャされた状態でchildの外側にマウスを動かすと、マウスイベントが呼ばれることを確認できます。

マウスのキャプチャはもう一度childをクリックするか、あるいは他のUI要素をクリックすると解除されます。

実行結果

キャプチャイベント

当該要素がマウスをキャプチャ・キャプチャ解除した時のイベントをコールバックとして登録することもできます。
これを行うには、以下のようにMouseCaptureEventMouseCaptureOutEventを使用します。

child.RegisterCallback<MouseCaptureEvent>(_ => Debug.Log("MouseCaptureEvent"));
child.RegisterCallback<MouseCaptureOutEvent>(_ => Debug.Log("MouseCaptureOutEvent"));

関連

light11.hatenadiary.com

参考

docs.unity3d.com

docs.unity3d.com

docs.unity3d.com