How to make mouse cursor only visible when hovering over GUI?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By NaughtyGnosiophile

I have a GUI at the bottom of the screen and I would like to make the mouse pointer only visible when hovering over it.

The GUI has a Panel as its root node and a whole tree of controls and containers under it, like most typical GUIs.

On start up, I immediately set the mouse to hidden.

I put this code in the root Panel:

func _on_BottomBar_mouse_entered():
	Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

func _on_BottomBar_mouse_exited():
	Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)

But the mouse pointer remains hidden, although it does flicker visible a little bit when I move the mouse.

I reckon it’s because the child controls are blocking the input to the root panel? So I tried setting the mouse Filter to “pass” on every child, but I get the exact same result.

Do I need to put the above code in every single child of my GUI? That seems like a ridiculous solution.

I reckon it’s because the child controls are blocking the input to the root panel? So I tried setting the mouse Filter to “pass” on every child, but I get the exact same result.

What happens when you hide all the child controls and only leave the root panel visible?

Calinou | 2018-07-27 21:17

Do you connect the methods to the Panel using code, or do you use the Signals menu of the Node tab?

SASUPERNOVA | 2018-07-27 22:18

I created a test ui with only a Panel as the root with the above code. When I run the test ui as a standalone scene, the mouse pointer appears and disappears properly. When I added the test ui to my main game scene, something weird happens. When I hover over the test ui, the mouse point turns into the “spinning blue circle” that windows gives you when it’s trying to do something. When I exit the panel, the pointer disappears like it should.

I am use the signals menu to connect, not code.

NaughtyGnosiophile | 2018-07-27 23:41

This seems to be a bug with the Godot Engine (see GitHub Issue), that will be fixed in Godot 3.1. The problem seems to only occur while the cursor is inside the window though. That is, as soon as the cursor goes out of the window and then back inside the window, the behavior of the cursor works as expected.

SASUPERNOVA | 2018-07-28 00:44

Ok thanks. Seems to have solved the mystery of my wait pointer. But still need a solution to my original question.

I tried placing an “invisible” control (I used ReferenceRectangle) as a child of the root Panel than covers the entire GUI. I now get the proper pointer appearing/disappearing behaviour, but I can’t click on anything underneath even though I set the filter to “pass”. Seems like the ReferenceRectangle is eating the clicks.

NaughtyGnosiophile | 2018-07-28 10:10

:bust_in_silhouette: Reply From: SASUPERNOVA

As I previously mentioned, the “spinning circle” cursor error is a bug in Godot engine, so you could probably wait until Godot 3.1 is released, and use the code you were previously using. Alternatively, you could try to take advantage of the fact that the issue is resolved if the cursor exits and re-enters the window by using the following code:

func _on_BottomBar_mouse_entered():
    Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
    var curPos = get_global_mouse_position() #Save position where cursor entered the Panel
    get_viewport().warp_mouse(Vector2(-50, -50)) # "Teleport" cursor outside of the window. Any position outside of the window will work. (-50, -50) is merely used as an example
    get_viewport().warp_mouse(curPos) #Bring cursor back to its original position

Note however that this is more of a hack than a fix, and it will not work if the window is running on full-screen mode, though it should work if the window is simply maximized. Furthermore, you will probably still see the spinning circle for a split second the first time the cursor enters the Panel, after which point it will default to its intended behavior.

Edit: After some testing, I noticed that warping above the window might not work if the window is maximized. An alternative solution would be to warp below the window, so that the cursor temporarily goes to the taskbar (e.g. by using Vector2(OS.get_real_window_size().x, OS.get_real_window_size().y + 20) instead).

This solves the wait cursor problem. Thanks.

I found a solution to my original problem.

In the root Panel of my GUI, I changed the code to:

func _ready():
	var children = get_tree().get_nodes_in_group("bottom_bar_control")
	for child in children:
		child.connect("gui_input", self, "_on_gui_input")
	
func _on_gui_input(event):
	Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

Then in the node that follows my mouse around in the main game screen I added:

func _unhandled_input(event):
	Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)

This works, but it still feels like a really clunky solution to me. And I’m worried that the constant calls to set_mouse_mode will be a performance issue.

Would still like a better solution, but this works for now.

NaughtyGnosiophile | 2018-07-28 14:31