Search Unity

[Solved] How to start DragAndDrop action so that SceneView and Hierarchy accept it like with prefabs

Discussion in 'UI Toolkit' started by Xarbrough, Feb 5, 2020.

  1. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    1,188
    Edit: Solution below.

    I'm building a level design tool which shows a list of prefabs in a custom editor window and lets users drag and drop these prefabs into the scene or hierarchy window, basically the same way it works with the project window.

    Here is some brief example code how this can be implemented:

    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4. using UnityEngine.UIElements;
    5.  
    6. public class MyWindow : EditorWindow
    7. {
    8.     [MenuItem("Window/MyWindow")]
    9.     private static void ShowWindow()
    10.     {
    11.         GetWindow<MyWindow>("MyWindow");
    12.     }
    13.  
    14.     private void OnEnable()
    15.     {
    16.         var box = new VisualElement();
    17.         box.style.backgroundColor = Color.red;
    18.         box.style.flexGrow = 1f;
    19.  
    20.         box.RegisterCallback<MouseDownEvent>(evt =>
    21.         {
    22.             DragAndDrop.PrepareStartDrag();
    23.             DragAndDrop.StartDrag("Dragging");
    24.             DragAndDrop.SetGenericData("Dragging", (object)1);
    25.         });
    26.  
    27.         box.RegisterCallback<DragUpdatedEvent>(evt =>
    28.         {
    29.             DragAndDrop.visualMode = DragAndDropVisualMode.Link;
    30.         });
    31.  
    32.         rootVisualElement.Add(box);
    33.  
    34.         SceneView.duringSceneGui += OnSceneGUI;
    35.         EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyGUI;
    36.     }
    37.  
    38.     private void OnDisable()
    39.     {
    40.         SceneView.duringSceneGui -= OnSceneGUI;
    41.         EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyGUI;
    42.     }
    43.  
    44.     private void OnSceneGUI(SceneView obj)
    45.     {
    46.         HandleDragAndDropEvents();
    47.     }
    48.  
    49.     private void OnHierarchyGUI(int instanceID, Rect selectionRect)
    50.     {
    51.         HandleDragAndDropEvents();
    52.     }
    53.  
    54.     private void HandleDragAndDropEvents()
    55.     {
    56.         if (Event.current.type == EventType.DragUpdated)
    57.         {
    58.             DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
    59.         }
    60.         if (Event.current.type == EventType.DragPerform)
    61.         {
    62.             Debug.Log("Drag and dropped: " + DragAndDrop.GetGenericData("Dragging"));
    63.             DragAndDrop.AcceptDrag();
    64.         }
    65.     }
    66. }
    67.  
    68.  
    There are three parts to this approach:
    1) Start a drag and drop operation via
    DragAndDrop.StartDrag
    in the custom editor window. Here we pass the references to the prefab or start doing some visual indication of the drag within the window.
    2) Query for and accept the drag operation within the SceneView. This is done by checking in the SceneView GUI loop for IMGUI events of type DragUpdated and DragPerform. We can now handle the dragged object reference and replicate the same behaviour as Unity has builtin when dragging prefabs from the project to the SceneView.
    3) I would hope for the Hierarchy window to be manageable the same way, but here we run into problems, since a lot of the default behaviour (like showing an insert-line between GameObjects) is difficult to reproduce.

    This approach generally works, but it's a lot of work to replicate all the features of dropping prefabs into the SceneView or Hierarchy window. For example:
    • Showing a preview in the scene
    • Placing the new prefab at the mouse position
    • Showing the blue insert-line between two GameObjects in the hierarchy
    • Inserting prefabs into the hierarchy at the right position
    • Highlight a row in the hierarchy and parent the new prefab under it
    I'd even say it's impossible for users to show the blue insert-line, since it's internal to the hierarchy window's drawing code.

    So instead of implementing all of this myself, how could I use Unity's builtin functionality to do it for me?

    I would imagine, that the project window starts a drag operation and the SceneView and Hierarchy windows respond do this operation with all of their nice features. So, in theory it should be possible for me to start a drag operation from my custom editor window the same way was from the project and let Unity handle the drop just as it would from the project. Is this possible?

    I had a look into the reference source and found the UnityEditor.DragAndDropService class which seems to be used internally for these operations, but I'm having trouble understanding how exactly it works. I'd be ok with using some reflection magic if I'd get it working, though.

    Solution:

    Apparently I was getting totally lost while trying different things and digging into UIElements and internal Unity classes at the same time, but actually all of this simply works like this:

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.UIElements;
    4.  
    5. public class MyWindow : EditorWindow
    6. {
    7.     [MenuItem("Window/MyWindow")]
    8.     private static void ShowWindow()
    9.     {
    10.         GetWindow<MyWindow>("MyWindow");
    11.     }
    12.  
    13.     private GameObject prefab;
    14.  
    15.     private void OnEnable()
    16.     {
    17.         prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Cube.prefab");
    18.  
    19.         var box = new VisualElement();
    20.         box.style.backgroundColor = Color.red;
    21.         box.style.flexGrow = 1f;
    22.  
    23.         box.RegisterCallback<MouseDownEvent>(evt =>
    24.         {
    25.             DragAndDrop.PrepareStartDrag();
    26.             DragAndDrop.StartDrag("Dragging");
    27.             DragAndDrop.objectReferences = new Object[] { prefab };
    28.         });
    29.  
    30.         box.RegisterCallback<DragUpdatedEvent>(evt =>
    31.         {
    32.             DragAndDrop.visualMode = DragAndDropVisualMode.Move;
    33.         });
    34.  
    35.         rootVisualElement.Add(box);
    36.     }
    37. }
    38.  
    The only difference to my original code is that I now set the object references array with an instance to my prefab. While testing I initially used objectReferences, but didn't assign a valid prefab (new GameObject() doesn't count...) and then I switched to SetGenericData, which doesn't work.
     
    Last edited: Feb 5, 2020
  2. dbdenny

    dbdenny

    Joined:
    Mar 13, 2020
    Posts:
    12
    Love you, save my life!!!
     
  3. bergolli

    bergolli

    Joined:
    Apr 12, 2014
    Posts:
    3
    Great! just what i was looking for!
     
  4. FuriousAvocado

    FuriousAvocado

    Joined:
    Jun 19, 2015
    Posts:
    3
    How in the world did you make this work? Trying the above code does nothing for me. It just shows the "Dragging" handle but when dropping nothing happens.

    Am I missing something? Does it require a specific min version of Unity to work ? I am on 2019.2.17f1.

    Would really love to know! :)
     
  5. Xan_9

    Xan_9

    Joined:
    Oct 7, 2020
    Posts:
    31
    You can do something like this:
    Code (CSharp):
    1. box.RegisterCallback<DragPerformEvent>(_ =>
    2. {
    3.     DragAndDrop.AcceptDrag();
    4.     //Get objects
    5.     var objects = DragAndDrop.objectReferences;
    6.     //Get paths
    7.     var paths = DragAndDrop.paths;
    8. });