feat(settings): menu animations

This commit is contained in:
2025-11-25 15:50:28 +01:00
parent 44a84452d0
commit 5ca5c5b892
15 changed files with 189 additions and 34 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import MENU_IMAGE from "/assets/images/settings/top-screen/clock/clock.webp"; import MENU_IMAGE from "/assets/images/settings/top-screen/clock/clock.webp";
import MENU_ACTIVE_IMAGE from "/assets/images/settings/top-screen/clock/clock-active.webp";
import MENU_DISABLED_IMAGE from "/assets/images/settings/top-screen/clock/clock-disabled.png";
import ALARM_IMAGE from "/assets/images/settings/top-screen/clock/alarm.webp"; import ALARM_IMAGE from "/assets/images/settings/top-screen/clock/alarm.webp";
import TIME_IMAGE from "/assets/images/settings/top-screen/clock/time.webp"; import TIME_IMAGE from "/assets/images/settings/top-screen/clock/time.webp";
import DATE_IMAGE from "/assets/images/settings/top-screen/clock/date.webp"; import DATE_IMAGE from "/assets/images/settings/top-screen/clock/date.webp";
@@ -8,24 +10,48 @@ const props = defineProps<{
x: number; x: number;
y: number; y: number;
isOpen: boolean; isOpen: boolean;
isAnyOtherMenuOpen: boolean;
}>(); }>();
const [menuImage, alarmImage, timeImage, dateImage] = useImages( const [
menuImage,
menuActiveImage,
menuDisabledImage,
alarmImage,
timeImage,
dateImage,
] = useImages(
MENU_IMAGE, MENU_IMAGE,
MENU_ACTIVE_IMAGE,
MENU_DISABLED_IMAGE,
ALARM_IMAGE, ALARM_IMAGE,
TIME_IMAGE, TIME_IMAGE,
DATE_IMAGE, DATE_IMAGE,
); );
const animation = useMenuAnimation(toRef(() => props.isOpen));
useRender((ctx) => { useRender((ctx) => {
ctx.translate(props.x, props.y); ctx.translate(props.x, props.y);
ctx.drawImage(menuImage!, 0, 0); if (props.isOpen || animation.playing) {
ctx.drawImage(
timeImage!,
48 - animation.stage2Offset,
-48 + animation.stage1Offset,
);
ctx.drawImage(
dateImage!,
0,
-96 + animation.stage2Offset + animation.stage1Offset,
);
ctx.drawImage(alarmImage!, 0, -48 + animation.stage1Offset);
if (props.isOpen) { ctx.drawImage(menuActiveImage!, 0, 0);
ctx.drawImage(alarmImage!, 0, -48); } else if (props.isAnyOtherMenuOpen) {
ctx.drawImage(timeImage!, 48, -48); ctx.drawImage(menuDisabledImage!, 0, 0);
ctx.drawImage(dateImage!, 0, -96); } else {
ctx.drawImage(menuImage!, 0, 0);
} }
}); });

View File

@@ -98,13 +98,44 @@ const { selectedButton: selected, selectorPosition } = useButtonNavigation({
}, },
}, },
}); });
const isMenuOpen = (menu: string) => {
const regex = new RegExp(`^${menu}[A-Z]`);
return regex.test(selected.value);
};
const isAnyOtherMenuOpen = (excludeMenu: string) => {
return ["options", "clock", "user"]
.filter((menu) => menu !== excludeMenu)
.some((menu) => isMenuOpen(menu));
};
</script> </script>
<template> <template>
<OptionsMenu :x="33" :y="121" :is-open="/^options[A-Z]/.test(selected)" /> <OptionsMenu
<ClockMenu :x="81" :y="121" :is-open="/^clock[A-Z]/.test(selected)" /> :x="33"
<UserMenu :x="129" :y="121" :is-open="/^user[A-Z]/.test(selected)" /> :y="121"
<TouchScreenMenu :x="177" :y="121" :opacity="1" /> :is-open="isMenuOpen('options')"
:is-any-other-menu-open="isAnyOtherMenuOpen('options')"
/>
<ClockMenu
:x="81"
:y="121"
:is-open="isMenuOpen('clock')"
:is-any-other-menu-open="isAnyOtherMenuOpen('clock')"
/>
<UserMenu
:x="129"
:y="121"
:is-open="isMenuOpen('user')"
:is-any-other-menu-open="isAnyOtherMenuOpen('user')"
/>
<TouchScreenMenu
:x="177"
:y="121"
:opacity="1"
:is-any-other-menu-open="isAnyOtherMenuOpen('touchScreen')"
/>
<Selector :rect="selectorPosition" :opacity="1" /> <Selector :rect="selectorPosition" :opacity="1" />
</template> </template>

