Added colours and gradients to pointers
This commit is contained in:
parent
72ecd002dd
commit
2108d32501
@ -14,6 +14,7 @@
|
|||||||
"@testing-library/user-event": "^12.2.2",
|
"@testing-library/user-event": "^12.2.2",
|
||||||
"ammo.js": "kripken/ammo.js#aab297a4164779c3a9d8dc8d9da26958de3cb778",
|
"ammo.js": "kripken/ammo.js#aab297a4164779c3a9d8dc8d9da26958de3cb778",
|
||||||
"case": "^1.6.3",
|
"case": "^1.6.3",
|
||||||
|
"color": "^3.1.3",
|
||||||
"comlink": "^4.3.0",
|
"comlink": "^4.3.0",
|
||||||
"deep-diff": "^1.0.2",
|
"deep-diff": "^1.0.2",
|
||||||
"dexie": "^3.0.3",
|
"dexie": "^3.0.3",
|
||||||
|
@ -9,6 +9,7 @@ import SelectMapButton from "./SelectMapButton";
|
|||||||
import FogToolSettings from "./controls/FogToolSettings";
|
import FogToolSettings from "./controls/FogToolSettings";
|
||||||
import DrawingToolSettings from "./controls/DrawingToolSettings";
|
import DrawingToolSettings from "./controls/DrawingToolSettings";
|
||||||
import MeasureToolSettings from "./controls/MeasureToolSettings";
|
import MeasureToolSettings from "./controls/MeasureToolSettings";
|
||||||
|
import PointerToolSettings from "./controls/PointerToolSettings";
|
||||||
|
|
||||||
import PanToolIcon from "../../icons/PanToolIcon";
|
import PanToolIcon from "../../icons/PanToolIcon";
|
||||||
import FogToolIcon from "../../icons/FogToolIcon";
|
import FogToolIcon from "../../icons/FogToolIcon";
|
||||||
@ -66,6 +67,7 @@ function MapContols({
|
|||||||
id: "pointer",
|
id: "pointer",
|
||||||
icon: <PointerToolIcon />,
|
icon: <PointerToolIcon />,
|
||||||
title: "Pointer Tool (Q)",
|
title: "Pointer Tool (Q)",
|
||||||
|
SettingsComponent: PointerToolSettings,
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
id: "note",
|
id: "note",
|
||||||
|
@ -21,6 +21,7 @@ function MapPointer({
|
|||||||
onPointerMove,
|
onPointerMove,
|
||||||
onPointerUp,
|
onPointerUp,
|
||||||
visible,
|
visible,
|
||||||
|
color,
|
||||||
}) {
|
}) {
|
||||||
const { mapWidth, mapHeight, interactionEmitter } = useContext(
|
const { mapWidth, mapHeight, interactionEmitter } = useContext(
|
||||||
MapInteractionContext
|
MapInteractionContext
|
||||||
@ -69,7 +70,7 @@ function MapPointer({
|
|||||||
{visible && (
|
{visible && (
|
||||||
<Trail
|
<Trail
|
||||||
position={multiply(position, { x: mapWidth, y: mapHeight })}
|
position={multiply(position, { x: mapWidth, y: mapHeight })}
|
||||||
color={colors.red}
|
color={colors[color]}
|
||||||
size={size}
|
size={size}
|
||||||
duration={200}
|
duration={200}
|
||||||
/>
|
/>
|
||||||
@ -78,4 +79,8 @@ function MapPointer({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MapPointer.defaultProps = {
|
||||||
|
color: "red",
|
||||||
|
};
|
||||||
|
|
||||||
export default MapPointer;
|
export default MapPointer;
|
||||||
|
@ -34,7 +34,7 @@ function ColorCircle({ color, selected, onClick, sx }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ColorControl({ color, onColorChange }) {
|
function ColorControl({ color, onColorChange, exclude }) {
|
||||||
const [showColorMenu, setShowColorMenu] = useState(false);
|
const [showColorMenu, setShowColorMenu] = useState(false);
|
||||||
const [colorMenuOptions, setColorMenuOptions] = useState({});
|
const [colorMenuOptions, setColorMenuOptions] = useState({});
|
||||||
|
|
||||||
@ -74,19 +74,21 @@ function ColorControl({ color, onColorChange }) {
|
|||||||
}}
|
}}
|
||||||
p={1}
|
p={1}
|
||||||
>
|
>
|
||||||
{colorOptions.map((c) => (
|
{colorOptions
|
||||||
<ColorCircle
|
.filter((color) => !exclude.includes(color))
|
||||||
key={c}
|
.map((c) => (
|
||||||
color={c}
|
<ColorCircle
|
||||||
selected={c === color}
|
key={c}
|
||||||
onClick={() => {
|
color={c}
|
||||||
onColorChange(c);
|
selected={c === color}
|
||||||
setShowColorMenu(false);
|
onClick={() => {
|
||||||
setColorMenuOptions({});
|
onColorChange(c);
|
||||||
}}
|
setShowColorMenu(false);
|
||||||
sx={{ width: "25%", paddingTop: "25%" }}
|
setColorMenuOptions({});
|
||||||
/>
|
}}
|
||||||
))}
|
sx={{ width: "25%", paddingTop: "25%" }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</MapMenu>
|
</MapMenu>
|
||||||
);
|
);
|
||||||
@ -104,4 +106,8 @@ function ColorControl({ color, onColorChange }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColorControl.defaultProps = {
|
||||||
|
exclude: [],
|
||||||
|
};
|
||||||
|
|
||||||
export default ColorControl;
|
export default ColorControl;
|
||||||
|
18
src/components/map/controls/PointerToolSettings.js
Normal file
18
src/components/map/controls/PointerToolSettings.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Flex } from "theme-ui";
|
||||||
|
|
||||||
|
import ColorControl from "./ColorControl";
|
||||||
|
|
||||||
|
function PointerToolSettings({ settings, onSettingChange }) {
|
||||||
|
return (
|
||||||
|
<Flex sx={{ alignItems: "center" }}>
|
||||||
|
<ColorControl
|
||||||
|
color={settings.color}
|
||||||
|
onColorChange={(color) => onSettingChange({ color })}
|
||||||
|
exclude={["black", "darkGray", "lightGray", "white"]}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PointerToolSettings;
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { Line, Group, Path, Circle } from "react-konva";
|
import { Line, Group, Path, Circle } from "react-konva";
|
||||||
import { lerp } from "./shared";
|
import Color from "color";
|
||||||
import * as Vector2 from "./vector2";
|
import * as Vector2 from "./vector2";
|
||||||
|
|
||||||
// Holes should be wound in the opposite direction as the containing points array
|
// Holes should be wound in the opposite direction as the containing points array
|
||||||
@ -142,10 +142,27 @@ export function Tick({ x, y, scale, onClick, cross }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Trail({ position, size, duration, segments }) {
|
export function Trail({ position, size, duration, segments, color }) {
|
||||||
const trailRef = useRef();
|
const trailRef = useRef();
|
||||||
const pointsRef = useRef([]);
|
const pointsRef = useRef([]);
|
||||||
const prevPositionRef = useRef(position);
|
const prevPositionRef = useRef(position);
|
||||||
|
const positionRef = useRef(position);
|
||||||
|
const circleRef = useRef();
|
||||||
|
// Color of the end of the trial
|
||||||
|
const transparentColorRef = useRef(
|
||||||
|
Color(color).lighten(0.5).alpha(0).string()
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Lighten color to give it a `glow` effect
|
||||||
|
transparentColorRef.current = Color(color).lighten(0.5).alpha(0).string();
|
||||||
|
}, [color]);
|
||||||
|
|
||||||
|
// Keep track of position so we can use it in the trail animation
|
||||||
|
useEffect(() => {
|
||||||
|
positionRef.current = position;
|
||||||
|
}, [position]);
|
||||||
|
|
||||||
// Add a new point every time position is changed
|
// Add a new point every time position is changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Vector2.compare(position, prevPositionRef.current, 0.0001)) {
|
if (Vector2.compare(position, prevPositionRef.current, 0.0001)) {
|
||||||
@ -178,6 +195,13 @@ export function Trail({ position, size, duration, segments }) {
|
|||||||
if (expired > 0) {
|
if (expired > 0) {
|
||||||
pointsRef.current = pointsRef.current.slice(expired);
|
pointsRef.current = pointsRef.current.slice(expired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the circle position to keep it in sync with the trail
|
||||||
|
if (circleRef.current) {
|
||||||
|
circleRef.current.x(positionRef.current.x);
|
||||||
|
circleRef.current.y(positionRef.current.y);
|
||||||
|
}
|
||||||
|
|
||||||
if (trailRef.current) {
|
if (trailRef.current) {
|
||||||
trailRef.current.getLayer().draw();
|
trailRef.current.getLayer().draw();
|
||||||
}
|
}
|
||||||
@ -192,20 +216,57 @@ export function Trail({ position, size, duration, segments }) {
|
|||||||
function sceneFunc(context) {
|
function sceneFunc(context) {
|
||||||
// Resample points to ensure a smooth trail
|
// Resample points to ensure a smooth trail
|
||||||
const resampledPoints = Vector2.resample(pointsRef.current, segments);
|
const resampledPoints = Vector2.resample(pointsRef.current, segments);
|
||||||
|
if (resampledPoints.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Draws a line offset in the direction perpendicular to its travel direction
|
||||||
|
const drawOffsetLine = (from, to, alpha) => {
|
||||||
|
const forward = Vector2.normalize(Vector2.subtract(from, to));
|
||||||
|
// Rotate the forward vector 90 degrees based off of the direction
|
||||||
|
const side = { x: forward.y, y: -forward.x };
|
||||||
|
|
||||||
|
// Offset the `to` position by the size of the point and in the side direction
|
||||||
|
const toSize = (alpha * size) / 2;
|
||||||
|
const toOffset = Vector2.add(to, Vector2.multiply(side, toSize));
|
||||||
|
|
||||||
|
context.lineTo(toOffset.x, toOffset.y);
|
||||||
|
};
|
||||||
|
context.beginPath();
|
||||||
|
// Sample the points starting from the tail then traverse counter clockwise drawing each point
|
||||||
|
// offset to make a taper, stops at the base of the trail
|
||||||
|
context.moveTo(resampledPoints[0].x, resampledPoints[0].y);
|
||||||
for (let i = 1; i < resampledPoints.length; i++) {
|
for (let i = 1; i < resampledPoints.length; i++) {
|
||||||
const from = resampledPoints[i - 1];
|
const from = resampledPoints[i - 1];
|
||||||
const to = resampledPoints[i];
|
const to = resampledPoints[i];
|
||||||
const alpha = i / resampledPoints.length;
|
drawOffsetLine(from, to, i / resampledPoints.length);
|
||||||
context.beginPath();
|
|
||||||
context.lineJoin = "round";
|
|
||||||
context.lineCap = "round";
|
|
||||||
context.lineWidth = alpha * size;
|
|
||||||
context.strokeStyle = `hsl(0, 63%, ${lerp(90, 50, alpha)}%)`;
|
|
||||||
context.moveTo(from.x, from.y);
|
|
||||||
context.lineTo(to.x, to.y);
|
|
||||||
context.stroke();
|
|
||||||
context.closePath();
|
|
||||||
}
|
}
|
||||||
|
// Start from the base of the trail and continue drawing down back to the end of the tail
|
||||||
|
for (let i = resampledPoints.length - 2; i >= 0; i--) {
|
||||||
|
const from = resampledPoints[i + 1];
|
||||||
|
const to = resampledPoints[i];
|
||||||
|
drawOffsetLine(from, to, i / resampledPoints.length);
|
||||||
|
}
|
||||||
|
context.lineTo(resampledPoints[0].x, resampledPoints[0].y);
|
||||||
|
context.closePath();
|
||||||
|
|
||||||
|
// Create a radial gradient from the center of the trail to the tail
|
||||||
|
const gradientCenter = resampledPoints[resampledPoints.length - 1];
|
||||||
|
const gradientEnd = resampledPoints[0];
|
||||||
|
const gradientRadius = Vector2.length(
|
||||||
|
Vector2.subtract(gradientCenter, gradientEnd)
|
||||||
|
);
|
||||||
|
let gradient = context.createRadialGradient(
|
||||||
|
gradientCenter.x,
|
||||||
|
gradientCenter.y,
|
||||||
|
0,
|
||||||
|
gradientCenter.x,
|
||||||
|
gradientCenter.y,
|
||||||
|
gradientRadius
|
||||||
|
);
|
||||||
|
gradient.addColorStop(0, color);
|
||||||
|
gradient.addColorStop(1, transparentColorRef.current);
|
||||||
|
context.fillStyle = gradient;
|
||||||
|
context.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -214,9 +275,10 @@ export function Trail({ position, size, duration, segments }) {
|
|||||||
<Circle
|
<Circle
|
||||||
x={position.x}
|
x={position.x}
|
||||||
y={position.y}
|
y={position.y}
|
||||||
fill="hsl(0, 63%, 50%)"
|
fill={color}
|
||||||
width={size}
|
width={size}
|
||||||
height={size}
|
height={size}
|
||||||
|
ref={circleRef}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import AuthContext from "../contexts/AuthContext";
|
|||||||
import MapPointer from "../components/map/MapPointer";
|
import MapPointer from "../components/map/MapPointer";
|
||||||
import { isEmpty } from "../helpers/shared";
|
import { isEmpty } from "../helpers/shared";
|
||||||
import { lerp, compare } from "../helpers/vector2";
|
import { lerp, compare } from "../helpers/vector2";
|
||||||
|
import useSetting from "../helpers/useSetting";
|
||||||
|
|
||||||
// Send pointer updates every 50ms (20fps)
|
// Send pointer updates every 50ms (20fps)
|
||||||
const sendTickRate = 50;
|
const sendTickRate = 50;
|
||||||
@ -13,6 +14,7 @@ const sendTickRate = 50;
|
|||||||
function NetworkedMapPointer({ session, active, gridSize }) {
|
function NetworkedMapPointer({ session, active, gridSize }) {
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
const [localPointerState, setLocalPointerState] = useState({});
|
const [localPointerState, setLocalPointerState] = useState({});
|
||||||
|
const [pointerColor] = useSetting("pointer.color");
|
||||||
|
|
||||||
const sessionRef = useRef(session);
|
const sessionRef = useRef(session);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -22,10 +24,15 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userId && !(userId in localPointerState)) {
|
if (userId && !(userId in localPointerState)) {
|
||||||
setLocalPointerState({
|
setLocalPointerState({
|
||||||
[userId]: { position: { x: 0, y: 0 }, visible: false, id: userId },
|
[userId]: {
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
visible: false,
|
||||||
|
id: userId,
|
||||||
|
color: pointerColor,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [userId, localPointerState]);
|
}, [userId, localPointerState, pointerColor]);
|
||||||
|
|
||||||
// Send pointer updates every sendTickRate to peers to save on bandwidth
|
// Send pointer updates every sendTickRate to peers to save on bandwidth
|
||||||
// We use requestAnimationFrame as setInterval was being blocked during
|
// We use requestAnimationFrame as setInterval was being blocked during
|
||||||
@ -65,9 +72,14 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
function updateOwnPointerState(position, visible) {
|
function updateOwnPointerState(position, visible) {
|
||||||
setLocalPointerState((prev) => ({
|
setLocalPointerState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[userId]: { position, visible, id: userId },
|
[userId]: { position, visible, id: userId, color: pointerColor },
|
||||||
}));
|
}));
|
||||||
ownPointerUpdateRef.current = { position, visible, id: userId };
|
ownPointerUpdateRef.current = {
|
||||||
|
position,
|
||||||
|
visible,
|
||||||
|
id: userId,
|
||||||
|
color: pointerColor,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleOwnPointerDown(position) {
|
function handleOwnPointerDown(position) {
|
||||||
@ -142,6 +154,7 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
id: interp.id,
|
id: interp.id,
|
||||||
visible: interp.from.visible,
|
visible: interp.from.visible,
|
||||||
position: lerp(interp.from.position, interp.to.position, alpha),
|
position: lerp(interp.from.position, interp.to.position, alpha),
|
||||||
|
color: interp.from.color,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (alpha > 1 && !interp.to.visible) {
|
if (alpha > 1 && !interp.to.visible) {
|
||||||
@ -149,6 +162,7 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
id: interp.id,
|
id: interp.id,
|
||||||
visible: interp.to.visible,
|
visible: interp.to.visible,
|
||||||
position: interp.to.position,
|
position: interp.to.position,
|
||||||
|
color: interp.to.color,
|
||||||
};
|
};
|
||||||
delete interpolationsRef.current[interp.id];
|
delete interpolationsRef.current[interp.id];
|
||||||
}
|
}
|
||||||
@ -178,6 +192,7 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
onPointerDown={pointer.id === userId && handleOwnPointerDown}
|
onPointerDown={pointer.id === userId && handleOwnPointerDown}
|
||||||
onPointerMove={pointer.id === userId && handleOwnPointerMove}
|
onPointerMove={pointer.id === userId && handleOwnPointerMove}
|
||||||
onPointerUp={pointer.id === userId && handleOwnPointerUp}
|
onPointerUp={pointer.id === userId && handleOwnPointerUp}
|
||||||
|
color={pointer.color}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -37,6 +37,11 @@ function loadVersions(settings) {
|
|||||||
...prev,
|
...prev,
|
||||||
game: { usePassword: true },
|
game: { usePassword: true },
|
||||||
}));
|
}));
|
||||||
|
// v1.7.1 - Added pointer color
|
||||||
|
settings.version(4, (prev) => ({
|
||||||
|
...prev,
|
||||||
|
pointer: { color: "red" },
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSettings() {
|
export function getSettings() {
|
||||||
|
@ -3781,7 +3781,7 @@ color-string@^1.5.4:
|
|||||||
color-name "^1.0.0"
|
color-name "^1.0.0"
|
||||||
simple-swizzle "^0.2.2"
|
simple-swizzle "^0.2.2"
|
||||||
|
|
||||||
color@^3.0.0:
|
color@^3.0.0, color@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
|
resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
|
||||||
integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
|
integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
|
||||||
|
Loading…
Reference in New Issue
Block a user