Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

How to use DragAndDrop with VisualElements and Manipulators

Discussion in 'UI Elements' started by arielsan, May 25, 2019.

  1. arielsan

    arielsan

    Joined:
    Dec 3, 2013
    Posts:
    25
    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:
    37
    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:
    25
    Thanks! will test it right away!
     
  4. arielsan

    arielsan

    Joined:
    Dec 3, 2013
    Posts:
    25
    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:
    25
    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:
    37
    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:
    25
    Didn't work but I might be doing something wrong, will try again later.