From 88c1f183c992a2d901f87a94ad00c982058fcafb Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Thu, 12 Feb 2026 00:57:46 +0100 Subject: [PATCH] feat(nds): handle swipes --- .../Projects/BottomScreen/Buttons.vue | 2 + app/components/Screen.vue | 61 +++++++++++++++++++ .../BottomScreen/Menus/Options/2048.vue | 8 ++- .../BottomScreen/Menus/TouchScreen/TapTap.vue | 6 +- .../BottomScreen/Menus/User/Snake.vue | 4 ++ 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/app/components/Projects/BottomScreen/Buttons.vue b/app/components/Projects/BottomScreen/Buttons.vue index 80e7b57..216953c 100644 --- a/app/components/Projects/BottomScreen/Buttons.vue +++ b/app/components/Projects/BottomScreen/Buttons.vue @@ -195,12 +195,14 @@ useKeyDown(({ key, repeated }) => { return; switch (key) { + case "NDS_SWIPE_RIGHT": case "NDS_LEFT": if (store.currentProject > 0) { startButtonAnimation("prev"); store.scrollProjects("left"); } break; + case "NDS_SWIPE_LEFT": case "NDS_RIGHT": if (store.currentProject < store.projects.length - 1) { startButtonAnimation("next"); diff --git a/app/components/Screen.vue b/app/components/Screen.vue index 2e591b5..f67cc9e 100644 --- a/app/components/Screen.vue +++ b/app/components/Screen.vue @@ -69,6 +69,57 @@ const handleCanvasWheel = (event: WheelEvent) => { } }; +const SWIPE_THRESHOLD = 30; +let swipeStartX = 0; +let swipeStartY = 0; + +const dispatchSwipe = (endX: number, endY: number) => { + const deltaX = endX - swipeStartX; + const deltaY = endY - swipeStartY; + const absDeltaX = Math.abs(deltaX); + const absDeltaY = Math.abs(deltaY); + + if (Math.max(absDeltaX, absDeltaY) < SWIPE_THRESHOLD) return; + + let direction: string; + if (absDeltaX > absDeltaY) { + direction = deltaX > 0 ? "RIGHT" : "LEFT"; + } else { + direction = deltaY > 0 ? "DOWN" : "UP"; + } + + window.dispatchEvent( + new KeyboardEvent("keydown", { key: `NDS_SWIPE_${direction}` }), + ); +}; + +const handleTouchStart = (event: TouchEvent) => { + const touch = event.touches[0]; + if (!touch) return; + swipeStartX = touch.clientX; + swipeStartY = touch.clientY; +}; + +const handleTouchEnd = (event: TouchEvent) => { + const touch = event.changedTouches[0]; + if (!touch) return; + dispatchSwipe(touch.clientX, touch.clientY); +}; + +let mouseSwiping = false; + +const handleSwipeMouseDown = (event: MouseEvent) => { + mouseSwiping = true; + swipeStartX = event.clientX; + swipeStartY = event.clientY; +}; + +const handleSwipeMouseUp = (event: MouseEvent) => { + if (!mouseSwiping) return; + mouseSwiping = false; + dispatchSwipe(event.clientX, event.clientY); +}; + const renderFrame = (timestamp: number) => { if (!ctx) return; @@ -122,6 +173,12 @@ onMounted(() => { canvas.value.addEventListener("click", handleCanvasClick); canvas.value.addEventListener("mousedown", handleCanvasMouseDown); canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true }); + canvas.value.addEventListener("touchstart", handleTouchStart, { + passive: true, + }); + canvas.value.addEventListener("touchend", handleTouchEnd, { passive: true }); + canvas.value.addEventListener("mousedown", handleSwipeMouseDown); + document.addEventListener("mouseup", handleSwipeMouseUp); animationFrameId = requestAnimationFrame(renderFrame); }); @@ -135,7 +192,11 @@ onUnmounted(() => { canvas.value.removeEventListener("click", handleCanvasClick); canvas.value.removeEventListener("mousedown", handleCanvasMouseDown); canvas.value.removeEventListener("wheel", handleCanvasWheel); + canvas.value.removeEventListener("touchstart", handleTouchStart); + canvas.value.removeEventListener("touchend", handleTouchEnd); + canvas.value.removeEventListener("mousedown", handleSwipeMouseDown); } + document.removeEventListener("mouseup", handleSwipeMouseUp); }); defineExpose({ diff --git a/app/components/Settings/BottomScreen/Menus/Options/2048.vue b/app/components/Settings/BottomScreen/Menus/Options/2048.vue index d2837d7..2b0e1a7 100644 --- a/app/components/Settings/BottomScreen/Menus/Options/2048.vue +++ b/app/components/Settings/BottomScreen/Menus/Options/2048.vue @@ -252,7 +252,7 @@ onRender((ctx) => { 10, 2, 118, - false, + true, ); drawButton( ctx, @@ -260,7 +260,7 @@ onRender((ctx) => { 138, 2, 108, - false, + true, ); }, 110); @@ -559,15 +559,19 @@ useKeyDown(({ key }) => { ]; break; case "NDS_UP": + case "NDS_SWIPE_UP": slide(-1, 0); break; case "NDS_DOWN": + case "NDS_SWIPE_DOWN": slide(1, 0); break; case "NDS_LEFT": + case "NDS_SWIPE_LEFT": slide(0, -1); break; case "NDS_RIGHT": + case "NDS_SWIPE_RIGHT": slide(0, 1); break; } diff --git a/app/components/Settings/BottomScreen/Menus/TouchScreen/TapTap.vue b/app/components/Settings/BottomScreen/Menus/TouchScreen/TapTap.vue index f9163c2..13d3959 100644 --- a/app/components/Settings/BottomScreen/Menus/TouchScreen/TapTap.vue +++ b/app/components/Settings/BottomScreen/Menus/TouchScreen/TapTap.vue @@ -393,7 +393,7 @@ onRender((ctx) => { 10, 2, 88, - false, + true, ); drawButton( ctx, @@ -401,7 +401,7 @@ onRender((ctx) => { 108, 2, 88, - false, + true, ); drawButton( ctx, @@ -409,7 +409,7 @@ onRender((ctx) => { 206, 2, 40, - false, + true, ); }, 110); diff --git a/app/components/Settings/BottomScreen/Menus/User/Snake.vue b/app/components/Settings/BottomScreen/Menus/User/Snake.vue index 3d9f688..fddc90a 100644 --- a/app/components/Settings/BottomScreen/Menus/User/Snake.vue +++ b/app/components/Settings/BottomScreen/Menus/User/Snake.vue @@ -309,15 +309,19 @@ useKeyDown(({ key }) => { const newDirection = direction.clone(); switch (key) { case "NDS_UP": + case "NDS_SWIPE_UP": newDirection.set(0, -1); break; case "NDS_RIGHT": + case "NDS_SWIPE_RIGHT": newDirection.set(1, 0); break; case "NDS_DOWN": + case "NDS_SWIPE_DOWN": newDirection.set(0, 1); break; case "NDS_LEFT": + case "NDS_SWIPE_LEFT": newDirection.set(-1, 0); break; }