I'm wondering if there's a way to get the Control node you're currently clicking. I'm aware of gui_get_focus_owner() but I'm not using focus on most of my nodes. Is there a way to get which Control node I'm clicking at any time, without having to implement an on click function for each node separately?
Is there a global way to get the Control node I'm clicking?
popcar2 No I don't think so.
One solution could be to once when your scene is set up go through all your child nodes either recursively or with find_children()
, check if a node is a Control and then connect its pressed signal to one central signal handler. That way you don't have to connect all the signals manually.
xyz What do you need this for?
I'm implementing a feature to open a context menu on right click. I want to populate the context menu depending on which Control nodes were clicked. The context menu is an autoload and I was hoping to keep all related code inside of it, but as it stands I would have to write code for every single control node to handle right clicks and call the context menu.
- Best Answerset by popcar2
popcar2 If controls don't run any code, make a script class that inherits Control. In this class declare an enhanced input event signal that forwards regular input event with added control identifier argument. On startup recursively set this script class as a script for every control of interest and connect each control's signal to a central handler.
- Edited
popcar2 Here's an example:
magic_control_script.gd
extends Control
signal my_event(who, what)
func _gui_input(event):
my_event.emit(self, event)
Then in main script:
func _ready():
for c in get_tree().get_nodes_in_group("my_controls"):
c.set_script(preload("res://magic_control_script.gd"))
c.connect("my_event", handler)
func handler(who, what):
print(who,", ", what)
The Godot editor knows which control was last clicked:
Debugger / Misc / Clicked Control
It might be worthwhile to dig into the source code and see if that's something that could be done in your project.
- Edited
xyz This is clever! And I've finally figured it out: Adding a component to each right clickable node. It's a bit roundabout but it works. Thanks everyone for all the ideas.
context_menu_manager.gd (Autoload)
var right_click_handler: PackedScene = preload("[...]/right_click_handler.tscn")
func _ready():
get_tree().node_added.connect(_add_right_click_handler)
func _add_right_click_handler(node: Node):
if node.is_in_group("right_click_enabled"):
node.add_child(right_click_handler.instantiate())
func handle_right_click(node: Control):
pass # Handle stuff here and show context menu
right_click_handler.tscn is just a Node that has right_click_handler.gd, so I won't have to replace any scripts:
extends Node
func _ready():
get_parent().gui_input.connect(_parent_gui_input)
func _parent_gui_input(event: InputEvent):
if event is InputEventMouseButton and event.button_index == 2 and event.is_pressed():
ContextMenu.handle_right_click(get_parent())
So basically the context menu manager adds a little component which calls it whenever its parent gets right clicked, sending the control node with it. I wonder what the performance hit for doing this is, but it's probably not big enough to care.
It looks like someone submitted a PR for it a while back that just hasn't made it in.
https://github.com/godotengine/godot/pull/60143
Since Rindbee made it and KoBeWi wants it, I'm sure it will make it in eventually.
I've created a proposal for a function to get all Control nodes at a position: https://github.com/godotengine/godot-proposals/issues/8408
Not sure how feasible that is from the engine side, but hopefully someone can look into it
I just realized there is a way to do this sort of thing without any script injection.
extends Control
func _ready() -> void:
attach_callback(self)
func attach_callback(node:Node) -> void:
if node is Control:
var cb = Callable(handler).bind(node)
node.gui_input.connect(cb)
for child in node.get_children():
attach_callback(child)
func handler(what:InputEvent, who:Node) -> void:
print(what, who)
We just use bind
to get the signal to send itself back to us.