Rework the virtual pad user interface across its three main surfaces.
VirtualControllerView renders controls from per-axis visible and hit-box
scales, centers each button face inside its larger touch target, resolves
art through the imported-skin descriptor with per-side analog base/stick
file fallbacks (including the legacy analog-button names), and gates the
UIKit press surface on iOS/iPadOS 27 while keeping SwiftUI drag gestures
otherwise.
PadLayoutEditView adds a selection-driven sizing panel with linked and
unlinked width/height steppers for visible and hit scales, overlap
detection, undo, plus toolbar actions to create, rename, duplicate,
update, delete, export, and per-game-assign layout presets and to pick the
skin.
VirtualPadSettingsView switches to the skin library for choosing, renaming,
and deleting imported skins, selects the global default layout preset, and
imports and exports layouts.
Cycle the on-screen display through Off, Simple, Detail, and Full from the
quick menu instead of a single on/off toggle, surfacing the new preset
label as a status message. Respect the display safe-area insets when
positioning the performance and RetroAchievements toasts so they clear the
notch and home indicator.
Resolve the virtual pad layout and skin per game at runtime. The per-game
settings panel gains a Virtual Pad section to assign a layout preset and
skin to the current game, apply a skin's linked layout, open the editor
scoped to the game, and reset layout, skin, or all vpad overrides back to
the global defaults. Saving is gated on the game having a settings
identity.
GameScreenView derives the active game identity, feeds the effective
layout snapshot and skin descriptor into the controller, and opens the
layout editor with a per-game context. The in-game controller skin menu
switches to the imported-skin library.
Add per-game EE Cycle Rate and Fast Boot overrides through the emulator
bridge. setGameSettings gains eeCycleRate and fastBoot parameters with
their override flags, the global game settings result reports the global
defaults, and per-game overrides are read, clamped, and written into the
EmuCore Speedhacks and Fast Boot INI keys. A negative EE underclock is
clamped back to zero while RetroAchievements hardcore mode is active.
The per-game settings panel surfaces EE Cycle Rate and Fast Boot pickers
with reset-to-global controls, and gates saving on the game having a
settings identity.
Extend the controller skin archive extractor so imported skin packs can
ship a manifest.json that references a linked layout document, and accept
the linked layout JSON alongside the button/full-skin images. Bound the
extractor against hostile or malformed archives with per-entry byte and
total-entry limits, and route JSON entries through a dedicated import-name
check so only manifest-allowed JSON files are extracted.
Add PadLayoutImportExport to serialize a layout preset to and from a
.layout.json document, with validation and clamping on import, and
ShareSheet as a UIActivityViewController wrapper for exporting files.
Register both in the iOS build.
Add PadLayoutPresetStore, a named, persisted collection of layout
snapshots that can be created, renamed, duplicated, updated, and deleted.
Presets back a global default selection plus per-game overrides and carry
safe fallbacks so a deleted or missing layout falls back to the next
available preset or the live current layout. The store also tracks the
effective skin descriptor and linked layout for each game identity and
exposes import helpers used by the layout sharing feature. Register
PadLayoutPresetStore in the iOS build.
Add a skin library that can hold multiple imported skins alongside the
built-in ones, each with a stable descriptor, display name, and optional
linked layout preset. VPadSkinLibraryStore manages importing skins from
loose images or archives into per-skin asset directories, normalizing
JPG/JPEG/WebP inputs, resolving duplicate display names, and tracking the
globally selected skin.
VirtualPadSkin gains built-in/legacy descriptor identifiers and separates
the active imported assets directory from the legacy single custom-skin
directory, and SettingsStore adopts any pre-existing legacy custom skin
selection into the new library on load. Register VPadSkinLibraryStore in
the iOS build.
Rework the pad layout data model around per-axis visible and hit-box
sizing. PadGroupPosition now stores scaleX/scaleY and hitScaleX/hitScaleY,
with uniform scale/hitScale accessors kept for backward compatibility, and
gains a per-axis initializer plus Codable/Equatable conformance.
Add PadLayoutMetrics for clamping and visible/touch length math, an
injectable PadLayoutINIStore protocol so the store can be exercised
without the live bridge, and group/per-button update helpers for visible,
hit, and unlinked per-axis sizing including aspect re-linking. Persist the
six per-axis keys alongside the legacy single-axis keys so older readers
keep working, and read them back through a fallback chain. Add
snapshot/apply helpers used by the editor, preset store, and importer.
Remove the dedicated UIKit gameplay hosting controller that was used to
force the home indicator and deferred system gestures while playing.
Gameplay now returns to a plain SwiftUI screen switch driven by
AppState.currentScreen, which drops the periodic system-chrome refresh
timer and its presentation-update notification plumbing.