feat(settings): render top and bottom bar, implement notifications stack and submenu navigation

This commit is contained in:
2025-11-26 11:07:03 +01:00
parent 3d5d326e53
commit 4d9371f1b0
16 changed files with 169 additions and 24 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 780 B

View File

@@ -1,10 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import BACKGROUND_IMAGE from "/assets/images/home/bottom-screen/background.webp"; import BACKGROUND_IMAGE from "/assets/images/home/bottom-screen/background.webp";
import TOP_BAR_IMAGE from "/assets/images/settings/bottom-screen/top-bar.webp";
import BOTTOM_BAR_IMAGE from "/assets/images/settings/bottom-screen/bottom-bar.webp";
const [backgroundImage] = useImages(BACKGROUND_IMAGE); const [backgroundImage, topBarImage, bottomBarImage] = useImages(
BACKGROUND_IMAGE,
TOP_BAR_IMAGE,
BOTTOM_BAR_IMAGE,
);
useRender((ctx) => { useRender((ctx) => {
ctx.drawImage(backgroundImage!, 0, 0); ctx.drawImage(backgroundImage!, 0, 0);
ctx.drawImage(topBarImage!, 0, 0);
ctx.drawImage(bottomBarImage!, 0, 168);
}); });
defineOptions({ defineOptions({

View File

@@ -1,15 +1,44 @@
<script setup lang="ts"> <script setup lang="ts">
import Background from "./Background.vue"; import Background from "./Background.vue";
import Menus from "./Menus/Menus.vue"; import Menus from "./Menus/Menus.vue";
import OptionsStartUp from "./Menus/Options/StartUp.vue";
import OptionsLanguage from "./Menus/Options/Language.vue";
import OptionsGbaMode from "./Menus/Options/GbaMode.vue";
const store = useSettingsStore(); const store = useSettingsStore();
const viewComponents: Record<string, Component> = {
optionsStartUp: OptionsStartUp,
optionsLanguage: OptionsLanguage,
optionsGbaMode: OptionsGbaMode,
};
const currentViewComponent = computed(() => {
if (store.currentView === "menu" || !store.currentView) return null;
return viewComponents[store.currentView] || null;
});
const handleBackNavigation = (event: KeyboardEvent) => {
if (event.key === "Escape" || event.key === "Backspace") {
if (store.navigationStack.length > 0) {
store.popNavigation();
event.preventDefault();
}
}
};
onMounted(() => { onMounted(() => {
store.$reset(); store.$reset();
window.addEventListener("keydown", handleBackNavigation);
});
onUnmounted(() => {
window.removeEventListener("keydown", handleBackNavigation);
}); });
</script> </script>
<template> <template>
<Background /> <Background />
<Menus /> <Menus v-if="store.currentView === 'menu'" />
<component :is="currentViewComponent" v-else-if="currentViewComponent" />
</template> </template>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import OptionsMenu from "./OptionsMenu.vue"; import OptionsMenu from "./Options/Menu.vue";
import ClockMenu from "./ClockMenu.vue"; import ClockMenu from "./Clock/Menu.vue";
import UserMenu from "./UserMenu.vue"; import UserMenu from "./User/Menu.vue";
import TouchScreenMenu from "./TouchScreenMenu.vue"; import TouchScreenMenu from "./TouchScreen/Menu.vue";
import Selector from "~/components/Common/ButtonSelector.vue"; import Selector from "~/components/Common/ButtonSelector.vue";
const { selectedButton: selected, selectorPosition } = useButtonNavigation({ const { selectedButton: selected, selectorPosition } = useButtonNavigation({
@@ -26,6 +26,11 @@ const { selectedButton: selected, selectorPosition } = useButtonNavigation({
touchScreen: [175, 119, 49, 49], touchScreen: [175, 119, 49, 49],
}, },
initialButton: "options", initialButton: "options",
onButtonClick: (buttonName: string) => {
if (isSubmenu(buttonName)) {
settingsStore.pushNavigation(buttonName);
}
},
navigation: { navigation: {
options: { options: {
right: "clock", right: "clock",
@@ -101,6 +106,13 @@ const { selectedButton: selected, selectorPosition } = useButtonNavigation({
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const isSubmenu = (buttonName: string) => {
return (
/^(options|clock|user|touchScreen)[A-Z]/.test(buttonName) &&
!["options", "clock", "user", "touchScreen"].includes(buttonName)
);
};
watch( watch(
selected, selected,
(newSelected) => { (newSelected) => {

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
useRender((ctx) => {
ctx.font = "10px NDS10";
ctx.fillStyle = "#000000";
ctx.fillText("GBA Mode", 10, 20);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
useRender((ctx) => {
ctx.font = "10px NDS10";
ctx.fillStyle = "#000000";
ctx.fillText("Language", 10, 20);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
useRender((ctx) => {
ctx.font = "10px NDS10";
ctx.fillStyle = "#000000";
ctx.fillText("Startup", 10, 20);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -5,6 +5,9 @@ import OPTIONS_IMAGE from "/assets/images/settings/top-screen/options/options.we
import CLOCK_IMAGE from "/assets/images/settings/top-screen/clock/clock.webp"; import CLOCK_IMAGE from "/assets/images/settings/top-screen/clock/clock.webp";
import USER_IMAGE from "/assets/images/settings/top-screen/user/user.webp"; import USER_IMAGE from "/assets/images/settings/top-screen/user/user.webp";
import TOUCH_SCREEN_IMAGE from "/assets/images/settings/top-screen/touch_screen/touch-screen.webp"; import TOUCH_SCREEN_IMAGE from "/assets/images/settings/top-screen/touch_screen/touch-screen.webp";
import START_UP_IMAGE from "/assets/images/settings/top-screen/options/start-up.webp";
import LANGUAGE_IMAGE from "/assets/images/settings/top-screen/options/language.webp";
import GBA_MODE_IMAGE from "/assets/images/settings/top-screen/options/gba-mode.webp";
const store = useSettingsStore(); const store = useSettingsStore();
@@ -15,6 +18,9 @@ const [
clockImage, clockImage,
userImage, userImage,
touchScreenImage, touchScreenImage,
startUpImage,
languageImage,
gbaModeImage,
] = useImages( ] = useImages(
NOTIFICATION_IMAGE, NOTIFICATION_IMAGE,
SETTINGS_IMAGE, SETTINGS_IMAGE,
@@ -22,6 +28,9 @@ const [
CLOCK_IMAGE, CLOCK_IMAGE,
USER_IMAGE, USER_IMAGE,
TOUCH_SCREEN_IMAGE, TOUCH_SCREEN_IMAGE,
START_UP_IMAGE,
LANGUAGE_IMAGE,
GBA_MODE_IMAGE,
); );
const activeMenu = computed(() => { const activeMenu = computed(() => {
@@ -64,25 +73,50 @@ const renderNotification = (
} }
}; };
useRender((ctx) => { const notificationStack = computed(() => {
ctx.translate(0, activeMenu.value ? 144 - 16 : 144); const stack: Array<{ id: string; image: HTMLImageElement }> = [
{ id: "main", image: settingsImage! },
renderNotification( ];
ctx,
settingsImage!,
$t(`settings.title`),
$t(`settings.description`),
);
if (activeMenu.value) { if (activeMenu.value) {
ctx.translate(0, 16); stack.push({ id: activeMenu.value.id, image: activeMenu.value.image! });
}
for (const view of store.navigationStack) {
const menuMatch = view.match(/^(options|clock|user|touchScreen)(.+)$/);
if (menuMatch) {
const [, menu, submenu] = menuMatch;
const submenuKey = submenu!.charAt(0).toLowerCase() + submenu!.slice(1);
const imageMap: Record<string, HTMLImageElement> = {
optionsStartUp: startUpImage!,
optionsLanguage: languageImage!,
optionsGbaMode: gbaModeImage!,
};
if (imageMap[view]) {
stack.push({ id: `${menu}.${submenuKey}`, image: imageMap[view]! });
}
}
}
return stack;
});
useRender((ctx) => {
const stackSize = notificationStack.value.length;
ctx.translate(0, 144 - (stackSize - 1) * 16);
for (let i = 0; i < stackSize; i++) {
const notification = notificationStack.value[i]!;
renderNotification( renderNotification(
ctx, ctx,
activeMenu.value.image!, notification.image,
$t(`settings.${activeMenu.value.id}.title`), $t(`settings.${notification.id}.title`),
$t(`settings.${activeMenu.value.id}.description`), $t(`settings.${notification.id}.description`),
); );
ctx.translate(0, 16);
} }
}); });

View File

@@ -1,23 +1,37 @@
export const useSettingsStore = defineStore("settings", { export const useSettingsStore = defineStore("settings", {
state: () => ({ state: () => ({
activeMenu: null as string | null, activeMenu: null as string | null,
navigationStack: [] as string[],
}), }),
getters: { getters: {
isMenuOpen: (state) => (menu: string) => { isMenuOpen: (state) => (menu: string) => {
if (!state.activeMenu) return false; if (!state.activeMenu) return false;
return new RegExp(`^${menu}[A-Z]`).test(state.activeMenu); return new RegExp(`^${menu}[A-Z]`).test(state.activeMenu);
}, },
isAnyOtherMenuOpen: (state) => (excludeMenu: string) => { isAnyOtherMenuOpen: (state) => (excludeMenu: string) => {
if (!state.activeMenu) return false; if (!state.activeMenu) return false;
const menus = ["options", "clock", "user", "touchScreen"]; return ["options", "clock", "user", "touchScreen"]
return menus
.filter((m) => m !== excludeMenu) .filter((m) => m !== excludeMenu)
.some((m) => new RegExp(`^${m}[A-Z]`).test(state.activeMenu!)); .some((m) => new RegExp(`^${m}[A-Z]`).test(state.activeMenu!));
}, },
currentView: (state) => {
if (state.navigationStack.length === 0) return "menu";
return state.navigationStack[state.navigationStack.length - 1];
},
}, },
actions: { actions: {
setActiveMenu(menu: string | null) { setActiveMenu(menu: string | null) {
this.activeMenu = menu; this.activeMenu = menu;
}, },
pushNavigation(view: string) {
this.navigationStack.push(view);
},
popNavigation() {
this.navigationStack.pop();
},
}, },
}); });

View File

@@ -1,11 +1,26 @@
{ {
"settings": { "settings": {
"main": {
"title": "Settings", "title": "Settings",
"description": "Change system settings here. Select\nthe settings you'd like to change.", "description": "Change system settings here. Select\nthe settings you'd like to change."
},
"options": { "options": {
"title": "Options", "title": "Options",
"description": "Change other settings." "description": "Change other settings.",
"startUp": {
"title": "Start-up",
"description": "Set how you would like your system to\nstart up when you turn the power on."
},
"language": {
"title": "Language",
"description": "Select the language to use."
},
"gbaMode": {
"title": "GBA Mode",
"description": "Select the screen you would like to use\nwhen starting GBA Mode."
}
}, },
"clock": { "clock": {
"title": "Clock", "title": "Clock",