Skip to content

Add 'mouse_entered', 'mouse_exited' and 'mouse_pressed' to Sprite2D and Polygon2D#114776

Open
Ezequias-Rodrigues wants to merge 36 commits intogodotengine:masterfrom
Ezequias-Rodrigues:master
Open

Add 'mouse_entered', 'mouse_exited' and 'mouse_pressed' to Sprite2D and Polygon2D#114776
Ezequias-Rodrigues wants to merge 36 commits intogodotengine:masterfrom
Ezequias-Rodrigues:master

Conversation

@Ezequias-Rodrigues
Copy link

@Ezequias-Rodrigues Ezequias-Rodrigues commented Jan 9, 2026

This PR improves mouse input handling for CanvasItem-based nodes by integrating _mouse_hit_test() into the input propagation path and emitting mouse-related signals directly from CanvasItem.

It enables accurate mouse interaction for 2D nodes without relying on physics.

Summary of changes

  • Added mouse hover and press signal emission to CanvasItem
  • mouse_entered_node
  • mouse_exited_node
  • mouse_pressed_node
  • Brought has_point() method up from control to canvas_Item, overriding it in sprite2d and polygon2d to be pixel perfect
  • Override input() from node in canvas_item to be able to call has_point()
  • Created a new toggle for CanvasItem, mouse_picking, which is set to false by default. It enables the mouse picking behavior when set to true.

Motivation

Currently, CanvasItem nodes receive mouse input in a decentralized way, requiring each node to manually perform hit tests, often using only bounding rectangles.

This PR:

  • Enables pixel-perfect and shape-aware interaction
  • Avoids physics-based picking for non-physical visuals
  • Aligns CanvasItem mouse behavior more closely with existing GUI patterns

Use cases

  • Pixel-perfect interaction for Sprite2D
  • Accurate picking for Polygon2D and Line2D
  • Lightweight 2D editors and tools
  • Interactive visuals that should not require physics bodies
  • My game, hehe

Compatibility

  • No impact on Control / GUI input handling
  • No physics or rendering changes
  • Needs to be enabled using set_process_input(true) AND set_mouse_picking(true), so won't affect older projects unless "opted in"

Notes

  • This PR does not introduce a new input system
  • It builds on existing input propagation and virtual hooks already present in CanvasItem
  • Designed to be minimal, efficient, and easy to extend

Ezequias Rodrigues added 7 commits January 6, 2026 15:13
Introduces a virtual _mouse_hit_test method in CanvasItem, overridden in Sprite2D and Polygon2D for accurate mouse interaction detection. Adds mouse_entered, mouse_exited, and mouse_pressed signals to Sprite2D and Polygon2D, and implements input event handling in CanvasItem to emit these signals based on mouse events. This enhances interactivity for 2D nodes by enabling per-item mouse event handling.
Updated CanvasItem::input to use global mouse position and improved mouse enter/exit signal logic. Removed debug print statements and made mouse_inside non-mutable for clarity.
@Ezequias-Rodrigues Ezequias-Rodrigues requested review from a team as code owners January 9, 2026 02:06
@AdriaandeJongh
Copy link
Contributor

what about performance? will this negatively impact nodes that don’t implement this feature?

@Ezequias-Rodrigues
Copy link
Author

Ezequias-Rodrigues commented Jan 9, 2026

The input function and _mouse_hit_test will only be called if input processing is set to true for a given node
So i don't think it will negatively impact nodes that makes no use of input(Unless the coder deliberately set input processing to true for every node or something like that)

Added a check in CanvasItem::input to return early if the item is not visible in the scene tree, preventing unnecessary input processing for hidden items.
@AThousandShips

This comment was marked as resolved.

@Ezequias-Rodrigues
Copy link
Author

Ezequias-Rodrigues commented Jan 9, 2026

Did you use AI to write this? If so you need to mention this

The PR contains strange random changes to unrelated files, and isn't valid C++ code, so it clearly hasn't been tested in the way it is written right now

I tried to use copilot at first, but got frustrated with it trying to override a inexistent function and decided to do the work myself.

At first i tried to change the scene tree because i wasn't understanding how input was being propragated but then i went to canvas item

