GUI drag-drop

by AngryAnt on September 18th, 2009

A lot of people have been asking for tips on how to implement drag-drop functionality in Unity GUI scripting, so I decided to put together a reusable script for the purpose.

Basically the solution requires that your data class derives from GUIDraggableObject and at some point in its OnGUI method call Drag( Rect ) – just like GUI.Window handles dragging.

Right. Codez. First off the GUIDraggableObject.cs file:
 

using UnityEngine;
using System.Collections;

public class GUIDraggableObject
{
    protected Vector2 m_Position;
    private Vector2 m_DragStart;
    private bool m_Dragging;

    public GUIDraggableObject (Vector2 position)
    {
        m_Position = position;
    }

    public bool Dragging
    {
        get
        {
            return m_Dragging;
        }
    }

    public Vector2 Position
    {
        get
        {
            return m_Position;
        }

        set
        {
            m_Position = value;
        }
    }

    public void Drag (Rect draggingRect)
    {
        if (Event.current.type == EventType.MouseUp)
        {
            m_Dragging = false;
        }
        else if (Event.current.type == EventType.MouseDown && draggingRect.Contains (Event.current.mousePosition))
        {
            m_Dragging = true;
            m_DragStart = Event.current.mousePosition - m_Position;
            Event.current.Use();
        }

        if (m_Dragging)
        {
            m_Position = Event.current.mousePosition - m_DragStart;
        }
    }
}

 
An example data class inheriting from GUIDraggableObject – DataObject.cs:
 

using UnityEngine;
using System.Collections;

