feat(settings): menu animations
BIN
app/assets/images/settings/top-screen/clock/clock-active.webp
Normal file
|
After Width: | Height: | Size: 322 B |
BIN
app/assets/images/settings/top-screen/clock/clock-disabled.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/assets/images/settings/top-screen/options/options-active.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 254 B |
BIN
app/assets/images/settings/top-screen/user/user-active.webp
Normal file
|
After Width: | Height: | Size: 302 B |
BIN
app/assets/images/settings/top-screen/user/user-disabled.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
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 TIME_IMAGE from "/assets/images/settings/top-screen/clock/time.webp";
|
||||
import DATE_IMAGE from "/assets/images/settings/top-screen/clock/date.webp";
|
||||
@@ -8,24 +10,48 @@ const props = defineProps<{
|
||||
x: number;
|
||||
y: number;
|
||||
isOpen: boolean;
|
||||
isAnyOtherMenuOpen: boolean;
|
||||
}>();
|
||||
|
||||
const [menuImage, alarmImage, timeImage, dateImage] = useImages(
|
||||
const [
|
||||
menuImage,
|
||||
menuActiveImage,
|
||||
menuDisabledImage,
|
||||
alarmImage,
|
||||
timeImage,
|
||||
dateImage,
|
||||
] = useImages(
|
||||
MENU_IMAGE,
|
||||
MENU_ACTIVE_IMAGE,
|
||||
MENU_DISABLED_IMAGE,
|
||||
ALARM_IMAGE,
|
||||
TIME_IMAGE,
|
||||
DATE_IMAGE,
|
||||
);
|
||||
|
||||
const animation = useMenuAnimation(toRef(() => props.isOpen));
|
||||
|
||||
useRender((ctx) => {
|
||||
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(alarmImage!, 0, -48);
|
||||
ctx.drawImage(timeImage!, 48, -48);
|
||||
ctx.drawImage(dateImage!, 0, -96);
|
||||
ctx.drawImage(menuActiveImage!, 0, 0);
|
||||
} else if (props.isAnyOtherMenuOpen) {
|
||||
ctx.drawImage(menuDisabledImage!, 0, 0);
|
||||
} else {
|
||||
ctx.drawImage(menuImage!, 0, 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
<template>
|
||||
<OptionsMenu :x="33" :y="121" :is-open="/^options[A-Z]/.test(selected)" />
|
||||
<ClockMenu :x="81" :y="121" :is-open="/^clock[A-Z]/.test(selected)" />
|
||||
<UserMenu :x="129" :y="121" :is-open="/^user[A-Z]/.test(selected)" />
|
||||
<TouchScreenMenu :x="177" :y="121" :opacity="1" />
|
||||
<OptionsMenu
|
||||
:x="33"
|
||||
:y="121"
|
||||
: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" />
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
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 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";
|
||||
@@ -8,24 +10,48 @@ const props = defineProps<{
|
||||
x: number;
|
||||
y: number;
|
||||
isOpen: boolean;
|
||||
isAnyOtherMenuOpen: boolean;
|
||||
}>();
|
||||
|
||||
const [menuImage, gbaModeImage, languageImage, startUpImage] = useImages(
|
||||
const [
|
||||
menuImage,
|
||||
menuActiveImage,
|
||||
menuDisabledImage,
|
||||
gbaModeImage,
|
||||
languageImage,
|
||||
startUpImage,
|
||||
] = useImages(
|
||||
MENU_IMAGE,
|
||||
MENU_ACTIVE_IMAGE,
|
||||
MENU_DISABLED_IMAGE,
|
||||
GBA_MODE_IMAGE,
|
||||
LANGUAGE_IMAGE,
|
||||
START_UP_IMAGE,
|
||||
);
|
||||
|
||||
const animation = useMenuAnimation(toRef(() => props.isOpen));
|
||||
|
||||
useRender((ctx) => {
|
||||
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(languageImage!, 0, -48);
|
||||
ctx.drawImage(gbaModeImage!, 48, -48);
|
||||
ctx.drawImage(startUpImage!, 0, -96);
|
||||
ctx.drawImage(menuActiveImage!, 0, 0);
|
||||
} else if (props.isAnyOtherMenuOpen) {
|
||||
ctx.drawImage(menuDisabledImage!, 0, 0);
|
||||
} else {
|
||||
ctx.drawImage(menuImage!, 0, 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
<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<{
|
||||
x: number;
|
||||
y: number;
|
||||
isAnyOtherMenuOpen: boolean;
|
||||
}>();
|
||||
|
||||
const [menuImage] = useImages(MENU_IMAGE);
|
||||
const [menuImage, menuDisabledImage] = useImages(
|
||||
MENU_IMAGE,
|
||||
MENU_DISABLED_IMAGE,
|
||||
);
|
||||
|
||||
useRender((ctx) => {
|
||||
if (props.isAnyOtherMenuOpen) {
|
||||
ctx.drawImage(menuDisabledImage!, props.x, props.y);
|
||||
} else {
|
||||
ctx.drawImage(menuImage!, props.x, props.y);
|
||||
}
|
||||
});
|
||||
|
||||
defineOptions({
|
||||
|
||||
@@ -1,35 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
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 COLOR_IMAGE from "/assets/images/settings/top-screen/user/color.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<{
|
||||
x: number;
|
||||
y: number;
|
||||
isOpen: boolean;
|
||||
isAnyOtherMenuOpen: boolean;
|
||||
}>();
|
||||
|
||||
const [menuImage, birthdayImage, colorImage, messageImage, userNameImage] =
|
||||
useImages(
|
||||
const [
|
||||
menuImage,
|
||||
menuActiveImage,
|
||||
menuDisabledImage,
|
||||
birthdayImage,
|
||||
colorImage,
|
||||
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) => {
|
||||
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(birthdayImage!, -48, -48);
|
||||
ctx.drawImage(userNameImage!, 0, -48);
|
||||
ctx.drawImage(messageImage!, 48, -48);
|
||||
ctx.drawImage(colorImage!, 0, -96);
|
||||
ctx.drawImage(menuActiveImage!, 0, 0);
|
||||
} else if (props.isAnyOtherMenuOpen) {
|
||||
ctx.drawImage(menuDisabledImage!, 0, 0);
|
||||
} else {
|
||||
ctx.drawImage(menuImage!, 0, 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
33
app/composables/useMenuAnimation.ts
Normal 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;
|
||||
};
|
||||