How to register drag and click events on the same VisualElement?

I’m trying to build a VisualElement control that can be clicked like a button but also dragged onto other controls as a reference. How would I approach handling this input situation?

I tried deriving a class from MouseManipulator and adding it to the VisualElement. In it, I can listen to MouseDownEvent and DragUpdatedEvent to implement the DragAndDrop logic, but when I try to also detect a click, it doesn’t work:

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

public class ItemDragManipulator : MouseManipulator
{
    protected override void RegisterCallbacksOnTarget()
    {
        target.RegisterCallback<MouseDownEvent>(OnMouseDown);
        target.RegisterCallback<DragUpdatedEvent>(OnDragUpdated);
        target.RegisterCallback<ClickEvent>(OnClicked);
    }

    private void OnClicked(ClickEvent evt)
    {
        Debug.Log("Clicked"); // Never happens.
    }

    protected override void UnregisterCallbacksFromTarget()
    {
        target.UnregisterCallback<MouseDownEvent>(OnMouseDown);
        target.UnregisterCallback<DragUpdatedEvent>(OnDragUpdated);
        target.UnregisterCallback<ClickEvent>(OnClicked);
    }

    private void OnMouseDown(MouseDownEvent evt)
    {
        DragAndDrop.PrepareStartDrag();
        DragAndDrop.StartDrag("FavorWindowDrag");
        DragAndDrop.objectReferences = new Object[] { target.userData as Object };
        //evt.StopImmediatePropagation();
    }

    private void OnDragUpdated(DragUpdatedEvent evt)
    {
        DragAndDrop.visualMode = DragAndDropVisualMode.Link;
        //evt.StopImmediatePropagation();
    }
}

I tried removing StopImmediatePropagation, but it doesn’t fix the issue and also causes the pressed and hover states to behave strangely.

The logic I’m trying to implement:

  • Detect mouse down, this could be a click or drag
  • If the mouse moves, abort the click and begin a drag
  • Update the drag if it was started
  • On mouse up, if a click was started, submit the click, if a drag took over, end the drag

Hey!
The DragXXXEvent are editor-only and are mostly catered around using the DragAndDrop class, so dragging assets.

In your case, if you want to handle drag and drop of VisualElement, you can use the PointerDownEvent, PointerMoveEvent and PointerUpEvent. The pointer variants are better than the mouse ones because they will be able to handle more situations, such as multi-touch.

I’ve included a small sample that should behave the way you want to, you can use that as a starting point. I’ve done it in 2021.2 and used the transform and transition properties (they might not work on previous versions).

Editor Window

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

public class DragAndDropExample : EditorWindow
{
    class ClickedOrDragAndDropManipulator : MouseManipulator
    {
        readonly EventCallback<ClickEvent> m_OnClickedHandler;
        readonly EventCallback<PointerDownEvent> m_PointerDownHandler;
        readonly EventCallback<PointerMoveEvent> m_OnPointerMove;
        readonly EventCallback<PointerUpEvent> m_PointerUpHandler;

        Vector3 m_InitialPosition;
        bool m_DidDrag = false;
        bool m_IsDragging = false;

        public ClickedOrDragAndDropManipulator()
        {
            m_OnClickedHandler = OnClicked;
            m_PointerDownHandler = OnPointerDown;
            m_OnPointerMove = OnPointerMove;
            m_PointerUpHandler = OnPointerUp;
        }

        void OnPointerDown(PointerDownEvent evt)
        {
            m_InitialPosition = evt.position;
            m_IsDragging = true;
            m_DidDrag = false;

            // Capturing the pointer in case of overlapping to ensure we get the pointer up event even if the pointer
            // moved outside of the target.
            target.CapturePointer(0);
        }

        void OnPointerUp(PointerUpEvent evt)
        {
            m_IsDragging = false;
            target.ReleasePointer(0);
        }

        void OnPointerMove(PointerMoveEvent evt)
        {
            if (!m_IsDragging)
                return;

            // If the pointer moved at least 4-ish pixels in any direction at any point, we consider it a drag and drop operation.
            if ((m_InitialPosition - evt.position).sqrMagnitude > 16.0f)
                m_DidDrag = true;

            var delta = evt.deltaPosition;
            var translate = target.style.translate.value;
            translate.x = new Length(translate.x.value + delta.x, LengthUnit.Pixel);
            translate.y = new Length(translate.y.value + delta.y, LengthUnit.Pixel);

            target.style.translate = translate;
        }

        void OnClicked(ClickEvent obj)
        {
            if (m_DidDrag)
                return;

            target.ToggleInClassList("drag-and-drop--selected");
        }

        protected override void RegisterCallbacksOnTarget()
        {
            target.RegisterCallback(m_PointerDownHandler);
            target.RegisterCallback(m_OnPointerMove);
            target.RegisterCallback(m_PointerUpHandler);
            target.RegisterCallback(m_OnClickedHandler);
        }

        protected override void UnregisterCallbacksFromTarget()
        {
            target.UnregisterCallback(m_PointerDownHandler);
            target.UnregisterCallback(m_OnPointerMove);
            target.UnregisterCallback(m_PointerUpHandler);
            target.UnregisterCallback(m_OnClickedHandler);
        }
    }

    [SerializeField] VisualTreeAsset m_VisualTreeAsset;

    [MenuItem("Window/Samples/Drag & Drop || Clicked")]
    public static void ShowExample()
    {
        var wnd = GetWindow<DragAndDropExample>();
        wnd.titleContent = new GUIContent("Drag & Drop || Clicked");
    }

    public void CreateGUI()
    {
        m_VisualTreeAsset.CloneTree(rootVisualElement);
        var container = rootVisualElement.Q(className: "drag-and-drop");
        container.AddManipulator(new ClickedOrDragAndDropManipulator());
    }
}

UXML

<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:engine="UnityEngine.UIElements"
    xmlns:editor="UnityEditor.UIElements"
    xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
>
    <Style src="project://database/Assets/Editor/DragAndDropExample.uss?fileID=7433441132597879392&guid=15af1e466a293fc44b22b5904119a0fd&type=3#DragAndDropExample" />
    <engine:VisualElement class="drag-and-drop" />
</engine:UXML>

USS

.drag-and-drop
{
    position: absolute;
    background-color: teal;
    width:100px;
    height:100px;

    transform-origin: center;
    transition: scale 50ms, border-color 50ms, rotate 50ms;
}

.drag-and-drop--selected
{
    border-color: white;
    border-width: 2px;
    scale:1.05 1.05 1.0;
    rotate: 45deg;
}

Hope that helps!

7610341–945373–DragAndDrop.zip (3.24 KB)

2 Likes