Files
modellierung-und-simulation…/web/src/komponenten/Gitter.tsx
T

67 lines
2.2 KiB
TypeScript

import { useEffect, useRef, type PointerEvent } from "react";
import { ZUSTAENDE, type Zelle } from "../modell";
// Zustandsfarben als RGB, einmal vorberechnet
const FARBEN_RGB = ZUSTAENDE.map(({ farbe }) => [1, 3, 5].map((i) => parseInt(farbe.slice(i, i + 2), 16)));
interface Props {
breite: number;
hoehe: number;
zustand: Uint8Array;
/** Erhöhen, um neu zu zeichnen, wenn das Gitter direkt verändert wurde */
version?: number;
/** Wird beim Klicken und Ziehen mit der Zelle unter dem Zeiger aufgerufen */
onZelle?: (zelle: Zelle) => void;
beschriftung: string;
}
export default function Gitter({ breite, hoehe, zustand, version, onZelle, beschriftung }: Props) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const maltRef = useRef(false);
useEffect(() => {
const ctx = canvasRef.current?.getContext("2d");
if (!ctx) return;
const bild = ctx.createImageData(breite, hoehe);
for (let i = 0; i < zustand.length; i++) {
const [r, g, b] = FARBEN_RGB[zustand[i]];
bild.data[4 * i] = r;
bild.data[4 * i + 1] = g;
bild.data[4 * i + 2] = b;
bild.data[4 * i + 3] = 255;
}
ctx.putImageData(bild, 0, 0);
}, [breite, hoehe, zustand, version]);
function zelleUnterZeiger(e: PointerEvent<HTMLCanvasElement>): Zelle {
const rechteck = e.currentTarget.getBoundingClientRect();
return [
Math.floor(((e.clientY - rechteck.top) / rechteck.height) * hoehe),
Math.floor(((e.clientX - rechteck.left) / rechteck.width) * breite),
];
}
return (
<canvas
ref={canvasRef}
className={onZelle ? "gitter gitter--interaktiv" : "gitter"}
width={breite}
height={hoehe}
style={{ aspectRatio: `${breite} / ${hoehe}` }}
role="img"
aria-label={beschriftung}
onPointerDown={(e) => {
if (!onZelle) return;
maltRef.current = true;
e.currentTarget.setPointerCapture(e.pointerId);
onZelle(zelleUnterZeiger(e));
}}
onPointerMove={(e) => {
if (onZelle && maltRef.current) onZelle(zelleUnterZeiger(e));
}}
onPointerUp={() => (maltRef.current = false)}
onPointerCancel={() => (maltRef.current = false)}
/>
);
}