godot
godot copied to clipboard
Buttons respond to input when set to PROCESS_MODE_DISABLED
Godot version
4.1.3.stable
System information
Godot v4.1.3.stable - Windows 10.0.22621 - Vulkan (Compatibility) - NVIDIA T500 (NVIDIA; 31.0.15.4601) - 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz (8 Threads)
Issue description
As I understand it, setting a node's process mode to Node.PROCESS_MODE_DISABLED should cause it to behave as if the scene tree were paused. This behavior is inconsistent in the case of Buttons. When the scene tree is paused, Buttons fail to respond to any input as expected; however, when Buttons are disabled via Node.process_mode, they continue to display active hover and focus behavior. This may be the case for other interactable Controls as well, I haven't tested.
Steps to reproduce
- Create any scene with a Button
- Set its process mode to "Disabled" in the Node settings
- Run the test scene and observe its behavior
Minimal reproduction project
N/A
related to #41487
Processing and input processing are different things. Buttons receive input events through "_gui_input" and there is no direct way to disable that. The inputs for that come from mouse or keyboard events while the control is focused. if you wish for your buttons to not receive input, you can arrange all pausable buttons in a layer and create a overlay on top of them which captures all clicks. if you wish you can even add blur or some color+opacity to this click blocker. also, upon pausing you may wish to store the focused control, and restore focus upon unpausing.
Disabling nodes via process mode does stop callbacks to _input() and _unhandled_input() though. I don't see why _gui_input() specifically should be an exception to that.
To add to the discussion: I am trying to find a proper way to disable all interactions for a certain element.
For example:
- A grid of buttons that when any one clicked brings up a popup.
- While the popup is open, the buttons in the background should not respond to hover or focussing (e.g. via keyboard tabbing)
- I don't want to disable all buttons manually as this is prone to errors
In web development for example there is the "inert" property that tells the browser to ignore all events for a node and its children but neither process_mode disabled nor mouse_filter ignore seem to function in such way.
@darthLeviN's approach sounds interesting (thanks for sharing ❤️) but I'd assume would still allow for keyboard tabbing? It seems kinda hacky (at least coming from web development).
To add to the discussion: I am trying to find a proper way to disable all interactions for a certain element.
For example:
- A grid of buttons that when any one clicked brings up a popup.
- While the popup is open, the buttons in the background should not respond to hover or focussing (e.g. via keyboard tabbing)
- I don't want to disable all buttons manually as this is prone to errors
In web development for example there is the "inert" property that tells the browser to ignore all events for a node and its children but neither
process_modedisablednormouse_filterignoreseem to function in such way.@darthLeviN's approach sounds interesting (thanks for sharing ❤️) but I'd assume would still allow for keyboard tabbing? It seems kinda hacky (at least coming from web development).
You can also use propagate_call. add a script to the controls you want.
the script needs to have
func custom_set_input_enabled(p_enabled: bool)
defined and call propagate_call(&"custom_set_input_enabled", [true/false]) on the parent node and it will search through the node recursively, and for every node that has the method, it will call the method.
i just have to add that this won't update the newly added nodes so you need to be careful with that. it only updates the nodes that are already there when you call the function.
If you want to go a step further and have multilayered control here and stop propagation at certain nodes, you will need to use a Autoload with a utility method that works similar to propagate_call but stops if it runs into a specific node group.
here is the implementaiton :
func layered_propagate_call(p_root: Node, p_func: StringName, p_args: Array, p_stopgroup := &"stop_propagation"):
if root.has_method(p_func):
root.callv(p_func, p_args)
for child in root.get_children():
if not child.is_in_group(p_stopgroup):
layered_propagate_call(child, p_func, p_args, p_stopgroup)
elif child.has_method(p_func):
child.callv(p_func, p_args)
You can then add the other root nodes to the "stop_propagation" group to make different layers of input
Another solution is to use propagate_call with a different function :
var input_blockers := {}
func custom_add_input_blocker(node):
if not input_blockers.has(node):
input_blockers[node] = true
node.tree_exiting.connect(_input_blocker_exiting_tree.bind(node), CONNECT_ONE_SHOT)
refresh_input_mode()
func custom_remove_input_blocker(node):
if input_blockers.has(node):
input_blockers.erase(node)
node.tree_exiting.disconnect(_input_blocker_exiting_tree)
refresh_input_mode()
func _input_blocker_exiting_tree(node):
input_blockers.erase(node)
refresh_input_mode()
func refresh_input_mode():
if input_blockers.is_empty():
# override to enabled input
pass
else:
# override to disabled input
pass
This will allow you to add input blockers recursively by calling custom_remove_input_blocker and custom_add_input_blcoker with the help of propagate_call
Reproduced (with much surprise) in v4.4.1.stable.official [49a5bc7b6], see attached project.