Hide cursor arrow and focus buttons

Godot Version

4.3

Question

`Hi. I have a main menu and I want players to navigate it by using their keyboard or a mouse. I want the cursor arrow to be hidden from the get go, and to only be visible if the player uses the mouse. If the player uses the mouse, I want the buttons to lose focus. If the player presses a key, then the first button would gain focus and the cursor arrow would hide again.

I’ve been trying to do it with MouseNode and MOUSE_FILTER_IGNORE but it doesn’t seem to work. Does anyone know how to do it? Thanks!`

I have tried implementing the autohide feature for the mouse before. Try using the Input.mouse_mode feature. You can set it into different modes like setting it to Input.MOUSE_MODE_HIDDEN by default on _ready(). Then, you can do something like this:

func _unhandled_input(event):
	if event is InputEventMouseMotion:
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE

func _process(delta):
	if Input.get_last_mouse_velocity() == Vector2.ZERO:
		Input.mouse_mode = Input.MOUSE_MODE_HIDDEN

or add a timer to have a little delay before hiding the mouse. As for the keypress and the focus-defocus cycle, you can add a condition to the unhandled input block like so:

func _unhandled_input(event):
	if event is InputEventMouseMotion:
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
		$Button.release_focus()
	elif event is InputEventKey and not event.is_echo():
		Input.mouse_mode = Input.MOUSE_MODE_HIDDEN
		$Button.grab_focus()

You can try and play around with those and see if you achieve your desired behavior. You could put the buttons inside a panel container and then use something like an @export var menu_buttons which you can use to get all the buttons by calling menu_buttons.get_children(). Hope this helps!

2 Likes
func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
	if event is InputEventKey:
		Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

Also, I do not recommend using MOUSE_MODE_HIDDEN because the mouse is still active and can be moved and clicked causing weird things to happen you don’t intend. It can also cause weird “bugs” that turn out to be things that you allowed but didn’t realize you allowed.

2 Likes

Yeah, I see the problem with that too. It should be combined with the mouse_filter properties of Control nodes for a better performance.

2 Likes

Hi ! I’m late to the party, but I also have a solution to propose.
There are probably better ways to do it, but I think it will works either way.

This is the result in my test scene:

device_focus_switch

And this is the scene setup:

This is the script I made (I made it verbose so it would be easier to modify to suit your needs)

extends Control

## The name of the input device used 
@export var current_input_device:StringName = ""

## Controls that will be ignored by mouse in Keyboard mode, but solid in Mouse mode
@export var controls_to_toggle_for_mouse:Array[Control]

## Control to focus on when the user switch from mouse to keyboard
@export var control_foccused_by_keyboard_on_switch:Control

func _ready() -> void:
	# assume the user is using a keyboard at first
	handle_input_device.call_deferred("Keyboard")

func _input(event: InputEvent) -> void:
	# When a key is pressed, assume the user is using a keyboard
	if event is InputEventKey:
		handle_input_device("Keyboard")
		
	# When the mouse is moving around, assume the user is using the mouse
	if event is InputEventMouseMotion:
		handle_input_device("Mouse")

## handles the UI focus/mouse filters based on the device (either Mouse or Keyboard)
func handle_input_device(device:StringName):
	# If the user is already using the same device, dont do anything
	if current_input_device == device : return
	current_input_device = device
	get_viewport().set_input_as_handled()
	
	# Setup the menu to work with a keyboard
	if current_input_device == "Keyboard":
		Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
		control_foccused_by_keyboard_on_switch.grab_focus()
		
		# These controls now ignore the mouse
		for control in controls_to_toggle_for_mouse:
			control.mouse_filter = MouseFilter.MOUSE_FILTER_IGNORE

	# Setup the menu to work with the mouse
	elif current_input_device == "Mouse":
		get_viewport().gui_release_focus()
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
		# These controls now detects the mouse
		for control in controls_to_toggle_for_mouse:
			control.mouse_filter = MouseFilter.MOUSE_FILTER_STOP

Code without comments
extends Control

@export var current_input_device:StringName = ""
@export var controls_to_toggle_for_mouse:Array[Control]
@export var control_foccused_by_keyboard_on_switch:Control

func _ready() -> void:
	handle_input_device.call_deferred("Keyboard")


func _input(event: InputEvent) -> void:
	if event is InputEventKey:
		handle_input_device("Keyboard")

	if event is InputEventMouseMotion:
		handle_input_device("Mouse")



func handle_input_device(device:StringName):
	if current_input_device == device : return
	current_input_device = device
	get_viewport().set_input_as_handled()
	
	if current_input_device == "Keyboard":
		Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
		control_foccused_by_keyboard_on_switch.grab_focus()
		
		for control in controls_to_toggle_for_mouse:
			control.mouse_filter = MouseFilter.MOUSE_FILTER_IGNORE

	elif current_input_device == "Mouse":
		get_viewport().gui_release_focus()
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

		for control in controls_to_toggle_for_mouse:
			control.mouse_filter = MouseFilter.MOUSE_FILTER_STOP

I hope this helps !

5 Likes

Thank you all so much!

So far, dragonforge-dev’s solution works the best, but MOUSE_MODE_CAPTURED still allows me to move the cursor and click the mouse around the center of the screen, and it doesn’t become visible when you use it after you’ve pressed a key.

I feel that I can fix that on my own by using mouse_filter maybe. I’ve tried a few things but nothing really works, but I’ll keep trying until I get it right.

Thanks again!

2 Likes