How to perform artificial input on Controls?

Godot Version

Godot 4.6.3

Question

Hey folks!

I’m trying to make InputEvent propagation do something…unconventional, and hopefully this is a solvable problem.

In my game, I have these computers that feature a SubViewport which is displayed on the computer mesh’s screen. When you examine the computer in-game, your inputs are routed to the Controls parented under the ScreenViewport and explicitly drive the movement of a TextureRect of a mouse cursor.

I want to make the mouse cursor properly usable, where if you move that cursor over a button control, it will trigger its focus_enter signal, and passing it an InputEvent with a “left mouse press” will activate it. The way I think I want to accomplish this is by fabricating InputEvents in GDScript with the info I want (position of the cursor, translating the player’s interact key to be a “left mouse button press”, etc) and then letting that loose into the SubViewport to work as it would normally.

I’ve tried manually emitting the SubViewport’s gui_input(event) signal, but this didn’t work. Is there a way I can accomplish this?

You can use .push_event(your_input_event) on the SubViewport to replicate or create custom events inside the viewport. I’ve made subviewports into clickable 3D menus with this, duplicating the click event and using .push_event.

Ah, thanks for this insight! I feel like this gets me really close to a working solution. My SubViewport is now getting input events. This is successfully having ‘_input’ get called, but not ‘_gui_input’ or Controls receiving input-related callbacks. Should that work normally, or is there a special setup needed for that?

Huh _gui_input is listed as working in the docs, I haven’t tested it much myself as I try to avoid _gui_input where possible. Tough to recommend any action without code, what types of inputs are you trying to send? Only mouse clicks? are you setting the event’s position too?

Okay back after sleep and errands and whatnot.

I created a blank Godot project that implements SubViewport input pushing with a setup that gives you a “cursor” and a Control-based button. But here’s the important piece of code:

class_name InputTest
extends Node

@export var viewport: SubViewport
@export var cursor: Control
@export var button: Button

func _ready() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
	if button:
		button.pressed.connect(_on_button_pressed)

func _input(event: InputEvent) -> void:
	if event is InputEventKey:
		if event.keycode == KEY_ESCAPE: get_tree().quit()
		
	if event is InputEventMouseMotion:
		cursor.position += event.relative
		cursor.position.x = clampf(cursor.position.x, 0, 512)
		cursor.position.y = clampf(cursor.position.y, 0, 512)
		event.position = cursor.position
		event.global_position = cursor.position
		viewport.push_input(event)
		
	if event.is_action_pressed("interact"):
		var click: InputEventMouseButton = InputEventMouseButton.new()
		click.button_index = 0
		click.pressed = true
		click.position = cursor.position
		click.global_position = cursor.position
		viewport.push_input(click)
		

func _on_button_pressed() -> void:
	print("Button Pressed!")

I believe I’m implementing this correctly, but I’m not getting any reactions from the GUI element.

Button index 0 is NONE, this spits out a ton of errors in the debugger, assign it to the proper enum MOUSE_BUTTON_LEFT. Then your scene is setup in such a way that this Control node is blocking mouse input, set “SpriteContainer” mouse filter to Ignore, and the TextureRect too.

Garrett, you are a true hero. Thank you so much!! Nobody can stop me now :heart_on_fire: