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

+21 -2
View File
@@ -1,6 +1,6 @@
# UI copy and button inventory
Status: dialogue and button-state passes complete; focus/layout audit in progress for 0.9.3.
Status: dialogue, button-state and keyboard/gamepad focus passes complete; layout/end-to-end audit in progress for 0.9.3.
## Screen inventory
@@ -109,10 +109,29 @@ appeared when a custom dark button was disabled or focused by keyboard.
| Radio | Send request, Send emergency, Cancel. Emergency hold uses press/release, so the action button stays enabled while holding. | All styled; disabling it during the hold would break confirmation. |
| Toggles | CheckButton/OptionButton controls keep the engine theme. | Intentional: the shared style targets action buttons, not checkbox/radio affordances. |
## Keyboard and gamepad focus
`UiFocus` builds an explicit focus ring for each modal, skips hidden and
disabled controls, and makes Up/Down, Tab/Shift+Tab and the D-pad/stick wrap.
`GuardController._ensure_gamepad_bindings()` adds A to `ui_accept` and B to
`ui_cancel`; the built-in directional UI actions already carry D-pad/stick
events.
| Surface | Default focus | Cancellation / trap check |
| --- | --- | --- |
| Start menu | First visible target: Continue when present, otherwise the first venue. | A/Enter/Space activates the focused row; B/Esc quits; Continue, venue rows and footer actions share one wrapping ring. |
| Pause | Resume. | B/Esc resumes; the four actions wrap. |
| Settings | First slider. | B/Esc cancels; focus ring covers sliders, toggles, difficulty, rebinds, Apply and Cancel; sliders keep Left/Right for value adjustment. |
| Help | Search field. | B/Esc closes; Search and Close share a ring. |
| Shift review | Continue when available, otherwise the timeline. | B/Esc returns to the menu; all four actions are in the ring and hidden Continue is skipped. |
| Timeline | First enabled action: Next when Previous is disabled, Close when empty. | B/Esc closes; Previous/Next leave the ring when disabled so focus cannot land on a dead button. |
| Radio | Send action. | B/Esc cancels; form fields, action and Cancel are linked; A confirms the focused action. |
| Tutorial | None; F1 is the documented skip shortcut. | Keyboard-only by design, no focus trap because the card is mouse-transparent and non-modal. |
## Remaining audit work
- Player-facing labels are now routed through `content/strings.en.json`; numeric
values, empty-state clears and debug-only text remain in code by design.
- Verify focus/hover/pressed/disabled states with keyboard, gamepad and mouse.
- Focus/hover/pressed/disabled states and keyboard/gamepad rings are recorded above.
- Test long strings and 1.75x HUD font scale.
- Capture every dialog/button state and run `screen_designs.py compare` plus the pinned judge.