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.

Collections Drag Options

  • Allow Drag bool

    Allow drag.

  • Handle DragSupportHandle optional

    Custom handle to drag, it not specified will be dragged by current instance.

  • ListView TListView optional

    ListView instance.
    Not available for TreeView.
  • DragInfo TComponent optional

    Component 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.
  • Cursors Cursors

    Custom cursors to show the allowed and denied drop states.

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 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.

  • 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 drag

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