Added brush blending and brush gesture options

This commit is contained in:
Mitchell McCaffrey 2020-04-20 23:52:21 +10:00
parent 36c3e76403
commit d2474ae198
10 changed files with 1357 additions and 34 deletions

View File

@ -17,6 +17,7 @@
"react-modal": "^3.11.2",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.0",
"shape-detector": "^0.2.1",
"shortid": "^2.2.15",
"simple-peer": "^9.6.2",
"simplebar-react": "^2.1.0",

View File

@ -46,6 +46,8 @@ function Map({
const [selectedTool, setSelectedTool] = useState("pan");
const [brushColor, setBrushColor] = useState("black");
const [useBrushGridSnapping, setUseBrushGridSnapping] = useState(false);
const [useBrushBlending, setUseBrushBlending] = useState(false);
const [useBrushGesture, setUseBrushGesture] = useState(false);
const [drawnShapes, setDrawnShapes] = useState([]);
function handleShapeAdd(shape) {
@ -285,6 +287,8 @@ function Map({
brushColor={brushColor}
useGridSnapping={useBrushGridSnapping}
gridSize={gridSizeNormalized}
useBrushBlending={useBrushBlending}
useBrushGesture={useBrushGesture}
/>
{mapTokens}
</Box>
@ -303,6 +307,10 @@ function Map({
onEraseAll={handleShapeRemoveAll}
useBrushGridSnapping={useBrushGridSnapping}
onBrushGridSnappingChange={setUseBrushGridSnapping}
useBrushBlending={useBrushBlending}
onBrushBlendingChange={setUseBrushBlending}
useBrushGesture={useBrushGesture}
onBrushGestureChange={setUseBrushGesture}
/>
</Box>
<ProxyToken

View File

@ -10,6 +10,10 @@ import UndoIcon from "../icons/UndoIcon";
import RedoIcon from "../icons/RedoIcon";
import GridOnIcon from "../icons/GridOnIcon";
import GridOffIcon from "../icons/GridOffIcon";
import BlendOnIcon from "../icons/BlendOnIcon";
import BlendOffIcon from "../icons/BlendOffIcon";
import GestureOnIcon from "../icons/GestureOnIcon";
import GestureOffIcon from "../icons/GestureOffIcon";
import colors, { colorOptions } from "../helpers/colors";
@ -30,6 +34,10 @@ function MapControls({
onEraseAll,
useBrushGridSnapping,
onBrushGridSnappingChange,
useBrushBlending,
onBrushBlendingChange,
useBrushGesture,
onBrushGestureChange,
}) {
const [isExpanded, setIsExpanded] = useState(false);
@ -71,35 +79,53 @@ function MapControls({
</Box>
))}
</Box>
<Box>
<Label
sx={{
fontSize: 1,
alignItems: "center",
":hover": { color: "primary", cursor: "pointer" },
":active": { color: "secondary" },
}}
<Flex sx={{ justifyContent: "space-between" }}>
<IconButton
aria-label={
useBrushGridSnapping
? "Disable Brush Grid Snapping"
: "Enable Brush Grid Snapping"
}
title={
useBrushGridSnapping
? "Disable Brush Grid Snapping"
: "Enable Brush Grid Snapping"
}
onClick={() => onBrushGridSnappingChange(!useBrushGridSnapping)}
>
{useBrushGridSnapping ? (
<IconButton
aria-label="Disable Brush Grid Snapping"
title="Disable Brush Grid Snapping"
onClick={() => onBrushGridSnappingChange(false)}
>
<GridOnIcon />
</IconButton>
) : (
<IconButton
aria-label="Enable Brush Grid Snapping"
title="Enable Brush Grid Snapping"
onClick={() => onBrushGridSnappingChange(true)}
>
<GridOffIcon />
</IconButton>
)}
Grid Lock
</Label>
</Box>
{useBrushGridSnapping ? <GridOnIcon /> : <GridOffIcon />}
</IconButton>
<IconButton
aria-label={
useBrushBlending
? "Disable Brush Blending"
: "Enable Brush Blending"
}
title={
useBrushBlending
? "Disable Brush Blending"
: "Enable Brush Blending"
}
onClick={() => onBrushBlendingChange(!useBrushBlending)}
>
{useBrushBlending ? <BlendOnIcon /> : <BlendOffIcon />}
</IconButton>
<IconButton
aria-label={
useBrushGesture
? "Disable Gesture Detection"
: "Enable Gesture Detection"
}
title={
useBrushGesture
? "Disable Gesture Detection"
: "Enable Gesture Detection"
}
onClick={() => onBrushGestureChange(!useBrushGesture)}
>
{useBrushGesture ? <GestureOnIcon /> : <GestureOffIcon />}
</IconButton>
</Flex>
</Box>
),
erase: (

View File

@ -5,6 +5,11 @@ import shortid from "shortid";
import colors from "../helpers/colors";
import { snapPositionToGrid } from "../helpers/shared";
import {
pointsToGesture,
convertPointsToGesturePath,
} from "../helpers/gestures";
function MapDrawing({
width,
height,
@ -15,6 +20,8 @@ function MapDrawing({
brushColor,
useGridSnapping,
gridSize,
useBrushBlending,
useBrushGesture,
}) {
const canvasRef = useRef();
const containerRef = useRef();
@ -87,11 +94,17 @@ function MapDrawing({
if (selectedTool === "brush") {
if (brushPoints.length > 1) {
const simplifiedPoints = simplify(brushPoints, 0.001);
const gesture = useBrushGesture
? pointsToGesture(simplifiedPoints)
: "none";
onShapeAdd({
id: shortid.generate(),
points: simplifiedPoints,
color: brushColor,
gesture,
blend: useBrushBlending,
});
setBrushPoints([]);
}
}
@ -112,7 +125,20 @@ function MapDrawing({
return path;
}
function drawPath(path, color, context) {
function shapeToPath(shape) {
return shape.gesture !== "none"
? convertPointsToGesturePath(
shape.points.map((p) => ({
x: p.x * width,
y: p.y * height,
})),
shape.gesture
)
: pointsToPath(shape.points);
}
function drawPath(path, color, blend, context) {
context.globalAlpha = blend ? 0.5 : 1.0;
context.fillStyle = color;
context.strokeStyle = color;
context.stroke(path);
@ -126,7 +152,7 @@ function MapDrawing({
context.clearRect(0, 0, width, height);
let hoveredShape = null;
for (let shape of shapes) {
const path = pointsToPath(shape.points);
const path = shapeToPath(shape);
// Detect hover
if (selectedTool === "erase") {
if (
@ -139,15 +165,15 @@ function MapDrawing({
hoveredShape = shape;
}
}
drawPath(path, colors[shape.color], context);
drawPath(path, colors[shape.color], shape.blend, context);
}
if (selectedTool === "brush" && brushPoints.length > 0) {
const path = pointsToPath(brushPoints);
drawPath(path, colors[brushColor], context);
drawPath(path, colors[brushColor], useBrushBlending, context);
}
if (hoveredShape) {
const path = pointsToPath(hoveredShape.points);
drawPath(path, "#BB99FF", context);
const path = shapeToPath(hoveredShape);
drawPath(path, "#BB99FF", true, context);
}
hoveredShapeRef.current = hoveredShape;
}
@ -160,6 +186,8 @@ function MapDrawing({
selectedTool,
brushPoints,
brushColor,
useBrushGesture,
useBrushBlending,
]);
return (

1181
src/helpers/gestures.js Normal file

File diff suppressed because it is too large Load Diff

18
src/icons/BlendOffIcon.js Normal file
View File

@ -0,0 +1,18 @@
import React from "react";
function BlendOffIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
fill="currentcolor"
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M12 5.1v4.05l7.4 7.4c1.15-2.88.59-6.28-1.75-8.61l-4.94-4.95c-.39-.39-1.02-.39-1.41 0L8.56 5.71l1.41 1.41L12 5.1zm-8.31-.02c-.39.39-.39 1.02 0 1.41l2.08 2.08c-2.54 3.14-2.35 7.75.57 10.68C7.9 20.8 9.95 21.58 12 21.58c1.78 0 3.56-.59 5.02-1.77l2 2c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L5.11 5.08c-.39-.39-1.03-.39-1.42 0zM12 19.59c-1.6 0-3.11-.62-4.24-1.76C6.62 16.69 6 15.19 6 13.59c0-1.32.43-2.56 1.21-3.59L12 14.79v4.8z" />
</svg>
);
}
export default BlendOffIcon;

18
src/icons/BlendOnIcon.js Normal file
View File

@ -0,0 +1,18 @@
import React from "react";
function BlendOnIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
fill="currentcolor"
>
<path d="M24 0H0v24h24V0zm0 0H0v24h24V0zM0 24h24V0H0v24z" fill="none" />
<path d="M17.66 8l-4.95-4.94c-.39-.39-1.02-.39-1.41 0L6.34 8C4.78 9.56 4 11.64 4 13.64s.78 4.11 2.34 5.67 3.61 2.35 5.66 2.35 4.1-.79 5.66-2.35S20 15.64 20 13.64 19.22 9.56 17.66 8zM6 14c.01-2 .62-3.27 1.76-4.4L12 5.27l4.24 4.38C17.38 10.77 17.99 12 18 14H6z" />
</svg>
);
}
export default BlendOnIcon;

View File

@ -0,0 +1,18 @@
import React from "react";
function GestureOffIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
fill="currentcolor"
>
<path fill="none" d="M0 0h24v24H0z" />
<path d="M4.222 4.808l16.97 16.97a1 1 0 01-1.414 1.414l-1.3-1.299a4.5 4.5 0 01-5.371-5.372l-2.112-2.112.005.091v6c0 .51-.388.935-.884.993L10 21.5H4c-.51 0-.935-.388-.993-.884L3 20.5v-6c0-.51.388-.935.884-.993L4 13.5h6l.09.005-7.282-7.283a1 1 0 011.414-1.414zM17.5 13a4.5 4.5 0 014.09 6.381l-5.971-5.97A4.483 4.483 0 0117.5 13zm-4.724-9.706l.074.106 3.71 6.08c.39.627-.024 1.434-.735 1.514L15.71 11h-2.503L9.046 6.839 11.15 3.4a.992.992 0 011.626-.106z" />
</svg>
);
}
export default GestureOffIcon;

View File

@ -0,0 +1,20 @@
import React from "react";
function GestureOnIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
fill="currentcolor"
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M11.15 3.4L7.43 9.48c-.41.66.07 1.52.85 1.52h7.43c.78 0 1.26-.86.85-1.52L12.85 3.4c-.39-.64-1.31-.64-1.7 0z" />
<circle cx="17.5" cy="17.5" r="4.5" />
<path d="M4 21.5h6c.55 0 1-.45 1-1v-6c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1z" />
</svg>
);
}
export default GestureOnIcon;

View File

@ -9879,6 +9879,11 @@ shallow-clone@^3.0.0:
dependencies:
kind-of "^6.0.2"
shape-detector@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/shape-detector/-/shape-detector-0.2.1.tgz#d69acf8a5f595100fee08b2d69d6b5c74d887e1e"
integrity sha1-1prPil9ZUQD+4Istada1x02Ifh4=
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"