public class DataObject : GUIDraggableObject
// This class just has the capability of being dragged in GUI - it could be any type of generic data class
{
    private string m_Name;
    private int m_Value;

    public DataObject (string name, int value, Vector2 position) : base (position)
    {
        m_Name = name;
        m_Value = value;
    }

    public void OnGUI ()
    {
        Rect drawRect = new Rect (m_Position.x, m_Position.y, 100.0f, 100.0f), dragRect;

        GUILayout.BeginArea (drawRect, GUI.skin.GetStyle ("Box"));
            GUILayout.Label (m_Name, GUI.skin.GetStyle ("Box"), GUILayout.ExpandWidth (true));

            dragRect = GUILayoutUtility.GetLastRect ();
            dragRect = new Rect (dragRect.x + m_Position.x, dragRect.y + m_Position.y, dragRect.width, dragRect.height);

            if (Dragging)
            {
                GUILayout.Label ("Wooo...");
            }
            else if (GUILayout.Button ("Yes!"))
            {
                Debug.Log ("Yes. It is " + m_Value + "!");
            }
        GUILayout.EndArea ();

        Drag (dragRect);
    }
}

 
And finally, this script demonstrates how you could have your data manager class use Unity GUI for data visualisation with drag-drop enabled – MyMonoBehaviour.cs:
 

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MyMonoBehaviour : MonoBehaviour
{
    private List< DataObject > m_Data = new List< DataObject > ();
    private Rect dropTargetRect = new Rect (10.0f, 10.0f, 30.0f, 30.0f);

    void Awake ()
    {
        m_Data.Add (new DataObject ("One", 1, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Two", 2, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Three", 3, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Four", 4, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Five", 5, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
    }

    public void OnGUI ()
    {
        DataObject toFront, dropDead;
        Color color;

        GUI.Box(dropTargetRect, "Die");

        toFront = dropDead = null;
        foreach (DataObject data in m_Data)
        {
            color = GUI.color;

            if (data.Dragging)
            {
                GUI.color = dropTargetRect.Contains (Event.current.mousePosition) ? Color.red : color;
            }

            data.OnGUI ();

            GUI.color = color;

            if (data.Dragging)
            {
                if (m_Data.IndexOf (data) != m_Data.Count - 1)
                {
                    toFront = data;
                }
            }
        }

        if (toFront != null)
        // Move an object to front if needed
        {
            m_Data.Remove (toFront);
            m_Data.Add (toFront);
        }
    }
}

 
Ah yea and an example of how you could do the same in an editor window – MyEditorWindow.cs:
 

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;

public class MyEditorWindow : EditorWindow
{
    private List< DataObject > m_Data = new List< DataObject > ();
    private bool doRepaint = false;
    private Rect dropTargetRect = new Rect (10.0f, 10.0f, 30.0f, 30.0f);

    public MyEditorWindow ()
    {
        m_Data.Add (new DataObject ("One", 1, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Two", 2, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Three", 3, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Four", 4, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Five", 5, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
    }

    [MenuItem ("Window/MyEditorWindow")]
    public static void Launch ()
    {
        GetWindow (typeof (MyEditorWindow)).Show ();
    }

    public void Update ()
    {
        if (doRepaint)
        {
            Repaint ();
        }
    }

    public void OnGUI ()
    {
        DataObject toFront, dropDead;
        bool previousState, flipRepaint;
        Color color;

        GUI.Box(dropTargetRect, "Die");

        toFront = dropDead = null;
        doRepaint = false;
        flipRepaint = false;
        foreach (DataObject data in m_Data)
        {
            previousState = data.Dragging;

            color = GUI.color;

            if (previousState)
            {
                GUI.color = dropTargetRect.Contains (Event.current.mousePosition) ? Color.red : color;
            }

            data.OnGUI ();

            GUI.color = color;

            if (data.Dragging)
            {
                doRepaint = true;

                if (m_Data.IndexOf (data) != m_Data.Count - 1)
                {
                    toFront = data;
                }
            }
            else if (previousState)
            {
                flipRepaint = true;

                if (dropTargetRect.Contains (Event.current.mousePosition))
                {
                    dropDead = data;
                }
            }
        }

        if (toFront != null)
        // Move an object to front if needed
        {
            m_Data.Remove (toFront);
            m_Data.Add (toFront);
        }

        if (dropDead != null)
        // Destroy an object if needed
        {
            m_Data.Remove (dropDead);
        }

        if (flipRepaint)
        // If some object just stopped being dragged, we should repaing for the state change
        {
            Repaint ();
        }
    }
}
10 Comments
  1. Works great!

    But i was wondering.. can i also use a textured ( for example a .png ) interface?

    Greets, Jorisshh

  2. Sure. Just modify the OnGUI of your GUIDraggableObject based class to render that.

  3. Ilan permalink

    I think it’d be awesome if you’ll provide a simple gui-drag-drop project. i`d be very very appreciated. ty

  4. Thanks a lot for the info provided. I started reading c# just to understand how you do it. I believe that in order to compile the last example, the script must be named MyEditorWindow.cs insted of EditorWindow.cs Again, thanks.

  5. Gibbs permalink

    Excellent tutorial… I had a question though. How would I make the dropped item be recognized by another object. For example….

    If I dragged an object into a box… Then do this animation or behavior… I understand the process but not how to do this with the GUI…

    Thanks in advance!!!

  6. @Ilan
    That would increase the time it takes to get an article ready, but I’ll look into it.

    @Ippokratis
    Thanks for the pointer. Will correct the post :)

    @Gibbs
    If you notice in MyMonoBehaviour.cs – on the line beginning with “GUI.color = dropTargetRect.Contains (Event…”, that is one example of handling drop-targets. Try running the script and dragging over the rect.

  7. Ilan permalink

    Still looking forward to see a project based on that script. ty in advance ;)

  8. maxdisher permalink

    would also love to see an example project, time permitting of course. love your site and your work, keep it up.

  9. George R permalink

    Thanks for this!

    Also, you guys are asking a bit too much – here you have the fundamental code snippets to provide the functionality – why do you want an entire project made and bundled up for you?

  10. Just starting with C# and was wondering 2 things.

    _1_
    How would you vary the window layout. (they all use the same layout)?

    _2_ How could I use a button to turn individual windows on and off?

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS

Spam protection by WP Captcha-Free