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

+20 -5
View File
@@ -11,6 +11,7 @@ const Rules := preload("res://src/core/rules.gd")
const Strings := preload("res://src/core/strings.gd")
const UiArt := preload("res://src/game/ui/art.gd")
const UiStyles := preload("res://src/game/ui/ui_styles.gd")
const UiFocus := preload("res://src/game/ui/ui_focus.gd")
var panel: PanelContainer
var npc_name_label: Label
@@ -19,6 +20,8 @@ var desc_edit: LineEdit
var weapons_check: CheckBox
var danger_option: OptionButton
var action_button: Button
var cancel_button: Button
var _focus_controls: Array[Control] = []
var hold_hint: Label
var status_label: Label
@@ -110,11 +113,14 @@ func _init() -> void:
action_button.pressed.connect(_on_action_pressed)
UiStyles.apply_button(action_button, 16)
buttons.add_child(action_button)
var cancel_btn := Button.new()
cancel_btn.text = Strings.lookup("ui.radio_cancel")
cancel_btn.pressed.connect(func(): cancelled.emit())
UiStyles.apply_button(cancel_btn, 16)
buttons.add_child(cancel_btn)
cancel_button = Button.new()
cancel_button.text = Strings.lookup("ui.radio_cancel")
cancel_button.pressed.connect(func(): cancelled.emit())
UiStyles.apply_button(cancel_button, 16)
buttons.add_child(cancel_button)
_focus_controls = [location_edit, desc_edit, weapons_check, danger_option,
action_button, cancel_button]
UiFocus.link(_focus_controls, false)
var _title_label: Label
@@ -148,6 +154,8 @@ func open(controller: Object, emergency: bool, truth: Dictionary) -> void:
_holding = false
_hold_time = 0.0
visible = true
UiFocus.link(_focus_controls, false)
action_button.grab_focus()
## Non-emergency requests are a normal click. Emergency calls require the
## press-and-hold path, so the click release must not submit early.
@@ -200,3 +208,10 @@ func _danger_name(level: int) -> String:
2: return "elevated"
3: return "immediate threat"
_: return "unknown"
func _unhandled_input(event: InputEvent) -> void:
if not visible:
return
if event.is_action_pressed("ui_cancel"):
cancelled.emit()
get_viewport().set_input_as_handled()