Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mouse released event is called at the mouse press location instead of the release location #84466

Open
Taneblade opened this issue Nov 5, 2023 · 7 comments

Comments

@Taneblade
Copy link

Taneblade commented Nov 5, 2023

Godot version

4.1.2

System information

Windows 11 Godot 4.1.2

Issue description

If you hold down a mouse button, move your mouse, then release it on a different gui element, the input event mouse released is called on the original gui element instead of the location of the new one. I believe a similar problem got fixed with mouse entered and exited recently,(#20881) but there is still a problem with mouse button releasing events.

Steps to reproduce

Make two gui elements. attach the same script to each. Connect the gui_input signal of both elements to themselves. In on_gui_input put the code below that prints the object and the action of an event. Play the scene, click and hold on one ui element, drag to the other, and let go. It will print that the mouse both pressed and released on the same object.

	if event is InputEventMouseButton and event.is_pressed():
		print_debug(str(self.name)+ "pressed")
	if event is InputEventMouseButton and event.is_released():
		print_debug(str(self.name)+ "released")

Minimal reproduction project

Debuggin.zip

@Sauermann
Copy link
Contributor

I can confirm, that this is the current way, how it is implemented. The request of this issue looks like a change, that can easily break existing behavior (especially Drag&Drop operations). So implementing it would require thorough discussion/testing.

@Taneblade
Copy link
Author

I had pictured it as fixing drag and drop. On mouse down would tell you what was originally selected. On mouse up would tell you what it was dragged on to. But I can see why someone would want it how it is at the moment.

@putgeminmouth
Copy link

I am also running into this issue, its making my own drag and drop implementation difficult to implement. Is there any workaround?

@Taneblade
Copy link
Author

Taneblade commented Jan 13, 2024

@putgeminmouth I found a bad ugly looking solution for my game but it works if you want to try to replicate it. Each drag and drop object has the on gui input signal connected to a script attached to the object. Objects that you can drag onto have the mouse entered signal, and the mouse exited signal connected their own script. Each drag and drop object is an empty node with a child that contains the sprite or image but the mouse filter property is set to ignore. When you click and hold the object, the on gui input detects it and moves the child around with the mouse. When you move over another drag and drop object the on mouse entered signal will fire and you can set a global "mouse target" variable to the new object. If the player releases the mouse it will check for a global target and if so it will do whatever action you need (for me it swaps the objects). If the mouse had moved off the global target the mouse exited event will set the mouse target to null. So If it you are holding the mouse down, move the child around. When you let go, if mouse target is not null then you know the card was dropped onto something else. If there is no target then you can set the child object back to the position of the parent. Its ugly but it works. Here is my code for it if you would like to try it

var child

func _ready():
	child = $Sprite #set to the child object

func _on_gui_input(event):
		if event is InputEventMouseButton and event.button_mask == 1:
           #left mouse just pressed
			child.global_position = get_global_mouse_position() - Vector2(20,20)
			pass
		elif event is InputEventMouseMotion and event.button_mask == 1:
#button left down and dragging
			if Global.Target != self and Global.Target != null: #optional to make the child snap onto other objects
				child.global_position = Global.Target.global_position - Vector2(20,20) #optional to make the child snap onto other objects
			else:
				child.global_position = get_global_mouse_position() - Vector2(20,20) #the vector2 is half the size of the image, so it is centered on the mouse
			pass
		elif event is InputEventMouseButton and event.button_mask == 0:
				#button left release
			child.global_position = global_position
			if Global.Target != self and Global.Target != null:
					#do the action

Add this to objects you can drop onto:

func _on_mouse_entered():
	Global.Target = self


func _on_mouse_exited():
	Global.Target = null

@kitbdev
Copy link
Contributor

kitbdev commented Jan 28, 2024

It currently behaves this way because Buttons, Sliders, Scrollbars, and others need to receive the mouse events after being pressed in order to handle their state, and the event should go to them first.
This does mean other uses for mouse releasing has issues, like custom drag and drop.

To fix this, we would need a capture system that can let Buttons and similar 'capture' the mouse while it is held down and get the event when it is released. When nothing has captured the mouse, the mouse pressed and mouse released would act separately. Only one Control can have mouse capture at a time, and it receives all mouse events (except maybe scroll wheel events).

Edit: It may be possible to adapt the existing mouse focus logic for this.

@dauntaun
Copy link

A dirty fix is to call grab_click_focus() on mouse_entered. This will make the new control register the button release.

@EmilyV99
Copy link

I can confirm, that this is the current way, how it is implemented. The request of this issue looks like a change, that can easily break existing behavior (especially Drag&Drop operations). So implementing it would require thorough discussion/testing.

So, initially looking at this, it seemed obvious to me that it would be better to be changed outright- every usecase I ran into, the current way was a problem.
..except I just ran into a use case where it's actually a problem no matter which way it is implemented. While my initial complaint was about InputEventMouseMotion (#94283), the same thing just became an issue for me with mouse_entered which I used as a workaround for the motion event (which, mouse_entered is already "fixed" as far as this is concerned).

The issue is, I have a sudoku grid, and if you have a cell focused, and drag the mouse across more cells, I want every cell to get an event so that I can select multiple of them (select being a boolean on my cell object, they become selected when they gain focus, and remain selected until something clears the selection). Having the event I was using act the "old way" here, prevented that from working entirely.

Switching to mouse_entered, it now works absolutely lovely! ...Except now when I drag to resize the HSplitContainer, I also select everything under the cursor. So really, either way causes issues. (Now, personally, having it the "new way" like mouse_entered was easier for me to patch that issue myself via script, so I still think it's a better behavior, but clearly both sides have their issues).

What would be really nice would be a way to say "This control can receive events, as long as [exported Other Control] or one of its' descendants has focus" (here, I would say that the cells get events when the grid or anything inside it has focus; so the grid itself or any cell being focused, allows the cells to receive the events, but when dragging the hsplitcontainer to resize it, they wouldn't)

(Not really sure about what's best here, but figured adding my two cents at least helps further discussion on the topic)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants