feat(android): console UI — per-controller glyphs, dialog scrolling, animated forms

- Hint-bar glyphs now wear the driving controller's family (kit
  Gamepad.styleFor by USB vendor id → MainActivity.lastPadStyle, kept live by
  real input like lastPadIsGamepad): PlayStation pads get Canvas-drawn
  cross/circle/square/triangle shapes in the classic colours, Nintendo pads
  monochrome lettering, Xbox/Valve/unknown the coloured letter discs. Hint
  chars stay semantic (KEYCODE_BUTTON names); only the rendering changes.
- The Options legend renders the pad's real Select-family button
  (SelectButtonGlyph): Xbox View windows, PlayStation Create capsule,
  Nintendo minus — instead of a bare capsule outline.
- GamepadDialog: body + action stack scroll together (title pinned) with
  BringIntoViewRequester keeping the focused button visible — a 5-action host
  options dialog compressed/clipped its last button in short landscape
  windows because the pinned stack could not scroll.
- Console form polish: shared animateConsoleFocus (bg/border cross-fade +
  spring scale) across settings rows / add-host fields / action rows;
  ConsoleSwitch (spring knob, tinting track) replaces On/Off text on toggle
  rows; choice values slide in the direction they were stepped
  (AnimatedContent + SizeTransform) with chevrons that fade in place; the
  focused row's detail unfolds via AnimatedVisibility; dialog buttons and
  keyboard keycaps cross-fade (keycaps at 90 ms for hold-to-repeat).
- Console settings gain the "Low-latency mode" (Video) and "Auto-wake on
  connect" (Interface) rows, round-tripping with the touch settings.
- Screenshot scene: StatsOverlay call updated to the 18-double layout + the
  new decoderLabel parameter (fixes the android-screenshots CI compile).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 07:34:42 +02:00
parent e490564316
commit f992298da8
7 changed files with 412 additions and 76 deletions
@@ -57,6 +57,7 @@ object Gamepad {
private const val VID_SONY = 0x054C
private const val VID_MICROSOFT = 0x045E
private const val VID_VALVE = 0x28DE
private const val VID_NINTENDO = 0x057E
// Sony product ids. DualSense (PS5) and DualShock 4 (PS4) map to distinct host pad types.
private val PID_DUALSENSE = setOf(0x0CE6, 0x0DF2)
@@ -98,6 +99,28 @@ object Gamepad {
}
}
/**
* The glyph family a controller's physical buttons belong to, for the console UI's hint bar —
* so a DualSense user sees ✕/○/□/△ shapes and a Switch pad its monochrome lettering instead of
* Xbox's coloured letters. PURELY visual: the wire mapping ([buttonBit]) is unaffected.
*/
enum class PadStyle { GENERIC, XBOX, PLAYSTATION, NINTENDO }
/**
* Resolve the [PadStyle] for a connected controller by USB vendor id. Vendor alone is enough —
* every pad a vendor ships wears its family's glyphs (any Sony pad has the shapes, any Nintendo
* pad the /+ system buttons), so unlike [prefFor] no PID table is needed. Valve renders as
* [PadStyle.XBOX]: Steam pads carry A/B/X/Y in Xbox positions. Unknown vendors (8BitDo & co.,
* which near-universally clone the Xbox layout) fall back to [PadStyle.GENERIC], drawn with the
* Xbox convention.
*/
fun styleFor(dev: InputDevice?): PadStyle = when (dev?.vendorId) {
VID_SONY -> PadStyle.PLAYSTATION
VID_MICROSOFT, VID_VALVE -> PadStyle.XBOX
VID_NINTENDO -> PadStyle.NINTENDO
else -> PadStyle.GENERIC
}
/** True when [dev]'s source classes include gamepad or joystick. */
fun isPad(dev: InputDevice?): Boolean {
val s = dev?.sources ?: return false