Search Unity

How to use DragAndDrop with VisualElements and Manipulators

Discussion in 'UIElements' started by arielsan, May 25, 2019.

  1. arielsan

    arielsan

    Joined:
    Dec 3, 2013
    Posts:
    27
    Hi, I am trying to use DragAndDrop in a MouseManipulator when dragging the mouse but it is not working, it throws this error:

    Code (CSharp):
    1. Drags can only be started from MouseDown or MouseDrag events
    2. UnityEditor.DragAndDrop:StartDrag(String)
    3. HistoryItemDragManipulator:OnMouseMove(MouseMoveEvent) (at Assets/Gemserk.SelectionHistory/Editor/NewWindow/SelectionHistoryNewWindow.cs:212)
    4. UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
    5.  
    I am using it inside the MouseDown/MouseMove event of the Manipulator but I understand it is processing a queued event from the event dispatcher, so my code is not being performed when an Event of MouseDown or MouseDrag is performed but when the dispatcher sent an EventBase of type MouseMoveEvent or MouseDownEvent.

    Here is my code (based on another example of how to implement drag detection with visual elements):

    Code (CSharp):
    1.     public class HistoryItemDragManipulator : MouseManipulator
    2.     {
    3.         #region Init
    4.         protected bool m_Active;
    5.  
    6.         private Object _historyItem;
    7.        
    8.         public HistoryItemDragManipulator(Object objectAdded)
    9.         {
    10.             _historyItem = objectAdded;
    11.             activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse });
    12.             m_Active = false;
    13.         }
    14.         #endregion
    15.  
    16.         #region Registrations
    17.         protected override void RegisterCallbacksOnTarget()
    18.         {
    19.             target.RegisterCallback<MouseDownEvent>(OnMouseDown);
    20.             target.RegisterCallback<MouseMoveEvent>(OnMouseMove);
    21.             target.RegisterCallback<MouseUpEvent>(OnMouseUp);
    22.         }
    23.  
    24.         protected override void UnregisterCallbacksFromTarget()
    25.         {
    26.             target.UnregisterCallback<MouseDownEvent>(OnMouseDown);
    27.             target.UnregisterCallback<MouseMoveEvent>(OnMouseMove);
    28.             target.UnregisterCallback<MouseUpEvent>(OnMouseUp);
    29.         }
    30.         #endregion
    31.  
    32.         #region OnMouseDown
    33.         protected void OnMouseDown(MouseDownEvent e)
    34.         {
    35.             Debug.Log("on mouse down");
    36.            
    37.             if (m_Active)
    38.             {
    39.                 Debug.Log("on mouse down and active");
    40.                 e.StopImmediatePropagation();
    41.                 return;
    42.             }
    43.            
    44.             if (CanStartManipulation(e))
    45.             {
    46.                 Debug.Log("on mouse down and can start manipulation");
    47.                 m_Active = true;
    48.                 target.CaptureMouse();
    49.                 e.StopPropagation();
    50.             }
    51.         }
    52.         #endregion
    53.  
    54.         #region OnMouseMove
    55.         protected void OnMouseMove(MouseMoveEvent e)
    56.         {
    57.             if (!m_Active || !target.HasMouseCapture())
    58.                 return;
    59.  
    60.             Debug.Log("on mouse drag");
    61.            
    62.             var historyItem = _historyItem;
    63.            
    64.             DragAndDrop.PrepareStartDrag ();
    65.             DragAndDrop.StartDrag (historyItem.name);
    66.             DragAndDrop.objectReferences = new Object[] { historyItem };
    67.            
    68.             if (EditorUtility.IsPersistent(historyItem)) {
    69.  
    70.                 DragAndDrop.paths = new string[] {
    71.                     AssetDatabase.GetAssetPath(historyItem)
    72.                 };
    73.             }
    74.  
    75.             e.StopPropagation();
    76.         }
    77.         #endregion
    78.  
    79.         #region OnMouseUp
    80.         protected void OnMouseUp(MouseUpEvent e)
    81.         {
    82.             if (!m_Active || !target.HasMouseCapture() || !CanStopManipulation(e))
    83.                 return;
    84.  
    85.             m_Active = false;
    86.             target.ReleaseMouse();
    87.             e.StopPropagation();
    88.         }
    89.         #endregion
    90.     }
    Is it possible to use the DragAndDrop with VisualElements? can you recommend a way? couldn't find any examples of this.

    Thanks
     
  2. patrickf

    patrickf

    Unity Technologies

    Joined:
    Oct 24, 2016
    Posts:
    43
    Hi! Yes, it is possible to use the DragAndDrop with VisualElements. You are getting this error because for some reason you call DragAndDrop.StartDrag() when the mouse button is not down (maybe because target.HasMouseCapture() is false in the OnMouseUp(); mouse capture can be stolen). Here is an example of drag an drop, for both the drop area and the draggable element. It was written for 2019.3 but should work with minor modifications in the currently released version.

    This code should be published soon to https://github.com/Unity-Technologies/UIElementsExamples

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4. using UnityEngine;
    5. using UnityEngine.UIElements;
    6.  
    7. namespace UIElementsExamples
    8. {
    9.     public class E18_DragAndDrop : EditorWindow
    10.     {
    11.         [MenuItem("UIElementsExamples/18_DragAndDrop")]
    12.         public static void ShowExample()
    13.         {
    14.             E18_DragAndDrop window = GetWindow<E18_DragAndDrop>();
    15.             window.minSize = new Vector2(450, 514);
    16.             window.titleContent = new GUIContent("Example 18");
    17.         }
    18.  
    19.         private VisualElement m_DropArea;
    20.         private Label m_Ghost;
    21.  
    22.         public void OnEnable()
    23.         {
    24.             var root = rootVisualElement;
    25.             root.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/dnd.uss"));
    26.  
    27.             m_DropArea = new VisualElement();
    28.             m_DropArea.AddToClassList("droparea");
    29.             m_DropArea.Add(new Label {text = "Drag and drop anything here"});
    30.             root.Add(m_DropArea);
    31.  
    32.             m_Ghost = new Label();
    33.             m_Ghost.AddToClassList("ghost");
    34.             m_DropArea.Add(m_Ghost);
    35.  
    36.             m_DropArea.RegisterCallback<DragEnterEvent>(OnDragEnterEvent);
    37.             m_DropArea.RegisterCallback<DragLeaveEvent>(OnDragLeaveEvent);
    38.             m_DropArea.RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
    39.             m_DropArea.RegisterCallback<DragPerformEvent>(OnDragPerformEvent);
    40.             m_DropArea.RegisterCallback<DragExitedEvent>(OnDragExitedEvent);
    41.         }
    42.  
    43.         void OnDragEnterEvent(DragEnterEvent e)
    44.         {
    45.             m_DropArea.AddToClassList("dragover");
    46.             m_Ghost.AddToClassList("visible");
    47.             m_Ghost.style.left = e.localMousePosition.x - m_Ghost.resolvedStyle.width / 2;
    48.             m_Ghost.style.top = e.localMousePosition.y - m_Ghost.resolvedStyle.height / 2;
    49.             m_Ghost.text = "";
    50.  
    51.             object draggedLabel = DragAndDrop.GetGenericData(DraggableLabel.s_DragDataType);
    52.             if (draggedLabel != null)
    53.             {
    54.                 var label = (DraggableLabel)draggedLabel;
    55.                 m_Ghost.text = label.text;
    56.  
    57.                 // if mouse exited then re-entered drop area, we need to call PrepareDraggingBox again.
    58.                 label.PrepareDraggingBox();
    59.  
    60.                 label.StartDraggingBox();
    61.             }
    62.             else
    63.             {
    64.                 List<string> names = new List<string>();
    65.                 foreach (var obj in DragAndDrop.objectReferences)
    66.                 {
    67.                     names.Add(obj.name);
    68.                 }
    69.  
    70.                 m_Ghost.text = String.Join(", ", names);
    71.             }
    72.         }
    73.  
    74.         void OnDragLeaveEvent(DragLeaveEvent e)
    75.         {
    76.             m_DropArea.RemoveFromClassList("dragover");
    77.             m_Ghost.RemoveFromClassList("visible");
    78.  
    79.             object draggedLabel = DragAndDrop.GetGenericData(DraggableLabel.s_DragDataType);
    80.             if (draggedLabel != null)
    81.             {
    82.                 var label = (DraggableLabel)draggedLabel;
    83.                 label.StopDraggingBox();
    84.             }
    85.         }
    86.  
    87.         void OnDragUpdatedEvent(DragUpdatedEvent e)
    88.         {
    89.             m_Ghost.style.left = e.localMousePosition.x - m_Ghost.resolvedStyle.width / 2;
    90.             m_Ghost.style.top = e.localMousePosition.y - m_Ghost.resolvedStyle.height / 2;
    91.  
    92.             object draggedLabel = DragAndDrop.GetGenericData(DraggableLabel.s_DragDataType);
    93.             if (draggedLabel != null)
    94.             {
    95.                 DragAndDrop.visualMode = DragAndDropVisualMode.Move;
    96.             }
    97.             else
    98.             {
    99.                 DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
    100.             }
    101.         }
    102.  
    103.         void OnDragPerformEvent(DragPerformEvent e)
    104.         {
    105.             DragAndDrop.AcceptDrag();
    106.  
    107.             object draggedLabel = DragAndDrop.GetGenericData(DraggableLabel.s_DragDataType);
    108.             if (draggedLabel != null)
    109.             {
    110.                 var label = (DraggableLabel)draggedLabel;
    111.                 label.style.top = m_Ghost.resolvedStyle.top;
    112.                 label.style.left = m_Ghost.resolvedStyle.left;
    113.                 label.StopDraggingBox();
    114.             }
    115.             else
    116.             {
    117.                 var newBox = new DraggableLabel();
    118.                 newBox.AddToClassList("box");
    119.                 newBox.style.top = m_Ghost.resolvedStyle.top;
    120.                 newBox.style.left = m_Ghost.resolvedStyle.left;
    121.                 newBox.text = m_Ghost.text;
    122.                 // Insert before ghost
    123.                 m_DropArea.Insert(m_DropArea.childCount - 1, newBox);
    124.             }
    125.         }
    126.  
    127.         void OnDragExitedEvent(DragExitedEvent e)
    128.         {
    129.             // Never called at the moment due to a bug. Listen to DragLeaveEvent instead.
    130.             Debug.Log("Should not be called unless bug was fixed.");
    131.         }
    132.     }
    133.  
    134.     public class DraggableLabel : Label
    135.     {
    136.         public static string s_DragDataType = "DraggableLabel";
    137.  
    138.         enum DragState
    139.         {
    140.             AtRest,
    141.             Ready,
    142.             Dragging
    143.         }
    144.  
    145.         private DragState m_DragState;
    146.  
    147.         public DraggableLabel()
    148.         {
    149.             m_DragState = DragState.AtRest;
    150.  
    151.             RegisterCallback<MouseDownEvent>(OnMouseDownEvent);
    152.             RegisterCallback<MouseMoveEvent>(OnMouseMoveEvent);
    153.             RegisterCallback<MouseUpEvent>(OnMouseUpEvent);
    154.         }
    155.  
    156.         void OnMouseDownEvent(MouseDownEvent e)
    157.         {
    158.             if (e.target == this && e.button == 0)
    159.             {
    160.                 PrepareDraggingBox();
    161.             }
    162.         }
    163.  
    164.         public void PrepareDraggingBox()
    165.         {
    166.             m_DragState = DragState.Ready;
    167.         }
    168.  
    169.         void OnMouseMoveEvent(MouseMoveEvent e)
    170.         {
    171.             if (m_DragState == DragState.Ready)
    172.             {
    173.                 DragAndDrop.PrepareStartDrag();
    174.                 DragAndDrop.SetGenericData(s_DragDataType, this);
    175.                 DragAndDrop.StartDrag(text);
    176.                 StartDraggingBox();
    177.             }
    178.         }
    179.  
    180.         void OnMouseUpEvent(MouseUpEvent e)
    181.         {
    182.             if (m_DragState == DragState.Ready && e.button == 0)
    183.             {
    184.                 StopDraggingBox();
    185.             }
    186.         }
    187.  
    188.         public void StartDraggingBox()
    189.         {
    190.             AddToClassList("dragged");
    191.             m_DragState = DragState.Dragging;
    192.         }
    193.  
    194.         public void StopDraggingBox()
    195.         {
    196.             RemoveFromClassList("dragged");
    197.             m_DragState = DragState.AtRest;
    198.         }
    199.     }
    200. }
    201.  

    .droparea {
    position: absolute;
    top: 20px;
    left: 20px;
    bottom: 20px;
    right: 20px;
    background-color: azure;
    border-width: 5px;
    border-color: azure;
    }

    Label {
    color: darkslategray;
    -unity-text-align: upper-center;
    }

    .droparea.dragover {
    border-width: 5px;
    border-color: red;
    }

    .ghost {
    -unity-text-align: upper-left;
    display: none;
    position: absolute;
    width: 64px;
    height: 64px;
    background-color: rgba(100, 149, 237, 0.6);
    color: #393636;
    white-space: normal;
    }

    .ghost.visible {
    display: flex;
    }

    .box {
    position: absolute;
    width: 64px;
    height: 64px;
    background-color: darkblue;
    color: antiquewhite;
    white-space: normal;
    border-width: 1px;
    border-color: orange;
    }

    .box.dragged {
    background-color: rgba(0, 0, 139, .6);
    }
     
  3. arielsan

    arielsan

    Joined:
    Dec 3, 2013
    Posts:
    27
    Thanks! will test it right away!
     
  4. arielsan

    arielsan

    Joined:
    Dec 3, 2013
    Posts:
    27
    It worked!!! :)

    I had some issues making the uss working since my current version of UIElements doesn't support some color definitions but I changed them to rgba() and the problem was fixed.

    Also, I added an example of using the dragging objects back when dragging from the container of elements, so you can throw prefabs for example to the drag box and then throw them back to the scene and they are instantiated (that is the use case I wanted). Pasting it here in case you want to add it to the example:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4. using UnityEngine;
    5. using UnityEngine.UIElements;
    6. using Object = UnityEngine.Object;
    7.  
    8. namespace UIElementsExamples
    9. {
    10.     public class E18_DragAndDrop : EditorWindow
    11.     {
    12.         [MenuItem("UIElementsExamples/18_DragAndDrop")]
    13.         public static void ShowExample()
    14.         {
    15.             E18_DragAndDrop window = GetWindow<E18_DragAndDrop>();
    16.             window.minSize = new Vector2(450, 514);
    17.             window.titleContent = new GUIContent("Example 18");
    18.         }
    19.         private VisualElement m_DropArea;
    20.         private Label m_Ghost;
    21.  
    22.         private Object[] m_objectReferences;
    23.         public void OnEnable()
    24.         {
    25.             var root = rootVisualElement;
    26.             root.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/DragAndDrop/E18_DragAndDrop.uss"));
    27.             m_DropArea = new VisualElement();
    28.             m_DropArea.AddToClassList("droparea");
    29.             m_DropArea.Add(new Label {text = "Drag and drop anything here"});
    30.             root.Add(m_DropArea);
    31.             m_Ghost = new Label();
    32.             m_Ghost.AddToClassList("ghost");
    33.             m_DropArea.Add(m_Ghost);
    34.             m_DropArea.RegisterCallback<DragEnterEvent>(OnDragEnterEvent);
    35.             m_DropArea.RegisterCallback<DragLeaveEvent>(OnDragLeaveEvent);
    36.             m_DropArea.RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
    37.             m_DropArea.RegisterCallback<DragPerformEvent>(OnDragPerformEvent);
    38.             m_DropArea.RegisterCallback<DragExitedEvent>(OnDragExitedEvent);
    39.         }
    40.         void OnDragEnterEvent(DragEnterEvent e)
    41.         {
    42.             m_DropArea.AddToClassList("dragover");
    43.             m_Ghost.AddToClassList("visible");
    44.             m_Ghost.style.left = e.localMousePosition.x - m_Ghost.resolvedStyle.width / 2;
    45.             m_Ghost.style.top = e.localMousePosition.y - m_Ghost.resolvedStyle.height / 2;
    46.             m_Ghost.text = "";
    47.          
    48.             m_objectReferences = null;
    49.  
    50.             object draggedLabel = DragAndDrop.GetGenericData(DraggableLabel.s_DragDataType);
    51.             if (draggedLabel != null)
    52.             {
    53.                 var label = (DraggableLabel)draggedLabel;
    54.                 m_Ghost.text = label.text;
    55.                 // if mouse exited then re-entered drop area, we need to call PrepareDraggingBox again.
    56.                 label.PrepareDraggingBox();
    57.                 label.StartDraggingBox();
    58.             }
    59.             else
    60.             {
    61.                 List<string> names = new List<string>();
    62.                 foreach (var obj in DragAndDrop.objectReferences)
    63.                 {
    64.                     names.Add(obj.name);
    65.                 }
    66.                 m_Ghost.text = String.Join(", ", names);
    67.                 m_objectReferences = DragAndDrop.objectReferences;
    68.             }
    69.         }
    70.         void OnDragLeaveEvent(DragLeaveEvent e)
    71.         {
    72.             m_DropArea.RemoveFromClassList("dragover");
    73.             m_Ghost.RemoveFromClassList("visible");
    74.             object draggedLabel = DragAndDrop.GetGenericData(DraggableLabel.s_DragDataType);
    75.             if (draggedLabel != null)
    76.             {
    77.                 var label = (DraggableLabel)draggedLabel;
    78.                 label.StopDraggingBox();
    79.             }
    80.         }
    81.         void OnDragUpdatedEvent(DragUpdatedEvent e)
    82.         {
    83.             m_Ghost.style.left = e.localMousePosition.x - m_Ghost.resolvedStyle.width / 2;
    84.             m_Ghost.style.top = e.localMousePosition.y - m_Ghost.resolvedStyle.height / 2;
    85.             object draggedLabel = DragAndDrop.GetGenericData(DraggableLabel.s_DragDataType);
    86.             if (draggedLabel != null)
    87.             {
    88.                 DragAndDrop.visualMode = DragAndDropVisualMode.Move;
    89.             }
    90.             else
    91.             {
    92.                 DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
    93.             }
    94.         }
    95.         void OnDragPerformEvent(DragPerformEvent e)
    96.         {
    97.             DragAndDrop.AcceptDrag();
    98.             object draggedLabel = DragAndDrop.GetGenericData(DraggableLabel.s_DragDataType);
    99.             if (draggedLabel != null)
    100.             {
    101.                 var label = (DraggableLabel)draggedLabel;
    102.                 label.style.top = m_Ghost.resolvedStyle.top;
    103.                 label.style.left = m_Ghost.resolvedStyle.left;
    104.                 label.StopDraggingBox();
    105.             }
    106.             else
    107.             {
    108.                 var newBox = new DraggableLabel();
    109.                 newBox.AddToClassList("box");
    110.                 newBox.style.top = m_Ghost.resolvedStyle.top;
    111.                 newBox.style.left = m_Ghost.resolvedStyle.left;
    112.                 newBox.text = m_Ghost.text;
    113.                 newBox.m_objectReferences = m_objectReferences;
    114.                 // Insert before ghost
    115.                 m_DropArea.Insert(m_DropArea.childCount - 1, newBox);
    116.             }
    117.         }
    118.         void OnDragExitedEvent(DragExitedEvent e)
    119.         {
    120.             // Never called at the moment due to a bug. Listen to DragLeaveEvent instead.
    121.             Debug.Log("Should not be called unless bug was fixed.");
    122.         }
    123.     }
    124.     public class DraggableLabel : Label
    125.     {
    126.         public static string s_DragDataType = "DraggableLabel";
    127.         enum DragState
    128.         {
    129.             AtRest,
    130.             Ready,
    131.             Dragging
    132.         }
    133.         private DragState m_DragState;
    134.      
    135.         public Object[] m_objectReferences;
    136.         public DraggableLabel()
    137.         {
    138.             m_DragState = DragState.AtRest;
    139.             RegisterCallback<MouseDownEvent>(OnMouseDownEvent);
    140.             RegisterCallback<MouseMoveEvent>(OnMouseMoveEvent);
    141.             RegisterCallback<MouseUpEvent>(OnMouseUpEvent);
    142.         }
    143.         void OnMouseDownEvent(MouseDownEvent e)
    144.         {
    145.             if (e.target == this && e.button == 0)
    146.             {
    147.                 PrepareDraggingBox();
    148.             }
    149.         }
    150.         public void PrepareDraggingBox()
    151.         {
    152.             m_DragState = DragState.Ready;
    153.         }
    154.         void OnMouseMoveEvent(MouseMoveEvent e)
    155.         {
    156.             if (m_DragState == DragState.Ready)
    157.             {
    158.                 DragAndDrop.PrepareStartDrag();
    159.                 DragAndDrop.SetGenericData(s_DragDataType, this);
    160.                 DragAndDrop.StartDrag(text);
    161.                 DragAndDrop.objectReferences = m_objectReferences;
    162.                 StartDraggingBox();
    163.             }
    164.         }
    165.         void OnMouseUpEvent(MouseUpEvent e)
    166.         {
    167.             if (m_DragState == DragState.Ready && e.button == 0)
    168.             {
    169.                 StopDraggingBox();
    170.             }
    171.         }
    172.         public void StartDraggingBox()
    173.         {
    174.             AddToClassList("dragged");
    175.             m_DragState = DragState.Dragging;
    176.         }
    177.         public void StopDraggingBox()
    178.         {
    179.             RemoveFromClassList("dragged");
    180.             m_DragState = DragState.AtRest;
    181.         }
    182.     }
    183. }
    184.  
     
  5. arielsan

    arielsan

    Joined:
    Dec 3, 2013
    Posts:
    27
    Well DragAndDrop still doesn't work on Mac in Unity 2019 or I don't know how to use it. It works fine on Windows.
     
  6. patrickf

    patrickf

    Unity Technologies

    Joined:
    Oct 24, 2016
    Posts:
    43
    For some reasons, it looks like OnDragEnterEvent is not called on MacOS. To make it work, move the entire OnDragEnterEvent() code in a new StartDragging(IMouseEvent e) function. Call StartDragging() in OnDragEnterEvent() and at the start of OnDragUpdatedEvent(), if you are not already dragging.
     
  7. arielsan

    arielsan

    Joined:
    Dec 3, 2013
    Posts:
    27
    Didn't work but I might be doing something wrong, will try again later.
     
  8. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    515
    Just a note to others who want to use DragAndDrop together with the DragEvents in UIElements.

    I spend hours getting it working, and when it finally worked, I realized that it (seemingly) is designed for other problems than what I was trying to solve. I wanted to drag nodes and connections between nodes around inside a single window.

    If you want to drag a kind of asset or scene object around between windows, perhaps interacting with build-in editor windows, use it. ELSE if you want to create special in-window drag interaction then don't use it. Just stick to the MouseEvents. Here is why I think so:
    • For DragPerformEvent to be send you must set DragAndDrop.visualMode to something else than None or Rejected. Setting visualMode will change the look of your cursor, and you might not want that.
    • DragAndDrop.objectReferences contain UnityEngine.Objects and therefore cannot contain VisualElements. This is inconvenient if you have VisualElements that are not associated with a specific UnityEngine.Object.
    • Finally, in my solution using DragAndDrop just made my code unnecessarily over-complicated.

    "DragAndDrop" sounds so general, but it is not designed to be used for every drag interaction.