Search Unity

  1. Get the latest news, tutorials and offers directly to your inbox with our newsletters. Sign up now.
    Dismiss Notice

Is Drag and Drop from custom Editor Window into Scene not possible?

Discussion in 'Immediate Mode GUI (IMGUI)' started by CDF, Apr 9, 2019.

  1. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,115
    Been on this for 2 days now without any success.
    Just want to know that it's absolutely not possible to use the DragAndDrop class for dragging something from and EditorWindow into the SceneView for custom instantiation?

    I think not, because every time the mouse leaves my window I get "DragExited" event. Which just kills any DragAndDrop events from that point on until the mouse comes back into the Window.

    I've tried looking at Unity source, but can't see any hidden magic going on.
    How have Unity managed to use DragAndDrop from the ProjectView to the SceneView without hitting a DragExit event?

    I'm completely baffled.

    Adding a SceneView.onSceneGUIDelegate callback is no help either, as that never receives any event other than "repaint" and "layout"
     
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,187
    If I recall correctly, this should technically be possible using some reflection magic. I investigated this for my own asset awhile back. I don't remember exactly what would be required. It's definitely not possible using the existing public APIs, however.
     
  3. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,115
    Yeah it doesn't work as one might expect. The issue is "DragExit" event occurs on both MouseUp and when Mouse leaves a window that has control.

    I was wrong about SceneView.onSceneGUIDelegate not receiving mouse events.
    I was setting hotControl to the control that initiated the drag, thus preventing any other drag events from occurring on other windows (i.e SceneView)

    The solution I came to was:
    • Listen to "MouseDown" event in custom control
    • Set hotControl to its controlId
    • Listen for "MouseDrag" when hotControl == controlId
    • Set hotControl to 0, Start the drag and add SceneView.onSceneGUIDelegate for drop handling
    • Listen for "DragUpdated", "DragPerform" and "DragExit" in SceneView delegate handler
    • Don't cancel and remove delegates from "DragExit" - Impossible to know if DragExit was a MouseUp or just lost Focus
    • Handle Drop functionality in DragUpdated and DragPerform
    • Only remove SceneView.onSceneGUIDelegate when "DragPerform" is accepted
    The issue I was trying to solve was how to clean up the SceneView.onSceneGUIDelegate on DragExit. Because you can never know if a DragExit was an actual cancellation of the Drag. So if you remove that delegate before a Drag was actually complete, Drag will break.

    So the solution is to check the DragAndDrop.GetGenericData and compare it with expected values, if you don't get the value you're looking for, then do nothing. If you do, then capture that event and handle
    So, yes, it's possible that the SceneView.onSceneGUIDelegate will just linger around if a Drop was never performed.

    Here's part of the Solution I came up with:
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. namespace UnityEditor {
    5.  
    6.     public static class SceneDragAndDrop {
    7.  
    8.         private static readonly int sceneDragHint = "SceneDragAndDrop".GetHashCode();
    9.         private const string DRAG_ID = "SceneDragAndDrop";
    10.  
    11.         private static readonly Object[] emptyObjects = new Object[0];
    12.         private static readonly string[] emptyPaths = new string[0];
    13.        
    14.         public static void StartDrag(ISceneDragReceiver receiver, string title) {
    15.  
    16.             //stop any drag before starting a new one
    17.  
    18.             StopDrag();
    19.  
    20.             if (receiver != null) {
    21.  
    22.                 //make sure we release any control from something that has it
    23.                 //this is done because SceneView delegate needs DragEvents!
    24.  
    25.                 GUIUtility.hotControl = 0;
    26.  
    27.                 //do the necessary steps to start a drag
    28.                 //we set the GenericData to our receiver so it can handle
    29.  
    30.                 DragAndDrop.PrepareStartDrag();              
    31.                 DragAndDrop.objectReferences = emptyObjects;
    32.                 DragAndDrop.paths = emptyPaths;
    33.                 DragAndDrop.SetGenericData(DRAG_ID, receiver);
    34.  
    35.                 receiver.StartDrag();
    36.  
    37.                 //start drag and listen for Scene drop
    38.  
    39.                 DragAndDrop.StartDrag(title);
    40.  
    41.                 SceneView.onSceneGUIDelegate += OnSceneGUI;
    42.             }
    43.         }
    44.  
    45.         public static void StopDrag() {
    46.  
    47.             //cleanup delegate
    48.  
    49.             SceneView.onSceneGUIDelegate -= OnSceneGUI;
    50.         }
    51.  
    52.         private static void OnSceneGUI(SceneView sceneView) {
    53.  
    54.             //get a controlId so we can grab events
    55.  
    56.             int controlId = GUIUtility.GetControlID(sceneDragHint, FocusType.Passive);
    57.  
    58.             Event evt = Event.current;
    59.             EventType eventType = evt.GetTypeForControl(controlId);
    60.  
    61.             ISceneDragReceiver receiver;
    62.  
    63.             switch (eventType) {
    64.  
    65.                 case EventType.DragPerform:
    66.                 case EventType.DragUpdated:
    67.  
    68.                     //check that GenericData is the expected type
    69.                     //if not, we do nothing
    70.                     //it would seem that whenever a Drag is started, GenericData is cleared, so we don't have to explicitly clear it ourself
    71.  
    72.                     receiver = DragAndDrop.GetGenericData(DRAG_ID) as ISceneDragReceiver;
    73.  
    74.                     if (receiver != null) {
    75.  
    76.                         //let receiver handle the drag functionality
    77.  
    78.                         DragAndDrop.visualMode = receiver.UpdateDrag(evt, eventType);
    79.  
    80.                         //perform drag if accepted
    81.  
    82.                         if (eventType == EventType.DragPerform && DragAndDrop.visualMode != DragAndDropVisualMode.None) {
    83.  
    84.                             receiver.PerformDrag(evt);
    85.  
    86.                             DragAndDrop.AcceptDrag();
    87.                             DragAndDrop.SetGenericData(DRAG_ID, default(ISceneDragReceiver));
    88.  
    89.                             //we can safely stop listening to scene gui now
    90.  
    91.                             StopDrag();
    92.                         }
    93.  
    94.                         evt.Use();
    95.                     }
    96.  
    97.                     break;
    98.  
    99.                 case EventType.DragExited:
    100.  
    101.                     //Drag exited, This can happen when:
    102.                     // - focus left the SceneView
    103.                     // - user cancelled manually (Escape Key)
    104.                     // - user released mouse
    105.                     //So we want to inform the receiver (if any) that is was cancelled, and it can handle appropriatley
    106.  
    107.                     receiver = DragAndDrop.GetGenericData(DRAG_ID) as ISceneDragReceiver;
    108.  
    109.                     if (receiver != null) {
    110.  
    111.                         receiver.StopDrag();
    112.                         evt.Use();
    113.                     }
    114.  
    115.                     break;
    116.             }
    117.         }
    118.     }
    119.  
    120.     public interface ISceneDragReceiver {
    121.  
    122.         void StartDrag();
    123.         void StopDrag();
    124.        
    125.         DragAndDropVisualMode UpdateDrag(Event evt, EventType eventType);
    126.  
    127.         void PerformDrag(Event evt);
    128.     }
    129. }
    130.  
    The other part is with a ISceneDragReceiver.
    That object should handle the creation/destruction of a Draggable object, whatever that might be
     
    Last edited: Apr 10, 2019
  4. zhutianlun810

    zhutianlun810

    Joined:
    Sep 17, 2017
    Posts:
    115
    New
    Hello, CDF. I am implementing my own custom Project Window and I am confusing on drag and drop operation. What is the other part "ISceneDragReceiver" like?
     
  5. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,115
    It's whatever you want it to be. You implement custom logic inside it.
    So for instantiating an object in the scene, you could do something like this:

    Code (CSharp):
    1. class MyObjectDrag : ISceneDragReceiver {
    2.  
    3.     private string assetPath;
    4.     private GameObject instance;
    5.  
    6.     public MyObjectDrag(string assetPath) {
    7.  
    8.         this.assetPath = assetPath;
    9.     }
    10.  
    11.     public void StartDrag() {
    12.     }
    13.  
    14.     public DragAndDropVisualMode UpdateDrag(Event evt, EventType eventType) {
    15.  
    16.         //don't have an asset path, so reject the drag
    17.  
    18.         if (string.IsNullOrEmpty(assetPath)) {
    19.  
    20.             return DragAndDropVisualMode.None;
    21.         }
    22.  
    23.         //see if we have an existing instance, if not, create one
    24.  
    25.         if (instance == null) {
    26.  
    27.             //load the asset and instantiate. Set the HideFlags to HideInHierarchy as user may not actually complete the drag
    28.             //only on perform do we reveal the instance in the hierarchy
    29.  
    30.             var asset = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
    31.  
    32.             instance = Instantiate(asset);
    33.             instance.hideFlags = HideFlags.HideInHierarchy;
    34.             instance.name = asset.name;
    35.         }
    36.  
    37.         //move the instance to the ground plane of the scene
    38.  
    39.         MoveInstanceToMouse(instance, evt);
    40.  
    41.         //return that we're going to "Copy"
    42.  
    43.         return DragAndDropVisualMode.Copy;
    44.     }
    45.  
    46.     public void PerformDrag(Event evt) {
    47.  
    48.         //drag was performed, so if we have an instance, reset the hideFlags and nullify our reference so it doesn't get destroyed
    49.  
    50.         if (instance != null) {
    51.  
    52.             instance.hideFlags = HideFlags.None;
    53.             instance = null;
    54.         }
    55.     }
    56.  
    57.     public void StopDrag() {
    58.  
    59.         //drag was stopped (either by a cancel or mouse release), so check if we have an instance and if so, destroy it
    60.  
    61.         if (instance != null) {
    62.  
    63.             Object.DestroyImmediate(instance, false);
    64.         }
    65.     }
    66.  
    67.     private void MoveInstanceToMouse(GameObject instance, Event evt) {
    68.  
    69.         //get the ground mouse position and place the instance there
    70.  
    71.         Ray ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
    72.         Plane plane = new Plane(Vector3.up, 0);
    73.      
    74.         if (plane.Raycast(ray, out float hit)) {
    75.  
    76.             Vector3 point = ray.GetPoint(hit);
    77.  
    78.             instance.transform.position = point;
    79.         }
    80.     }
    81. }
     
    Guest2_Unit040 likes this.
  6. Bladdock

    Bladdock

    Joined:
    Apr 22, 2018
    Posts:
    1
    I experienced this problem but with dragging into the inspector (instead of the scene view) from my custom window. I just wanted to share that the solution to set the hotControl to 0 in MouseDrag worked for me too. Thanks for posting your solution, CDF!
     
unityunity