feat(2d-nds): physical buttons

This commit is contained in:
2026-02-23 14:32:26 +01:00
parent 36e4e85016
commit 4a2575a7bd

View File

@@ -6,6 +6,49 @@ const app = useAppStore();
const windowSize = useWindowSize();
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 scaleX = (windowSize.width.value - 40) / 235;
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">
<div class="nds2d-d-pad-base"></div>
<div class="nds2d-d-pad-light"></div>
<div class="nds2d-d-pad-tip nds2d-tip-up"></div>
<div class="nds2d-d-pad-tip nds2d-tip-down"></div>
<div class="nds2d-d-pad-tip nds2d-tip-left"></div>
<div class="nds2d-d-pad-tip nds2d-tip-right"></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-left"></div>
<div class="nds2d-d-pad-marker nds2d-dpm-right"></div>
<div class="nds2d-d-pad-inner" :style="dPadStyle">
<div class="nds2d-d-pad-base"></div>
<div class="nds2d-d-pad-light"></div>
<div
class="nds2d-d-pad-tip nds2d-tip-up"
:class="{ pressed: buttonsDown.has('UP') }"
@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-down"></div>
<div class="nds2d-d-pad-marker nds2d-dpm-left"></div>
<div class="nds2d-d-pad-marker nds2d-dpm-right"></div>
</div>
</div>
<div class="nds2d-button nds2d-btn-x">X</div>
<div class="nds2d-button nds2d-btn-a">A</div>
<div class="nds2d-button nds2d-btn-b">B</div>
<div class="nds2d-button nds2d-btn-y">Y</div>
<div
class="nds2d-button nds2d-btn-x"
:class="{ pressed: buttonsDown.has('X') }"
@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 class="nds2d-small-button nds2d-select"></div>
<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
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 {
position: absolute;
width: 100%;
@@ -351,6 +453,10 @@ defineExpose({ ndsScale });
.nds2d-d-pad-tip {
position: absolute;
cursor: pointer;
transition:
filter 0.06s ease,
box-shadow 0.06s ease;
}
.nds2d-tip-up {
@@ -361,6 +467,11 @@ defineExpose({ ndsScale });
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 {
left: 19px;
bottom: 0;
@@ -369,6 +480,11 @@ defineExpose({ ndsScale });
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 {
left: 0;
top: 19px;
@@ -377,6 +493,11 @@ defineExpose({ ndsScale });
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 {
right: 0;
top: 19px;
@@ -385,9 +506,15 @@ defineExpose({ ndsScale });
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 {
position: absolute;
background: #999;
pointer-events: none;
}
.nds2d-dpm-up,
@@ -424,6 +551,7 @@ defineExpose({ ndsScale });
.nds2d-button {
user-select: none;
cursor: pointer;
position: absolute;
width: 21px;
height: 21px;
@@ -438,6 +566,16 @@ defineExpose({ ndsScale });
box-shadow:
3px 2px 3px -2px #111,
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 {
@@ -462,6 +600,7 @@ defineExpose({ ndsScale });
.nds2d-small-button {
user-select: none;
cursor: pointer;
position: absolute;
width: 11px;
height: 11px;
@@ -473,6 +612,16 @@ defineExpose({ ndsScale });
box-shadow:
3px 2px 3px -2px #111,
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 {
@@ -480,22 +629,21 @@ defineExpose({ ndsScale });
right: 70px;
}
.nds2d-start::after {
.nds2d-small-button-label {
position: absolute;
content: "START";
font-size: 7px;
color: #999;
left: 15px;
top: 0px;
pointer-events: none;
}
.nds2d-select::after {
position: absolute;
content: "SELECT";
font-size: 7px;
color: #999;
left: 15px;
top: 0px;
.nds2d-start-label {
bottom: 53px;
right: 44px;
}
.nds2d-select-label {
bottom: 29px;
right: 40px;
}
.nds2d-hints-container {