feat(settings): render top and bottom bar, implement notifications stack and submenu navigation
This commit is contained in:
BIN
app/assets/images/settings/bottom-screen/bottom-bar.webp
Normal file
BIN
app/assets/images/settings/bottom-screen/bottom-bar.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 B |
BIN
app/assets/images/settings/bottom-screen/top-bar.webp
Normal file
BIN
app/assets/images/settings/bottom-screen/top-bar.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 B |
Binary file not shown.
|
Before Width: | Height: | Size: 780 B |
@@ -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({
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,26 @@
|
|||||||
{
|
{
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"main": {
|
||||||
"description": "Change system settings here. Select\nthe settings you'd like to change.",
|
"title": "Settings",
|
||||||
|
"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",
|
||||||
|
|||||||
Reference in New Issue
Block a user