Add wrapping keyboard and gamepad focus rings
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Skipped

- Link every modal's enabled controls into a wrapping UiFocus ring and give each screen a default focus target.
- Add gamepad A/B bindings for ui_accept/ui_cancel; include Start menu footer actions and Continue in its focus ring.
- Skip hidden/disabled controls, close focus traps via per-modal cancel behavior, and return review to the menu on cancel.
- Record the focus audit and add regression coverage.
This commit is contained in:
Gig Line Dev committed 2026-11-02 16:05:53 +01:00
1 parent 9692f262f1
commit 8f65751975
17 files changed
+262 -76

No files matched your search

+1
View File
@@ -43,6 +43,7 @@ func test_gamepad_bindings_are_added_once() -> bool:
if InputMap.action_get_deadzone(action) > 0.25:
problems.append("%s deadzone too high" % action)
var buttons := {"action_menu": JOY_BUTTON_A, "cancel": JOY_BUTTON_B,
"ui_accept": JOY_BUTTON_A, "ui_cancel": JOY_BUTTON_B,
"inspect": JOY_BUTTON_X, "walk_slow": JOY_BUTTON_LEFT_SHOULDER}
for action in buttons.keys():
var found := false
+8 -5
View File
@@ -34,16 +34,19 @@ func test_start_menu_emits_selected_venue() -> bool:
menu.free()
return a and b
func test_start_menu_clamps_focus() -> bool:
func test_start_menu_wraps_focus() -> bool:
var menu: Node = StartMenu.new()
menu._build()
menu._focus_index(-5)
var targets: Array = menu._focus_targets
menu._focus_index(-1)
var low: int = menu._index
menu._focus_index(99)
menu._focus_index(targets.size())
var high: int = menu._index
menu.free()
return TestUtils.check_eq(low, 0, "focus clamps at the top") \
and TestUtils.check_eq(high, StartMenu.VENUES.size() - 1, "focus clamps at the bottom")
return TestUtils.check_eq(low, targets.size() - 1, "focus wraps from the top to the bottom") \
and TestUtils.check_eq(high, 0, "focus wraps from the bottom to the top") \
and TestUtils.check(targets.size() > StartMenu.VENUES.size(),
"Continue and footer actions are in the keyboard focus ring")
func test_pause_overlay_buttons_emit() -> bool:
var overlay: Node = PauseOverlay.new()
+14
View File
@@ -4,6 +4,7 @@ extends RefCounted
## theme when a control is focused or disabled.
const UiStyles := preload("res://src/game/ui/ui_styles.gd")
const UiFocus := preload("res://src/game/ui/ui_focus.gd")
func test_shared_button_states_are_complete() -> bool:
var button := Button.new()
@@ -24,3 +25,16 @@ func test_shared_button_states_are_complete() -> bool:
"the disabled state is visually distinct from normal")
button.free()
return a and b and c
func test_focus_helper_skips_hidden_and_disabled_controls() -> bool:
var hidden := Button.new()
hidden.visible = false
var disabled := Button.new()
disabled.disabled = true
var normal := Button.new()
var first := UiFocus.first_enabled([hidden, disabled, normal])
var a := TestUtils.check_eq(first, normal, "focus skips hidden and disabled controls")
hidden.free()
disabled.free()
normal.free()
return a