feat(settings/options/rendering-mode): implement
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const { onRender } = useScreen();
|
||||
|
||||
const app = useAppStore();
|
||||
const store = useHomeStore();
|
||||
const { assets } = useAssets();
|
||||
|
||||
@@ -44,7 +45,11 @@ onRender((ctx) => {
|
||||
|
||||
// icons
|
||||
assets.images.home.topScreen.statusBar.gbaDisplay.draw(ctx, 210, 2);
|
||||
assets.images.home.topScreen.statusBar.startupMode.draw(ctx, 226, 2);
|
||||
if (app.settings.renderingMode === "3d") {
|
||||
assets.images.home.topScreen.statusBar._3dMode.draw(ctx, 226, 2);
|
||||
} else {
|
||||
assets.images.home.topScreen.statusBar._2dMode.draw(ctx, 226, 2);
|
||||
}
|
||||
assets.images.home.topScreen.statusBar.battery.draw(ctx, 242, 4);
|
||||
});
|
||||
|
||||
|
||||
@@ -394,6 +394,8 @@ const handleMouseUp = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
app.ready = true;
|
||||
|
||||
if (renderer) {
|
||||
renderer.instance.domElement.addEventListener("mousedown", handleClick);
|
||||
renderer.instance.domElement.addEventListener("mouseup", handleMouseUp);
|
||||
|
||||
@@ -1,13 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import { until } from "@vueuse/core";
|
||||
|
||||
const app = useAppStore();
|
||||
const store = useSettingsStore();
|
||||
const confirmationModal = useConfirmationModal();
|
||||
const { onRender } = useScreen();
|
||||
const { assets } = useAssets();
|
||||
const renderingModeAssets =
|
||||
assets.images.settings.bottomScreen.options.renderingMode;
|
||||
|
||||
const { selected, selectorPosition } = useButtonNavigation({
|
||||
buttons: {
|
||||
_3dMode: [11, 27, 233, 74],
|
||||
_2dMode: [11, 91, 233, 74],
|
||||
},
|
||||
initialButton: app.settings.renderingMode === "3d" ? "_3dMode" : "_2dMode",
|
||||
navigation: {
|
||||
_3dMode: { down: "_2dMode" },
|
||||
_2dMode: { up: "_3dMode" },
|
||||
},
|
||||
selectorAnimation: {
|
||||
ease: "none",
|
||||
duration: 0.065,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
store.closeSubMenu();
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
const mode = selected.value === "_3dMode" ? "3d" : "2d";
|
||||
app.setRenderingMode(mode);
|
||||
|
||||
const showConfirmation = () => {
|
||||
confirmationModal.open({
|
||||
text: `Rendering mode set to ${mode === "3d" ? "3D" : "2D"}`,
|
||||
onClosed: () => {
|
||||
store.closeSubMenu();
|
||||
},
|
||||
});
|
||||
setTimeout(() => confirmationModal.close(), 2000);
|
||||
};
|
||||
|
||||
until(() => app.ready)
|
||||
.toBeTruthy()
|
||||
.then(showConfirmation);
|
||||
};
|
||||
|
||||
onRender((ctx) => {
|
||||
ctx.font = "10px NDS10";
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.fillText("Startup", 10, 20);
|
||||
});
|
||||
assets.images.home.topScreen.background.draw(ctx, 0, 0);
|
||||
|
||||
defineOptions({
|
||||
render: () => null,
|
||||
ctx.font = "10px NDS10";
|
||||
ctx.textBaseline = "top";
|
||||
|
||||
const drawButton = (
|
||||
title: string,
|
||||
logo: AtlasImage,
|
||||
y: number,
|
||||
active: boolean,
|
||||
) => {
|
||||
ctx.save();
|
||||
ctx.translate(16, y);
|
||||
|
||||
if (active) {
|
||||
renderingModeAssets.buttonActive.draw(ctx, 0, 0, { colored: true });
|
||||
} else {
|
||||
renderingModeAssets.button.draw(ctx, 0, 0);
|
||||
}
|
||||
|
||||
const buttonWidth = renderingModeAssets.button.rect.width;
|
||||
|
||||
ctx.fillStyle = "#000000";
|
||||
fillImageTextHCentered(ctx, logo, title, 0, 4, buttonWidth, 4);
|
||||
|
||||
ctx.fillStyle = "#282828";
|
||||
const text =
|
||||
"The Main Menu will appear\nautomatically when you turn\nthe power on.";
|
||||
fillTextHCenteredMultiline(ctx, text, 0, y, buttonWidth, 15);
|
||||
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
drawButton(
|
||||
"3D Mode",
|
||||
renderingModeAssets._3dMode,
|
||||
32,
|
||||
selected.value === "_3dMode",
|
||||
);
|
||||
drawButton(
|
||||
"2D Mode",
|
||||
renderingModeAssets._2dMode,
|
||||
96,
|
||||
selected.value === "_2dMode",
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonButtons
|
||||
:y-offset="confirmationModal.buttonsYOffset"
|
||||
b-label="Cancel"
|
||||
a-label="Confirm"
|
||||
@activate-b="handleCancel"
|
||||
@activate-a="handleConfirm"
|
||||
/>
|
||||
|
||||
<CommonButtonSelector :rect="selectorPosition" />
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const { onRender } = useScreen();
|
||||
|
||||
const app = useAppStore();
|
||||
const { assets } = useAssets();
|
||||
|
||||
onRender((ctx) => {
|
||||
@@ -39,7 +40,11 @@ onRender((ctx) => {
|
||||
|
||||
// icons
|
||||
assets.images.home.topScreen.statusBar.gbaDisplay.draw(ctx, 210, 2);
|
||||
assets.images.home.topScreen.statusBar.startupMode.draw(ctx, 226, 2);
|
||||
if (app.settings.renderingMode === "3d") {
|
||||
assets.images.home.topScreen.statusBar._3dMode.draw(ctx, 226, 2);
|
||||
} else {
|
||||
assets.images.home.topScreen.statusBar._2dMode.draw(ctx, 226, 2);
|
||||
}
|
||||
assets.images.home.topScreen.statusBar.battery.draw(ctx, 242, 4);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { Screen as NDSScreen } from "#components";
|
||||
|
||||
const ENABLE_3D = true;
|
||||
|
||||
type ScreenInstance = InstanceType<typeof NDSScreen>;
|
||||
|
||||
const { isReady } = useAssets();
|
||||
@@ -15,6 +13,8 @@ const bottomScreen = useTemplateRef<ScreenInstance>("bottomScreen");
|
||||
const topScreenCanvas = computed(() => topScreen.value?.canvas ?? null);
|
||||
const bottomScreenCanvas = computed(() => bottomScreen.value?.canvas ?? null);
|
||||
|
||||
const a = useAchievementsStore();
|
||||
|
||||
const keyToButton: Record<string, string> = {
|
||||
ArrowUp: "UP",
|
||||
ArrowDown: "DOWN",
|
||||
@@ -22,8 +22,8 @@ const keyToButton: Record<string, string> = {
|
||||
ArrowRight: "RIGHT",
|
||||
d: "A",
|
||||
s: "B",
|
||||
w: "X",
|
||||
a: "Y",
|
||||
z: "X",
|
||||
q: "Y",
|
||||
" ": "SELECT",
|
||||
Enter: "START",
|
||||
};
|
||||
@@ -33,17 +33,33 @@ const keyToButton: Record<string, string> = {
|
||||
// that's a bit dirty but who cares, there is a lot of dirty things going on here
|
||||
// like who choose Nuxt to build such an app
|
||||
useKeyDown((key) => {
|
||||
if (ENABLE_3D) return;
|
||||
if (app.settings.renderingMode === "3d") return;
|
||||
const button = keyToButton[key];
|
||||
if (button) {
|
||||
window.dispatchEvent(
|
||||
new KeyboardEvent("keydown", { key: `NDS_${button}` }),
|
||||
);
|
||||
}
|
||||
|
||||
// testing purpose only
|
||||
if (key === "m") {
|
||||
a.reset();
|
||||
} else if (key === "o") {
|
||||
for (const ach of ACHIEVEMENTS) {
|
||||
a.unlock(ach);
|
||||
}
|
||||
} else if (key === "p") {
|
||||
for (const ach of ACHIEVEMENTS) {
|
||||
if (!a.isUnlocked(ach)) {
|
||||
a.unlock(ach);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
useKeyUp((key) => {
|
||||
if (ENABLE_3D) return;
|
||||
if (app.settings.renderingMode === "3d") return;
|
||||
const button = keyToButton[key];
|
||||
if (button) {
|
||||
window.dispatchEvent(new KeyboardEvent("keyup", { key: `NDS_${button}` }));
|
||||
@@ -54,7 +70,11 @@ useKeyUp((key) => {
|
||||
<template>
|
||||
<LoadingScreen v-if="!isReady" />
|
||||
<div v-else>
|
||||
<TresCanvas v-if="ENABLE_3D" window-size clear-color="#181818">
|
||||
<TresCanvas
|
||||
v-if="app.settings.renderingMode === '3d'"
|
||||
window-size
|
||||
clear-color="#181818"
|
||||
>
|
||||
<TresPerspectiveCamera :args="[45, 1, 0.001, 1000]" />
|
||||
|
||||
<TresAmbientLight />
|
||||
@@ -67,7 +87,11 @@ useKeyUp((key) => {
|
||||
/>
|
||||
</TresCanvas>
|
||||
|
||||
<div :style="{ visibility: ENABLE_3D ? 'hidden' : 'visible' }">
|
||||
<div
|
||||
:style="{
|
||||
visibility: app.settings.renderingMode === '3d' ? 'hidden' : 'visible',
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<Screen ref="topScreen">
|
||||
<IntroTopScreen v-if="!app.booted" />
|
||||
@@ -89,6 +113,7 @@ useKeyUp((key) => {
|
||||
<ProjectsBottomScreen v-else-if="app.screen === 'projects'" />
|
||||
<SettingsBottomScreen v-else-if="app.screen === 'settings'" />
|
||||
<GalleryBottomScreen v-else-if="app.screen === 'gallery'" />
|
||||
<AchievementsBottomScreen v-else-if="app.screen === 'achievements'" />
|
||||
</Screen>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,14 @@ const settingsSchema = z.object({
|
||||
col: z.number(),
|
||||
row: z.number(),
|
||||
}),
|
||||
renderingMode: z.enum(["3d", "2d"]),
|
||||
});
|
||||
|
||||
type Settings = z.infer<typeof settingsSchema>;
|
||||
|
||||
const defaultSettings = (): Settings => ({
|
||||
color: { col: 0, row: 0 },
|
||||
renderingMode: "3d",
|
||||
});
|
||||
|
||||
export const useAppStore = defineStore("app", {
|
||||
@@ -27,7 +29,8 @@ export const useAppStore = defineStore("app", {
|
||||
}
|
||||
|
||||
return {
|
||||
booted: false,
|
||||
ready: false,
|
||||
booted: true,
|
||||
settings,
|
||||
previousScreen: "home" as AppScreen,
|
||||
screen: "home" as AppScreen,
|
||||
@@ -40,6 +43,12 @@ export const useAppStore = defineStore("app", {
|
||||
this.settings.color = { col, row };
|
||||
},
|
||||
|
||||
setRenderingMode(mode: Settings["renderingMode"]) {
|
||||
this.ready = mode === "2d";
|
||||
this.settings.renderingMode = mode;
|
||||
this.save();
|
||||
},
|
||||
|
||||
navigateTo(screen: AppScreen) {
|
||||
this.previousScreen = this.screen;
|
||||
this.screen = screen;
|
||||
|
||||
@@ -13,6 +13,23 @@ export const fillTextCentered = (
|
||||
return measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent + 1;
|
||||
};
|
||||
|
||||
export const fillImageTextHCentered = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
image: AtlasImage,
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
gap: number,
|
||||
) => {
|
||||
const { actualBoundingBoxRight: textWidth } = ctx.measureText(text);
|
||||
const totalWidth = textWidth + gap + image.rect.width;
|
||||
const groupX = Math.floor(x + width / 2 - totalWidth / 2);
|
||||
|
||||
image.draw(ctx, groupX, y);
|
||||
ctx.fillText(text, groupX + gap + image.rect.width, y);
|
||||
};
|
||||
|
||||
export const fillTextHCentered = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
text: string,
|
||||
@@ -25,6 +42,20 @@ export const fillTextHCentered = (
|
||||
ctx.fillText(text, textX, y);
|
||||
};
|
||||
|
||||
export const fillTextHCenteredMultiline = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
lineHeight: number,
|
||||
) => {
|
||||
const lines = text.split("\n");
|
||||
for (let i = 0, y = 20; i < lines.length; i += 1, y += lineHeight) {
|
||||
fillTextHCentered(ctx, lines[i]!, 0, y, width);
|
||||
}
|
||||
};
|
||||
|
||||
export const fillTextWordWrapped = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
text: string,
|
||||
|
||||
BIN
public/nds/images/home/top-screen/status-bar/2d-mode.webp
Normal file
|
After Width: | Height: | Size: 82 B |
BIN
public/nds/images/home/top-screen/status-bar/3d-mode.webp
Normal file
|
After Width: | Height: | Size: 80 B |
|
After Width: | Height: | Size: 48 B |
|
After Width: | Height: | Size: 50 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 168 B |
|
After Width: | Height: | Size: 88 B |