new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Godot 4 Patterns · all subjects

input_handling/focus

12 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Set focus_mode = NONE on display-only controls

Set focus_mode = NONE on Controls that should never take keyboard focus, such as RichTextLabel (display-only), ScrollContainer (containers), decorative ColorRect, Label, background rects, and overlay dim rects. This prevents them from intercepting focus.

Set mouse_filter = IGNORE on visual-only controls

Set mouse_filter = IGNORE on visual-only Controls to prevent clicks on them from eating input and blocking other controls. This applies to background ColorRect, decorative Labels, spacer Controls, and logo VBox. MOUSE_FILTER_PASS receives clicks; STOP swallows them. IGNORE is correct for non-interactive visuals.

Use viewport.gui_focus_changed signal for focus failsafe

Connect to get_viewport().gui_focus_changed to implement a bulletproof focus retention system. When the signal fires with new_focus == null, call call_deferred("grab_focus") on the target control to restore focus. This handles cases where focus is lost unexpectedly.

Example: gui_focus_changed failsafe implementation

func _ready() -> void: get_viewport().gui_focus_changed.connect(_on_focus_changed) input_line.call_deferred("grab_focus") func _on_focus_changed(new_focus: Control) -> void: if new_focus == null: input_line.call_deferred("grab_focus")

_process failsafe for focus recovery

As a heavyweight but bulletproof alternative, implement a _process failsafe that checks if no control has focus and restores it: if get_viewport().gui_get_focus_owner() == null, call grab_focus(). Use only when other approaches fail; calling grab_focus() is cheap.

Always use call_deferred("grab_focus") not grab_focus()

When calling grab_focus() inside signal handlers or _ready, use call_deferred("grab_focus") instead of direct grab_focus(). Direct calls may run before the scene tree is settled. call_deferred ensures the focus grab runs at the end of the frame after layout updates.

LineEdit loses focus after text_submitted signal

When a LineEdit loses focus immediately after text_submitted fires, the root cause is usually that a sibling or overlay (RichTextLabel, ScrollContainer, toast, notification) grabbed focus during render. The handler may have called grab_focus() before layout settled, or an overlay became visible and captured focus. Fix by clearing the input, using call_deferred("grab_focus"), then emitting signals.

Example: restore focus after text submission

func _on_input_submitted(text: String) -> void: input_line.clear() input_line.call_deferred("grab_focus") # Ensure focus is restored after layout GameSignals.command.emit(text)

Modal overlay focus pattern: open

When opening a modal overlay (inventory, pause menu), set visible = true, then find the first focusable Control and call grab_focus() on it. This ensures keyboard input goes to the overlay, not the background.

Modal overlay focus pattern: close

When closing a modal overlay, set visible = false. Do not call grab_focus() in the close handler. Let viewport.gui_focus_changed(null) fire and the terminal's failsafe handler pick up focus automatically. This prevents focus conflicts between overlay and background.

Block input to background when overlay is open

To prevent keyboard input from reaching the background (LineEdit, game controls) while an overlay is open, either set get_tree().paused = true in a PROCESS_MODE_WHEN_PAUSED overlay, or check overlay visibility in _unhandled_input and return early if any overlay is visible. Hidden (visible=false) is not the same as invisible-but-existing (modulate.a=0).

StringName comparison pitfall with focus logic

When comparing Node.name (which is StringName) to check node type in focus logic, do not compare directly to a String literal. Use str(node.name) == "Toast" or node.name == &"Toast" (& creates a StringName literal). Comparing StringName to String literal can fail and break focus blocking logic that depends on type checks.

Give your agent this brain