diff --git a/src/components/map/Map.js b/src/components/map/Map.js
index 2820078..682e254 100644
--- a/src/components/map/Map.js
+++ b/src/components/map/Map.js
@@ -5,6 +5,7 @@ import MapControls from "./MapControls";
import MapInteraction from "./MapInteraction";
import MapToken from "./MapToken";
import MapDrawing from "./MapDrawing";
+import MapFog from "./MapFog";
import TokenDataContext from "../../contexts/TokenDataContext";
import TokenMenu from "../token/TokenMenu";
@@ -85,8 +86,29 @@ function Map({
}
const [mapShapes, setMapShapes] = useState([]);
+
+ function handleMapShapeAdd(shape) {
+ onMapDraw({ type: "add", shapes: [shape] });
+ }
+
+ function handleMapShapeRemove(shapeId) {
+ onMapDraw({ type: "remove", shapeIds: [shapeId] });
+ }
+
const [fogShapes, setFogShapes] = useState([]);
+ function handleFogShapeAdd(shape) {
+ onFogDraw({ type: "add", shapes: [shape] });
+ }
+
+ function handleFogShapeRemove(shapeId) {
+ onFogDraw({ type: "remove", shapeIds: [shapeId] });
+ }
+
+ function handleFogShapeEdit(shape) {
+ onFogDraw({ type: "edit", shapes: [shape] });
+ }
+
// Replay the draw actions and convert them to shapes for the map drawing
useEffect(() => {
if (!mapState) {
@@ -116,14 +138,6 @@ function Map({
);
}, [mapState]);
- function handleMapShapeAdd(shape) {
- onMapDraw({ type: "add", shapes: [shape] });
- }
-
- function handleMapShapeRemove(shapeId) {
- onMapDraw({ type: "remove", shapeIds: [shapeId] });
- }
-
const disabledControls = [];
if (!allowMapDrawing) {
disabledControls.push("brush");
@@ -237,6 +251,18 @@ function Map({
/>
);
+ const mapFog = (
+
+ );
+
return (
{mapDrawing}
{mapTokens}
+ {mapFog}
);
}
diff --git a/src/components/map/MapFog.js b/src/components/map/MapFog.js
index c0cbfba..f0dd0f3 100644
--- a/src/components/map/MapFog.js
+++ b/src/components/map/MapFog.js
@@ -1,104 +1,82 @@
-import React, { useRef, useEffect, useState, useContext } from "react";
+import React, { useContext, useEffect, useState } from "react";
import shortid from "shortid";
+import { Group, Line } from "react-konva";
+import useImage from "use-image";
+
+import diagonalPattern from "../../images/DiagonalPattern.png";
+
+import MapInteractionContext from "../../contexts/MapInteractionContext";
import { compare as comparePoints } from "../../helpers/vector2";
import {
getBrushPositionForTool,
- isShapeHovered,
- drawShape,
simplifyPoints,
- getRelativePointerPosition,
+ getStrokeWidth,
} from "../../helpers/drawing";
-import MapInteractionContext from "../../contexts/MapInteractionContext";
-
-import diagonalPattern from "../../images/DiagonalPattern.png";
+import colors from "../../helpers/colors";
function MapFog({
- width,
- height,
- isEditing,
- toolSettings,
shapes,
onShapeAdd,
onShapeRemove,
onShapeEdit,
+ 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 isEditing = selectedToolId === "fog";
const shouldHover =
isEditing &&
- (toolSettings.type === "toggle" || toolSettings.type === "remove");
+ (selectedToolSettings.type === "toggle" ||
+ selectedToolSettings.type === "remove");
- const { scaleRef } = useContext(MapInteractionContext);
+ const [patternImage] = useImage(diagonalPattern);
- // Reset pointer position when tool changes
useEffect(() => {
- setPointerPosition({ x: -1, y: -1 });
- }, [isEditing, toolSettings]);
-
- 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,
- "fog",
- toolSettings,
- gridSize,
- shapes
- );
- if (isEditing && toolSettings.type === "add") {
- setDrawingShape({
- type: "fog",
- data: { points: [brushPosition] },
- strokeWidth: 0.5,
- color: "black",
- blend: true, // Blend while drawing
- id: shortid.generate(),
- visible: true,
- });
- }
- }
- function handleMove(event) {
- if (!isEditing) {
- return;
- }
- if (event.touches && event.touches.length !== 1) {
- return;
- }
- const pointer = event.touches ? event.touches[0] : event;
- const position = getRelativePointerPosition(pointer, containerRef.current);
- // Set pointer position every frame for erase tool and fog
- if (shouldHover) {
- setPointerPosition(position);
- }
- if (isPointerDown) {
- setPointerPosition(position);
+ function startShape() {
const brushPosition = getBrushPositionForTool(
- position,
- "fog",
- toolSettings,
+ mapDragPosition,
+ selectedToolId,
+ selectedToolSettings,
gridSize,
shapes
);
- if (isEditing && toolSettings.type === "add" && drawingShape) {
+ if (selectedToolSettings.type === "add") {
+ setDrawingShape({
+ type: "fog",
+ data: { points: [brushPosition] },
+ strokeWidth: 0.5,
+ color: "black",
+ blend: false,
+ id: shortid.generate(),
+ visible: true,
+ });
+ }
+ }
+
+ function continueShape() {
+ const brushPosition = getBrushPositionForTool(
+ mapDragPosition,
+ selectedToolId,
+ selectedToolSettings,
+ gridSize,
+ shapes
+ );
+ if (selectedToolSettings.type === "add") {
setDrawingShape((prevShape) => {
const prevPoints = prevShape.data.points;
if (
@@ -117,159 +95,125 @@ function MapFog({
});
}
}
- }
- function handleStop(event) {
+ function endShape() {
+ if (selectedToolSettings.type === "add" && drawingShape) {
+ if (drawingShape.data.points.length > 1) {
+ const shape = {
+ ...drawingShape,
+ data: {
+ points: simplifyPoints(
+ drawingShape.data.points,
+ gridSize,
+ // Downscale fog as smoothing doesn't currently work with edge snapping
+ stageScale / 2
+ ),
+ },
+ };
+ onShapeAdd(shape);
+ }
+ }
+ setDrawingShape(null);
+ }
+
+ switch (stageDragState) {
+ case "first":
+ startShape();
+ return;
+ case "dragging":
+ continueShape();
+ return;
+ case "last":
+ endShape();
+ return;
+ default:
+ return;
+ }
+ }, [
+ stageDragState,
+ mapDragPosition,
+ selectedToolId,
+ selectedToolSettings,
+ isEditing,
+ gridSize,
+ stageScale,
+ onShapeAdd,
+ shapes,
+ drawingShape,
+ ]);
+
+ function handleShapeClick(_, shape) {
if (!isEditing) {
return;
}
- if (event.touches && event.touches.length !== 0) {
- return;
- }
- if (isEditing && toolSettings.type === "add" && drawingShape) {
- if (drawingShape.data.points.length > 1) {
- const shape = {
- ...drawingShape,
- data: {
- points: simplifyPoints(
- drawingShape.data.points,
- gridSize,
- // Downscale fog as smoothing doesn't currently work with edge snapping
- scaleRef.current / 2
- ),
- },
- blend: false,
- };
- onShapeAdd(shape);
- }
- }
- if (hoveredShapeRef.current && isPointerDown) {
- if (toolSettings.type === "remove") {
- onShapeRemove(hoveredShapeRef.current.id);
- } else if (toolSettings.type === "toggle") {
- onShapeEdit({
- ...hoveredShapeRef.current,
- visible: !hoveredShapeRef.current.visible,
- });
- }
+ if (selectedToolSettings.type === "remove") {
+ onShapeRemove(shape.id);
+ } else if (selectedToolSettings.type === "toggle") {
+ onShapeEdit({ ...shape, visible: !shape.visible });
}
- setDrawingShape(null);
- setIsPointerDown(false);
}
- // 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);
- const diagonalPatternRef = useRef();
-
- useEffect(() => {
- let image = new Image();
- image.src = diagonalPattern;
- diagonalPatternRef.current = image;
- }, []);
-
- useEffect(() => {
- const canvas = canvasRef.current;
- if (canvas) {
- const context = canvas.getContext("2d");
-
- context.clearRect(0, 0, width, height);
- let hoveredShape = null;
- if (isEditing) {
- const editPattern = context.createPattern(
- diagonalPatternRef.current,
- "repeat"
- );
- for (let shape of shapes) {
- if (shouldHover) {
- if (
- isShapeHovered(shape, context, pointerPosition, width, height)
- ) {
- hoveredShape = shape;
- }
- }
- drawShape(
- {
- ...shape,
- blend: true,
- color: shape.visible ? "black" : editPattern,
- },
- 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);
- }
+ function handleShapeMouseOver(event, shape) {
+ if (shouldHover) {
+ const path = event.target;
+ if (shape.visible) {
+ const hoverColor = "#BB99FF";
+ path.fill(hoverColor);
} else {
- // Not editing
- for (let shape of shapes) {
- if (shape.visible) {
- drawShape(shape, context, gridSize, width, height);
- }
- }
+ path.opacity(1);
}
- hoveredShapeRef.current = hoveredShape;
+ path.getLayer().draw();
}
- }, [
- shapes,
- width,
- height,
- pointerPosition,
- isEditing,
- drawingShape,
- gridSize,
- shouldHover,
- ]);
+ }
+
+ function handleShapeMouseOut(event, shape) {
+ if (shouldHover) {
+ const path = event.target;
+ if (shape.visible) {
+ const color = colors[shape.color] || shape.color;
+ path.fill(color);
+ } else {
+ path.opacity(0.5);
+ }
+ path.getLayer().draw();
+ }
+ }
+
+ function renderShape(shape) {
+ return (
+ handleShapeMouseOver(e, shape)}
+ onMouseOut={(e) => handleShapeMouseOut(e, shape)}
+ onClick={(e) => handleShapeClick(e, shape)}
+ points={shape.data.points.reduce(
+ (acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
+ []
+ )}
+ stroke={colors[shape.color] || shape.color}
+ fill={colors[shape.color] || shape.color}
+ closed
+ lineCap="round"
+ strokeWidth={getStrokeWidth(
+ shape.strokeWidth,
+ gridSize,
+ mapWidth,
+ mapHeight
+ )}
+ visible={isEditing || shape.visible}
+ opacity={isEditing ? 0.5 : 1}
+ fillPatternImage={patternImage}
+ fillPriority={isEditing && !shape.visible ? "pattern" : "color"}
+ />
+ );
+ }
return (
-
-
-
+
+ {shapes.map(renderShape)}
+ {drawingShape && renderShape(drawingShape)}
+
);
}