-
-
Notifications
You must be signed in to change notification settings - Fork 21.5k
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
base: master
Are you sure you want to change the base?
Document how focus and mouse events affect signals in Control
#44257
Conversation
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). |
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.
cad6a49
to
681299a
Compare
This PR needs to be updated against the current master version, as the documentation for Control nodes has changed a lot. |
There was a problem hiding this 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.
Control
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()
,mouse_entered
,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 :
Explanation & points changed :
enum MouseFilter
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 thatMOUSE_FILTER_PASS
andMOUSE_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 toMOUSE_FILTER_STOP
orMOUSE_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 signalsmouse_entered
andmouse_exited
, signalgui_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
functionViewport::_gui_input_event(..)
which is called when regularNode._input()
calls do not handle an event. All gui input signals and scriptedfunc _gui_input(event)
events originate here.Now look at ~lines 1821, 1872, 1897 in
viewport.cpp
functionViewport::_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) {
MOUSE_FILTER_PASS
passes the event straight up, whereasMOUSE_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 functionsViewport::_gui_find_control(..)
andViewport::_gui_find_control_at_pos(..)
around ~line 1703 to see it in the code.Explanation & points changed :
signal mouse_entered
signal 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
andmouse_exited
:Control.has_point()
andControl.get_rect()
, asRect
has its own section in the Inspector Dock, but it does not have a dedicated section in the API. Referring the reader toControl.get_rect()
will make it apparent what this line is referencing with the wordRect
(and exactly where to read more).Button
control under anotherButton
control, then set the child'smouse_filter
toMOUSE_FILTER_PASS
:hovering on the child will hover the parent (and emit
mouse_entered
for the child then the parent) and clicking the childButton
will click the child and the parentButton
.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 inviewport.cpp
around line 1604 for the methodViewport._gui_call_input(..)
. This is called for all gui events (mouse button, mouse motion, touch, drag, gesture) which are not keypress and joypad events fromViewport::_gui_input_event(..)
's c++ code. All gui mouse events flow through here.Control
different than the one that was clicked, the clicked control will emitmouse_exited
and theControl
actually under the mouse will emitmouse_entered
and propagate the signal upwards if appropriate (these will both happen during the sameInputEventMouseButton
orInputEventMouseMotion
event inViewport::_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);
mouse_entered
uses[code]mouse_entered[/code]
and[signal mouse_exited]
andmouse_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)
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.
_gui_input()
made it seem possible that keypress events were dependent onmouse_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.mouse_filter
if they are mouse or touch events.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.rect_clip_content
does not clip input. Specified this, but largely left this line untouched.Demonstration :
Button
to aPanel
,_clips_input()
in a script,Button
to where part of it is invisiblethe invisible parts of the
Button
func _clips_input(){ return true }
the invisible parts of the
Button
._gui_input()
is not called if a node marks an event as handled infunc _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.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.