Added back map drawing

This commit is contained in:
Mitchell McCaffrey
2020-05-22 13:47:11 +10:00
parent 9e01ad1d0e
commit d26932d17c
4 changed files with 286 additions and 197 deletions

View File

@@ -1,123 +1,89 @@
import React, { useRef, useEffect, useState, useContext } from "react";
import React, { useContext, useEffect, useState } from "react";
import shortid from "shortid";
import { Group, Line, Rect, Circle } from "react-konva";
import MapInteractionContext from "../../contexts/MapInteractionContext";
import { compare as comparePoints } from "../../helpers/vector2";
import {
getBrushPositionForTool,
getDefaultShapeData,
getUpdatedShapeData,
isShapeHovered,
drawShape,
simplifyPoints,
getRelativePointerPosition,
getStrokeWidth,
} from "../../helpers/drawing";
import MapInteractionContext from "../../contexts/MapInteractionContext";
import colors from "../../helpers/colors";
function MapDrawing({
width,
height,
selectedTool,
toolSettings,
shapes,
onShapeAdd,
onShapeRemove,
selectedToolId,
selectedToolSettings,
gridSize,
}) {
const canvasRef = useRef();
const containerRef = useRef();
const [isPointerDown, setIsPointerDown] = useState(false);
const {
stageDragState,
mapDragPosition,
stageScale,
mapWidth,
mapHeight,
} = useContext(MapInteractionContext);
const [drawingShape, setDrawingShape] = useState(null);
const [pointerPosition, setPointerPosition] = useState({ x: -1, y: -1 });
const shouldHover = selectedTool === "erase";
const shouldHover = selectedToolId === "erase";
const isEditing =
selectedTool === "brush" ||
selectedTool === "shape" ||
selectedTool === "erase";
selectedToolId === "brush" ||
selectedToolId === "shape" ||
selectedToolId === "erase";
const { scaleRef } = useContext(MapInteractionContext);
// Reset pointer position when tool changes
useEffect(() => {
setPointerPosition({ x: -1, y: -1 });
}, [selectedTool]);
function handleStart(event) {
if (!isEditing) {
return;
}
if (event.touches && event.touches.length !== 1) {
setIsPointerDown(false);
setDrawingShape(null);
return;
}
const pointer = event.touches ? event.touches[0] : event;
const position = getRelativePointerPosition(pointer, containerRef.current);
setPointerPosition(position);
setIsPointerDown(true);
const brushPosition = getBrushPositionForTool(
position,
selectedTool,
toolSettings,
gridSize,
shapes
);
const commonShapeData = {
color: toolSettings && toolSettings.color,
blend: toolSettings && toolSettings.useBlending,
id: shortid.generate(),
};
if (selectedTool === "brush") {
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,
});
}
}
function handleMove(event) {
if (!isEditing) {
return;
}
if (event.touches && event.touches.length !== 1) {
return;
}
const pointer = event.touches ? event.touches[0] : event;
// Set pointer position every frame for erase tool and fog
if (shouldHover) {
const position = getRelativePointerPosition(
pointer,
containerRef.current
);
setPointerPosition(position);
}
if (isPointerDown) {
const position = getRelativePointerPosition(
pointer,
containerRef.current
);
setPointerPosition(position);
function startShape() {
const brushPosition = getBrushPositionForTool(
position,
selectedTool,
toolSettings,
mapDragPosition,
selectedToolId,
selectedToolSettings,
gridSize,
shapes
);
if (selectedTool === "brush") {
const commonShapeData = {
color: selectedToolSettings && selectedToolSettings.color,
blend: selectedToolSettings && selectedToolSettings.useBlending,
id: shortid.generate(),
};
if (selectedToolId === "brush") {
setDrawingShape({
type: "path",
pathType: selectedToolSettings.type,
data: { points: [brushPosition] },
strokeWidth: selectedToolSettings.type === "stroke" ? 1 : 0,
...commonShapeData,
});
} else if (selectedToolId === "shape") {
setDrawingShape({
type: "shape",
shapeType: selectedToolSettings.type,
data: getDefaultShapeData(selectedToolSettings.type, brushPosition),
strokeWidth: 0,
...commonShapeData,
});
}
}
function continueShape() {
const brushPosition = getBrushPositionForTool(
mapDragPosition,
selectedToolId,
selectedToolSettings,
gridSize,
shapes
);
if (selectedToolId === "brush") {
setDrawingShape((prevShape) => {
const prevPoints = prevShape.data.points;
if (
@@ -132,14 +98,14 @@ function MapDrawing({
const simplified = simplifyPoints(
[...prevPoints, brushPosition],
gridSize,
scaleRef.current
stageScale
);
return {
...prevShape,
data: { points: simplified },
};
});
} else if (selectedTool === "shape") {
} else if (selectedToolId === "shape") {
setDrawingShape((prevShape) => ({
...prevShape,
data: getUpdatedShapeData(
@@ -151,110 +117,145 @@ function MapDrawing({
}));
}
}
}
function handleStop(event) {
if (!isEditing) {
return;
}
if (event.touches && event.touches.length !== 0) {
return;
}
if (selectedTool === "brush" && drawingShape) {
if (drawingShape.data.points.length > 1) {
function endShape() {
if (selectedToolId === "brush" && drawingShape) {
if (drawingShape.data.points.length > 1) {
onShapeAdd(drawingShape);
}
} else if (selectedToolId === "shape" && drawingShape) {
onShapeAdd(drawingShape);
}
} else if (selectedTool === "shape" && drawingShape) {
onShapeAdd(drawingShape);
setDrawingShape(null);
}
if (selectedTool === "erase" && hoveredShapeRef.current && isPointerDown) {
onShapeRemove(hoveredShapeRef.current.id);
}
setIsPointerDown(false);
setDrawingShape(null);
}
// 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);
};
});
/**
* 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) {
if (shouldHover) {
if (isShapeHovered(shape, context, pointerPosition, width, height)) {
hoveredShape = shape;
}
}
drawShape(shape, context, gridSize, width, height);
}
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;
switch (stageDragState) {
case "first":
startShape();
return;
case "dragging":
continueShape();
return;
case "last":
endShape();
return;
default:
return;
}
}, [
shapes,
width,
height,
pointerPosition,
isPointerDown,
selectedTool,
drawingShape,
stageDragState,
mapDragPosition,
selectedToolId,
selectedToolSettings,
isEditing,
gridSize,
shouldHover,
stageScale,
onShapeAdd,
shapes,
drawingShape,
]);
function handleShapeClick(_, shape) {
if (selectedToolId === "erase") {
onShapeRemove(shape.id);
}
}
function handleShapeMouseOver(event, shape) {
if (shouldHover) {
const path = event.target;
const hoverColor = "#BB99FF";
path.fill(hoverColor);
if (shape.type === "path") {
path.stroke(hoverColor);
}
path.getLayer().draw();
}
}
function handleShapeMouseOut(event, shape) {
if (shouldHover) {
const path = event.target;
const color = colors[shape.color] || shape.color;
path.fill(color);
if (shape.type === "path") {
path.stroke(color);
}
path.getLayer().draw();
}
}
function renderShape(shape) {
const defaultProps = {
key: shape.id,
onMouseOver: (e) => handleShapeMouseOver(e, shape),
onMouseOut: (e) => handleShapeMouseOut(e, shape),
onClick: (e) => handleShapeClick(e, shape),
fill: colors[shape.color] || shape.color,
opacity: shape.blend ? 0.5 : 1,
};
if (shape.type === "path") {
return (
<Line
points={shape.data.points.reduce(
(acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
[]
)}
stroke={colors[shape.color] || shape.color}
tension={0.5}
closed={shape.pathType === "fill"}
fillEnabled={shape.pathType === "fill"}
lineCap="round"
strokeWidth={getStrokeWidth(
shape.strokeWidth,
gridSize,
mapWidth,
mapHeight
)}
{...defaultProps}
/>
);
} else if (shape.type === "shape") {
if (shape.shapeType === "rectangle") {
return (
<Rect
x={shape.data.x * mapWidth}
y={shape.data.y * mapHeight}
width={shape.data.width * mapWidth}
height={shape.data.height * mapHeight}
{...defaultProps}
/>
);
} else if (shape.shapeType === "circle") {
const minSide = mapWidth < mapHeight ? mapWidth : mapHeight;
return (
<Circle
x={shape.data.x * mapWidth}
y={shape.data.y * mapHeight}
radius={shape.data.radius * minSide}
{...defaultProps}
/>
);
} else if (shape.shapeType === "triangle") {
return (
<Line
points={shape.data.points.reduce(
(acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
[]
)}
closed={true}
{...defaultProps}
/>
);
}
}
}
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
pointerEvents: "none",
}}
ref={containerRef}
>
<canvas
ref={canvasRef}
width={width}
height={height}
style={{ width: "100%", height: "100%" }}
/>
</div>
<Group>
{shapes.map(renderShape)}
{drawingShape && renderShape(drawingShape)}
</Group>
);
}