Moved back to drag events for map and fog drawing

Moved to an event driven model for dragging
This commit is contained in:
Mitchell McCaffrey 2020-06-24 09:27:20 +10:00
parent afb22c7a73
commit 9a4d047cd5
4 changed files with 93 additions and 83 deletions

View File

@ -25,7 +25,9 @@ function MapDrawing({
selectedToolSettings, selectedToolSettings,
gridSize, gridSize,
}) { }) {
const { stageScale, mapWidth, mapHeight } = useContext(MapInteractionContext); const { stageScale, mapWidth, mapHeight, interactionEmitter } = useContext(
MapInteractionContext
);
const mapStageRef = useContext(MapStageContext); const mapStageRef = useContext(MapStageContext);
const [drawingShape, setDrawingShape] = useState(null); const [drawingShape, setDrawingShape] = useState(null);
const [isBrushDown, setIsBrushDown] = useState(false); const [isBrushDown, setIsBrushDown] = useState(false);
@ -145,14 +147,14 @@ function MapDrawing({
setIsBrushDown(false); setIsBrushDown(false);
} }
mapStage.on("mousedown touchstart", handleBrushDown); interactionEmitter.on("dragStart", handleBrushDown);
mapStage.on("mousemove touchmove", handleBrushMove); interactionEmitter.on("drag", handleBrushMove);
mapStage.on("mouseup touchend", handleBrushUp); interactionEmitter.on("dragEnd", handleBrushUp);
return () => { return () => {
mapStage.off("mousedown touchstart", handleBrushDown); interactionEmitter.off("dragStart", handleBrushDown);
mapStage.off("mousemove touchmove", handleBrushMove); interactionEmitter.off("drag", handleBrushMove);
mapStage.off("mouseup touchend", handleBrushUp); interactionEmitter.off("dragEnd", handleBrushUp);
}; };
}, [ }, [
drawingShape, drawingShape,
@ -169,6 +171,7 @@ function MapDrawing({
selectedToolSettings, selectedToolSettings,
shapes, shapes,
stageScale, stageScale,
interactionEmitter,
]); ]);
function handleShapeOver(shape, isDown) { function handleShapeOver(shape, isDown) {
@ -188,6 +191,7 @@ function MapDrawing({
onTouchStart: () => handleShapeOver(shape, true), onTouchStart: () => handleShapeOver(shape, true),
fill: colors[shape.color] || shape.color, fill: colors[shape.color] || shape.color,
opacity: shape.blend ? 0.5 : 1, opacity: shape.blend ? 0.5 : 1,
id: shape.id,
}; };
if (shape.type === "path") { if (shape.type === "path") {
return ( return (

View File

@ -31,7 +31,9 @@ function MapFog({
selectedToolSettings, selectedToolSettings,
gridSize, gridSize,
}) { }) {
const { stageScale, mapWidth, mapHeight } = useContext(MapInteractionContext); const { stageScale, mapWidth, mapHeight, interactionEmitter } = useContext(
MapInteractionContext
);
const mapStageRef = useContext(MapStageContext); const mapStageRef = useContext(MapStageContext);
const [drawingShape, setDrawingShape] = useState(null); const [drawingShape, setDrawingShape] = useState(null);
const [isBrushDown, setIsBrushDown] = useState(false); const [isBrushDown, setIsBrushDown] = useState(false);
@ -109,18 +111,6 @@ function MapFog({
}; };
}); });
} }
if (selectedToolSettings.type === "polygon" && drawingShape) {
const brushPosition = getBrushPosition();
setDrawingShape((prevShape) => {
return {
...prevShape,
data: {
...prevShape.data,
points: [...prevShape.data.points.slice(0, -1), brushPosition],
},
};
});
}
} }
function handleBrushUp() { function handleBrushUp() {
@ -152,29 +142,10 @@ function MapFog({
onShapeAdd(shape); onShapeAdd(shape);
} }
} }
setDrawingShape(null); setDrawingShape(null);
} }
if (selectedToolSettings.type === "polygon") { // Erase
const brushPosition = getBrushPosition();
setDrawingShape({
type: "fog",
data: {
points: [
...(drawingShape ? drawingShape.data.points : [brushPosition]),
brushPosition,
],
holes: [],
},
strokeWidth: 0.5,
color: selectedToolSettings.useFogSubtract ? "red" : "black",
blend: false,
id: shortid.generate(),
visible: true,
});
}
if (editingShapes.length > 0) { if (editingShapes.length > 0) {
if (selectedToolSettings.type === "remove") { if (selectedToolSettings.type === "remove") {
onShapesRemove(editingShapes.map((shape) => shape.id)); onShapesRemove(editingShapes.map((shape) => shape.id));
@ -192,14 +163,69 @@ function MapFog({
setIsBrushDown(false); setIsBrushDown(false);
} }
mapStage.on("mousedown touchstart", handleBrushDown); function handlePolygonClick() {
mapStage.on("mousemove touchmove", handleBrushMove); if (selectedToolSettings.type === "polygon") {
mapStage.on("mouseup touchend", handleBrushUp); const brushPosition = getBrushPosition();
setDrawingShape((prevDrawingShape) => {
if (prevDrawingShape) {
return {
...prevDrawingShape,
data: {
...prevDrawingShape.data,
points: [...prevDrawingShape.data.points, brushPosition],
},
};
} else {
return {
type: "fog",
data: {
points: [brushPosition, brushPosition],
holes: [],
},
strokeWidth: 0.5,
color: selectedToolSettings.useFogSubtract ? "red" : "black",
blend: false,
id: shortid.generate(),
visible: true,
};
}
});
}
}
function handlePolygonMove() {
if (selectedToolSettings.type === "polygon" && drawingShape) {
const brushPosition = getBrushPosition();
setDrawingShape((prevShape) => {
if (!prevShape) {
return;
}
return {
...prevShape,
data: {
...prevShape.data,
points: [...prevShape.data.points.slice(0, -1), brushPosition],
},
};
});
}
}
interactionEmitter.on("dragStart", handleBrushDown);
interactionEmitter.on("drag", handleBrushMove);
interactionEmitter.on("dragEnd", handleBrushUp);
// Use mouse events for polygon and erase to allow for single clicks
mapStage.on("mousedown touchstart", handlePolygonMove);
mapStage.on("mousemove touchmove", handlePolygonMove);
mapStage.on("click tap", handlePolygonClick);
return () => { return () => {
mapStage.off("mousedown touchstart", handleBrushDown); interactionEmitter.off("dragStart", handleBrushDown);
mapStage.off("mousemove touchmove", handleBrushMove); interactionEmitter.off("drag", handleBrushMove);
mapStage.off("mouseup touchend", handleBrushUp); interactionEmitter.off("dragEnd", handleBrushUp);
mapStage.off("mousedown touchstart", handlePolygonMove);
mapStage.off("mousemove touchmove", handlePolygonMove);
mapStage.off("click tap", handlePolygonClick);
}; };
}, [ }, [
mapStageRef, mapStageRef,
@ -216,6 +242,7 @@ function MapFog({
selectedToolSettings, selectedToolSettings,
shapes, shapes,
stageScale, stageScale,
interactionEmitter,
]); ]);
const finishDrawingPolygon = useCallback(() => { const finishDrawingPolygon = useCallback(() => {
@ -317,15 +344,16 @@ function MapFog({
if (shape.data.points.length === 0) { if (shape.data.points.length === 0) {
return; return;
} }
const isCross = shape.data.points.length < 4;
return ( return (
<Tick <Tick
x={shape.data.points[0].x * mapWidth} x={shape.data.points[0].x * mapWidth}
y={shape.data.points[0].y * mapHeight} y={shape.data.points[0].y * mapHeight}
scale={1 / stageScale} scale={1 / stageScale}
cross={shape.data.points.length < 4} cross={isCross}
onClick={() => { onClick={(e) => {
// Check that there is enough points after clicking e.cancelBubble = true;
if (shape.data.points.length < 5) { if (isCross) {
setDrawingShape(null); setDrawingShape(null);
} else { } else {
finishDrawingPolygon(); finishDrawingPolygon();

View File

@ -4,6 +4,7 @@ import { useGesture } from "react-use-gesture";
import ReactResizeDetector from "react-resize-detector"; import ReactResizeDetector from "react-resize-detector";
import useImage from "use-image"; import useImage from "use-image";
import { Stage, Layer, Image } from "react-konva"; import { Stage, Layer, Image } from "react-konva";
import { EventEmitter } from "events";
import usePreventOverscroll from "../../helpers/usePreventOverscroll"; import usePreventOverscroll from "../../helpers/usePreventOverscroll";
import useDataSource from "../../helpers/useDataSource"; import useDataSource from "../../helpers/useDataSource";
@ -34,15 +35,12 @@ function MapInteraction({
const [stageWidth, setStageWidth] = useState(1); const [stageWidth, setStageWidth] = useState(1);
const [stageHeight, setStageHeight] = useState(1); const [stageHeight, setStageHeight] = useState(1);
const [stageScale, setStageScale] = useState(1); const [stageScale, setStageScale] = useState(1);
// "none" | "first" | "dragging" | "last"
const [stageDragState, setStageDragState] = useState("none");
const [preventMapInteraction, setPreventMapInteraction] = useState(false); const [preventMapInteraction, setPreventMapInteraction] = useState(false);
const stageWidthRef = useRef(stageWidth); const stageWidthRef = useRef(stageWidth);
const stageHeightRef = useRef(stageHeight); const stageHeightRef = useRef(stageHeight);
// Avoid state udpates when panning the map by using a ref and updating the konva element directly // Avoid state udpates when panning the map by using a ref and updating the konva element directly
const stageTranslateRef = useRef({ x: 0, y: 0 }); const stageTranslateRef = useRef({ x: 0, y: 0 });
const mapDragPositionRef = useRef({ x: 0, y: 0 });
// Reset transform when map changes // Reset transform when map changes
useEffect(() => { useEffect(() => {
@ -62,30 +60,13 @@ function MapInteraction({
} }
}, [map]); }, [map]);
// Convert a client space XY to be normalized to the map image
function getMapDragPosition(xy) {
const [x, y] = xy;
const container = containerRef.current;
const mapImage = mapImageRef.current;
if (container && mapImage) {
const containerRect = container.getBoundingClientRect();
const mapRect = mapImage.getClientRect();
const offsetX = x - containerRect.left - mapRect.x;
const offsetY = y - containerRect.top - mapRect.y;
const normalizedX = offsetX / mapRect.width;
const normalizedY = offsetY / mapRect.height;
return { x: normalizedX, y: normalizedY };
}
}
const pinchPreviousDistanceRef = useRef(); const pinchPreviousDistanceRef = useRef();
const pinchPreviousOriginRef = useRef(); const pinchPreviousOriginRef = useRef();
const isInteractingWithCanvas = useRef(false); const isInteractingWithCanvas = useRef(false);
const previousSelectedToolRef = useRef(selectedToolId); const previousSelectedToolRef = useRef(selectedToolId);
const [interactionEmitter] = useState(new EventEmitter());
const bind = useGesture({ const bind = useGesture({
onWheelStart: ({ event }) => { onWheelStart: ({ event }) => {
isInteractingWithCanvas.current = isInteractingWithCanvas.current =
@ -168,15 +149,14 @@ function MapInteraction({
layer.draw(); layer.draw();
stageTranslateRef.current = newTranslate; stageTranslateRef.current = newTranslate;
} }
mapDragPositionRef.current = getMapDragPosition(xy); if (first) {
const newDragState = first ? "first" : last ? "last" : "dragging"; interactionEmitter.emit("dragStart");
if (stageDragState !== newDragState) { } else if (last) {
setStageDragState(newDragState); interactionEmitter.emit("dragEnd");
} else {
interactionEmitter.emit("drag");
} }
}, },
onDragEnd: () => {
setStageDragState("none");
},
}); });
function handleResize(width, height) { function handleResize(width, height) {
@ -214,11 +194,10 @@ function MapInteraction({
stageScale, stageScale,
stageWidth, stageWidth,
stageHeight, stageHeight,
stageDragState,
setPreventMapInteraction, setPreventMapInteraction,
mapWidth, mapWidth,
mapHeight, mapHeight,
mapDragPositionRef, interactionEmitter,
}; };
// Enable keyboard interaction for map stage container // Enable keyboard interaction for map stage container

View File

@ -4,11 +4,10 @@ const MapInteractionContext = React.createContext({
stageScale: 1, stageScale: 1,
stageWidth: 1, stageWidth: 1,
stageHeight: 1, stageHeight: 1,
stageDragState: "none",
setPreventMapInteraction: () => {}, setPreventMapInteraction: () => {},
mapWidth: 1, mapWidth: 1,
mapHeight: 1, mapHeight: 1,
mapDragPositionRef: { current: undefined }, interactionEmitter: null,
}); });
export const MapInteractionProvider = MapInteractionContext.Provider; export const MapInteractionProvider = MapInteractionContext.Provider;