Merge pull request #5 from rubiidev18alt/codex/integrate-controller-button-assets-into-ui

Add controller layout preset UI with interactions and sprite attribution
This commit is contained in:
rubiidev18alt 2026-03-12 00:02:01 -07:00 committed by GitHub
commit bb721c5ae0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 166 additions and 2 deletions

View file

@ -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

View file

@ -274,6 +274,16 @@
<option value="xbox">Xbox Style (A = Confirm, B = Cancel)</option>
<option value="nintendo">Nintendo Style (B = Confirm, A = Cancel)</option>
</select>
<div id="controller-layout-presets" class="controller-layout-presets" role="radiogroup" aria-label="Controller layout presets">
<button type="button" class="controller-layout-preset nav-item" data-layout="xbox" tabindex="0" aria-pressed="false">
<span class="controller-layout-icon controller-layout-icon-xbox" aria-hidden="true"></span>
<span class="controller-layout-text">Xbox Style (A = Confirm, B = Cancel)</span>
</button>
<button type="button" class="controller-layout-preset nav-item" data-layout="nintendo" tabindex="0" aria-pressed="false">
<span class="controller-layout-icon controller-layout-icon-switch" aria-hidden="true"></span>
<span class="controller-layout-text">Nintendo Style (B = Confirm, A = Cancel)</span>
</button>
</div>
</div>
<div class="flex gap-4 w-full mt-4">

View file

@ -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) {

View file

@ -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;
}