I'll take a better look when i get to home, sorry for the mess, this is my first PR

@AThousandShips
Copy link
Member

AThousandShips commented Jan 9, 2026

Please test your code before opening a PR, making sure it compiles first of all but just making sure it actually does what you want it to do is a basic requirement (might have missed something but the code doesn't look like it compiles to me, but it doesn't seem to be tested with that in mind)

Did you intend to add the functionality to Line2D as well?

@Ezequias-Rodrigues
Copy link
Author

Ezequias-Rodrigues commented Jan 9, 2026

So, what makes the code fails is the styling(i took a time to read the logs, GHA / 📊 Static checks / Code style, file formatting, and docs (pull_request) is what is failing)

The code actually compiles, i've tested it before with a minimal script. The only thing i haven't tested, because was like the last thing i made before i went to sleep was the addition of the check if the node is visible or not, i had the idea right after reading the comment about performance.

And yes, i intended to edit line 2D, but haven't implemented yet because of laziness, if this PR goes thru, i'll take a better look at it(Being totally honest, polygon2d alone solves what i want to my game, i extended it to Sprite2D because seemed natural, and wanted Line2D but this one wasn't so straightforward)

@AThousandShips
Copy link
Member

Good then that was just a misunderstanding from the code, I meant there looked like there was some compile issues with the code (not from CI) so I asked

But this PR is not really in a good state for being ready it seems to be a work in progress, with all the unrelated changes and formatting

@Ezequias-Rodrigues
Copy link
Author

The unrelated file change is just a linebreak/whitespace. I tried changing scenetree to implement this, but after i discovered a better way to do it, i just deleted the function i was writing, and the linebreak/whitespace went unnoticed.
I'm fixing the styling rn, mostly whitespaces and tabs mixed. I'll pay closer attention to that in the future

@Ezequias-Rodrigues Ezequias-Rodrigues marked this pull request as draft January 9, 2026 13:07
@Ezequias-Rodrigues
Copy link
Author

Ezequias-Rodrigues commented Jan 9, 2026

I'll implement this "mouse_picking", good idea.

I think i don't quite understand what you mean in your second point. Isn't it expected that the input is "consumed", and ignored if it was already "consumed" by something up in the propagation path? (English is not my mother tongue, sorry)

The signals exists in control, i can "bring them up", which i haven't done yet because it may cause some unexpected behavior, or i can rename and put them at canvas item, something like "cursor_entered/exited/pressed". I'm accepting suggestions.

About the _has_point: will do.
Thanks for your feedback

@Ezequias-Rodrigues Ezequias-Rodrigues marked this pull request as draft January 9, 2026 17:59
@MarianoGnu
Copy link
Contributor

MarianoGnu commented Jan 9, 2026

The second point is related to the order in which inputs are propagated in the engine (however i was mistakenly beliving _gui_input was happening before _input)

If your Sprite captures mouse_enter, and then another Sprite captures the MouseMotion because you entered it, that was ment to cause the mouse_exit, and you click the mouse, then the Engine will still believe the mouse is inside the first sprite and not call mouse_exited. Also the order of propagation of _input is from top to bottom (in the scene tree) meaning that the node that has priority to set the input as handled is the one drawn behind, not in the top

All of this complex interactions is the reason why mouse picking was considered exclusive to Controls, _gui_input is processed from bottom to top, meaning the node that handles the input will be the one drawn last, and also viewport keeps internal state related to the last hovered Control to mark it as unhovered when another control takes that place

A simple workaround to mouse enter/exit in sprites is useing a TextureRect and override _has_point(local_position) to check the texture pixel and return color.a > 0.01

Polygon2D requires to override _input tho

@Ezequias-Rodrigues
Copy link
Author

Ezequias-Rodrigues commented Jan 9, 2026

Ok about the second point, i think that i'll have to make the change from scene_tree(so, my initial idea was not that far off, i should've tried that first instead of going for canvasitem)

