Drag and Drop
Drag-and-Drop Support for the Collections
Different drag-and-drop components used with different widgets. Default widgets already have drag-and-drop components. For the generated widgets drag-and-drop components create automatically. Default Drag components usually attached to DefaultItem. Default Drop components usually attached to widgets (ListView, TreeView) and TreeView.DefaultItem.
Drag will be cancelled with OnCancel
event from EventSystem
(for example by pressing Esc).
You can remove drag-and-drop components from the widgets gameobjects to disable drag-and-drop functionality.
How Drag&Drop works
There are two components: one to process drag and another to process drop.
The Drag component is inherited from DragSupport<TItem>
and is attached to a game object with data to drag (like ListView.DefaultItem, item in inventory).
It’s used:
- to receive data from the game object
- to show draggable data
- to process results (like removing the dropped item from the original ListView).
The Drop component implements the IDropSupport<TItem>
interface (it can implement multiple interfaces with different types) and is attached to the game object which can receive data (like ListView, inventory or inventory cell, terrain).
It is used:
- to check if a drop is possible when the pointer over the game object (like container has enough space or data meets some condition, show a DropIndicator if the drop is possible)
- to process drop when the pointer is released (like adding an item to the container)
- to process canceled drop when the pointer leaves the game object (like hide DropIndicator)
The Drag component looks for the target under the pointer with the Drop component that can accept a TItem
and call bool CanReceiveDrop(TItem data, PointerEventData eventData)
to check if the target can receive the dragged item.
On pointer release called Drop(TItem data, PointerEventData eventData)
for the drop component if found and then Dropped(bool success)
for the drag component.
Common Drag Options
Allow Drag
bool
Allow drag.
Cursors
Cursors
Custom cursors to show the allowed and denied drop states.
Event System Select
bool
Makes the draggable object selected by EventSystem.
Handle
DragSupportHandle
optionalCustom handle to drag, it not specified will be dragged by current instance.
Drag Button
PointerEventData.InputButton
Pressed pointer button to drag object.
Drag Delay
float
How many seconds must pass from the click to the start of dragging.
Redirect Drag To ScrollRect
bool
Redirects the drag events to the parent ScrollRect if the drag starts before DragDelay time is passed.
Unscaled Time
bool
If enabled Drag Delay time will counted using
Time.unscaledTime
; otherwise will be usedTime.time
Collections Drag Options
ListView
TListView
optionalListView instance.Not available for TreeView.DragInfo
TComponent
optionalComponent to display the dragged data.
DragInfo Offset
Vector3
Offset from the cursor position for the DragInfo.
Delete After Drop
bool
Delete item from collection after drop.Not available for TreeView.
Collections Drop Options
Drop Position
NearestType
Drop position.
Auto
insert dropped item to the nearest position.Before
insert dropped item before item under pointer.After
insert dropped item after item under pointer.
Drop Indicator
ListViewDropIndicator
Indicator to display position where dropped item will be inserted.
Delete Node After Drop
bool
Delete dropped node from TreeView.Not available for TreeView.Receive Items
bool
Receive dropped items.
Receive Nodes
bool
Receive dropped nodes.
TreeView Drop Options
Drop Position
NearestType
Drop position.
Auto
insert dropped item to the nearest positionBefore
insert dropped item before item under pointerAfter
insert dropped item after item under pointer
Drop Indicator
ListViewDropIndicator
Indicator to display position where dropped item will be inserted.
Receive Items
bool
Receive dropped items.
Receive Nodes
bool
Receive dropped nodes.
TreeView Node Drop Options
Drop Indicator
ListViewDropIndicator
Indicator to display position where dropped item will be inserted.
Delete Node After Drop
bool
Delete dropped node from TreeView.
Receive Items
bool
Receive dropped items.
Reorder Area
float
Distance in percent of height from border to add dropped node before/after instead of drop as sub-node. Allowed value range is 0f..0.5f
Custom Drag Support
You can add own drag support with component inherited from DragSupport<TItem>
implementation.
Methods
InitDrag(
PointerEventData
eventData) required: set Data value to dragDropped(
bool
success) optional: what to do after the drop happened or canceled
Here is basic example of the drag support for the InputField:
namespace UIWidgets.Examples
{
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// Drag support for the InputField.
/// </summary>
[RequireComponent(typeof(InputField))]
public class InputFieldDragSupportBase : DragSupport<string>
{
/// <summary>
/// Set Data, which will be passed to the Drop component.
/// </summary>
/// <param name="eventData">Current event data.</param>
protected override void InitDrag(PointerEventData eventData)
{
Data = GetComponent<InputField>().text;
}
}
}
This example show how to display draggable data:
namespace UIWidgets
{
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
/// <summary>
/// Drag support for the InputField.
/// </summary>
[RequireComponent(typeof(InputField))]
public class InputFieldDragSupport : DragSupport<string>
{
/// <summary>
/// Set Data, which will be passed to Drop component.
/// </summary>
/// <param name="eventData">Current event data.</param>
protected override void InitDrag(PointerEventData eventData)
{
Data = GetComponent<InputField>().text;
ShowDragInfo();
}
/// <summary>
/// Called after the drop completed.
/// </summary>
/// <param name="success">true if Drop component received data; otherwise, false.</param>
public override void Dropped(bool success)
{
HideDragInfo();
base.Dropped(success);
}
/// <summary>
/// Component to display draggable info.
/// </summary>
[SerializeField]
public GameObject DragInfo;
/// <summary>
/// DragInfo offset.
/// </summary>
[SerializeField]
public Vector3 DragInfoOffset = new Vector3(-5, 5, 0);
/// <summary>
/// Start this instance.
/// </summary>
protected virtual void Start()
{
if (DragInfo != null)
{
DragInfo.SetActive(false);
}
}
/// <summary>
/// Shows the drag info.
/// </summary>
protected virtual void ShowDragInfo()
{
if (DragInfo == null)
{
return;
}
DragInfo.transform.SetParent(DragPoint, false);
DragInfo.transform.localPosition = DragInfoOffset;
DragInfo.SetActive(true);
DragInfo.GetComponentInChildren<Text>().text = Data;
}
/// <summary>
/// Hides the drag info.
/// </summary>
protected virtual void HideDragInfo()
{
if (DragInfo == null)
{
return;
}
DragInfo.SetActive(false);
}
}
}
Custom Drop Support
You can add own the drop support with IDropSupport<TItem>>
implementation.
Methods
CanReceiveDrop(TItem data, PointerEventData eventData)
: determine if the drop can be accepted or not, can used to display the drop preview.Drop(TItem data, PointerEventData eventData)
: process the dropped data.DropCanceled(TItem data, PointerEventData eventData)
: process the cancelled drop, can used to hide the drop preview or the drop indicator.
Here is example code shows how to add TreeNode<TreeViewItem>
and string
drop support to the InputField, after drop InputField value would be set to the dropped node name or the dropped string.
CanReceiveDrop
function allows to accept only nodes with names ends with 1.
namespace UIWidgets.Examples
{
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
/// <summary>
/// TreeNode drop support for the InputField.
/// </summary>
[RequireComponent(typeof(InputField))]
public class InputFieldDropSupport : MonoBehaviour, IDropSupport<TreeNode<TreeViewItem>>, IDropSupport<string>
{
/// <summary>
/// InputField.text value before drop.
/// Can be used to swap content with drag source.
/// </summary>
public string OriginalData;
#region IDropSupport<string>
/// <summary>
/// Handle dropped data.
/// </summary>
/// <param name="data">Data.</param>
/// <param name="eventData">Event data.</param>
public void Drop(string data, PointerEventData eventData)
{
var input = GetComponent<InputField>();
OriginalData = input.text;
input.text = data;
}
/// <summary>
/// Determines whether this instance can receive drop with the specified data and eventData.
/// </summary>
/// <returns>true if this instance can receive drop with the specified data and eventData; otherwise, false.</returns>
/// <param name="data">Data.</param>
/// <param name="eventData">Event data.</param>
public bool CanReceiveDrop(string data, PointerEventData eventData)
{
return true;
}
/// <summary>
/// Handle canceled drop.
/// </summary>
/// <param name="data">Data.</param>
/// <param name="eventData">Event data.</param>
public void DropCanceled(string data, PointerEventData eventData)
{
}
#endregion
#region IDropSupport<TreeNode<TreeViewItem>>
/// <summary>
/// Handle dropped data.
/// </summary>
/// <param name="data">Data.</param>
/// <param name="eventData">Event data.</param>
public void Drop(TreeNode<TreeViewItem> data, PointerEventData eventData)
{
var input = GetComponent<InputField>();
OriginalData = input.text;
input.text = data.Item.Name;
}
/// <summary>
/// Determines whether this instance can receive drop with the specified data and eventData.
/// </summary>
/// <returns>true if this instance can receive drop with the specified data and eventData; otherwise, false.</returns>
/// <param name="data">Data.</param>
/// <param name="eventData">Event data.</param>
public bool CanReceiveDrop(TreeNode<TreeViewItem> data, PointerEventData eventData)
{
return data.Item.Name.EndsWith("1");
}
/// <summary>
/// Handle canceled drop.
/// </summary>
/// <param name="data">Data.</param>
/// <param name="eventData">Event data.</param>
public void DropCanceled(TreeNode<TreeViewItem> data, PointerEventData eventData)
{
}
#endregion
}
}
Swapping content between Drag and Drop components
Original content of the drop component saved to IDropSupport<T>.OriginalData
field.
And content should be swapped in the DragSupport<T>.OnEndDrag()
function
namespace UIWidgets.Examples
{
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// Drag support with content swap for the InputField.
/// </summary>
[RequireComponent(typeof(InputField))]
public class InputFieldDragSwapSupport : InputFieldDragSupport
{
/// <summary>
/// Called by a BaseInputModule when a drag is ended.
/// </summary>
/// <param name="eventData">Current event data.</param>
public override void OnEndDrag(PointerEventData eventData)
{
if (!IsDragged)
{
return;
}
var target = FindTarget(eventData);
if (target != null)
{
target.Drop(Data, eventData);
Dropped(true);
// replace dragged text with drop target text
GetComponent<InputField>().text = (target as InputFieldDropSupport).OriginalData;
}
else
{
Dropped(false);
}
ResetCursor();
}
}
}
Adding limitations to the Drop component
In this example, ListViewIcons will receive drag-and-drop data only if DataSource.Count less than MaxQuantity.
namespace UIWidgets.Examples
{
using UnityEngine;
using UnityEngine.EventSystems;
public class ListViewIconsDropSupportLimitedQuantity : ListViewIconsDropSupport
{
[SerializeField]
public int MaxQuantity = 10;
public override bool CanReceiveDrop(ListViewIconsItemDescription data, PointerEventData eventData)
{
// disable drop if quantity limit reached
if ((MaxQuantity >= 0) && (ListView.DataSource.Count >= MaxQuantity))
{
return false;
}
return base.CanReceiveDrop(data, eventData);
}
}
}