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

Document how focus and mouse events affect signals in Control #44257

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

gamepad-coder
Copy link

API clarification, doc-bug fixes, & added undocumented features.

Closes godotengine/godot-docs#4266

Changes :
Specified how holding mouse button down blocks other controls until release.
Corrected 2 false statements in _gui_input().
Added more precise details to

  • _gui_input(),
  • signal mouse_entered,
  • signal mouse_exited,
  • enum MouseFilter.

Added InputEvent tutorial to Tutorial section.

Why is this Pull Request so detailed? :

The commit itself is not very long, but this is my first contribution. So I thought it would be nice to detail my thought processes and to show my research to justify these additions. Perhaps this is unnecessary, but I figure revealing too much of the backstory here is better than explaining too little.

Feel free to skip any detailed meta-descriptions, unless you plan to revise or add to these sections. I mainly wish to show that these changes are needed, and that the additions are correct. (Also, being a new contributor, I wanted to prove that I am not recklessly altering the API docs =).

Conversely, about the changes I'm submitting :
I've tried very hard to keep the actual changes brief, comprehensive, and correct. I've gone through several revisions to condense it into something that should make sense in as few words as possible. I have verified each added line of text by experimenting with projects in Godot, and I've traced the C++ code for all the changes to make sure (1) what I describe is correct (2) there is nothing vital (within scope) which I've omitted.

Thematically most of the changes in this Pull Request are related to mouse events and mouse filters. However there are a few things out of scope I fixed and corrected while I was in the area (particularly, this Pull Request fixes 2 inaccurate descriptions in Control._gui_input, and there were 2 minor typos elsewhere).


What API will look like after this commit :

no commentary





Explanation & points changed :

enum MouseFilter

