Created
March 24, 2026 15:00
-
-
Save HungryProton/d5c2acdd71bcfc3dba17fe040204929f to your computer and use it in GitHub Desktop.
Hack to force the controls focus to follow the hover
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| extends Node | |
| ## User Interface Manager | |
| ## Add as an autoload | |
| ## | |
| ## Handle switching between gamepad and M+K | |
| ## Automatically gives focus to a hovered button | |
| signal gamepad_changed(enabled: bool) | |
| var _controls: Dictionary = {} # A list of controls that can grab focus | |
| var _last_focused: Control | |
| var _viewport: Viewport | |
| var _gamepad_active: bool = true: | |
| set(val): | |
| if _gamepad_active == val: | |
| return | |
| _gamepad_active = val | |
| if _gamepad_active: | |
| Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) | |
| else: | |
| Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) | |
| gamepad_changed.emit(_gamepad_active) | |
| func _enter_tree() -> void: | |
| process_mode = Node.PROCESS_MODE_ALWAYS | |
| _viewport = get_tree().get_root().get_viewport() | |
| get_tree().node_added.connect(_on_node_added) | |
| get_tree().node_removed.connect(_on_node_removed) | |
| _viewport.gui_focus_changed.connect(_on_gui_focus_changed) | |
| func _input(event: InputEvent) -> void: | |
| _gamepad_active = event is InputEventJoypadButton or event is InputEventJoypadMotion | |
| # Ensure pressing any UI_ACTION will focus a valid control (if there is any). | |
| # Useful when switching from M+K to gamepad when nothing is focused. | |
| func _unhandled_input(event: InputEvent) -> void: | |
| if event is InputEventMouseMotion or event is InputEventMouseButton: | |
| return | |
| if _viewport.gui_get_focus_owner(): | |
| return # A control already have the focus | |
| var ui_action_triggered := false | |
| for action: String in UI_ACTION: | |
| if Input.is_action_just_pressed(action): | |
| ui_action_triggered = true | |
| break | |
| # User pressed one of the ui action | |
| if ui_action_triggered: | |
| # If nothing was focused already, find a visible control on screen | |
| if not is_instance_valid(_last_focused) or not _last_focused.is_visible_in_tree(): | |
| for control: Control in _controls.keys(): | |
| if control.is_visible_in_tree(): | |
| _last_focused = control | |
| break | |
| if not _last_focused: | |
| return | |
| _last_focused.grab_focus() | |
| _viewport.set_input_as_handled() | |
| func is_gamepad_active() -> bool: | |
| return _gamepad_active | |
| func _on_control_hovered(control: Control) -> void: | |
| if control.focus_mode != Control.FOCUS_NONE: | |
| control.grab_focus.call_deferred() | |
| func _on_control_hovered_stopped(control: Control) -> void: | |
| control.release_focus() | |
| func _on_node_added(node: Node) -> void: | |
| if not node is Control: | |
| return | |
| var control := node as Control | |
| if control.focus_mode != Control.FOCUS_NONE: | |
| _controls[node] = true | |
| if control is Button: | |
| var button := node as Button | |
| button.pressed.connect(_on_button_pressed.bind(button)) | |
| button.mouse_entered.connect(_on_control_hovered.bind(button)) | |
| button.mouse_exited.connect(_on_control_hovered_stopped.bind(button)) | |
| func _on_node_removed(node: Node) -> void: | |
| if node is Control: | |
| var _res := _controls.erase(node) | |
| func _on_gui_focus_changed(control: Control) -> void: | |
| if is_instance_valid(control): | |
| _last_focused = control | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment