diff --git a/README.md b/README.md index ec59e86..9079e8f 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,11 @@ The launcher supports several compatibility options for Linux: - **discord-rpc**: Discord Rich Presence integration - **extract-zip**: ZIP archive extraction - **Tailwind CSS**: UI styling (via CDN) -- **UI Sounds**: Using the free version of [JDSherbert's Ultimate UI SFX Pack on itch.io](https://jdsherbert.itch.io/ultimate-ui-sfx-pack) + +## Assets + +- Controller button sprites: [greatdocbrown](https://greatdocbrown.itch.io/gamepad-ui) +- UI Sounds: Using the free version of [JDSherbert's Ultimate UI SFX Pack on itch.io](https://jdsherbert.itch.io/ultimate-ui-sfx-pack) ## Development diff --git a/index.html b/index.html index d20df4b..5740caf 100644 --- a/index.html +++ b/index.html @@ -274,6 +274,16 @@ +
+ + +
diff --git a/renderer.js b/renderer.js index f33b165..c52a9c3 100644 --- a/renderer.js +++ b/renderer.js @@ -617,6 +617,52 @@ function applyRepoPreset() { repoInput.value = presetSelect.value; } + +function applyControllerLayoutPresetState(layoutMode = 'auto') { + const presets = document.querySelectorAll('.controller-layout-preset'); + const activeLayout = layoutMode === 'nintendo' ? 'nintendo' : 'xbox'; + presets.forEach((preset) => { + const isActive = preset.dataset.layout === activeLayout; + preset.classList.toggle('active', isActive); + preset.setAttribute('aria-pressed', isActive ? 'true' : 'false'); + }); +} + +function initControllerLayoutPresets() { + const layoutSelect = document.getElementById('controller-layout-select'); + if (!layoutSelect) return; + + const presets = document.querySelectorAll('.controller-layout-preset'); + presets.forEach((preset) => { + const pressOn = () => preset.classList.add('is-pressed'); + const pressOff = () => preset.classList.remove('is-pressed'); + + preset.addEventListener('pointerdown', pressOn); + preset.addEventListener('pointerup', pressOff); + preset.addEventListener('pointerleave', pressOff); + preset.addEventListener('blur', pressOff); + + preset.addEventListener('keydown', (event) => { + if (event.key === 'Enter' || event.key === ' ') pressOn(); + }); + preset.addEventListener('keyup', (event) => { + if (event.key === 'Enter' || event.key === ' ') pressOff(); + }); + + preset.addEventListener('click', () => { + const selectedLayout = preset.dataset.layout || 'xbox'; + layoutSelect.value = selectedLayout; + applyControllerLayoutPresetState(selectedLayout); + }); + }); + + layoutSelect.addEventListener('change', () => { + applyControllerLayoutPresetState(layoutSelect.value || 'auto'); + }); + + applyControllerLayoutPresetState(layoutSelect.value || 'auto'); +} + window.onload = async () => { try { await migrateLegacyConfig(); @@ -641,6 +687,7 @@ window.onload = async () => { if (serverCheck) serverCheck.checked = currentInstance.isServer; if (installInput) installInput.value = currentInstance.installPath; if (controllerLayoutSelect) controllerLayoutSelect.value = await Store.get('legacy_controller_layout_mode', 'auto'); + initControllerLayoutPresets(); syncRepoPresetFromInput(); if (process.platform === 'linux' || process.platform === 'darwin') { @@ -1349,6 +1396,7 @@ async function toggleOptions(show) { const savedLayoutMode = await Store.get('legacy_controller_layout_mode', 'auto'); layoutSelect.value = savedLayoutMode; GamepadManager.setControlLayoutMode(savedLayoutMode); + applyControllerLayoutPresetState(savedLayoutMode); } syncRepoPresetFromInput(); document.activeElement?.blur(); modal.style.display = 'flex'; modal.style.opacity = '1'; @@ -1471,6 +1519,7 @@ async function saveOptions() { await Store.set('legacy_steamdeck_mode', isSteamDeckMode); await Store.set('legacy_controller_layout_mode', controllerLayoutMode); GamepadManager.setControlLayoutMode(controllerLayoutMode); + applyControllerLayoutPresetState(controllerLayoutMode); applyTheme(isClassic); applySteamDeckMode(isSteamDeckMode); await saveInstancesToStore(); toggleOptions(false); fetchGitHubData(); updatePlayButtonText(); showToast("Settings Saved"); @@ -1716,7 +1765,10 @@ async function loadControllerLayoutMode() { const mode = await Store.get('legacy_controller_layout_mode', 'auto'); GamepadManager.setControlLayoutMode(mode); const select = document.getElementById('controller-layout-select'); - if (select) select.value = mode; + if (select) { + select.value = mode; + applyControllerLayoutPresetState(mode); + } } function applySteamDeckMode(enabled) { diff --git a/style.css b/style.css index 9901299..8a14551 100644 --- a/style.css +++ b/style.css @@ -452,6 +452,18 @@ body.steamdeck-mode .nav-item:focus { box-shadow: 0 0 0 2px rgba(255,255,255,0.95), 0 0 22px rgba(255,255,255,0.7), 0 0 34px rgba(90,170,255,0.36) !important; } + +body.steamdeck-mode .controller-layout-preset { + min-height: 62px; + font-size: 22px; +} + +body.steamdeck-mode .controller-layout-icon { + transform: scale(1.3); + transform-origin: left center; + margin-right: 4px; +} + body.steamdeck-mode .sidebar { width: 420px; } @@ -671,6 +683,92 @@ body.steamdeck-mode .sidebar { border-color: #fff !important; } + +.controller-layout-presets { + display: grid; + grid-template-columns: 1fr; + gap: 10px; + margin-top: 12px; +} + +.controller-layout-preset { + width: 100%; + min-height: 52px; + border: 2px solid #555; + background: #0a0a0a; + color: #fff; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 12px; + padding: 8px 12px; + cursor: pointer; + font-size: 19px; + text-align: left; +} + +.controller-layout-preset:hover, +.controller-layout-preset:focus { + border-color: #fff; +} + +.controller-layout-preset.active { + border-color: #55ff55; + box-shadow: inset 0 0 0 1px #55ff55; + background: #111; +} + +.controller-layout-icon { + --sprite-x: -51px; + --sprite-y: -531px; + --sprite-w: 26px; + --sprite-h: 26px; + --sprite-pressed-x: -51px; + --sprite-pressed-y: -563px; + width: var(--sprite-w); + height: var(--sprite-h); + flex: 0 0 var(--sprite-w); + display: inline-block; + overflow: hidden; + image-rendering: pixelated; + background-repeat: no-repeat; + background-size: 560px 640px; + background-position: var(--sprite-x) var(--sprite-y); +} + +.controller-layout-preset:hover .controller-layout-icon, +.controller-layout-preset:focus .controller-layout-icon, +.controller-layout-preset.active .controller-layout-icon, +.controller-layout-preset.is-pressed .controller-layout-icon { + background-position: var(--sprite-pressed-x) var(--sprite-pressed-y); +} + +.controller-layout-icon-xbox { + background-image: url('gdb-xbox-2.png'); + /* action_button_189 (idle) + action_button_228 (pressed) from spritesheet JSON */ + --sprite-x: -51px; + --sprite-y: -531px; + --sprite-w: 26px; + --sprite-h: 26px; + --sprite-pressed-x: -51px; + --sprite-pressed-y: -563px; +} + +.controller-layout-icon-switch { + background-image: url('gdb-switch-2.png'); + /* Switch sheet uses the same atlas layout coordinates */ + --sprite-x: -51px; + --sprite-y: -531px; + --sprite-w: 26px; + --sprite-h: 26px; + --sprite-pressed-x: -51px; + --sprite-pressed-y: -563px; +} + +.controller-layout-text { + line-height: 1.2; +} + #server-checkbox.focused { outline: 2px solid #fff; }