3-enum MouseFilter

  • Now describes why signals sometimes fail to emit and why events fail to fire (when another control is hogging mouse input by design) even though the MouseFilter is set up to recognize mouse entry.
    It is intuitive based on the current API that MOUSE_FILTER_IGNORE will result in no signals being emitted, however it is not apparent that MOUSE_FILTER_PASS and MOUSE_FILTER_STOP do not guarantee that mouse motion events will always be routed to the Control.

    For the following, assume a setup where a Control has mouse_filter set to MOUSE_FILTER_STOP or MOUSE_FILTER_PASS and assume that it normally receives events and signals and _gui_input() when the mouse clicks, enters, or exits it.

    These signals will not emit and func _gui_input() will not fire if the mouse was clicked on another Control (which can receive mouse input) and has not been released yet. This applies to signals mouse_entered and mouse_exited, signal gui_event(e), and virtual _gui_input(), all will be blocked and all events are routed up through the clicked Control instead.

    See ~line 1809 in viewport.cpp function Viewport::_gui_input_event(..) which is called when regular Node._input() calls do not handle an event. All gui input signals and scripted func _gui_input(event) events originate here.

    Now look at ~lines 1821, 1872, 1897 in viewport.cpp function Viewport::_gui_input_event(..)

    1821 -- if (gui.mouse_focus_mask) {
    1872 -- if (mb->get_button_index() == BUTTON_LEFT) { //assign focus
    1897 -- if (gui.mouse_focus && gui.mouse_focus->can_process()) {

    When a Control which can receive mouse input is clicked, it remains in mouse focus until release. Even when the mouse hovers over other Controls, the event will be passed to the Control which the mouse initially clicked via (Control *control) gui.mouse_focus.
    See viewport.cpp's same _gui_input_event(..) in the branch for InputEventMouseMotion around lines 2009, 2057, and 2063 to see why events are called on the Control which was clicked (and not released) instead of the Control which is now under the mouse:

    2009 -- if (!gui.drag_attempted && gui.mouse_focus && mm->get_button_mask() & ..
    2057 -- if (gui.mouse_focus) {
    2063 -- if (over != gui.mouse_over) {


  • Also added detail emphasizing that MOUSE_FILTER_PASS passes the event straight up, whereas MOUSE_FILTER_IGNORE will look for nodes outside of ancestor branches.

    When looking for the Control under the mouse, Godot will find any node in the Viewport which is visually below (aka higher in the tree than) the one with MOUSE_FILTER_IGNORE, it doesn't have to be a parent or a parent of a parent; Godot searches whole tree in reverse order.

    See viewport.cpp the functions Viewport::_gui_find_control(..) and Viewport::_gui_find_control_at_pos(..) around ~line 1703 to see it in the code.





Explanation & points changed :

signal mouse_entered
signal mouse_exited

4-signals mouse_entered mouse_exited

These signals were minimally documented.

I spent a lot of time debugging a personal project until I discovered that the unexpected behavior of my GDScripts was due to the mouse-hogging behavior of clicked Controls, and not due to a bug in my code. Then I discovered that this mouse-hogging behavior was undocumented (see my previous section of this Pull Request for more details, also see doc bug report @ godotengine/godot-docs#4266).

Note: this is desired functionality. When you click and drag on a scroll bar's handle, it's standard that other Controls will not display a hover animation, and will similarly be blocked until mouse button release.

This was my entry point in researching everything that led to this Pull Request.

Fun fact.

Changes to mouse_entered and mouse_exited :

  • Added links to Control.has_point() and Control.get_rect(), as Rect has its own section in the Inspector Dock, but it does not have a dedicated section in the API. Referring the reader to Control.get_rect() will make it apparent what this line is referencing with the word Rect (and exactly where to read more).

  • Added undocumented detail : if you parent a Button control under another Button control, then set the child's mouse_filter to MOUSE_FILTER_PASS:
    hovering on the child will hover the parent (and emit mouse_entered for the child then the parent) and clicking the child Button will click the child and the parent Button.

    You can do this with any Control type and MOUSE_FILTER_PASS, but this is the simplest example to visualize.

    This is not intuitive (unless you look at the code for viewport.cpp) and I thought it aught to be documented. Look in viewport.cpp around line 1604 for the method Viewport._gui_call_input(..). This is called for all gui events (mouse button, mouse motion, touch, drag, gesture) which are not keypress and joypad events from Viewport::_gui_input_event(..)'s c++ code. All gui mouse events flow through here.

  • Added details about how a control will hog mouse focus and hog signals/events/_gui_input until mouse release. Upon mouse button release, if the mouse is now under a Control different than the one that was clicked, the clicked control will emit mouse_exited and the Control actually under the mouse will emit mouse_entered and propagate the signal upwards if appropriate (these will both happen during the same InputEventMouseButton or InputEventMouseMotion event in Viewport::_gui_input_event()

    1983 -- _gui_call_notification(gui.mouse_over, Control::NOTIFICATION_MOUSE_EXIT);
    2065 -- _gui_call_notification(gui.mouse_over, Control::NOTIFICATION_MOUSE_EXIT);

  • For this Pull Request's additions to these signals,
    mouse_entered uses [code]mouse_entered[/code] and [signal mouse_exited] and
    mouse_exited uses [code]mouse_exited[/code] and [signal mouse_entered]
    so they do not link to themselves. (Also this gives a visual cue which one you are currently reading; Not the main reason, but a fun side-effect).




Explanation & points changed :

func _gui_input(e)

  • Added reference to the InputEvent tutorial : https://docs.godotengine.org/en/3.2/tutorials/inputs/inputevent.html
    I added this to the tutorials section at the top of the Control API page (see the first picture in this post). But I also wanted to refer the reader to it here specifically. It's vitally important to understand that tutorial in conjunction with the following description of _gui_input() in Control's API.

    I am unaware of a way to directly link to this tutorial's webpage in the online docs from the XML file. Further, I am unaware if this addition this breaches any conventions. This is the one non-strictly-technical point I've added which I'm unsure about, feel free to accept it or not, but I think it would be very beneficial to many.

  • The existing documentation for _gui_input() made it seem possible that keypress events were dependent on mouse_filter. I added a bullet point that keypress events and joypad events will only fire for the Control with key focus. This is briefly mentioned in the Intro section of Control's current API, but this section is not comprehensive without mentioning it.

  • Likewise, I added a phrase specifying that Control gui events are dependent on mouse_filter if they are mouse or touch events.

  • [DOC-BUG FIX] The current API for Godot 3 and Godot 4 both incorrectly state that parents pass events down to children unless they have MOUSE_FILTER_STOP. The current implementation passes all events (which are not joypad nor keypress events) up through the tree (see the while loop where this happens in the picture below). I corrected this phrase. See the first red outline in the picture below.

  • [DOC-BUG FIX] rect_clip_content does not clip input. Specified this, but largely left this line untouched.

    Demonstration :
    • Add a Button to a Panel,
    • clip input in the panel (you can do this from the Inspector Dock),
    • don't override _clips_input() in a script,
    • offset the Button to where part of it is invisible
    • run the scene and observe that you can still click
      the invisible parts of the Button
    • add a script, add func _clips_input(){ return true }
    • run scene, observe that you can no longer click
      the invisible parts of the Button.

  • Added details about mouse-hogging behavior (see the previous section of this Pull Request)

  • Added an important nuance : _gui_input() is not called if a node marks an event as handled in func _input(). This is outlined in the InputEvent tutorial, but will not be apparent without a lot of cross-referencing for beginners. This is an effort to make this method's documentation complete. Perhaps out of scope, but it's brief + more important than my main scope.

5-_gui_input





Concluding remarks :
Again, the changes are much smaller than these explanations.

If in the future anyone wants to reword or expand on this contribution, I wanted to lay out all the details in one place. Hopefully someday this will help someone who is exploring this area of the API + engine code for the first time.

I plan on contributing fairly regularly to the Documentation. But since I haven't issued a Pull Request before, I wanted to back up my work with reasons.

I took notes on several other changes which were out of scope, and will submit bug reports to the godot-docs project and issue Pull Requests for those separately in the future.

If I need to issue multiple Pull Requests for the changes in this one, feel free to let me know and I will break it up into chunks.

I'll rebase my fork every 12 hours or so to keep it up to date.

Thanks to all who contribute their time and efforts to Godot.

@Calinou
Copy link
Member

Calinou commented Dec 10, 2020

I'll rebase my fork every 12 hours or so to keep it up to date.

You don't need to rebase your fork unless there are merge conflicts (which GitHub will tell you about at the bottom of a pull request).

@gamepad-coder
Copy link
Author

gamepad-coder commented Dec 11, 2020

Oh wow, thanks. That makes sense. Read the Godot PR doc pages, but haven't read the Git book yet. Appreciate it!

Specified how holding mouse button down blocks other controls until release.
Corrected 2 false statements in `_gui_input()`.
Added more precise details to
- `_gui_input()`,
- signal `mouse_entered`,
- signal `mouse_exited`,
- `enum MouseFilter`.
Added cross-references to
- `NOTIFICATION_MOUSE_ENTER`
- `NOTIFICATION_MOUSE_EXIT`
- property `mouse_filter`
Added InputEvent tutorial to Tutorial section.
Fixed 2 miscellaneous typos.
@gamepad-coder gamepad-coder force-pushed the docs-4266-document-how-mouse-down-blocks-gui-events branch from cad6a49 to 681299a Compare January 8, 2021 01:49
@YuriSizov YuriSizov modified the milestones: 4.0, 4.x Feb 9, 2023
@YuriSizov
Copy link
Contributor

This PR needs to be updated against the current master version, as the documentation for Control nodes has changed a lot.

Copy link
Contributor

@Sauermann Sauermann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note, that this behavior has changed in #67791 for Godot v4. These changes need to be taken into account, when updating the PR.

@YuriSizov YuriSizov changed the title [Docs] adds to Control API -- describe when C++ Viewport::gui.mouse_focus blocks other Controls from receiving signals and events [undocumented] Document how focus and mouse events affect signals in Control Aug 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug cherrypick:3.x Considered for cherry-picking into a future 3.x release documentation enhancement
Projects
None yet
4 participants