160 lines
3.9 KiB
Vue
160 lines
3.9 KiB
Vue
<script setup lang="ts">
|
|
const { onRender } = useScreen();
|
|
|
|
const store = useProjectsStore();
|
|
const { assets } = useAssets();
|
|
|
|
const drawTextWithShadow = (
|
|
ctx: CanvasRenderingContext2D,
|
|
color: "white" | "black",
|
|
text: string,
|
|
x: number,
|
|
y: number,
|
|
) => {
|
|
ctx.fillStyle = color === "white" ? "#505050" : "#a8b8b8";
|
|
ctx.fillText(text, x + 1, y + 13 + 0);
|
|
ctx.fillText(text, x + 1, y + 13 + 1);
|
|
ctx.fillText(text, x + 0, y + 13 + 1);
|
|
|
|
ctx.fillStyle = color === "white" ? "#f8f8f8" : "#101820";
|
|
ctx.fillText(text, x, y + 13);
|
|
};
|
|
|
|
const drawTextWithShadow2Lines = (
|
|
ctx: CanvasRenderingContext2D,
|
|
text: string,
|
|
x: number,
|
|
y: number,
|
|
maxWidth: number,
|
|
line1Color: "white" | "black",
|
|
line2Color: "white" | "black",
|
|
) => {
|
|
const { actualBoundingBoxRight: textWidth } = ctx.measureText(text);
|
|
|
|
if (textWidth <= maxWidth) {
|
|
drawTextWithShadow(ctx, line1Color, text, x, y);
|
|
return;
|
|
}
|
|
|
|
const words = text.split(" ");
|
|
let firstLine = "";
|
|
let secondLine = "";
|
|
|
|
for (let i = 0; i < words.length; i++) {
|
|
const testLine = firstLine + (firstLine ? " " : "") + words[i];
|
|
const { actualBoundingBoxRight: testWidth } = ctx.measureText(testLine);
|
|
|
|
if (testWidth > maxWidth && firstLine) {
|
|
secondLine = words.slice(i).join(" ");
|
|
break;
|
|
}
|
|
firstLine = testLine;
|
|
}
|
|
|
|
drawTextWithShadow(ctx, line1Color, firstLine, x, y);
|
|
drawTextWithShadow(ctx, line2Color, secondLine, x, y + 16);
|
|
};
|
|
|
|
onRender((ctx) => {
|
|
ctx.font = "16px Pokemon DP Pro";
|
|
|
|
const project = store.projects[store.currentProject];
|
|
if (!project) return;
|
|
|
|
const thumbnailCenterX = 52;
|
|
const thumbnailCenterY = 104;
|
|
const thumbnailSpacing = 104;
|
|
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.rect(0, 56, 104, 101);
|
|
ctx.clip();
|
|
|
|
// draw previous - current - next
|
|
for (let i = store.currentProject - 1; i <= store.currentProject + 1; i++) {
|
|
if (i < 0 || i >= store.projects.length) continue;
|
|
|
|
const offsetFromCurrent = i - store.currentProject;
|
|
// TODO: project.id should be typed from useAssets, shouldn't be just a string
|
|
const thumbnailImage =
|
|
assets.images.projects.pokemons[
|
|
store.projects[i]!.id as keyof typeof assets.images.projects.pokemons
|
|
]!;
|
|
|
|
thumbnailImage.draw(
|
|
ctx,
|
|
Math.floor(
|
|
thumbnailCenterX +
|
|
offsetFromCurrent * thumbnailSpacing -
|
|
thumbnailImage.rect.width / 2 +
|
|
store.offsetX,
|
|
),
|
|
Math.floor(thumbnailCenterY - thumbnailImage.rect.height / 2),
|
|
);
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
// text
|
|
drawTextWithShadow(ctx, "white", project.title.toUpperCase(), 23, 23);
|
|
drawTextWithShadow(ctx, "black", project.scope, 12, 39);
|
|
|
|
drawTextWithShadow2Lines(
|
|
ctx,
|
|
project.description,
|
|
8,
|
|
159,
|
|
90,
|
|
"white",
|
|
"black",
|
|
);
|
|
|
|
const { actualBoundingBoxRight: textWidth } = ctx.measureText(
|
|
project.summary,
|
|
);
|
|
drawTextWithShadow(
|
|
ctx,
|
|
"black",
|
|
project.summary,
|
|
Math.floor(181 - textWidth / 2),
|
|
17,
|
|
);
|
|
|
|
let textY = 35;
|
|
for (let i = 0; i < project.tasks.length; i += 1) {
|
|
const lines = project.tasks[i]!.split("\\n");
|
|
|
|
ctx.fillStyle = i % 2 === 0 ? "#6870d8" : "#8890f8";
|
|
ctx.fillRect(106, textY - 1, 150, lines.length * 16);
|
|
|
|
ctx.fillStyle = i % 2 === 0 ? "#8890f8" : "#b0b8d0";
|
|
ctx.fillRect(105, textY - 1, 1, lines.length * 16);
|
|
|
|
for (let j = 0; j < lines.length; j += 1) {
|
|
drawTextWithShadow(ctx, "white", lines[j]!, 118, textY - 2);
|
|
textY += 16;
|
|
}
|
|
}
|
|
|
|
drawTextWithShadow2Lines(
|
|
ctx,
|
|
project.technologies.join(", "),
|
|
111,
|
|
159,
|
|
145,
|
|
"black",
|
|
"black",
|
|
);
|
|
|
|
drawTextWithShadow(ctx, "white", $t("projects.title"), 7, -1);
|
|
drawTextWithShadow(ctx, "black", $t("projects.technologies"), 167, 143);
|
|
|
|
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);
|
|
});
|
|
|
|
defineOptions({
|
|
render: () => null,
|
|
});
|
|
</script>
|