View File

@@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import MENU_IMAGE from "/assets/images/settings/top-screen/options/options.webp"; import MENU_IMAGE from "/assets/images/settings/top-screen/options/options.webp";
import MENU_ACTIVE_IMAGE from "/assets/images/settings/top-screen/options/options-active.png";
import MENU_DISABLED_IMAGE from "/assets/images/settings/top-screen/options/options-disabled.png";
import GBA_MODE_IMAGE from "/assets/images/settings/top-screen/options/gba-mode.webp"; import GBA_MODE_IMAGE from "/assets/images/settings/top-screen/options/gba-mode.webp";
import LANGUAGE_IMAGE from "/assets/images/settings/top-screen/options/language.webp"; import LANGUAGE_IMAGE from "/assets/images/settings/top-screen/options/language.webp";
import START_UP_IMAGE from "/assets/images/settings/top-screen/options/start-up.webp"; import START_UP_IMAGE from "/assets/images/settings/top-screen/options/start-up.webp";
@@ -8,24 +10,48 @@ const props = defineProps<{
x: number; x: number;
y: number; y: number;
isOpen: boolean; isOpen: boolean;
isAnyOtherMenuOpen: boolean;
}>(); }>();
const [menuImage, gbaModeImage, languageImage, startUpImage] = useImages( const [
menuImage,
menuActiveImage,
menuDisabledImage,
gbaModeImage,
languageImage,
startUpImage,
] = useImages(
MENU_IMAGE, MENU_IMAGE,
MENU_ACTIVE_IMAGE,
MENU_DISABLED_IMAGE,
GBA_MODE_IMAGE, GBA_MODE_IMAGE,
LANGUAGE_IMAGE, LANGUAGE_IMAGE,
START_UP_IMAGE, START_UP_IMAGE,
); );
const animation = useMenuAnimation(toRef(() => props.isOpen));
useRender((ctx) => { useRender((ctx) => {
ctx.translate(props.x, props.y); ctx.translate(props.x, props.y);
ctx.drawImage(menuImage!, 0, 0); if (props.isOpen || animation.playing) {
ctx.drawImage(languageImage!, 0, -48 + animation.stage1Offset);
ctx.drawImage(
gbaModeImage!,
48 - animation.stage2Offset,
-48 + animation.stage1Offset,
);
ctx.drawImage(
startUpImage!,
0,
-96 + animation.stage2Offset + animation.stage1Offset,
);
if (props.isOpen) { ctx.drawImage(menuActiveImage!, 0, 0);
ctx.drawImage(languageImage!, 0, -48); } else if (props.isAnyOtherMenuOpen) {
ctx.drawImage(gbaModeImage!, 48, -48); ctx.drawImage(menuDisabledImage!, 0, 0);
ctx.drawImage(startUpImage!, 0, -96); } else {
ctx.drawImage(menuImage!, 0, 0);
} }
}); });

View File

