251 lines
5.8 KiB
TypeScript
251 lines
5.8 KiB
TypeScript
export const drawLine = (
|
|
ctx: CanvasRenderingContext2D,
|
|
x0: number,
|
|
y0: number,
|
|
x1: number,
|
|
y1: number,
|
|
width: number,
|
|
) => {
|
|
const dx = Math.abs(x1 - x0);
|
|
const dy = Math.abs(y1 - y0);
|
|
const sx = x0 < x1 ? 1 : -1;
|
|
const sy = y0 < y1 ? 1 : -1;
|
|
let err = dx - dy;
|
|
|
|
const drawThickPixel = (x: number, y: number) => {
|
|
const isVertical = dy > dx;
|
|
|
|
if (width === 1) {
|
|
ctx.fillRect(x, y, 1, 1);
|
|
} else if (isVertical) {
|
|
const offset = Math.floor((width - 1) / 2);
|
|
ctx.fillRect(x - offset, y, width, 1);
|
|
} else {
|
|
const offset = Math.floor((width - 1) / 2);
|
|
ctx.fillRect(x, y - offset, 1, width);
|
|
}
|
|
};
|
|
|
|
while (true) {
|
|
drawThickPixel(x0, y0);
|
|
|
|
if (x0 === x1 && y0 === y1) break;
|
|
|
|
const e2 = 2 * err;
|
|
if (e2 > -dy) {
|
|
err -= dy;
|
|
x0 += sx;
|
|
}
|
|
if (e2 < dx) {
|
|
err += dx;
|
|
y0 += sy;
|
|
}
|
|
}
|
|
};
|
|
|
|
export const fillTextCentered = (
|
|
ctx: CanvasRenderingContext2D,
|
|
text: string,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
): number => {
|
|
const measure = ctx.measureText(text);
|
|
const textX = Math.floor(x + width / 2 - measure.actualBoundingBoxRight / 2);
|
|
const textY =
|
|
measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
|
|
ctx.fillText(text, textX, y + textY);
|
|
return measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent + 1;
|
|
};
|
|
|
|
export const fillImageTextHCentered = (
|
|
ctx: CanvasRenderingContext2D,
|
|
image: AtlasImage,
|
|
text: string,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
gap: number,
|
|
) => {
|
|
const { actualBoundingBoxRight: textWidth } = ctx.measureText(text);
|
|
const totalWidth = textWidth + gap + image.rect.width;
|
|
const groupX = Math.floor(x + width / 2 - totalWidth / 2);
|
|
|
|
image.draw(ctx, groupX, y);
|
|
ctx.fillText(
|
|
text,
|
|
groupX + gap + image.rect.width,
|
|
y + (ctx.font === "10px NDS10" ? 9 : 7),
|
|
);
|
|
};
|
|
|
|
export const fillTextHCentered = (
|
|
ctx: CanvasRenderingContext2D,
|
|
text: string,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
) => {
|
|
const measure = ctx.measureText(text);
|
|
const textX = Math.floor(x + width / 2 - measure.actualBoundingBoxRight / 2);
|
|
ctx.fillText(text, textX, y);
|
|
};
|
|
|
|
export const fillTextHCenteredMultiline = (
|
|
ctx: CanvasRenderingContext2D,
|
|
text: string,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
lineHeight: number,
|
|
) => {
|
|
const lines = text.split("\n");
|
|
for (let i = 0; i < lines.length; i += 1, y += lineHeight) {
|
|
fillTextHCentered(ctx, lines[i]!, x, y, width);
|
|
}
|
|
};
|
|
|
|
export const CHECKBOX_SIZE = 7;
|
|
export const CHECKBOX_TEXT_GAP = 5;
|
|
|
|
export const drawCheckbox = (
|
|
ctx: CanvasRenderingContext2D,
|
|
x: number,
|
|
y: number,
|
|
checked: boolean,
|
|
) => {
|
|
ctx.fillRect(x, y, CHECKBOX_SIZE, 1);
|
|
ctx.fillRect(x, y + CHECKBOX_SIZE - 1, CHECKBOX_SIZE, 1);
|
|
ctx.fillRect(x, y + 1, 1, CHECKBOX_SIZE - 2);
|
|
ctx.fillRect(x + CHECKBOX_SIZE - 1, y + 1, 1, CHECKBOX_SIZE - 2);
|
|
|
|
if (checked) {
|
|
for (let i = 2; i < CHECKBOX_SIZE - 2; i++) {
|
|
ctx.fillRect(x + i, y + i, 1, 1);
|
|
ctx.fillRect(x + CHECKBOX_SIZE - 1 - i, y + i, 1, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
export const fillCirclePixelated = (
|
|
ctx: CanvasRenderingContext2D,
|
|
cx: number,
|
|
cy: number,
|
|
radius: number,
|
|
) => {
|
|
const r = Math.floor(radius);
|
|
for (let dy = -r; dy <= r; dy++) {
|
|
for (let dx = -r; dx <= r; dx++) {
|
|
if (dx * dx + dy * dy <= r * r) {
|
|
ctx.fillRect(Math.floor(cx) + dx, Math.floor(cy) + dy, 1, 1);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
export const strokeCirclePixelated = (
|
|
ctx: CanvasRenderingContext2D,
|
|
cx: number,
|
|
cy: number,
|
|
radius: number,
|
|
thickness: number = 2,
|
|
) => {
|
|
const outerR = Math.floor(radius);
|
|
const innerR = Math.floor(radius - thickness);
|
|
for (let dy = -outerR; dy <= outerR; dy++) {
|
|
for (let dx = -outerR; dx <= outerR; dx++) {
|
|
const distSq = dx * dx + dy * dy;
|
|
if (distSq <= outerR * outerR && distSq > innerR * innerR) {
|
|
ctx.fillRect(Math.floor(cx) + dx, Math.floor(cy) + dy, 1, 1);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
export const fillTextWordWrapped = (
|
|
ctx: CanvasRenderingContext2D,
|
|
text: string,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
lineHeight?: number,
|
|
): number => {
|
|
const words = text.split(" ");
|
|
let line = "";
|
|
let currentY = y;
|
|
let lineCount = 0;
|
|
|
|
const height =
|
|
lineHeight ||
|
|
ctx.measureText("M").actualBoundingBoxAscent +
|
|
ctx.measureText("M").actualBoundingBoxDescent;
|
|
|
|
currentY += height;
|
|
|
|
for (let i = 0; i < words.length; i++) {
|
|
const testLine = line + (line ? " " : "") + words[i];
|
|
const metrics = ctx.measureText(testLine);
|
|
const testWidth = metrics.width;
|
|
|
|
if (testWidth > width && line) {
|
|
ctx.fillText(line, x, currentY);
|
|
line = words[i]!;
|
|
currentY += height;
|
|
lineCount++;
|
|
} else {
|
|
line = testLine;
|
|
}
|
|
}
|
|
|
|
if (line) {
|
|
ctx.fillText(line, x, currentY);
|
|
lineCount++;
|
|
}
|
|
|
|
return lineCount * height;
|
|
};
|
|
|
|
export const drawButton = (
|
|
ctx: CanvasRenderingContext2D,
|
|
text: string,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
pressed: boolean,
|
|
) => {
|
|
const { assets } = useAssets((a) => a.images.common);
|
|
|
|
const LEFT_WIDTH = assets.buttonLeft.rect.width;
|
|
const RIGHT_WIDTH = assets.buttonRight.rect.width;
|
|
const BODY_WIDTH = assets.buttonBody.rect.width;
|
|
|
|
const bodySpace = width - LEFT_WIDTH - RIGHT_WIDTH;
|
|
const bodyCount = Math.ceil(bodySpace / BODY_WIDTH);
|
|
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
|
|
(pressed ? assets.buttonLeftPressed : assets.buttonLeft).draw(ctx, 0, 0);
|
|
|
|
for (let i = 0; i < bodyCount; i++) {
|
|
(pressed ? assets.buttonBodyPressed : assets.buttonBody).draw(
|
|
ctx,
|
|
LEFT_WIDTH + i * BODY_WIDTH,
|
|
0,
|
|
);
|
|
}
|
|
|
|
(pressed ? assets.buttonRightPressed : assets.buttonRight).draw(
|
|
ctx,
|
|
width - RIGHT_WIDTH,
|
|
0,
|
|
);
|
|
|
|
ctx.font = "10px NDS10";
|
|
ctx.fillStyle = "#494118";
|
|
|
|
fillTextHCentered(ctx, text, 1, 4 + 9, width);
|
|
|
|
ctx.restore();
|
|
};
|