godot
godot copied to clipboard
"mouse_entered" and "mouse_exited" signals are broken while holding down a mouse button
Godot version: fbb5ca4
OS/device including version: Linux Manjaro
Issue description: The "mouse_entered" and "mouse_exited" signals of control nodes don't get fired while holding any mouse button down when the click (any mouse button down) started on a control that does not ignore the mouse.
it's actually because in viewport.cpp in line 1843 to 1850 "over" get's set to the mouse_focus
if (gui.mouse_focus) {
over = gui.mouse_focus;
//recompute focus_inv_xform again here
} else {
over = _gui_find_control(mpos);
}
and shortly after ( line 1884 to 1893 ) the mouse_exited and mouse_entered signals get not fired because the gui.mouse_focus and the gui.mouse_over are the same
if (over != gui.mouse_over) {
if (gui.mouse_over)
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT);
_gui_cancel_tooltip();
if (over)
over->notification(Control::NOTIFICATION_MOUSE_ENTER);
}
This may be a desired behavior, for a lot of cases. But there are also cases where it is very useful to send those signals. (I'll update this post soon to show such a case)
Steps to reproduce:
- Connect the "mouse_entered" or "mouse_exited" signal of any control node
- add another control node, that does not ignore the mouse
- start your drag on the control element of point 2 and drag over the control element from point 1
- notice that no "mouse_entered" and no "mouse_exited" signals get fired
Confirmed on my master build too. Note: if you set mouse filter to "ignore" on the parent control the color rectangle signals works as expected.
linked to #23296
Note from #32727, this does also happen for is_hovered()
Godot版本: fbb5ca4
操作系统/设备,包括版本: Linux Manjaro
问题描述: 当在不忽略鼠标的控件上单击(按下任何鼠标按钮)时,按住任意鼠标按钮时,不会触发控制节点的“ mouse_entered”和“ mouse_exited”信号。
实际上是因为在1843至1850行的viewport.cpp中,“ over”被设置为mouse_focus
if (gui.mouse_focus) { over = gui.mouse_focus; //recompute focus_inv_xform again here } else { over = _gui_find_control(mpos); }
并且在之后(第1884至1893行)不久,由于gui.mouse_focus和gui.mouse_over相同,因此未触发mouse_exited和mouse_entered信号
if (over != gui.mouse_over) { if (gui.mouse_over) gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT); _gui_cancel_tooltip(); if (over) over->notification(Control::NOTIFICATION_MOUSE_ENTER); }
在很多情况下,这可能是理想的行为。 但是在某些情况下,发送这些信号非常有用。(我将尽快更新此帖子以显示这种情况)
重现步骤:
- 连接任何控制节点的“ mouse_entered”或“ mouse_exited”信号
- 添加另一个不忽略鼠标的控制节点
- 在点2的控件元素上开始拖动,然后从点1的控件元素上拖动
- 请注意,没有触发“ mouse_entered”和“ mouse_exited”信号
how solve it...cry... #35262
Still an issue in Godot 3.2.2beta ed27b7e6b973eb6be6fdac4439e99a1907b9cb58
Still in 3.2.4RC2. Try to click on bottom element. With out releasing it move cursor away from element! New Game Project.zip UPD Works with some fixes from code. https://github.com/godotengine/godot/issues/20881#issuecomment-822360392
managed to get around this by making my own _input(event:InputEventMouseButton) function. I then had to make sure these two lines were executed as part of the code when the mouse is clicked down:
event.set_pressed(false)
set("mouse_filter", Control.MOUSE_FILTER_STOP)
I then reset the mouse_filter to ..._PASS on the mouse release. This only worked with an _input() function, not a _gui_input() function
Can not get this to work. The line get_global_rect().has_point(get_global_mouse_position())
does not take into account either scale or rotation of the node :(
Can not get this to work. The line
get_global_rect().has_point(get_global_mouse_position())
does not take into account either scale or rotation of the node :(
get_global_rect().has_point(get_local_mouse_position())
Can not get this to work. The line
get_global_rect().has_point(get_global_mouse_position())
does not take into account either scale or rotation of the node :(get_global_rect().has_point(get_local_mouse_position())
Both get_local_mouse_position
or get_global_mouse_position
do not work. As I said, the Rect2 that get_global_rect
returns does not take into account rotation or scale.
EDIT: Actually, here's what worked for me:
var rect_check = Rect2(Vector2(0,0), node.get_global_rect().size)
var local_mouse_pos = node.get_local_mouse_position()
if rect_check.has_point(local_mouse_pos):
inside = true
get_local_mouse_position
does indeed take into account rotation and scale, as it's in local space. Just had to check it against the local sizes of the Rect2 instead of the global ones.
@Banderi Local mouse position, rotating, scaling and pivot :D Mouse pressed signals bug.zip
Wasted hours thinking it was my code :-( v3.3.1 Appreciate the work around but hoping this gets a fix/resolution
This is probably related to a common UI behavior, which I saw called "mouse capture" on Windows. The idea is to make it easier to implement exclusive dragging logic, by making the control receive mouse events and appear pressed as long as the mouse was pressed inside and remains pressed even if it exits the area of the control. I would not be surprised if this wasnt documented because it's a subtle behavior happening with many widgets, and has no associated option.
For example, take a scrollbar. Press it, keep the mouse held, and move away from it. It will remain pressed, and will still respond to your movements, while every other control will ignore the mouse. You can reproduce this behavior on non-Godot UIs as well, even sometimes on controls that are not usually draggable (see how some buttons remain pressed when you do this too). Things can differ a bit though, because there are many UI systems around. Sometimes hover effects don't follow this, while pressed effects do. Godot happens to do both, including enter/exit.
About mouse_entered
and mouse_exited
: arguably, many widgets probably don't need mouse capture. But if these were changed to always fire when the mouse goes in and out of the control's area, regardless of what's actually happening, then likely a bunch of existing code might need to be fixed somehow (if they rely on it), without being able to rely on them. Or maybe controls can have an option to turn off mouse capture.
This is probably related to a common UI behavior, which I saw called "mouse capture" on Windows. The idea is to make it easier to implement exclusive dragging logic, by making the control receive mouse events and appear pressed as long as the mouse was pressed inside and remains pressed even if it exits the area of the control. I would not be surprised if this wasnt documented because it's a subtle behavior happening with many widgets, and has no associated option.
For example, take a scrollbar. Press it, keep the mouse held, and move away from it. It will remain pressed, and will still respond to your movements, while every other control will ignore the mouse. You can reproduce this behavior on non-Godot UIs as well, even sometimes on controls that are not usually draggable (see how some buttons remain pressed when you do this too). Things can differ a bit though, because there are many UI systems around. Sometimes hover effects don't follow this, while pressed effects do. Godot happens to do both, including enter/exit.
About
mouse_entered
andmouse_exited
: arguably, many widgets probably don't need mouse capture. But if these were changed to always fire when the mouse goes in and out of the control's area, regardless of what's actually happening, then likely a bunch of existing code might need to be fixed somehow, without being able to rely on them. Or maybe controls can have an option to turn off mouse capture.
Thanks for that, makes sense. If this is the case then I would def upvote an option to switch it off for certain scenarios.
Out of curiosity, does this behavior happen with CollisionObject2D/3D's mouse_enter
/mouse_exit
signals too?
Thanks for that, makes sense. If this is the case then I would def upvote an option to switch it off for certain scenarios.
Feel free to open a pull request to do this :slightly_smiling_face:
The property should be added to Control and the default behavior should be kept as it is now.
This is also a huge problem for gui_input(event)
signals from any Control based nodes.
For example when trying to trigger an animation when the left-mouse is released over the Control:
func _on_Control_gui_input(event):
if event.is_action_released("LMB"):
$AnimationPlayer.play("New Anim")
This should trigger the animation only when the left mouse is released over the Control, but if clicked onto the Control then dragged the mouse away from the control (while still being pressed) the animation will play, regardless of where the mouse is released.
This seems to be connected to the mouse_enter and mouse_exit signal, as can be easily tested with this:
var mouse_hover = false
func _on_Control_gui_input(event):
if event.is_action_released("LMB") and mouse_hover:
$AnimationPlayer.play("New Anim")
print("gui ",mouse_hover)
func _on_Control_mouse_entered():
mouse_hover = true
print("mouse_hover = true")
func _on_Control_mouse_exited():
mouse_hover = false
print("mouse_hover = false")
Both "gui True" and "mouse_hover = false" will print only after mouse release.
This is also a huge problem for
gui_input(event)
signals from any Control based nodes.For example when trying to trigger an animation when the left-mouse is released over the Control:
func _on_Control_gui_input(event): if event.is_action_released("LMB"): $AnimationPlayer.play("New Anim")
This should trigger the animation only when the left mouse is released over the Control, but if clicked onto the Control then dragged the mouse away from the control (while still being pressed) the animation will play, regardless of where the mouse is released.
This seems to be connected to the mouse_enter and mouse_exit signal, as can be easily tested with this:
var mouse_hover = false func _on_Control_gui_input(event): if event.is_action_released("LMB") and mouse_hover: $AnimationPlayer.play("New Anim") print("gui ",mouse_hover) func _on_Control_mouse_entered(): mouse_hover = true print("mouse_hover = true") func _on_Control_mouse_exited(): mouse_hover = false print("mouse_hover = false")
Both "gui True" and "mouse_hover = false" will print only after mouse release.
https://github.com/godotengine/godot/issues/20881#issuecomment-841614529
One workaround is to toggle the visibility of the clicked Control immediately after being pressed:
func _gui_input(event):
if event is InputEventMouseButton and event.pressed:
visible = false
visible = true
Another hacky workaround:
get_viewport().notification(NOTIFICATION_WM_FOCUS_OUT)
@volkathemartian How is this workaround supposed to be implemented?
I tried it with my example above, and it does not work:
var mouse_hover = false
func _on_Control_gui_input(event):
if event is InputEventMouseButton and event.pressed:
visible = false
visible = true
if event.is_action_released("LMB") and mouse_hover:
$AnimationPlayer.play("New Anim")
print("gui ",mouse_hover)
func _on_Control_mouse_entered():
mouse_hover = true
print("mouse_hover = true")
func _on_Control_mouse_exited():
mouse_hover = false
print("mouse_hover = false")
If I click and release directly on the Control, turning visibility on and off results only in the Control triggering the mouse_entered signal again, but this will also eat up the event.is_action_released() test for some reason. The animation never plays and neither does it print "gui ...".
Here is my test project if you want to try for yourself: Control_click_drag_release_issue.zip Please let me know if you find a working workaround, this bug is driving me nuts.
One workaround is to toggle the visibility of the clicked Control immediately after being pressed:
func _gui_input(event): if event is InputEventMouseButton and event.pressed: visible = false visible = true
Another hacky workaround:
get_viewport().notification(NOTIFICATION_WM_FOCUS_OUT)
Did you check it yourself? I'm not sure if what you posted is a solution to the problem!
@volkathemartian How is this workaround supposed to be implemented?
I tried it with my example above, and it does not work:
var mouse_hover = false func _on_Control_gui_input(event): if event is InputEventMouseButton and event.pressed: visible = false visible = true if event.is_action_released("LMB") and mouse_hover: $AnimationPlayer.play("New Anim") print("gui ",mouse_hover) func _on_Control_mouse_entered(): mouse_hover = true print("mouse_hover = true") func _on_Control_mouse_exited(): mouse_hover = false print("mouse_hover = false")
If I click and release directly on the Control, turning visibility on and off results only in the Control triggering the mouse_entered signal again, but this will also eat up the event.is_action_released() test for some reason. The animation never plays and neither does it print "gui ...".
Here is my test project if you want to try for yourself: Control_click_drag_release_issue.zip Please let me know if you find a working workaround, this bug is driving me nuts.
He probably forgot to add that this is his theory. Try my attachments, there is the only working option at the moment. https://github.com/godotengine/godot/files/6483692/Mouse.pressed.signals.bug.zip
This is probably related to a common UI behavior, which I saw called "mouse capture" on Windows. The idea is to make it easier to implement exclusive dragging logic, by making the control receive mouse events and appear pressed as long as the mouse was pressed inside and remains pressed even if it exits the area of the control. I would not be surprised if this wasnt documented because it's a subtle behavior happening with many widgets, and has no associated option.
For example, take a scrollbar. Press it, keep the mouse held, and move away from it. It will remain pressed, and will still respond to your movements, while every other control will ignore the mouse. You can reproduce this behavior on non-Godot UIs as well, even sometimes on controls that are not usually draggable (see how some buttons remain pressed when you do this too). Things can differ a bit though, because there are many UI systems around. Sometimes hover effects don't follow this, while pressed effects do. Godot happens to do both, including enter/exit.
About
mouse_entered
andmouse_exited
: arguably, many widgets probably don't need mouse capture. But if these were changed to always fire when the mouse goes in and out of the control's area, regardless of what's actually happening, then likely a bunch of existing code might need to be fixed somehow (if they rely on it), without being able to rely on them. Or maybe controls can have an option to turn off mouse capture.
This has nothing to do with mouse behavior in Windows.
This has nothing to do with mouse behavior in Windows.
What do you mean? This behaviour is consistent across a whole lot of the programs I use on linux, and I imagine it is the same on Windows.
If I click something that does not have drag-and-drop functionality in a window (any application, not just godot ones) and then move the mouse elsewhere in the window (or even to other windows) with the button down, almost without exception there will be no indication the mouse hovers over anything until the button is released.
This has nothing to do with mouse behavior in Windows.
What do you mean? This behaviour is consistent across a whole lot of the programs I use on linux, and I imagine it is the same on Windows.
If I click something that does not have drag-and-drop functionality in a window (any application, not just godot ones) and then move the mouse elsewhere in the window (or even to other windows) with the button down, almost without exception there will be no indication the mouse hovers over anything until the button is released.
I mean exactly what I mean - the interpretation of the cursor behavior within the godot's engine logic has nothing to do with the interpretation and behavior of the mouse anywhere, even in Windows, even in Linux, even on an alien spaceship.
@MadDogMayCry0 I can't get your workaround to work with my gui_input(event) example. I suppose it would be possible if I copy it line by line, use _input(event) instead of gui_input(event) and separate scripts like you did, but this is way too complicated for what I want to do.
@MadDogMayCry0 even though it works, what you do with
if event.is_pressed(): event.pressed = false
is to by pass the code that starts here : https://github.com/godotengine/godot/blob/3.x/scene/main/viewport.cpp#L1861
and thus break dragging modal windows and some yet unclear to me gui focus related code.
@MadDogMayCry0 I can't get your workaround to work with my gui_input(event) example. I suppose it would be possible if I copy it line by line, use _input(event) instead of gui_input(event) and separate scripts like you did, but this is way too complicated for what I want to do.
@MadDogMayCry0 even though it works, what you do with
if event.is_pressed(): event.pressed = false
is to by pass the code that starts here : https://github.com/godotengine/godot/blob/3.x/scene/main/viewport.cpp#L1861 and thus break dragging modal windows and some yet unclear to me gui focus related code.
Of course, you must adapt my logic to what you are trying to do with your code. My logic only explains and demonstrates how to fix this bug. However, it can be applied to anything, both modals and dialogs and absolutely any situation in general.
@MadDogMayCry0 it seems to me that we don't understand each other.
The single line trick you used event.pressed = false
, fixes this issue, but creates at least 3 more issues.
Add an AcceptDialog in your test project, see that you can't drag the window anymore.
Add a TextEdit and see you can't click to focus in it anymore.
Add you trick to a drad'n drop test, see it breaks too.
@MadDogMayCry0 it seems to me that we don't understand each other. The single line trick you used
event.pressed = false
, fixes this issue, but creates at least 3 more issues. Add an AcceptDialog in your test project, see that you can't drag the window anymore. Add a TextEdit and see you can't click to focus in it anymore. Add you trick to a drad'n drop test, see it breaks too.
If you look at the code further than that piece you refer to all the time, you will probably see that there is a fork from the native functions and of course all the approaches you listed like drag and drop and other nonsense you need to add yourself. This works for about 10 minutes of time. I have not argued or asserted that my approach is universal, I will repeat myself once again - this is a demonstration of solving a problem, a demonstration that allows you to bypass native logic and build your own.