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?

  • Toxe and xyz replied to this.
  • 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.

    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.

    • xyz replied to this.

      popcar2 Any type of control node or just specific inherited type(s)?

        xyz Preferably any control node. I'd like to check and handle each one separately, so I'm thinking of doing something like:

        if clicked_control_node is ClassName:
            # (do stuff here)

        Where clicked_control_node was the last control node that got right clicked.

          popcar2 You could add all your Control nodes to a group to check which one should open a context menu.

          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.

          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.