From 1b97cedfb9deecd4d8c268d30cf4cbc78eb8bbe3 Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Wed, 4 Feb 2026 16:51:00 +0100 Subject: [PATCH] feat(settings/touchScreen/tapTap): implement --- .../Settings/BottomScreen/Menus/Menus.vue | 6 +- .../BottomScreen/Menus/TouchScreen/TapTap.vue | 349 ++++++++++++++++++ .../Settings/TopScreen/Notifications.vue | 1 + app/stores/settings.ts | 1 + app/utils/canvas.ts | 71 ++++ i18n/locales/en.json | 25 +- public/nds/images/common/button-body.webp | Bin 0 -> 86 bytes public/nds/images/common/button-left.webp | Bin 0 -> 88 bytes public/nds/images/common/button-right.webp | Bin 0 -> 88 bytes 9 files changed, 449 insertions(+), 4 deletions(-) create mode 100644 app/components/Settings/BottomScreen/Menus/TouchScreen/TapTap.vue create mode 100644 public/nds/images/common/button-body.webp create mode 100644 public/nds/images/common/button-left.webp create mode 100644 public/nds/images/common/button-right.webp diff --git a/app/components/Settings/BottomScreen/Menus/Menus.vue b/app/components/Settings/BottomScreen/Menus/Menus.vue index fec45f4..f21f2fd 100644 --- a/app/components/Settings/BottomScreen/Menus/Menus.vue +++ b/app/components/Settings/BottomScreen/Menus/Menus.vue @@ -15,6 +15,7 @@ import ClockAchievements from "./Clock/Achievements.vue"; import ClockDate from "./Clock/Date.vue"; import ClockTime from "./Clock/Time.vue"; import TouchScreenMenu from "./TouchScreen/Menu.vue"; +import TouchScreenTapTap from "./TouchScreen/TapTap.vue"; import Selector from "~/components/Common/ButtonSelector.vue"; const app = useAppStore(); @@ -60,7 +61,8 @@ const { select, selected, selectorPosition } = useButtonNavigation({ if (buttonName === "options") select("optionsLanguage"); if (buttonName === "clock") select("clockAchievements"); if (buttonName === "user") select("userUserName"); - if (buttonName === "touchScreen") throw new Error("Not implemented"); + if (buttonName === "touchScreen") + settingsStore.openSubMenu("touchScreenTapTap"); } }, navigation: { @@ -190,6 +192,8 @@ const viewComponents: Record = { userBirthday: UserBirthday, userUserName: UserUserName, userSnake: UserSnake, + + touchScreenTapTap: TouchScreenTapTap, }; diff --git a/app/components/Settings/BottomScreen/Menus/TouchScreen/TapTap.vue b/app/components/Settings/BottomScreen/Menus/TouchScreen/TapTap.vue new file mode 100644 index 0000000..cd52516 --- /dev/null +++ b/app/components/Settings/BottomScreen/Menus/TouchScreen/TapTap.vue @@ -0,0 +1,349 @@ + + + diff --git a/app/components/Settings/TopScreen/Notifications.vue b/app/components/Settings/TopScreen/Notifications.vue index 3f21f62..f4763d3 100644 --- a/app/components/Settings/TopScreen/Notifications.vue +++ b/app/components/Settings/TopScreen/Notifications.vue @@ -49,6 +49,7 @@ const IMAGES_MAP: Record = { userColor: assets.images.settings.topScreen.user.color, touchScreen: assets.images.settings.topScreen.touchScreen.touchScreen, + touchScreenTapTap: assets.images.settings.topScreen.touchScreen.touchScreen, }; const menuNotification = computed(() => { diff --git a/app/stores/settings.ts b/app/stores/settings.ts index 5a78528..03ac2e0 100644 --- a/app/stores/settings.ts +++ b/app/stores/settings.ts @@ -16,6 +16,7 @@ export const SETTINGS_SUB_MENUS = [ "userUserName", "userSnake", "userColor", + "touchScreenTapTap", ] as const; export type SettingsMenu = (typeof SETTINGS_MENUS)[number]; diff --git a/app/utils/canvas.ts b/app/utils/canvas.ts index 1aefeae..7878c1f 100644 --- a/app/utils/canvas.ts +++ b/app/utils/canvas.ts @@ -78,6 +78,41 @@ export const drawCheckbox = ( } }; +export const fillCirclePixelated = ( + ctx: CanvasRenderingContext2D, + cx: number, + cy: number, + radius: number, +) => { + const r = Math.floor(radius); + for (let dy = -r; dy <= r; dy++) { + for (let dx = -r; dx <= r; dx++) { + if (dx * dx + dy * dy <= r * r) { + ctx.fillRect(Math.floor(cx) + dx, Math.floor(cy) + dy, 1, 1); + } + } + } +}; + +export const strokeCirclePixelated = ( + ctx: CanvasRenderingContext2D, + cx: number, + cy: number, + radius: number, + thickness: number = 2, +) => { + const outerR = Math.floor(radius); + const innerR = Math.floor(radius - thickness); + for (let dy = -outerR; dy <= outerR; dy++) { + for (let dx = -outerR; dx <= outerR; dx++) { + const distSq = dx * dx + dy * dy; + if (distSq <= outerR * outerR && distSq > innerR * innerR) { + ctx.fillRect(Math.floor(cx) + dx, Math.floor(cy) + dy, 1, 1); + } + } + } +}; + export const fillTextWordWrapped = ( ctx: CanvasRenderingContext2D, text: string, @@ -120,3 +155,39 @@ export const fillTextWordWrapped = ( return lineCount * height; }; + +export const drawButton = ( + ctx: CanvasRenderingContext2D, + text: string, + x: number, + y: number, + width: number, +) => { + const { assets } = useAssets((a) => a.images.common); + + const LEFT_WIDTH = assets.buttonLeft.rect.width; + const RIGHT_WIDTH = assets.buttonRight.rect.width; + const BODY_WIDTH = assets.buttonBody.rect.width; + + const bodySpace = width - LEFT_WIDTH - RIGHT_WIDTH; + const bodyCount = Math.ceil(bodySpace / BODY_WIDTH); + + ctx.save(); + ctx.translate(x, y); + + assets.buttonLeft.draw(ctx, 0, 0); + + for (let i = 0; i < bodyCount; i++) { + assets.buttonBody.draw(ctx, LEFT_WIDTH + i * BODY_WIDTH, 0); + } + + assets.buttonRight.draw(ctx, width - RIGHT_WIDTH, 0); + + ctx.textBaseline = "top"; + ctx.font = "10px NDS10"; + ctx.fillStyle = "#494118"; + + fillTextHCentered(ctx, text, 1, 4, width); + + ctx.restore(); +}; diff --git a/i18n/locales/en.json b/i18n/locales/en.json index a5de017..1eaf8e1 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -7,7 +7,9 @@ "restart": "Restart", "reset": "Reset", "select": "Select", - "goBack": "Go back" + "goBack": "Go back", + "yes": "Yes", + "no": "No" }, "achievementsScreen": { "title": "Achievements" @@ -23,6 +25,11 @@ "settings_color_change": "Change the system\ncolor", "snake_play": "Play Snake", "snake_score_40": "Score 40 points\nin Snake", + "2048_play": "Play 2048", + "2048_score_512": "Reach the 512 tile\nin 2048", + "taptap_play": "Play TapTap", + "taptap_score_10": "Score 10 points\nin TapTap", + "taptap_score_20": "Score 20 points\nin TapTap", "settings_color_try_all": "Try all colors", "settings_language_try_all": "Try all languages", "settings_visit_all": "Visit all settings\nsubmenus", @@ -130,14 +137,26 @@ "description": "Play a quick game of Snake!", "score": "Score: {score}", "best": "Best: {best}", - "startPrompt": "[A] Start", + "startPrompt": "Press icon_a to start.", "quitConfirmation": "Quit the game?", "restartConfirmation": "Restart the game?" } }, "touchScreen": { "title": "Touch Screen", - "description": "TODO" + "description": "Touch screen calibration and games.", + "tapTap": { + "title": "TapTap", + "description": "Tap the circles before they disappear!", + "startPrompt": "Press icon_a to start.", + "score": "Score: {score}", + "best": "Best: {best}", + "gameOver": "Game Over!", + "finalScore": "Final Score: {score}", + "newRecord": "New Record!", + "quitConfirmation": "Quit the game?", + "restartConfirmation": "Restart the game?" + } } }, "contact": { diff --git a/public/nds/images/common/button-body.webp b/public/nds/images/common/button-body.webp new file mode 100644 index 0000000000000000000000000000000000000000..e15c106e97e04cb900cfb300234e4d1056b2ff39 GIT binary patch literal 86 zcmWIYbaV4#U|xKa&x+723Mv?~3QU$B4n+cgH^iA(_6LLj98 literal 0 HcmV?d00001 diff --git a/public/nds/images/common/button-left.webp b/public/nds/images/common/button-left.webp new file mode 100644 index 0000000000000000000000000000000000000000..e4e02091cab783ffbedad971cf853a64330a25a6 GIT binary patch literal 88 zcmWIYbaM+}U|0mb;+r_C%k(p*tU!T05HlP1ONa4 literal 0 HcmV?d00001