@@ -1,15 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import MENU_IMAGE from "/assets/images/settings/top-screen/touch_screen/touch_screen.webp"; import MENU_IMAGE from "/assets/images/settings/top-screen/touch_screen/touch-screen.webp";
import MENU_DISABLED_IMAGE from "/assets/images/settings/top-screen/touch_screen/touch-screen-disabled.png";
const props = defineProps<{ const props = defineProps<{
x: number; x: number;
y: number; y: number;
isAnyOtherMenuOpen: boolean;
}>(); }>();
const [menuImage] = useImages(MENU_IMAGE); const [menuImage, menuDisabledImage] = useImages(
MENU_IMAGE,
MENU_DISABLED_IMAGE,
);
useRender((ctx) => { useRender((ctx) => {
ctx.drawImage(menuImage!, props.x, props.y); if (props.isAnyOtherMenuOpen) {
ctx.drawImage(menuDisabledImage!, props.x, props.y);
} else {
ctx.drawImage(menuImage!, props.x, props.y);
}
}); });
defineOptions({ defineOptions({

View File

@@ -1,35 +1,65 @@
<script setup lang="ts"> <script setup lang="ts">
import MENU_IMAGE from "/assets/images/settings/top-screen/user/user.webp"; import MENU_IMAGE from "/assets/images/settings/top-screen/user/user.webp";
import MENU_ACTIVE_IMAGE from "/assets/images/settings/top-screen/user/user-active.webp";
import MENU_DISABLED_IMAGE from "/assets/images/settings/top-screen/user/user-disabled.png";
import BIRTHDAY_IMAGE from "/assets/images/settings/top-screen/user/birthday.webp"; import BIRTHDAY_IMAGE from "/assets/images/settings/top-screen/user/birthday.webp";
import COLOR_IMAGE from "/assets/images/settings/top-screen/user/color.webp"; import COLOR_IMAGE from "/assets/images/settings/top-screen/user/color.webp";
import MESSAGE_IMAGE from "/assets/images/settings/top-screen/user/message.webp"; import MESSAGE_IMAGE from "/assets/images/settings/top-screen/user/message.webp";
import USER_NAME_IMAGE from "/assets/images/settings/top-screen/user/user_name.webp"; import USER_NAME_IMAGE from "/assets/images/settings/top-screen/user/user-name.webp";
const props = defineProps<{ const props = defineProps<{
x: number; x: number;
y: number; y: number;
isOpen: boolean; isOpen: boolean;
isAnyOtherMenuOpen: boolean;
}>(); }>();
const [menuImage, birthdayImage, colorImage, messageImage, userNameImage] = const [
useImages( menuImage,
MENU_IMAGE, menuActiveImage,
BIRTHDAY_IMAGE, menuDisabledImage,
COLOR_IMAGE, birthdayImage,
MESSAGE_IMAGE, colorImage,
USER_NAME_IMAGE, messageImage,
); userNameImage,
] = useImages(
MENU_IMAGE,
MENU_ACTIVE_IMAGE,
MENU_DISABLED_IMAGE,
BIRTHDAY_IMAGE,
COLOR_IMAGE,
MESSAGE_IMAGE,
USER_NAME_IMAGE,
);
const animation = useMenuAnimation(toRef(() => props.isOpen));
useRender((ctx) => { useRender((ctx) => {
ctx.translate(props.x, props.y); ctx.translate(props.x, props.y);
ctx.drawImage(menuImage!, 0, 0); if (props.isOpen || animation.playing) {
ctx.drawImage(
birthdayImage!,
-48 + animation.stage2Offset,
-48 + animation.stage1Offset,
);
ctx.drawImage(userNameImage!, 0, -48 + animation.stage1Offset);
ctx.drawImage(
messageImage!,
48 - animation.stage2Offset,
-48 + animation.stage1Offset,
);
ctx.drawImage(
colorImage!,
0,
-96 + animation.stage2Offset + animation.stage1Offset,
);
if (props.isOpen) { ctx.drawImage(menuActiveImage!, 0, 0);
ctx.drawImage(birthdayImage!, -48, -48); } else if (props.isAnyOtherMenuOpen) {
ctx.drawImage(userNameImage!, 0, -48); ctx.drawImage(menuDisabledImage!, 0, 0);
ctx.drawImage(messageImage!, 48, -48); } else {
ctx.drawImage(colorImage!, 0, -96); ctx.drawImage(menuImage!, 0, 0);
} }
}); });

View File

@@ -0,0 +1,33 @@
import gsap from "gsap";
export const useMenuAnimation = (isOpen: Ref<boolean>) => {
const animation = reactive({
playing: false,
stage1Offset: 48,
stage2Offset: 48,
});
watch(isOpen, (current, previous) => {
const duration = 0.1;
const timeline = gsap.timeline({
onStart: () => {
animation.playing = true;
},
onComplete: () => {
animation.playing = false;
},
});
if (current === true && previous === false) {
timeline
.fromTo(animation, { stage1Offset: 48 }, { stage1Offset: 0, duration })
.fromTo(animation, { stage2Offset: 48 }, { stage2Offset: 0, duration });
} else if (current === false && previous === true) {
timeline
.fromTo(animation, { stage2Offset: 0 }, { stage2Offset: 48, duration })
.fromTo(animation, { stage1Offset: 0 }, { stage1Offset: 48, duration });
}
});
return animation;
};