diff --git a/src/components/map/Map.js b/src/components/map/Map.js
index df9384a..553bfdf 100644
--- a/src/components/map/Map.js
+++ b/src/components/map/Map.js
@@ -10,6 +10,7 @@ import MapDice from "./MapDice";
import MapGrid from "./MapGrid";
import MapMeasure from "./MapMeasure";
import MapLoadingOverlay from "./MapLoadingOverlay";
+import NetworkedMapPointer from "../../network/NetworkedMapPointer";
import TokenDataContext from "../../contexts/TokenDataContext";
@@ -35,6 +36,7 @@ function Map({
allowFogDrawing,
allowMapChange,
disabledTokens,
+ session,
}) {
const { tokensById } = useContext(TokenDataContext);
@@ -139,6 +141,7 @@ function Map({
if (!map) {
disabledControls.push("pan");
disabledControls.push("measure");
+ disabledControls.push("pointer");
}
if (!allowFogDrawing) {
disabledControls.push("fog");
@@ -304,6 +307,14 @@ function Map({
/>
);
+ const mapPointer = (
+
+ );
+
return (
);
diff --git a/src/components/map/MapControls.js b/src/components/map/MapControls.js
index 33873ba..ca46ae1 100644
--- a/src/components/map/MapControls.js
+++ b/src/components/map/MapControls.js
@@ -15,6 +15,7 @@ import FogToolIcon from "../../icons/FogToolIcon";
import BrushToolIcon from "../../icons/BrushToolIcon";
import MeasureToolIcon from "../../icons/MeasureToolIcon";
import ExpandMoreIcon from "../../icons/ExpandMoreIcon";
+import PointerToolIcon from "../../icons/PointerToolIcon";
function MapContols({
onMapChange,
@@ -55,8 +56,13 @@ function MapContols({
title: "Measure Tool",
SettingsComponent: MeasureToolSettings,
},
+ pointer: {
+ id: "pointer",
+ icon: ,
+ title: "Pointer Tool",
+ },
};
- const tools = ["pan", "fog", "drawing", "measure"];
+ const tools = ["pan", "fog", "drawing", "measure", "pointer"];
const sections = [
{
diff --git a/src/components/map/MapPointer.js b/src/components/map/MapPointer.js
new file mode 100644
index 0000000..141e9cf
--- /dev/null
+++ b/src/components/map/MapPointer.js
@@ -0,0 +1,81 @@
+import React, { useContext, useEffect } from "react";
+import { Group, Circle } from "react-konva";
+
+import MapInteractionContext from "../../contexts/MapInteractionContext";
+import MapStageContext from "../../contexts/MapStageContext";
+
+import { getStrokeWidth } from "../../helpers/drawing";
+import { getRelativePointerPositionNormalized } from "../../helpers/konva";
+
+import colors from "../../helpers/colors";
+
+function MapPointer({
+ gridSize,
+ active,
+ position,
+ onPointerDown,
+ onPointerMove,
+ onPointerUp,
+ visible,
+}) {
+ const { mapWidth, mapHeight, interactionEmitter } = useContext(
+ MapInteractionContext
+ );
+ const mapStageRef = useContext(MapStageContext);
+
+ // const [isBrushDown, setIsBrushDown] = useState(false);
+ // const [brushPosition, setBrushPosition] = useState({ x: 0, y: 0 });
+
+ useEffect(() => {
+ if (!active) {
+ return;
+ }
+
+ const mapStage = mapStageRef.current;
+
+ function getBrushPosition() {
+ const mapImage = mapStage.findOne("#mapImage");
+ return getRelativePointerPositionNormalized(mapImage);
+ }
+
+ function handleBrushDown() {
+ onPointerDown && onPointerDown(getBrushPosition());
+ }
+
+ function handleBrushMove() {
+ onPointerMove && onPointerMove(getBrushPosition());
+ }
+
+ function handleBrushUp() {
+ onPointerMove && onPointerUp({ x: 0, y: 0 });
+ }
+
+ interactionEmitter.on("dragStart", handleBrushDown);
+ interactionEmitter.on("drag", handleBrushMove);
+ interactionEmitter.on("dragEnd", handleBrushUp);
+
+ return () => {
+ interactionEmitter.off("dragStart", handleBrushDown);
+ interactionEmitter.off("drag", handleBrushMove);
+ interactionEmitter.off("dragEnd", handleBrushUp);
+ };
+ });
+
+ const size = getStrokeWidth(2, gridSize, mapWidth, mapHeight);
+
+ return (
+
+ {visible && (
+
+ )}
+
+ );
+}
+
+export default MapPointer;
diff --git a/src/icons/PointerToolIcon.js b/src/icons/PointerToolIcon.js
new file mode 100644
index 0000000..7704c99
--- /dev/null
+++ b/src/icons/PointerToolIcon.js
@@ -0,0 +1,18 @@
+import React from "react";
+
+function PointerToolIcon() {
+ return (
+
+ );
+}
+
+export default PointerToolIcon;
diff --git a/src/network/NetworkedMapAndTokens.js b/src/network/NetworkedMapAndTokens.js
index c3e99e4..9355c1e 100644
--- a/src/network/NetworkedMapAndTokens.js
+++ b/src/network/NetworkedMapAndTokens.js
@@ -427,6 +427,7 @@ function NetworkedMapAndTokens({ session }) {
allowFogDrawing={canEditFogDrawing}
allowMapChange={canChangeMap}
disabledTokens={disabledMapTokens}
+ session={session}
/>
>
diff --git a/src/network/NetworkedMapPointer.js b/src/network/NetworkedMapPointer.js
new file mode 100644
index 0000000..a7cda69
--- /dev/null
+++ b/src/network/NetworkedMapPointer.js
@@ -0,0 +1,76 @@
+import React, { useState, useContext, useEffect } from "react";
+import { Group } from "react-konva";
+
+import AuthContext from "../contexts/AuthContext";
+
+import MapPointer from "../components/map/MapPointer";
+
+function NetworkedMapPointer({ session, active, gridSize }) {
+ const { userId } = useContext(AuthContext);
+ const [pointerState, setPointerState] = useState({});
+
+ useEffect(() => {
+ if (userId && !(userId in pointerState)) {
+ setPointerState({
+ [userId]: { position: { x: 0, y: 0 }, visible: false, id: userId },
+ });
+ }
+ }, [userId, pointerState]);
+
+ function updateOwnPointerState(position, visible) {
+ const update = { [userId]: { position, visible, id: userId } };
+ setPointerState((prev) => ({
+ ...prev,
+ ...update,
+ }));
+ session.send("pointer", update);
+ }
+
+ function handleOwnPointerDown(position) {
+ updateOwnPointerState(position, true);
+ }
+
+ function handleOwnPointerMove(position) {
+ updateOwnPointerState(position, true);
+ }
+
+ function handleOwnPointerUp(position) {
+ updateOwnPointerState(position, false);
+ }
+
+ useEffect(() => {
+ function handlePeerData({ id, data }) {
+ if (id === "pointer") {
+ setPointerState((prev) => ({
+ ...prev,
+ ...data,
+ }));
+ }
+ }
+
+ session.on("data", handlePeerData);
+
+ return () => {
+ session.off("data", handlePeerData);
+ };
+ });
+
+ return (
+
+ {Object.values(pointerState).map((pointer) => (
+
+ ))}
+
+ );
+}
+
+export default NetworkedMapPointer;