feat(2d-nds): physical buttons

This commit is contained in:
2026-02-23 14:32:26 +01:00
parent 2a976f9db2
commit 01aefc15b4

View File

@@ -6,6 +6,49 @@ const app = useAppStore();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const hintsContainer = useTemplateRef("hintsContainer"); const hintsContainer = useTemplateRef("hintsContainer");
const buttonsDown = reactive(new Set<string>());
let mousePressedButton: string | null = null;
const dPadStyle = computed(() => {
const rx =
(buttonsDown.has("UP") ? -1 : 0) + (buttonsDown.has("DOWN") ? 1 : 0);
const ry =
(buttonsDown.has("LEFT") ? -1 : 0) + (buttonsDown.has("RIGHT") ? 1 : 0);
if (!rx && !ry) return {};
return { transform: `rotateX(${rx * 12}deg) rotateY(${ry * 12}deg)` };
});
const pressButton = (button: string) => {
if (mousePressedButton) {
buttonsDown.delete(mousePressedButton);
window.dispatchEvent(
new KeyboardEvent("keyup", { key: `NDS_${mousePressedButton}` }),
);
}
buttonsDown.add(button);
mousePressedButton = button;
window.dispatchEvent(new KeyboardEvent("keydown", { key: `NDS_${button}` }));
};
const releaseButton = () => {
if (!mousePressedButton) return;
buttonsDown.delete(mousePressedButton);
window.dispatchEvent(
new KeyboardEvent("keyup", { key: `NDS_${mousePressedButton}` }),
);
mousePressedButton = null;
};
useKeyDown(({ ndsButton }) => {
if (ndsButton) buttonsDown.add(ndsButton);
});
useKeyUp(({ ndsButton }) => {
if (ndsButton) buttonsDown.delete(ndsButton);
});
useMouseUp(releaseButton);
const ndsScale = computed(() => { const ndsScale = computed(() => {
const scaleX = (windowSize.width.value - 40) / 235; const scaleX = (windowSize.width.value - 40) / 235;
const scaleY = (windowSize.height.value - 40) / 431; const scaleY = (windowSize.height.value - 40) / 431;
@@ -59,25 +102,77 @@ defineExpose({ ndsScale });
<div class="nds2d-d-pad-shadow"></div> <div class="nds2d-d-pad-shadow"></div>
<div class="nds2d-d-pad"> <div class="nds2d-d-pad">
<div class="nds2d-d-pad-inner" :style="dPadStyle">
<div class="nds2d-d-pad-base"></div> <div class="nds2d-d-pad-base"></div>
<div class="nds2d-d-pad-light"></div> <div class="nds2d-d-pad-light"></div>
<div class="nds2d-d-pad-tip nds2d-tip-up"></div> <div
<div class="nds2d-d-pad-tip nds2d-tip-down"></div> class="nds2d-d-pad-tip nds2d-tip-up"
<div class="nds2d-d-pad-tip nds2d-tip-left"></div> :class="{ pressed: buttonsDown.has('UP') }"
<div class="nds2d-d-pad-tip nds2d-tip-right"></div> @mousedown.prevent="pressButton('UP')"
></div>
<div
class="nds2d-d-pad-tip nds2d-tip-down"
:class="{ pressed: buttonsDown.has('DOWN') }"
@mousedown.prevent="pressButton('DOWN')"
></div>
<div
class="nds2d-d-pad-tip nds2d-tip-left"
:class="{ pressed: buttonsDown.has('LEFT') }"
@mousedown.prevent="pressButton('LEFT')"
></div>
<div
class="nds2d-d-pad-tip nds2d-tip-right"
:class="{ pressed: buttonsDown.has('RIGHT') }"
@mousedown.prevent="pressButton('RIGHT')"
></div>
<div class="nds2d-d-pad-marker nds2d-dpm-up"></div> <div class="nds2d-d-pad-marker nds2d-dpm-up"></div>
<div class="nds2d-d-pad-marker nds2d-dpm-down"></div> <div class="nds2d-d-pad-marker nds2d-dpm-down"></div>
<div class="nds2d-d-pad-marker nds2d-dpm-left"></div> <div class="nds2d-d-pad-marker nds2d-dpm-left"></div>
<div class="nds2d-d-pad-marker nds2d-dpm-right"></div> <div class="nds2d-d-pad-marker nds2d-dpm-right"></div>
</div> </div>
</div>
<div class="nds2d-button nds2d-btn-x">X</div> <div
<div class="nds2d-button nds2d-btn-a">A</div> class="nds2d-button nds2d-btn-x"
<div class="nds2d-button nds2d-btn-b">B</div> :class="{ pressed: buttonsDown.has('X') }"
<div class="nds2d-button nds2d-btn-y">Y</div> @mousedown.prevent="pressButton('X')"
>
X
</div>
<div
class="nds2d-button nds2d-btn-a"
:class="{ pressed: buttonsDown.has('A') }"
@mousedown.prevent="pressButton('A')"
>
A
</div>
<div
class="nds2d-button nds2d-btn-b"
:class="{ pressed: buttonsDown.has('B') }"
@mousedown.prevent="pressButton('B')"
>
B
</div>
<div
class="nds2d-button nds2d-btn-y"
:class="{ pressed: buttonsDown.has('Y') }"
@mousedown.prevent="pressButton('Y')"
>
Y
</div>
<div class="nds2d-small-button nds2d-start"></div> <div
<div class="nds2d-small-button nds2d-select"></div> class="nds2d-small-button nds2d-start"
:class="{ pressed: buttonsDown.has('START') }"
@mousedown.prevent="pressButton('START')"
></div>
<div class="nds2d-small-button-label nds2d-start-label">START</div>
<div
class="nds2d-small-button nds2d-select"
:class="{ pressed: buttonsDown.has('SELECT') }"
@mousedown.prevent="pressButton('SELECT')"
></div>
<div class="nds2d-small-button-label nds2d-select-label">SELECT</div>
<div <div
ref="hintsContainer" ref="hintsContainer"
@@ -335,6 +430,13 @@ defineExpose({ ndsScale });
); );
} }
.nds2d-d-pad-inner {
position: absolute;
width: 100%;
height: 100%;
transition: transform 0.06s ease;
}
.nds2d-d-pad-base { .nds2d-d-pad-base {
position: absolute; position: absolute;
width: 100%; width: 100%;
@@ -351,6 +453,10 @@ defineExpose({ ndsScale });
.nds2d-d-pad-tip { .nds2d-d-pad-tip {
position: absolute; position: absolute;
cursor: pointer;
transition:
filter 0.06s ease,
box-shadow 0.06s ease;
} }
.nds2d-tip-up { .nds2d-tip-up {
@@ -361,6 +467,11 @@ defineExpose({ ndsScale });
box-shadow: inset 0 2px 3px -1px #444; box-shadow: inset 0 2px 3px -1px #444;
} }
.nds2d-tip-up.pressed {
filter: brightness(0.75);
box-shadow: inset 0 6px 4px -6px #222;
}
.nds2d-tip-down { .nds2d-tip-down {
left: 19px; left: 19px;
bottom: 0; bottom: 0;
@@ -369,6 +480,11 @@ defineExpose({ ndsScale });
box-shadow: inset 0 -2px 3px -1px #444; box-shadow: inset 0 -2px 3px -1px #444;
} }
.nds2d-tip-down.pressed {
filter: brightness(0.75);
box-shadow: inset 0 -6px 4px -6px #222;
}
.nds2d-tip-left { .nds2d-tip-left {
left: 0; left: 0;
top: 19px; top: 19px;
@@ -377,6 +493,11 @@ defineExpose({ ndsScale });
box-shadow: inset 2px 0 3px -1px #444; box-shadow: inset 2px 0 3px -1px #444;
} }
.nds2d-tip-left.pressed {
filter: brightness(0.75);
box-shadow: inset 6px 0 4px -6px #222;
}
.nds2d-tip-right { .nds2d-tip-right {
right: 0; right: 0;
top: 19px; top: 19px;
@@ -385,9 +506,15 @@ defineExpose({ ndsScale });
box-shadow: inset -2px 0 3px -1px #444; box-shadow: inset -2px 0 3px -1px #444;
} }
.nds2d-tip-right.pressed {
filter: brightness(0.75);
box-shadow: inset -6px 0 4px -6px #222;
}
.nds2d-d-pad-marker { .nds2d-d-pad-marker {
position: absolute; position: absolute;
background: #999; background: #999;
pointer-events: none;
} }
.nds2d-dpm-up, .nds2d-dpm-up,
@@ -424,6 +551,7 @@ defineExpose({ ndsScale });
.nds2d-button { .nds2d-button {
user-select: none; user-select: none;
cursor: pointer;
position: absolute; position: absolute;
width: 21px; width: 21px;
height: 21px; height: 21px;
@@ -438,6 +566,16 @@ defineExpose({ ndsScale });
box-shadow: box-shadow:
3px 2px 3px -2px #111, 3px 2px 3px -2px #111,
inset 2px 2px 3px -1px #444; inset 2px 2px 3px -1px #444;
transition:
box-shadow 0.06s ease,
transform 0.06s ease;
}
.nds2d-button.pressed {
box-shadow:
1px 1px 1px -1px #111,
inset 2px 2px 3px -1px #222;
transform: scale(0.91) translateY(1px);
} }
.nds2d-btn-x { .nds2d-btn-x {
@@ -462,6 +600,7 @@ defineExpose({ ndsScale });
.nds2d-small-button { .nds2d-small-button {
user-select: none; user-select: none;
cursor: pointer;
position: absolute; position: absolute;
width: 11px; width: 11px;
height: 11px; height: 11px;
@@ -473,6 +612,16 @@ defineExpose({ ndsScale });
box-shadow: box-shadow:
3px 2px 3px -2px #111, 3px 2px 3px -2px #111,
inset 2px 2px 3px -1px #444; inset 2px 2px 3px -1px #444;
transition:
box-shadow 0.06s ease,
transform 0.06s ease;
}
.nds2d-small-button.pressed {
box-shadow:
1px 1px 1px -1px #111,
inset 1px 1px 2px -1px #222;
transform: scale(0.88) translateY(1px);
} }
.nds2d-select { .nds2d-select {
@@ -480,22 +629,21 @@ defineExpose({ ndsScale });
right: 70px; right: 70px;
} }
.nds2d-start::after { .nds2d-small-button-label {
position: absolute; position: absolute;
content: "START";
font-size: 7px; font-size: 7px;
color: #999; color: #999;
left: 15px; pointer-events: none;
top: 0px;
} }
.nds2d-select::after { .nds2d-start-label {
position: absolute; bottom: 53px;
content: "SELECT"; right: 44px;
font-size: 7px; }
color: #999;
left: 15px; .nds2d-select-label {
top: 0px; bottom: 29px;
right: 40px;
} }
.nds2d-hints-container { .nds2d-hints-container {