// UI Draggable Item|UI|80030 namespace VRTK { using UnityEngine; using UnityEngine.EventSystems; /// /// Event Payload /// /// The target the item is dragged onto. public struct UIDraggableItemEventArgs { public GameObject target; } /// /// Event Payload /// /// this object /// public delegate void UIDraggableItemEventHandler(object sender, UIDraggableItemEventArgs e); /// /// Denotes a Unity UI Element as being draggable on the UI Canvas. /// /// /// > If a UI Draggable item is set to `Restrict To Drop Zone = true` then the UI Draggable item must be a child of an element that has the VRTK_UIDropZone script applied to it to ensure it starts in a valid drop zone. /// /// **Script Usage:** /// * Place the `VRTK_UIDraggableItem` script on the Unity UI element that is to be dragged. /// /// /// `VRTK/Examples/034_Controls_InteractingWithUnityUI` demonstrates a collection of UI elements that are draggable /// [RequireComponent(typeof(CanvasGroup))] [AddComponentMenu("VRTK/Scripts/UI/VRTK_UIDraggableItem")] public class VRTK_UIDraggableItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { [Tooltip("If checked then the UI element can only be dropped in valid a VRTK_UIDropZone object and must start as a child of a VRTK_UIDropZone object. If unchecked then the UI element can be dropped anywhere on the canvas.")] public bool restrictToDropZone = false; [Tooltip("If checked then the UI element can only be dropped on the original parent canvas. If unchecked the UI element can be dropped on any valid VRTK_UICanvas.")] public bool restrictToOriginalCanvas = false; [Tooltip("The offset to bring the UI element forward when it is being dragged.")] public float forwardOffset = 0.1f; /// /// The current valid drop zone the dragged element is hovering over. /// [HideInInspector] public GameObject validDropZone; /// /// Emitted when the draggable item is successfully dropped. /// public event UIDraggableItemEventHandler DraggableItemDropped; /// /// Emitted when the draggable item is reset. /// public event UIDraggableItemEventHandler DraggableItemReset; protected RectTransform dragTransform; protected Vector3 startPosition; protected Quaternion startRotation; protected GameObject startDropZone; protected Transform startParent; protected Canvas startCanvas; protected CanvasGroup canvasGroup; public virtual void OnDraggableItemDropped(UIDraggableItemEventArgs e) { if (DraggableItemDropped != null) { DraggableItemDropped(this, e); } } public virtual void OnDraggableItemReset(UIDraggableItemEventArgs e) { if (DraggableItemReset != null) { DraggableItemReset(this, e); } } public virtual void OnBeginDrag(PointerEventData eventData) { startPosition = transform.position; startRotation = transform.rotation; startParent = transform.parent; startCanvas = GetComponentInParent(); canvasGroup.blocksRaycasts = false; if (restrictToDropZone) { startDropZone = GetComponentInParent().gameObject; validDropZone = startDropZone; } SetDragPosition(eventData); VRTK_UIPointer pointer = GetPointer(eventData); if (pointer != null) { pointer.OnUIPointerElementDragStart(pointer.SetUIPointerEvent(pointer.pointerEventData.pointerPressRaycast, gameObject)); } } public virtual void OnDrag(PointerEventData eventData) { SetDragPosition(eventData); } public virtual void OnEndDrag(PointerEventData eventData) { canvasGroup.blocksRaycasts = true; dragTransform = null; transform.position += (transform.forward * forwardOffset); bool validDragEnd = true; if (restrictToDropZone) { if (validDropZone != null && validDropZone != startDropZone) { transform.SetParent(validDropZone.transform); } else { ResetElement(); validDragEnd = false; } } Canvas destinationCanvas = (eventData.pointerEnter != null ? eventData.pointerEnter.GetComponentInParent() : null); if (restrictToOriginalCanvas) { if (destinationCanvas != null && destinationCanvas != startCanvas) { ResetElement(); validDragEnd = false; } } if (destinationCanvas == null) { //We've been dropped off of a canvas ResetElement(); validDragEnd = false; } if (validDragEnd) { VRTK_UIPointer pointer = GetPointer(eventData); if (pointer != null) { pointer.OnUIPointerElementDragEnd(pointer.SetUIPointerEvent(pointer.pointerEventData.pointerPressRaycast, gameObject)); } OnDraggableItemDropped(SetEventPayload(validDropZone)); } validDropZone = null; startParent = null; startCanvas = null; } protected virtual void OnEnable() { canvasGroup = GetComponent(); if (restrictToDropZone && GetComponentInParent() == null) { enabled = false; VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "VRTK_UIDraggableItem", "VRTK_UIDropZone", "the parent", " if `freeDrop = false`")); } } protected virtual VRTK_UIPointer GetPointer(PointerEventData eventData) { GameObject controller = VRTK_DeviceFinder.GetControllerByIndex((uint)eventData.pointerId, false); return (controller != null ? controller.GetComponent() : null); } protected virtual void SetDragPosition(PointerEventData eventData) { if (eventData.pointerEnter != null && eventData.pointerEnter.transform as RectTransform != null) { dragTransform = eventData.pointerEnter.transform as RectTransform; } Vector3 pointerPosition; if (dragTransform != null && RectTransformUtility.ScreenPointToWorldPointInRectangle(dragTransform, eventData.position, eventData.pressEventCamera, out pointerPosition)) { transform.position = pointerPosition - (transform.forward * forwardOffset); transform.rotation = dragTransform.rotation; } } protected virtual void ResetElement() { transform.position = startPosition; transform.rotation = startRotation; transform.SetParent(startParent); OnDraggableItemReset(SetEventPayload(startParent.gameObject)); } protected virtual UIDraggableItemEventArgs SetEventPayload(GameObject target) { UIDraggableItemEventArgs e; e.target = target; return e; } } }