How to handle clicking + click dragging

Godot Version

4.4.beta2

Question

I’m in the process of creating an inventory system with drag-n-drop functionality and I’m struggling to get picking up items to behave as I want. I want to be able to pickup the item by dragging with click+hold and place on release, and also to click to pick up and click again to place. I’ve tried every combination of structure but can’t figure out the one to get the desired behavior.

This code drags items and places items perfectly. It just can’t differentiate between a click and a drag, so clicking will pick an item up and place is the moment you release LMB.

var picked_slot: int = 0 # Indicates slot index. 0 = no item/slot picked from
var picked_item = [item_name, item_stack, can_stack] 
var focused_slot: int = 0 # Indicates slot index. 0 = no item/slot is focused
var focused_item = [item_name, item_stack, can_stack] 

func _input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
		if event.is_pressed():
			if picked_slot > 0:
				return
			elif focused_slot > 0 and focused_slot <= inv_size: 
				pick_from_slot(focused_item)
				is_dragging = true

		elif event.is_released():
			if is_dragging:
				if focused_slot_has_item:
					swap_items(picked_item, focused_item)
					is_dragging = false
				else:
					place_into_slot(picked_item, focused_slot)
					is_dragging = false
			else:
				if picked_item == 0 and focused_slot_has_item:
					pick_from_slot(focused_item)

use a Control for this and override the _get_drag_data(), _drop_data() and _can_drop_data() with _set_drag_preview() to show what your are dragging or display some animation at the cursor.

1 Like

I don’t know, I really tried to make it work using those earlier but I couldn’t get it to drop the data or work with my slots properly. I just don’t understand them enough to use them effectively.

I did however figure out a way to get it working but it might be kinda jank. I basically just time the click with an incrementing float. If click_timer < click_threshold, it’s considered a click and a drag if greater than.

var click_timer = 0.0
@export var click_threshold: float = 0.2

func _process(delta: float) -> void:
	if is_dragging: click_timer += 1 * delta # Increments while pressing
	else: click_timer = 0.0 # Reset when not pressing

if event is InputEventMouseButton:
	if event.button_index == MOUSE_BUTTON_LEFT:
		if event.is_pressed():
			if focused_slot > 0 and focused_slot <= inv_size: 
				if picked_slot == 0 and !is_dragging and focused_slot_has_item: 
					pick_from_slot(focused_item) # Pickup regardless of dragging or click event.
					is_dragging = true
		elif event.is_released():
			if is_dragging:
				if click_timer < click_threshold: # Considered a click
					is_dragging = false
					return
				else: # Considered a drag
					if focused_slot_has_item: swap_items(picked_item, focused_item)
					else: place_into_slot(picked_item, focused_slot)
				is_dragging = false

In Godot this is solved by checking the distance, that the mouse has traveled between the mouse-button pressed and mouse-button released events.

If the distance is smaller than 10 pixel, then Godot assumes a click.

If the move distance while mouse-button is pressed accumulates to 10 pixel or more, then dragging is started.

For details on how this is implemented in C++ in Godot, you can dig into the function Viewport::_gui_input_event.

1 Like

That was a lot simpler and much clearer to implement, thank you! For future users, here’s a very basic example of how you could check the distance between events:

var is_dragging := false
var initial_pos := Vector2.ZERO

func _input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
		if event.is_pressed():
			initial_pos = event.position
		elif event.is_released():
			var distance = event.position.distance_to(initial_pos)
			if distance < 10: # Click
				is_dragging = false
				print("Click")
			else: # Drag
				print("Dragging")
				is_dragging = true