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 ();
}
}
}