InputEvent gets detected by the wrong Control

Godot Version

4.2.2

Question

I’m trying to add drag-n-drop to a pre-existing inventory. In theory it looks like the original approach is solid: all clicks get grabbed with gui_input of specific slots and then signalled up to a “handler” to figure out the rest. However when I started detecting “mouse release”, for some reason it always gets detected by the same slot where the mouse was originally pressed. The docs state that gui_input can’t get detected when the mouse is outside of the confines, so what’s the deal here?

Here’s the minimal reproduction project, with nothing but click/hold detection:

Node structure. Control has “mouse filter pass”, the rest is default except for positions
image
Code:

extends Control

var dragging_slot : Node
var dragging_event : InputEvent
@onready var slot1 = $Slot
@onready var slot2 = $Slot2
@onready var pickup_timer : Timer = $Timer

func _ready():
	slot1.gui_input.connect(_on_slot_gui_input.bind(slot1))
	slot2.gui_input.connect(_on_slot_gui_input.bind(slot2))
	pickup_timer.one_shot = true
	pickup_timer.timeout.connect(timer_timeout)

func _on_slot_gui_input(event : InputEvent, slot_obj):
	if event is InputEventMouseButton:
		if event.pressed:
			print(event)
			print("Tap or pressdown started, called by slot:")
			print(slot_obj)
			start_timer(event, slot_obj)
		else: # !pressed = released
			if not pickup_timer.is_stopped():
				print(event)
				print("tap ended, called by slot:")
				print(slot_obj)
				pickup_timer.stop()
				slot_delayed_pointdown(event, slot_obj)
			else:
				print(event)
				print("pressdown ended, called by slot:")
				print(slot_obj)
				slot_delayed_pointdown(event, slot_obj)

# Actual interaction cut from the slot gui input to allow for flexible input
func slot_delayed_pointdown(event : InputEvent, slot_obj):
	print("Pretend emit interaction signal")

func start_timer(event, slot_obj):
	pickup_timer.start(0.2)
	dragging_slot = slot_obj
	dragging_event = event
	
func timer_timeout():
	print(dragging_event)
	print("pressdown detected, called by a saved slot:")
	print(dragging_slot)
	slot_delayed_pointdown(dragging_event, dragging_slot)

Scene and output

In the example above, I’ve pressed down my mouse above the first slot, then pretended to drag it to the second slot and released there. As you can see from the log, all those events fired correctly, but the release event was grabbed by the first slot for some reason, despite happening over the second one. Is it just the same InputEvent object that gets passed around? If so, then how can I detect a new one without disrupting the structure too much (such as moving everything to _process or making the “draggable” object itself responsible for input)?

Thanks, and sorry if this is basic; I just want to rely on gui_input for better scalability so I want to understand the mechanics really well.

Godot already has a drag and drop system in place for Controls Here’s a demo godot-demo-projects/gui/drag_and_drop at master · godotengine/godot-demo-projects · GitHub

The issue is that the second Panel eats the release event as it’s after the first Panel in the scene tree. You may need to call CanvasItem.move_to_front() to the first Panel to move it on top of the second Panel

1 Like

I see. I guess my choice is between rearranging the tree on the fly (which would include inventories, not just slots, so probably not the best idea) or embracing the standard system and overhauling the pre-existing code. I’ll probably go with the latter.

I’ve now realized the source of my confusion: this part of the docs technically describes only clicking as being dependent on has_point, but it ends up a bit misleading by omitting other mouse events. Thanks!