Files
pihkaal-me/app/components/Projects/BottomScreen/Buttons.vue

232 lines
5.6 KiB
Vue

<script setup lang="ts">
import gsap from "gsap";
const { onRender, onClick } = useScreen();
const store = useProjectsStore();
const { assets } = useAssets();
const CLICK_RADIUS = 22;
const BUTTONS = {
prev: {
position: [36, 100],
image: assets.images.projects.bottomScreen.prevPressed,
},
quit: {
position: [88, 156],
image: assets.images.projects.bottomScreen.quitPressed,
},
link: {
position: [168, 156],
image: assets.images.projects.bottomScreen.linkPressed,
},
next: {
position: [220, 100],
image: assets.images.projects.bottomScreen.nextPressed,
},
} as const satisfies Record<
string,
{
position: Point;
image: {
draw: (ctx: CanvasRenderingContext2D, x: number, y: number) => void;
};
}
>;
type ButtonType = keyof typeof BUTTONS;
type ButtonAnimation = {
type: ButtonType;
position: Point;
showButton: boolean;
showSmallCircle: boolean;
showBigCircle: boolean;
};
let currentAnimation: ButtonAnimation | null = null;
const circleContains = (
[cx, cy]: Point,
[x, y]: Point,
radius: number,
): boolean => Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2)) < radius;
const SMALL_CIRCLE_DURATION = 0.07;
const BIG_CIRCLE_DURATION = 0.09;
const POST_DURATION = 0.07;
const startButtonAnimation = (type: ButtonType) => {
const anim: ButtonAnimation = {
type,
position: BUTTONS[type].position,
showButton: true,
showSmallCircle: true,
showBigCircle: false,
};
currentAnimation = anim;
gsap
.timeline()
.to(anim, { duration: SMALL_CIRCLE_DURATION })
.call(() => {
anim.showSmallCircle = false;
anim.showBigCircle = true;
})
.to(anim, { duration: BIG_CIRCLE_DURATION })
.call(() => {
anim.showBigCircle = false;
})
.to(anim, { duration: POST_DURATION })
.call(() => {
currentAnimation = null;
});
};
onClick((x, y) => {
if (
currentAnimation ||
store.isIntro ||
store.isOutro ||
store.showConfirmationPopup
)
return;
if (
circleContains(BUTTONS.prev.position, [x, y], CLICK_RADIUS) &&
store.currentProject > 0
) {
startButtonAnimation("prev");
store.scrollProjects("left");
} else if (
circleContains(BUTTONS.next.position, [x, y], CLICK_RADIUS) &&
store.currentProject < store.projects.length - 1
) {
startButtonAnimation("next");
store.scrollProjects("right");
} else if (circleContains(BUTTONS.quit.position, [x, y], CLICK_RADIUS)) {
startButtonAnimation("quit");
store.animateOutro();
} else if (circleContains(BUTTONS.link.position, [x, y], CLICK_RADIUS)) {
startButtonAnimation("link");
setTimeout(
() => (store.showConfirmationPopup = true),
1000 * (SMALL_CIRCLE_DURATION + BIG_CIRCLE_DURATION + POST_DURATION),
);
}
});
const DOT_SIZE = 3;
const DOT_GAP = 5;
const SCROLLBAR_Y = 8;
onRender((ctx) => {
const projectsCount = store.projects.length;
const step = DOT_SIZE + DOT_GAP;
const totalWidth = projectsCount * DOT_SIZE + (projectsCount - 1) * DOT_GAP;
const startX = Math.floor((LOGICAL_WIDTH - totalWidth) / 2);
for (let i = 0; i < projectsCount; i++) {
const x = startX + i * step;
if (i === store.currentProject) {
ctx.fillStyle = "#304961";
ctx.fillRect(x - 1, SCROLLBAR_Y - 1, DOT_SIZE + 2, DOT_SIZE + 2);
ctx.fillStyle = "#f8f8f8";
ctx.fillRect(x, SCROLLBAR_Y, DOT_SIZE, DOT_SIZE);
} else {
ctx.fillStyle = "#4a7898";
ctx.fillRect(x, SCROLLBAR_Y, DOT_SIZE, DOT_SIZE);
}
}
if (store.currentProject === 0) {
assets.images.projects.bottomScreen.prevDisabled.draw(
ctx,
BUTTONS.prev.position[0] - 14,
BUTTONS.prev.position[1] - 14,
);
} else if (store.currentProject === store.projects.length - 1) {
assets.images.projects.bottomScreen.nextDisabled.draw(
ctx,
BUTTONS.next.position[0] - 14,
BUTTONS.next.position[1] - 14,
);
}
if (currentAnimation?.showButton) {
const image = BUTTONS[currentAnimation.type].image;
image.draw(
ctx,
currentAnimation.position[0] - 14,
currentAnimation.position[1] - 14,
);
}
if (currentAnimation?.showSmallCircle) {
assets.images.projects.bottomScreen.circleSmall.draw(
ctx,
currentAnimation.position[0] - 28,
currentAnimation.position[1] - 28,
);
}
if (currentAnimation?.showBigCircle) {
assets.images.projects.bottomScreen.circleBig.draw(
ctx,
currentAnimation.position[0] - 44,
currentAnimation.position[1] - 44,
);
}
ctx.fillStyle = `rgba(0, 0, 0, ${store.isIntro ? store.intro.fadeOpacity : store.isOutro ? store.outro.fadeOpacity : 0})`;
ctx.fillRect(0, 0, LOGICAL_WIDTH, LOGICAL_HEIGHT);
});
useKeyDown(({ key, repeated }) => {
if (
currentAnimation ||
store.isIntro ||
store.isOutro ||
store.showConfirmationPopup
)
return;
switch (key) {
case "NDS_LEFT":
if (store.currentProject > 0) {
startButtonAnimation("prev");
store.scrollProjects("left");
}
break;
case "NDS_RIGHT":
if (store.currentProject < store.projects.length - 1) {
startButtonAnimation("next");
store.scrollProjects("right");
}
break;
case "NDS_B":
if (repeated) break;
startButtonAnimation("quit");
store.animateOutro();
break;
case "NDS_A":
case "NDS_START":
if (repeated) break;
startButtonAnimation("link");
setTimeout(
() => (store.showConfirmationPopup = true),
1000 * (SMALL_CIRCLE_DURATION + BIG_CIRCLE_DURATION + POST_DURATION),
);
break;
}
});
defineOptions({
render: () => null,
});
</script>