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.

    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.

    • xyz replied to this.

      popcar2 If you have existing scripts attached to controls, you can inject signal handling piece of code into their source text and reattach modified scripts at startup. A bit hacky but should work.

        popcar2 You can also make your approach more lightweight by using a plain RefCounted object instead of a node to receive control's signal.

        xyz "sender" would be a nice feature for signals to have by default, especially ones forwarding events. I wonder if anyone has proposed that for the engine.

          award "sender" would be a nice feature for signals to have by default, especially ones forwarding events. I wonder if anyone has proposed that for the engine.

          Yeah, it surely would be nice having it. Make a request.

          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.

          • xyz replied to this.

            award This should be set as the best answer.