In line 1435 in scene_tree.cpp, there's a loop that iterate over all the nodes, from the last to the first(if i got this correctly). So, i think that i should use scene_tree to keep track of which node is being focused(like viewport for Controls elements), and "intercept" the input in this iteration, which already exists so it shouldn't have much of a performance impact. This then should respect the priority order and, if i implement the tracking correctly, avoid the situation that you've described.
Is this logic sound? I'm still familiarizing myself with the engine source, so sorry if this seems kinda of a newbie question.

About the workarounds the sprite2D kinda of does that in _mouse_hit_test(i'll refactor it to _has_point for better clarity).

Note: Please correct me if i got anything wrong. I don't understand why the function the said loop is called _call_input_pause, but it seems that it is the one where the input propagation starts from.

@MarianoGnu
Copy link
Contributor

MarianoGnu commented Jan 9, 2026

as i pointed out, the same input is propagated several times trugh the game
first _input from top to bottom
then _gui_input from bottom to top, and only Control(s)
then _shortcut_input (i'm unaware of the order of this, and if is exclusive to controls, but doesn't matter in your case because only propagates keyboard inputs)
then _unhandled_key_input (again unaware of the order, but is still keyboard inputs
then _unhandled_input all nodes from top to bottom, this is a second chance to catch an input if wasn't handled previously
then and only then, Physics checks for mouse events, i think this happens always, no matter if event was handled or not

So, i dont know which one of all those events is the loop you are seeing in scene_tree, or maybe is all of them

this is all much better explained in the documentation

Ezequias Rodrigues added 12 commits January 14, 2026 13:32
Introduces a unified mouse picking system for CanvasItem, replacing per-node mouse signals in Sprite2D and Polygon2D with generic signals (mouse_entered_node, mouse_exited_node, mouse_pressed_node) in CanvasItem. Adds has_point and mouse_picking methods to CanvasItem and updates SceneTree to handle mouse picking and signal emission. Removes mouse-related signals and refactored _hit_mouse_test to has_point from Sprite2D and Polygon2D, centralizing logic in CanvasItem, that may be overriden.

Let's pray now that the code styling test won't obliterate this commit
I must concede that i don't fully understand why this won't compile for android/ios, but it think it has something to do with canvasitem being a "friend class", i'll take a while to study this later. For now, i will just hotfix it, because the casting is not really necessary i think
@AThousandShips
Copy link
Member

Please do not push so many times in a row, you are a first time contributor so you need approval to run CI, but otherwise this would really take up a lot of CI processing and time, so please make your changes in a batch

@Ezequias-Rodrigues
Copy link
Author

Ok, i'm sorry, i wasn't paying attention to this PR actually
I think i'll have to make a branch in my fork, all commits i send there are mirrored here and i wasn't really expecting that. My bad

@Ezequias-Rodrigues
Copy link
Author

Ezequias-Rodrigues commented Jan 14, 2026

Ok, i've tested the code by hand, and all 20 automated checks were successfully in my branch

I'm sorry for the mess i created, i really wanted to help but was not really familiarized with how things are done.

I hope my contribution help someone, and thank you for the patience.

Also, newbie question: These commits that appeared after i merged my branch: are they ok? i was expecting that only the final one showed up here

@MarianoGnu
Copy link
Contributor

MarianoGnu commented Jan 14, 2026

you need to squash the 36 commits in a single one to be considered for merging, this can wait until it's reviewed, but is good practice to keep the PR clean

@Ezequias-Rodrigues Ezequias-Rodrigues marked this pull request as ready for review January 14, 2026 21:11
@Ezequias-Rodrigues Ezequias-Rodrigues requested a review from a team as a code owner January 14, 2026 21:11
@Saulo-de-Souza
Copy link
Contributor

I believe this feature doesn't fit with Godot's methodology as specified in the documentation: https://docs.godotengine.org/en/stable/about/faq.html#why-does-godot-aim-to-keep-its-core-feature-set-small

@Ezequias-Rodrigues
Copy link
Author

Well, one can say that you can implement this feature with addons or scripts, but imho it's so convoluted, like, why do i need to use physics or create another function that checks for inputs to pick mouse events in a Sprite2D when the engine code already provides the base for this system? It's also not quite intuitive why this system won't affect canvas items but is present in controls.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants