How to get target of a UI click?

Godot Version

4.4-stable

Question

I have a UI container that instantiates multiple child nodes of type CharacterSelect. I supress mouse clicks on the child nodes of CharacterSelect, margin_container is the direct child of the CharacterSelect root node.

func _ready() -> void:
	margin_container.propagate_call("set_mouse_filter", [Control.MOUSE_FILTER_IGNORE])

I want to change the background color of the CharacterSelect container when one is clicked:

func _input(event):
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
			var stylebox: StyleBoxFlat = get_theme_stylebox("panel").duplicate()
			stylebox.bg_color = Color.RED
			add_theme_stylebox_override("panel", stylebox)

That works, but the bg_color of all CharacterSelect containers changes, not just the one I clicked on.

Is there any way to get the target element of a click? Or do I misunderstand some fundamentals regarding mouse click handling?

I solved my problem by comparing the event.position with the bounds of the UI element:

if (event_pos.x >= position.x && event_pos.x <= position.x + size.x):
    if (event_pos.y >= position.y && event_pos.y <= position.y + size.y):
        var stylebox: StyleBoxFlat = get_theme_stylebox("panel").duplicate()
    	stylebox.bg_color = Color.RED
        add_theme_stylebox_override("panel", stylebox)

But I’d be interested if there is a simpler solution?

One thing you may want to consider is collision masks. so I have an inputManager but i guess you could put this in the main? heres how I handle it;
the card and deck have different collision masks set on their scene via area2D

#const collision_mask_card = 2
#const collision_mask_deck = 4

I set the collision masks in a global

func _input(event):
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
		if event.pressed:
			emit_signal("left_mouse_button_clicked")
			raycast_at_cursor()
		else:
			emit_signal("left_mouse_button_released")

and then something to handle the raycast:

func raycast_at_cursor():
	var space_state = get_world_2d().direct_space_state
	var parameters = PhysicsPointQueryParameters2D.new()
	parameters.position = get_global_mouse_position()
	parameters.collide_with_areas = true
	var result = space_state.intersect_point(parameters)
	if result.size() > 0:
		var result_collision_mask = result[0].collider.collision_mask
		
		if result_collision_mask == Global.collision_mask_card:
			#card clicked code
		elif result_collision_mask == Global.collision_mask_deck:
			#deck clicked code
		else:
			pass

hope this was helpful!

If CharacterSelect is a Control then you can use Control._gui_input() instead of _input()

1 Like

Oh I didn’t know about that method, you are right, it works fine with that one!