Luna UI provides a flexible drag and drop system through the DragAndDropManipulator class. This abstract manipulator handles pointer events, visual feedback, and drop slot detection.

Extend DragAndDropManipulator and implement the abstract methods:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using CupkekGames.Luna;
public class ItemDragManipulator : DragAndDropManipulator
{
private Sprite _itemSprite;
private VisualElement _dragElement;
public ItemDragManipulator(
LunaUIManager uiManager,
VisualElement dragArea,
int itemKey,
List<VisualElement> dropSlots,
Sprite itemSprite
) : base(uiManager, dragArea, itemKey, dropSlots)
{
_itemSprite = itemSprite;
}
public override VisualElement CreateDragElement()
{
_dragElement = new VisualElement();
_dragElement.AddToClassList("drag-item");
_dragElement.style.width = 64;
_dragElement.style.height = 64;
_dragElement.style.backgroundImage = new StyleBackground(_itemSprite);
return _dragElement;
}
public override void DisposeDragElement()
{
_dragElement = null;
}
}// Get references
LunaUIManager uiManager = LunaUIManager.Instance;
VisualElement dragArea = _uiDocument.rootVisualElement;
List<VisualElement> dropSlots = dragArea.Query<VisualElement>(className: "inventory-slot").ToList();
// Create and add manipulator to draggable item
var manipulator = new ItemDragManipulator(
uiManager,
dragArea,
itemKey: 0,
dropSlots,
itemSprite
);
// Subscribe to events
manipulator.OnDrop += HandleDrop;
manipulator.OnCancel += HandleCancel;
// Add to the draggable element
draggableItem.AddManipulator(manipulator);private void HandleDrop(int key, int dropSlotIndex, VisualElement dropSlot)
{
Debug.Log($"Item {key} dropped on slot {dropSlotIndex}");
// Move item to new slot
}
private void HandleCancel()
{
Debug.Log("Drag cancelled - no valid drop target");
}public DragAndDropManipulator(
LunaUIManager uiElementManager,
VisualElement dragArea,
int key,
List<VisualElement> dropSlots,
Func<List<VisualElement>> getDropSlots = null,
bool worldSpace = false
)| Parameter | Type | Description |
|---|---|---|
| uiElementManager | LunaUIManager | Reference to LunaUIManager for audio handling (can be null) |
| dragArea | VisualElement | The area where the drag clone will be added |
| key | int | Unique identifier for the dragged item |
| dropSlots | List<VisualElement> | Static list of valid drop targets |
| getDropSlots | Func<List<VisualElement>> | Optional function to get dynamic drop slots |
| worldSpace | bool | Set to true for World Space UI |
Called when drag starts. Must return the visual element to display during drag.
public abstract VisualElement CreateDragElement();Called when drag ends. Clean up any resources used by the drag element.
public abstract void DisposeDragElement();| Event | Parameters | Description |
|---|---|---|
| OnStart | Vector3 position | Fired when drag begins |
| OnMove | Vector3 position | Fired while dragging |
| OnDrop | int key, int slotIndex, VisualElement slot | Fired when dropped on valid slot |
| OnCancel | - | Fired when dropped outside valid slots |
Use SetDropSlotClasses to apply USS classes to drop slots during drag:
// Set classes to apply during drag
manipulator.SetDropSlotClasses(new List<string> { "drop-highlight", "glow" });/* USS styles */
.drop-highlight {
border-color: var(--color-primary);
border-width: 2px;
}
.glow {
background-color: rgba(255, 255, 255, 0.1);
}For drop slots that change at runtime, use the getDropSlots parameter:
var manipulator = new ItemDragManipulator(
uiManager,
dragArea,
key: 0,
dropSlots: null, // No static slots
getDropSlots: () => GetCurrentInventorySlots() // Dynamic function
);For World Space UI Toolkit panels, set worldSpace to true:
var manipulator = new ItemDragManipulator(
uiManager,
dragArea,
key: 0,
dropSlots,
itemSprite,
worldSpace: true // Enable world space coordinate conversion
);The manipulator automatically handles coordinate conversion between screen space and panel local coordinates.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using CupkekGames.Luna;
public class InventoryDragDrop : MonoBehaviour
{
[SerializeField] private UIDocument _uiDocument;
private List<ItemDragManipulator> _manipulators = new();
private void Start()
{
SetupDragAndDrop();
}
private void SetupDragAndDrop()
{
var root = _uiDocument.rootVisualElement;
var dropSlots = root.Query<VisualElement>(className: "inventory-slot").ToList();
foreach (var slot in dropSlots)
{
var itemElement = slot.Q<VisualElement>("Item");
if (itemElement == null) continue;
int slotIndex = dropSlots.IndexOf(slot);
var manipulator = new ItemDragManipulator(
LunaUIManager.Instance,
root,
slotIndex,
dropSlots,
GetItemSprite(slotIndex)
);
manipulator.SetDropSlotClasses(new List<string> { "drop-target-active" });
manipulator.OnDrop += OnItemDropped;
manipulator.OnCancel += OnDragCancelled;
itemElement.AddManipulator(manipulator);
_manipulators.Add(manipulator);
}
}
private void OnItemDropped(int sourceSlot, int targetSlot, VisualElement targetElement)
{
Debug.Log($"Moved item from slot {sourceSlot} to slot {targetSlot}");
SwapItems(sourceSlot, targetSlot);
RefreshUI();
}
private void OnDragCancelled()
{
Debug.Log("Drag cancelled");
}
private void OnDestroy()
{
foreach (var manipulator in _manipulators)
{
manipulator.OnDrop -= OnItemDropped;
manipulator.OnCancel -= OnDragCancelled;
}
}
}LunaUIManager's purpose in DragAndDropManipulator is to prevent hover audio from playing when hovering over other buttons and interactable elements while dragging. When a drag operation starts, if LunaUIManager is not null, hover audio will be muted until the drag operation ends.
Note: For more advanced examples, refer to the Inventory implementation in the Game samples.
Settings
Theme
Light
Contrast
Material
Dark
Dim
Material Dark
System
Sidebar(Light & Contrast only)
Font Family
DM Sans
Wix
Inclusive Sans
AR One Sans
Direction