Drag and Drop in Godot 4.x
Tags: godot, godotengine, gamedev, tutorial
Published: 2023-11-28 16:17:47

I was working on an inventory system, and as such one must enable players to play inventory Tetris in case they're bored. I have made drag and drop solutions before on different platforms, and know that there are potential edge-cases and problems waiting to suck time and life out of me. Looking for the go-to Godot solution for drag and drop, search engine results were all throwing older, manually implemented approaches mostly.

But I did finally stumble on the built-in solution which already exists in Godot, and if there's one thing I've tried to get away from, it's from DIY/NIH rabbitholes. Perfect!
Here's a quick run through so you don't have to:

# Control node interface
`Control` nodes have 3 virtual private functions you can override and will enable drag and drop functionality:
```swift
# Control that can be dragged from
func _get_drag_data(at_position:Vector2)->Variant
# Control that can be dragged to
func _can_drop_data(at_position:Vector2, data:Variant)->bool
func _drop_data(at_position:Vector2, data:Variant)->void
```
Docs: [_get_drag_data](https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-private-method-get-drag-data), [_can_drop_data](https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-private-method-can-drop-data), [_drop_data](https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-private-method-drop-data)

The drag and drop mechanism also works in that order:
1. `_get_drag_data` - returns the data that can be dragged from the current `Control`. In my case, if a slot has an item, then I'll return the item, otherwise `null`.
2. `_can_drop_data` - will continuously be called on the `Control` under the mouse position, passing the relative mouse position and `data`, and returns whether or not this `data` can be accepted. This is where I check if the current item fits in the inventory grid at the mouse position, and if the current slot is even compatible with the item type (Weapon slot for weapons, Back slot for backpacks, etc.)
3. `_drop_data` - the final call, which is the same as `_can_drop_data`, except here we are accepting the drop, and should handle removing the item from the previous container, and add it to the current `Control` (or whatever else you plan on doing with the `data`)

To handle the display preview of the drag and drop operation, we use:
`func set_drag_preview(control:Control)->void` ([docs](https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-method-set-drag-preview))

As one can expect, this will use the `Control` node you pass as the display icon of the drag and drop, add it to the scene tree, and destroy it once the drag is complete.

> There is also `force_drag(data:Variant, preview:Control)->void` ([docs](https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-method-force-drag)) which will initiate a drag and drop programmatically. If you are calling this from the `_drop_data` handler, use `call_deferred` to call it in the next frame, since the current drag operation is still finishing. I use this when dragging items to a slot that already has an item, so I initiate a drag on the previous item for a faster swapping UX.

# Using it
It's a simple interface, the basic usage is straight forward - Create some data that will be dragged from a `Control`, check this data, and potentially accept this data in a `Control` that can be dragged to.

One last problem I had was detecting if the user ends the drag, but on a `Control` that will not accept it. Here we can return to `Node` basics and use the lifetime of the preview `Control`! The full lifecycle then looks as follows:
1. User clicks and drags, Godot calls: `_get_drag_data`
2. We call: `set_drag_preview`
3. User drags over a `Control`, Godot calls: `_can_drop_data`
4. User releases the drag, and `_can_drop_data` was true, Godot calls: `_drop_data`
5. `preview_control.tree_exiting` signal is emitted

To simplify this lifecycle, I'm using a separate object for managing the drag operation:
```swift
class_name ItemDrag

signal drag_completed(data:ItemDrag)

var source: Control = null
var destination: Control = null

var item: Item
var preview: Control

func _init(_source: Control, _item: Item, _preview: Control):
    self.source = _source
    self.item = _item
    self.preview = _preview
    self.preview.tree_exiting.connect(_on_tree_exiting)

func _on_tree_exiting()->void:
    drag_completed.emit(self)
```

Using this class, an example of the other functions could be:
```swift
func remove_item(item:Item)->void:
	inventory.remove_item(item)

func _get_drag_data(at_position:Vector2)->Variant:
	var item := inventory.item_at(at_position)
	if item == null: return null
	
	var drag_data = ItemDrag.new(self, item, _create_item_preview(item))
	set_drag_preview(drag_data.preview)

	return drag_data

func _can_drop_data(at_position:Vector2, data:Variant)->bool:
	if !data is ItemDrag: return false
	var drag_data := data as ItemDrag
	# Check if the item can fit in the inventory at this position
	return !inventory.intersects_at(drag_data.item, at_position)

func _drop_data(at_position:Vector2, data:Variant)->void:
	if !data is ItemDrag: return
	var drag_data := data as ItemDrag
	
	drag_data.destination = self
	if drag_data.source: drag_data.source.remove_item(drag_data.item)
	
	inventory.add_item_at(drag_data.item, at_position)
```

Some of the functionality could be enforced by the `ItemDrag` class, especially giving things like `source` and `destination` stronger types that are guaranteed to have an interface for handling/adding/removing items. But having this set of functions defined is a good start to handling the full drag and drop cycle of operations.

# Global drag and drop
In addition to the above `Control`-based (GUI-based) approach, Godot provides some more event hooks into the lifecycle. `Viewport` and `Control` provide functions on querying the state of any drag and drop operations:
```swift
# Viewport
func gui_is_dragging()->bool
func gui_get_drag_data()->Variant
func gui_is_drag_successful()->bool
# Control
func is_drag_successful()->bool
```
The first 2 should be obvious - the first can be queried to see if a drag and drop is happening at any point, the second to get the same `data` we returned in the `_get_drag_data` callback in our `Control` (or whatever `data` is contained from `force_drag` or other ways a drag has been initiated).

The last 2 can be used with the `func _notification(what:int)->void` handler to receive global notifications when a drag and drop has started or ended, and if it ended successfully:
```swift
func _notification(what:int)->void:
	if what == Node.NOTIFICATION_DRAG_BEGIN:
		# Drag data is available (populated by our _get_drag_data() function for example)
		var data = get_viewport().gui_get_drag_data()
		# Use the drag data
	if what == Node.NOTIFICATION_DRAG_END:
		# Drag data is no longer available and has been disposed already
		print("Drag ended. Success: ", get_viewport().gui_is_drag_successful())
```
These may come in handy, but are further from the drag and drop context. Some use-cases could be used by relevant drop receivers to highlight themselves, based on the drag data.

With these tools, any drag and drop scenario can be handled, hopefully this saves you any attempts at writing a custom drag and drop implementation. I wish you the best of luck and success in using this information!

P.