2020-04-18 18:54:13 +10:00
|
|
|
import React, { useRef, useEffect, useState } from "react";
|
2020-04-18 19:09:48 +10:00
|
|
|
import simplify from "simplify-js";
|
2020-04-19 13:33:31 +10:00
|
|
|
import shortid from "shortid";
|
2020-04-18 18:11:21 +10:00
|
|
|
|
2020-04-23 10:09:12 +10:00
|
|
|
import colors from "../../helpers/colors";
|
2020-04-27 21:39:21 +10:00
|
|
|
import {
|
|
|
|
|
getBrushPositionForTool,
|
|
|
|
|
getDefaultShapeData,
|
|
|
|
|
getUpdatedShapeData,
|
|
|
|
|
getStrokeSize,
|
|
|
|
|
shapeHasFill,
|
|
|
|
|
} from "../../helpers/drawing";
|
2020-04-20 11:56:56 +10:00
|
|
|
|
2020-04-19 13:33:31 +10:00
|
|
|
function MapDrawing({
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
selectedTool,
|
2020-04-27 17:29:46 +10:00
|
|
|
toolSettings,
|
2020-04-19 13:33:31 +10:00
|
|
|
shapes,
|
|
|
|
|
onShapeAdd,
|
|
|
|
|
onShapeRemove,
|
2020-04-20 15:17:56 +10:00
|
|
|
gridSize,
|
2020-04-19 13:33:31 +10:00
|
|
|
}) {
|
2020-04-18 18:54:13 +10:00
|
|
|
const canvasRef = useRef();
|
2020-04-18 18:11:21 +10:00
|
|
|
const containerRef = useRef();
|
|
|
|
|
|
2020-04-27 21:39:21 +10:00
|
|
|
// const [brushPoints, setBrushPoints] = useState([]);
|
2020-04-19 17:39:26 +10:00
|
|
|
const [isDrawing, setIsDrawing] = useState(false);
|
2020-04-27 21:39:21 +10:00
|
|
|
const [drawingShape, setDrawingShape] = useState(null);
|
2020-04-19 17:39:26 +10:00
|
|
|
const [pointerPosition, setPointerPosition] = useState({ x: -1, y: -1 });
|
|
|
|
|
|
|
|
|
|
// 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;
|
2020-04-18 19:09:48 +10:00
|
|
|
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);
|
2020-04-27 21:39:21 +10:00
|
|
|
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);
|
2020-04-27 21:39:21 +10:00
|
|
|
const brushPosition = getBrushPositionForTool(
|
|
|
|
|
position,
|
|
|
|
|
toolSettings,
|
|
|
|
|
gridSize,
|
|
|
|
|
shapes
|
|
|
|
|
);
|
|
|
|
|
const commonShapeData = {
|
|
|
|
|
id: shortid.generate(),
|
|
|
|
|
color: toolSettings && toolSettings.color,
|
|
|
|
|
blend: toolSettings && toolSettings.useBlending,
|
|
|
|
|
};
|
2020-04-19 00:24:06 +10:00
|
|
|
if (selectedTool === "brush") {
|
2020-04-27 21:39:21 +10:00
|
|
|
setDrawingShape({
|
|
|
|
|
type: "path",
|
|
|
|
|
pathType: toolSettings.type,
|
|
|
|
|
data: { points: [brushPosition] },
|
|
|
|
|
strokeWidth: toolSettings.type === "stroke" ? 1 : 0,
|
|
|
|
|
...commonShapeData,
|
|
|
|
|
});
|
|
|
|
|
} else if (selectedTool === "shape") {
|
|
|
|
|
setDrawingShape({
|
|
|
|
|
type: "shape",
|
|
|
|
|
shapeType: toolSettings.type,
|
|
|
|
|
data: getDefaultShapeData(toolSettings.type, brushPosition),
|
|
|
|
|
strokeWidth: 0,
|
|
|
|
|
...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-19 00:24:06 +10:00
|
|
|
if (selectedTool === "erase") {
|
2020-04-19 17:39:26 +10:00
|
|
|
setPointerPosition(position);
|
2020-04-19 00:24:06 +10:00
|
|
|
}
|
2020-04-27 21:39:21 +10:00
|
|
|
if (isDrawing) {
|
2020-04-19 17:39:26 +10:00
|
|
|
setPointerPosition(position);
|
2020-04-27 21:39:21 +10:00
|
|
|
const brushPosition = getBrushPositionForTool(
|
|
|
|
|
position,
|
|
|
|
|
toolSettings,
|
|
|
|
|
gridSize,
|
|
|
|
|
shapes
|
|
|
|
|
);
|
|
|
|
|
if (selectedTool === "brush") {
|
|
|
|
|
setDrawingShape((prevShape) => {
|
|
|
|
|
const prevPoints = prevShape.data.points;
|
|
|
|
|
if (prevPoints[prevPoints.length - 1] === brushPosition) {
|
|
|
|
|
return prevPoints;
|
|
|
|
|
}
|
|
|
|
|
const simplified = simplify(
|
|
|
|
|
[...prevPoints, brushPosition],
|
|
|
|
|
getStrokeSize(drawingShape.strokeWidth, gridSize, 1, 1) * 0.1
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
...prevShape,
|
|
|
|
|
data: { points: simplified },
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
} else if (selectedTool === "shape") {
|
|
|
|
|
setDrawingShape((prevShape) => ({
|
|
|
|
|
...prevShape,
|
|
|
|
|
data: getUpdatedShapeData(
|
|
|
|
|
prevShape.shapeType,
|
|
|
|
|
prevShape.data,
|
|
|
|
|
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") {
|
2020-04-27 21:39:21 +10:00
|
|
|
if (drawingShape.data.points.length > 1) {
|
|
|
|
|
// const simplifiedPoints = simplify(
|
|
|
|
|
// drawingShape.data.points,
|
|
|
|
|
// getStrokeSize(drawingShape.strokeWidth, gridSize, 1, 1) * 0.1
|
|
|
|
|
// );
|
2020-04-20 23:52:21 +10:00
|
|
|
|
2020-04-27 21:39:21 +10:00
|
|
|
// const data = { points: simplifiedPoints };
|
|
|
|
|
// onShapeAdd({ ...drawingShape, data });
|
|
|
|
|
onShapeAdd(drawingShape);
|
2020-04-19 17:39:26 +10:00
|
|
|
}
|
2020-04-27 21:39:21 +10:00
|
|
|
} else if (selectedTool === "shape") {
|
|
|
|
|
onShapeAdd(drawingShape);
|
2020-04-19 13:33:31 +10:00
|
|
|
}
|
2020-04-27 21:39:21 +10:00
|
|
|
|
|
|
|
|
setDrawingShape(null);
|
2020-04-19 13:33:31 +10:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2020-04-27 17:40:36 +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-19 13:33:31 +10:00
|
|
|
const hoveredShapeRef = useRef(null);
|
2020-04-18 18:54:13 +10:00
|
|
|
useEffect(() => {
|
2020-04-27 21:39:21 +10:00
|
|
|
function pointsToPath(points, close) {
|
2020-04-19 13:33:31 +10:00
|
|
|
const path = new Path2D();
|
|
|
|
|
path.moveTo(points[0].x * width, points[0].y * height);
|
|
|
|
|
for (let point of points.slice(1)) {
|
|
|
|
|
path.lineTo(point.x * width, point.y * height);
|
|
|
|
|
}
|
2020-04-27 21:39:21 +10:00
|
|
|
if (close) {
|
|
|
|
|
path.closePath();
|
|
|
|
|
}
|
2020-04-19 13:33:31 +10:00
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 10:04:26 +10:00
|
|
|
function circleToPath(x, y, radius) {
|
|
|
|
|
const path = new Path2D();
|
|
|
|
|
const minSide = width < height ? width : height;
|
|
|
|
|
path.arc(x * width, y * height, radius * minSide, 0, 2 * Math.PI, true);
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rectangleToPath(x, y, w, h) {
|
|
|
|
|
const path = new Path2D();
|
|
|
|
|
path.rect(x * width, y * height, w * width, h * height);
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-20 23:52:21 +10:00
|
|
|
function shapeToPath(shape) {
|
2020-04-21 10:04:26 +10:00
|
|
|
const data = shape.data;
|
|
|
|
|
if (shape.type === "path") {
|
2020-04-27 21:39:21 +10:00
|
|
|
return pointsToPath(data.points, shape.pathType === "fill");
|
|
|
|
|
} else if (shape.type === "shape") {
|
|
|
|
|
if (shape.shapeType === "circle") {
|
|
|
|
|
return circleToPath(data.x, data.y, data.radius);
|
|
|
|
|
} else if (shape.shapeType === "rectangle") {
|
|
|
|
|
return rectangleToPath(data.x, data.y, data.width, data.height);
|
|
|
|
|
} else if (shape.shapeType === "triangle") {
|
|
|
|
|
return pointsToPath(data.points, true);
|
|
|
|
|
}
|
2020-04-21 10:04:26 +10:00
|
|
|
}
|
2020-04-20 23:52:21 +10:00
|
|
|
}
|
|
|
|
|
|
2020-04-27 21:39:21 +10:00
|
|
|
function drawPath(path, color, fill, strokeWidth, blend, context) {
|
2020-04-20 23:52:21 +10:00
|
|
|
context.globalAlpha = blend ? 0.5 : 1.0;
|
2020-04-19 13:33:31 +10:00
|
|
|
context.fillStyle = color;
|
|
|
|
|
context.strokeStyle = color;
|
2020-04-27 21:39:21 +10:00
|
|
|
if (strokeWidth > 0) {
|
|
|
|
|
context.lineCap = "round";
|
|
|
|
|
context.lineWidth = getStrokeSize(strokeWidth, gridSize, width, height);
|
|
|
|
|
context.stroke(path);
|
|
|
|
|
}
|
|
|
|
|
if (fill) {
|
|
|
|
|
context.fill(path);
|
|
|
|
|
}
|
2020-04-19 13:33:31 +10:00
|
|
|
}
|
|
|
|
|
|
2020-04-18 18:54:13 +10:00
|
|
|
const canvas = canvasRef.current;
|
|
|
|
|
if (canvas) {
|
|
|
|
|
const context = canvas.getContext("2d");
|
|
|
|
|
|
|
|
|
|
context.clearRect(0, 0, width, height);
|
2020-04-19 13:33:31 +10:00
|
|
|
let hoveredShape = null;
|
|
|
|
|
for (let shape of shapes) {
|
2020-04-20 23:52:21 +10:00
|
|
|
const path = shapeToPath(shape);
|
2020-04-19 13:33:31 +10:00
|
|
|
// Detect hover
|
2020-04-19 00:24:06 +10:00
|
|
|
if (selectedTool === "erase") {
|
|
|
|
|
if (
|
|
|
|
|
context.isPointInPath(
|
|
|
|
|
path,
|
2020-04-19 17:39:26 +10:00
|
|
|
pointerPosition.x * width,
|
|
|
|
|
pointerPosition.y * height
|
2020-04-19 00:24:06 +10:00
|
|
|
)
|
|
|
|
|
) {
|
2020-04-19 13:33:31 +10:00
|
|
|
hoveredShape = shape;
|
2020-04-19 00:24:06 +10:00
|
|
|
}
|
|
|
|
|
}
|
2020-04-27 21:39:21 +10:00
|
|
|
|
|
|
|
|
drawPath(
|
|
|
|
|
path,
|
|
|
|
|
colors[shape.color],
|
|
|
|
|
shapeHasFill(shape),
|
|
|
|
|
shape.strokeWidth,
|
|
|
|
|
shape.blend,
|
|
|
|
|
context
|
|
|
|
|
);
|
2020-04-19 00:24:06 +10:00
|
|
|
}
|
2020-04-27 21:39:21 +10:00
|
|
|
if (drawingShape) {
|
|
|
|
|
const path = shapeToPath(drawingShape);
|
|
|
|
|
drawPath(
|
|
|
|
|
path,
|
|
|
|
|
colors[drawingShape.color],
|
|
|
|
|
shapeHasFill(drawingShape),
|
|
|
|
|
drawingShape.strokeWidth,
|
|
|
|
|
drawingShape.blend,
|
|
|
|
|
context
|
|
|
|
|
);
|
2020-04-19 13:33:31 +10:00
|
|
|
}
|
|
|
|
|
if (hoveredShape) {
|
2020-04-20 23:52:21 +10:00
|
|
|
const path = shapeToPath(hoveredShape);
|
2020-04-27 21:39:21 +10:00
|
|
|
drawPath(path, "#BB99FF", true, 1, true, context);
|
2020-04-18 18:54:13 +10:00
|
|
|
}
|
2020-04-19 13:33:31 +10:00
|
|
|
hoveredShapeRef.current = hoveredShape;
|
2020-04-18 18:54:13 +10:00
|
|
|
}
|
2020-04-19 13:33:31 +10:00
|
|
|
}, [
|
|
|
|
|
shapes,
|
|
|
|
|
width,
|
|
|
|
|
height,
|
2020-04-19 17:39:26 +10:00
|
|
|
pointerPosition,
|
|
|
|
|
isDrawing,
|
2020-04-19 13:33:31 +10:00
|
|
|
selectedTool,
|
2020-04-27 21:39:21 +10:00
|
|
|
drawingShape,
|
|
|
|
|
gridSize,
|
2020-04-19 13:33:31 +10:00
|
|
|
]);
|
2020-04-18 18:54:13 +10:00
|
|
|
|
2020-04-18 18:11:21 +10:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }}
|
|
|
|
|
ref={containerRef}
|
2020-04-18 18:54:13 +10:00
|
|
|
>
|
|
|
|
|
<canvas
|
|
|
|
|
ref={canvasRef}
|
|
|
|
|
width={width}
|
|
|
|
|
height={height}
|
|
|
|
|
style={{ width: "100%", height: "100%" }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2020-04-18 18:11:21 +10:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default MapDrawing;
|