godotengine / godot-docs

Godot Engine official documentation
https://docs.godotengine.org
Other
3.78k stars 3.07k forks source link

Specify that holding Mouse Button down on a Control will prevent `mouse_exited` on itself and `mouse_entered` on others until release. #4266

Open gamepad-coder opened 3 years ago

gamepad-coder commented 3 years ago

Godot Engine v3.2.3.stable.official

Current Documentation:

mouse_entered ( )

Emitted when the mouse enters the control's Rect area, provided its mouse_filter lets the event reach it. API -> class Control -> Signals -> mouse_entered

mouse_exited ( )

Emitted when the mouse leaves the control's Rect area, provided its mouse_filter lets the event reach it. API -> class Control -> Signals -> mouse_entered

Both found at : https://docs.godotengine.org/en/stable/classes/class_control.html#signals

Issue Summary:

It is not documented and not intuitive that the Control class's mouse_entered signal will not fire if mouse button is clicked over another Control, then (without releasing the mouse button) the mouse is moved within the bounds of the Control node which has the mouse_entered signal connected.

This happens because the mouse_exited signal is not triggered for the first Control (even if the mouse is outside the Control) until the mouse is released.

This blocking happens any time the first Control is clicked and it has a mouse_filter of "Stop" or "Pass", regardless of whether the first Control has signals or a script with _gui_input(event).

All other controls will have mouse_entered and mouse_exited and mouse motion detected in _gui_input(event) blocked until the mouse is released, even if the mouse is within their Rect area.

Detailed Example for mouse_entered and mouse_exited signals:

Scene Tree Setup:

Expected Behavior, based on the Docs:

When no mouse button is held, Control derived nodes which connect their mouse_entered signal work as expected, any time the mouse initially enters the bounds of their Rect area.

Clicking on a blank area of the screen, holding the mouse button down, then hovering over Controls will trigger each mouse_entered as expected. As does clicking and holding on a Sprite, then moving the mouse over the Control before releasing the mouse button.

Unexpected Behavior, based on the Docs:

The following happens with Left Mouse Button, Right Mouse Button, or Middle Mouse Button.

A Control derived node will not fire mouse_entered if

  1. The Control node with mouse_entered is not obstructed and has mouse_filter set to "Pass" (or "Stop").
  2. The mouse is clicked on another Control,
  3. the mouse button is held down,
  4. then the mouse is moved within the bounds of the node connected to mouse_entered.

Upon releasing the mouse, the mouse_entered signal immediately fires. If the first Control has mouse_exited it will also emit at this point.

Demonstration:

Two Label nodes with mouse_filter set to "Pass". Each has mouse_entered connected to itself, and prints to output.

For simplicity, mouse_exited is not connected in this demo.

docs - mouse_entered - github issue post

(Note: The yellow circle represents when the mouse is held down. It is not part of Godot -- it's an overlay feature of ScreenToGif.)

Suggestion:

I'm assuming this is expected (and at times desired) behavior.

But this behavior seems to be undocumented & contradicts the documentation for the mouse_entered signal:

"Emitted when the mouse enters the control's Rect area"

Similarly, mouse_exited does not always emit

"when the mouse leaves the control's Rect area".

I'd like to propose just a few details be added here to reflect this case.

Identical Behavior using _gui_input(event) instead of (or in addition to) signals

This behavior (of clicking on one control, then all mouse_entered signals on all other control nodes being blocked until the mouse button is released) happens identically if some (or all) of the signals are replaced with _gui_input(event) functions waiting for InputEventMouseMotion over the control node.

The outcome is identical to the demo above.

func _gui_input(event):
    if event is InputEventMouseMotion:
        print("Hovered over control #2")

This behavior using _gui_input(event) is unexpected given this currently listed information :

https://docs.godotengine.org/en/stable/classes/class_control.html#class-control-method-gui-input API -> class Control -> Methods -> void _gui_input ( InputEvent event ) virtual

func _gui_input(event):     if event is InputEventMouseButton:         if event.button_index == BUTTON_LEFT and event.pressed:             print("I've been clicked D:")

The event won't trigger if:

  • clicking outside the control (see has_point);

  • control has mouse_filter set to MOUSE_FILTER_IGNORE;

  • control is obstructed by another Control on top of it, which doesn't have mouse_filter set to MOUSE_FILTER_IGNORE;

  • control's parent has mouse_filter set to MOUSE_FILTER_STOP or has accepted the event;

  • it happens outside parent's rectangle and the parent has either rect_clip_content or _clips_input enabled.

The Control which is clicked does not need to have a _gui_input(event) in order for it to block the InputEventMouseMotion in another Control node's _gui_input(event).

Regarding "clicking outside the control": If anything other than a Control is clicked and held, a node listening for mouse motion in _gui_input(event) will fire, even when the click originated outside its Rect area. So the current description for _gui_input(event) is partially describing InputEventMouseButton's bounds within _gui_input() (which is very useful), but not the specific bounds of when _gui_input(event) itself is triggered (or blocked by other gui elements).

Suggestion:

I'd like to propose adding a little detail here to clarify that when holding the mouse button down on one Control, other Controls with _gui_input(event) functions will be blocked. (I haven't read the code for this, so I'm unsure if it's more complicated or nuanced than this, but even a little more information will save users from discovering this rule through trial and error alone).


This took a while to figure out all the different bounds and interactions of this behavior, so I hope this is useful.

I absolutely love these docs, (+) I appreciate everyone who puts effort into Godot. Thanks for your time.

Calinou commented 3 years ago

Feel free to open a pull request for this :slightly_smiling_face:

gamepad-coder commented 3 years ago

Feel free to open a pull request for this 🙂

I didn't forget :)

https://github.com/godotengine/godot/pull/44257

Sauermann commented 1 year ago

With the changes of godotengine/godot#67791, I believe, that this issue can be closed or tagged as relevant for Godot v3.