Files
grungnet/src/components/map/MapDrawing.js

288 lines
7.9 KiB
JavaScript
Raw Normal View History

import React, { useRef, useEffect, useState } from "react";
import shortid from "shortid";
2020-04-18 18:11:21 +10:00
2020-04-28 17:04:31 +10:00
import { compare as comparePoints } from "../../helpers/vector2";
import {
getBrushPositionForTool,
getDefaultShapeData,
getUpdatedShapeData,
isShapeHovered,
drawShape,
2020-04-28 17:04:31 +10:00
simplifyPoints,
} from "../../helpers/drawing";
function MapDrawing({
width,
height,
selectedTool,
toolSettings,
shapes,
onShapeAdd,
onShapeRemove,
2020-04-20 15:17:56 +10:00
gridSize,
}) {
const canvasRef = useRef();
2020-04-18 18:11:21 +10:00
const containerRef = useRef();
2020-04-19 17:39:26 +10:00
const [isDrawing, setIsDrawing] = useState(false);
const [drawingShape, setDrawingShape] = useState(null);
2020-04-19 17:39:26 +10:00
const [pointerPosition, setPointerPosition] = useState({ x: -1, y: -1 });
2020-04-28 17:04:31 +10:00
const shouldHover =
selectedTool === "erase" ||
(selectedTool === "fog" &&
(toolSettings.type === "toggle" || toolSettings.type === "remove"));
2020-04-19 17:39:26 +10:00
// Reset pointer position when tool changes
useEffect(() => {
setPointerPosition({ x: -1, y: -1 });
}, [selectedTool]);
function getRelativePointerPosition(event) {
2020-04-18 18:11:21 +10:00
const container = containerRef.current;
if (container) {
const containerRect = container.getBoundingClientRect();
const x = (event.clientX - containerRect.x) / containerRect.width;
const y = (event.clientY - containerRect.y) / containerRect.height;
return { x, y };
2020-04-18 18:11:21 +10:00
}
}
2020-04-19 17:39:26 +10:00
function handleStart(event) {
if (event.touches && event.touches.length !== 1) {
setIsDrawing(false);
setDrawingShape(null);
2020-04-19 17:39:26 +10:00
return;
}
const pointer = event.touches ? event.touches[0] : event;
const position = getRelativePointerPosition(pointer);
setPointerPosition(position);
setIsDrawing(true);
const brushPosition = getBrushPositionForTool(
position,
selectedTool,
2020-04-28 17:04:31 +10:00
toolSettings,
gridSize,
shapes
);
const commonShapeData = {
id: shortid.generate(),
};
2020-04-19 00:24:06 +10:00
if (selectedTool === "brush") {
setDrawingShape({
type: "path",
pathType: toolSettings.type,
data: { points: [brushPosition] },
strokeWidth: toolSettings.type === "stroke" ? 1 : 0,
2020-04-28 17:04:31 +10:00
color: toolSettings && toolSettings.color,
blend: toolSettings && toolSettings.useBlending,
...commonShapeData,
});
} else if (selectedTool === "shape") {
setDrawingShape({
type: "shape",
shapeType: toolSettings.type,
data: getDefaultShapeData(toolSettings.type, brushPosition),
strokeWidth: 0,
2020-04-28 17:04:31 +10:00
color: toolSettings && toolSettings.color,
blend: toolSettings && toolSettings.useBlending,
...commonShapeData,
});
} else if (selectedTool === "fog" && toolSettings.type === "add") {
setDrawingShape({
type: "fog",
data: { points: [brushPosition] },
strokeWidth: 0.1,
color: "black",
blend: true, // Blend while drawing
...commonShapeData,
});
2020-04-19 00:24:06 +10:00
}
2020-04-18 18:11:21 +10:00
}
2020-04-19 17:39:26 +10:00
function handleMove(event) {
if (event.touches && event.touches.length !== 1) {
return;
}
const pointer = event.touches ? event.touches[0] : event;
const position = getRelativePointerPosition(pointer);
2020-04-28 17:04:31 +10:00
// Set pointer position every frame for erase tool and fog
if (shouldHover) {
2020-04-19 17:39:26 +10:00
setPointerPosition(position);
2020-04-19 00:24:06 +10:00
}
if (isDrawing) {
2020-04-19 17:39:26 +10:00
setPointerPosition(position);
const brushPosition = getBrushPositionForTool(
position,
selectedTool,
2020-04-28 17:04:31 +10:00
toolSettings,
gridSize,
shapes
);
if (selectedTool === "brush") {
setDrawingShape((prevShape) => {
const prevPoints = prevShape.data.points;
2020-04-28 17:04:31 +10:00
if (
comparePoints(
prevPoints[prevPoints.length - 1],
brushPosition,
0.001
)
) {
return prevShape;
}
2020-04-28 17:04:31 +10:00
const simplified = simplifyPoints(
[...prevPoints, brushPosition],
2020-04-28 17:04:31 +10:00
gridSize
);
return {
...prevShape,
data: { points: simplified },
};
});
} else if (selectedTool === "shape") {
setDrawingShape((prevShape) => ({
...prevShape,
data: getUpdatedShapeData(
prevShape.shapeType,
prevShape.data,
brushPosition
),
}));
2020-04-28 17:04:31 +10:00
} else if (selectedTool === "fog" && toolSettings.type === "add") {
setDrawingShape((prevShape) => {
const prevPoints = prevShape.data.points;
if (
comparePoints(
prevPoints[prevPoints.length - 1],
brushPosition,
0.001
)
) {
return prevShape;
}
return {
...prevShape,
data: { points: [...prevPoints, brushPosition] },
};
});
}
2020-04-18 18:11:21 +10:00
}
}
2020-04-19 17:39:26 +10:00
function handleStop(event) {
if (event.touches && event.touches.length !== 0) {
return;
}
setIsDrawing(false);
2020-04-19 00:24:06 +10:00
if (selectedTool === "brush") {
if (drawingShape.data.points.length > 1) {
onShapeAdd(drawingShape);
2020-04-19 17:39:26 +10:00
}
} else if (selectedTool === "shape") {
onShapeAdd(drawingShape);
2020-04-28 17:04:31 +10:00
} else if (selectedTool === "fog" && toolSettings.type === "add") {
if (drawingShape.data.points.length > 1) {
const shape = {
...drawingShape,
data: { points: simplifyPoints(drawingShape.data.points, gridSize) },
blend: false,
};
onShapeAdd(shape);
}
}
setDrawingShape(null);
if (selectedTool === "erase" && hoveredShapeRef.current) {
onShapeRemove(hoveredShapeRef.current.id);
2020-04-19 00:24:06 +10:00
}
2020-04-18 18:11:21 +10:00
}
// Add listeners for draw events on map to allow drawing past the bounds
// of the container
useEffect(() => {
const map = document.querySelector(".map");
map.addEventListener("mousedown", handleStart);
map.addEventListener("mousemove", handleMove);
map.addEventListener("mouseup", handleStop);
map.addEventListener("touchstart", handleStart);
map.addEventListener("touchmove", handleMove);
map.addEventListener("touchend", handleStop);
return () => {
map.removeEventListener("mousedown", handleStart);
map.removeEventListener("mousemove", handleMove);
map.removeEventListener("mouseup", handleStop);
map.removeEventListener("touchstart", handleStart);
map.removeEventListener("touchmove", handleMove);
map.removeEventListener("touchend", handleStop);
};
});
2020-04-28 10:14:45 +10:00
/**
* Rendering
*/
const hoveredShapeRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (canvas) {
const context = canvas.getContext("2d");
context.clearRect(0, 0, width, height);
let hoveredShape = null;
for (let shape of shapes) {
2020-04-28 17:04:31 +10:00
if (shouldHover) {
if (isShapeHovered(shape, context, pointerPosition, width, height)) {
hoveredShape = shape;
2020-04-19 00:24:06 +10:00
}
}
2020-04-28 17:04:31 +10:00
if (selectedTool === "fog") {
drawShape(
{ ...shape, blend: true },
context,
gridSize,
width,
height
);
} else {
drawShape(shape, context, gridSize, width, height);
}
2020-04-19 00:24:06 +10:00
}
if (drawingShape) {
drawShape(drawingShape, context, gridSize, width, height);
}
if (hoveredShape) {
const shape = { ...hoveredShape, color: "#BB99FF", blend: true };
drawShape(shape, context, gridSize, width, height);
}
hoveredShapeRef.current = hoveredShape;
}
}, [
shapes,
width,
height,
2020-04-19 17:39:26 +10:00
pointerPosition,
isDrawing,
selectedTool,
drawingShape,
gridSize,
]);
2020-04-18 18:11:21 +10:00
return (
<div
style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }}
ref={containerRef}
>
<canvas
ref={canvasRef}
width={width}
height={height}
style={{ width: "100%", height: "100%" }}
/>
</div>
2020-04-18 18:11:21 +10:00
);
}
export default MapDrawing;