Readded map fog
This commit is contained in:
parent
a81eed1a48
commit
a6768727fe
@ -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 = (
|
||||
<MapFog
|
||||
shapes={fogShapes}
|
||||
onShapeAdd={handleFogShapeAdd}
|
||||
onShapeRemove={handleFogShapeRemove}
|
||||
onShapeEdit={handleFogShapeEdit}
|
||||
selectedToolId={selectedToolId}
|
||||
selectedToolSettings={toolSettings[selectedToolId]}
|
||||
gridSize={gridSizeNormalized}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<MapInteraction
|
||||
map={map}
|
||||
@ -251,6 +277,7 @@ function Map({
|
||||
>
|
||||
{mapDrawing}
|
||||
{mapTokens}
|
||||
{mapFog}
|
||||
</MapInteraction>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
function startShape() {
|
||||
const brushPosition = getBrushPositionForTool(
|
||||
position,
|
||||
"fog",
|
||||
toolSettings,
|
||||
mapDragPosition,
|
||||
selectedToolId,
|
||||
selectedToolSettings,
|
||||
gridSize,
|
||||
shapes
|
||||
);
|
||||
if (isEditing && toolSettings.type === "add") {
|
||||
if (selectedToolSettings.type === "add") {
|
||||
setDrawingShape({
|
||||
type: "fog",
|
||||
data: { points: [brushPosition] },
|
||||
strokeWidth: 0.5,
|
||||
color: "black",
|
||||
blend: true, // Blend while drawing
|
||||
blend: false,
|
||||
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 continueShape() {
|
||||
const brushPosition = getBrushPositionForTool(
|
||||
position,
|
||||
"fog",
|
||||
toolSettings,
|
||||
mapDragPosition,
|
||||
selectedToolId,
|
||||
selectedToolSettings,
|
||||
gridSize,
|
||||
shapes
|
||||
);
|
||||
if (isEditing && toolSettings.type === "add" && drawingShape) {
|
||||
if (selectedToolSettings.type === "add") {
|
||||
setDrawingShape((prevShape) => {
|
||||
const prevPoints = prevShape.data.points;
|
||||
if (
|
||||
@ -117,16 +95,9 @@ function MapFog({
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleStop(event) {
|
||||
if (!isEditing) {
|
||||
return;
|
||||
}
|
||||
if (event.touches && event.touches.length !== 0) {
|
||||
return;
|
||||
}
|
||||
if (isEditing && toolSettings.type === "add" && drawingShape) {
|
||||
function endShape() {
|
||||
if (selectedToolSettings.type === "add" && drawingShape) {
|
||||
if (drawingShape.data.points.length > 1) {
|
||||
const shape = {
|
||||
...drawingShape,
|
||||
@ -135,141 +106,114 @@ function MapFog({
|
||||
drawingShape.data.points,
|
||||
gridSize,
|
||||
// Downscale fog as smoothing doesn't currently work with edge snapping
|
||||
scaleRef.current / 2
|
||||
stageScale / 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
// Not editing
|
||||
for (let shape of shapes) {
|
||||
if (shape.visible) {
|
||||
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,
|
||||
stageDragState,
|
||||
mapDragPosition,
|
||||
selectedToolId,
|
||||
selectedToolSettings,
|
||||
isEditing,
|
||||
drawingShape,
|
||||
gridSize,
|
||||
shouldHover,
|
||||
stageScale,
|
||||
onShapeAdd,
|
||||
shapes,
|
||||
drawingShape,
|
||||
]);
|
||||
|
||||
function handleShapeClick(_, shape) {
|
||||
if (!isEditing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedToolSettings.type === "remove") {
|
||||
onShapeRemove(shape.id);
|
||||
} else if (selectedToolSettings.type === "toggle") {
|
||||
onShapeEdit({ ...shape, visible: !shape.visible });
|
||||
}
|
||||
}
|
||||
|
||||
function handleShapeMouseOver(event, shape) {
|
||||
if (shouldHover) {
|
||||
const path = event.target;
|
||||
if (shape.visible) {
|
||||
const hoverColor = "#BB99FF";
|
||||
path.fill(hoverColor);
|
||||
} else {
|
||||
path.opacity(1);
|
||||
}
|
||||
path.getLayer().draw();
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<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%" }}
|
||||
<Line
|
||||
key={shape.id}
|
||||
onMouseOver={(e) => 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"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Group>
|
||||
{shapes.map(renderShape)}
|
||||
{drawingShape && renderShape(drawingShape)}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user