Added basic selection visual

This commit is contained in:
Mitchell McCaffrey 2021-07-19 11:37:41 +10:00
parent 07058db9c2
commit fa2190dd7d
5 changed files with 207 additions and 3 deletions

View File

@ -43,6 +43,7 @@ import Action from "../../actions/Action";
import Konva from "konva";
import { TokenDraggingOptions, TokenMenuOptions } from "../../types/Token";
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note";
import MapSelect from "./MapSelect";
type MapProps = {
map: MapType | null;
@ -385,6 +386,13 @@ function Map({
/>
) : null;
const mapSelect = (
<MapSelect
active={selectedToolId === "select"}
toolSettings={settings.select}
/>
);
return (
<Box sx={{ flexGrow: 1 }}>
<MapInteraction
@ -410,6 +418,7 @@ function Map({
{mapFog}
{mapPointer}
{mapMeasure}
{mapSelect}
</MapInteraction>
</Box>
);

View File

@ -75,7 +75,7 @@ function MapContols({
select: {
id: "select",
icon: <SelectToolIcon />,
title: "Select Tool (S)",
title: "Select Tool (L)",
SettingsComponent: SelectToolSettings,
},
fog: {

View File

@ -0,0 +1,195 @@
import { useState, useEffect } from "react";
import { Group, Line, Rect } from "react-konva";
import {
useDebouncedStageScale,
useMapWidth,
useMapHeight,
useInteractionEmitter,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
import {
getDefaultShapeData,
getUpdatedShapeData,
simplifyPoints,
} from "../../helpers/drawing";
import Vector2 from "../../helpers/Vector2";
import colors from "../../helpers/colors";
import { getRelativePointerPosition } from "../../helpers/konva";
import { Selection, SelectToolSettings } from "../../types/Select";
import { RectData } from "../../types/Drawing";
import {
useGridCellNormalizedSize,
useGridStrokeWidth,
} from "../../contexts/GridContext";
type MapSelectProps = {
active: boolean;
toolSettings: SelectToolSettings;
};
function MapSelect({ active, toolSettings }: MapSelectProps) {
const stageScale = useDebouncedStageScale();
const mapWidth = useMapWidth();
const mapHeight = useMapHeight();
const interactionEmitter = useInteractionEmitter();
const gridCellNormalizedSize = useGridCellNormalizedSize();
const gridStrokeWidth = useGridStrokeWidth();
const mapStageRef = useMapStage();
const [selection, setSelection] = useState<Selection | null>(null);
const [isBrushDown, setIsBrushDown] = useState(false);
useEffect(() => {
if (!active) {
return;
}
const mapStage = mapStageRef.current;
const mapImage = mapStage?.findOne("#mapImage");
function getBrushPosition() {
if (!mapImage) {
return;
}
let position = getRelativePointerPosition(mapImage);
if (!position) {
return;
}
return Vector2.divide(position, {
x: mapImage.width(),
y: mapImage.height(),
});
}
function handleBrushDown() {
const brushPosition = getBrushPosition();
if (!brushPosition) {
return;
}
if (toolSettings.type === "path") {
setSelection({
type: "path",
nodes: [],
data: { points: [brushPosition] },
});
} else {
setSelection({
type: "rectangle",
nodes: [],
data: getDefaultShapeData("rectangle", brushPosition) as RectData,
});
}
setIsBrushDown(true);
}
function handleBrushMove() {
const brushPosition = getBrushPosition();
if (isBrushDown && selection && brushPosition && mapImage) {
if (selection.type === "path") {
setSelection((prevSelection) => {
if (prevSelection?.type !== "path") {
return prevSelection;
}
const prevPoints = prevSelection.data.points;
if (
Vector2.compare(
prevPoints[prevPoints.length - 1],
brushPosition,
0.001
)
) {
return prevSelection;
}
const simplified = simplifyPoints(
[...prevPoints, brushPosition],
1 / 1000 / stageScale
);
return {
...prevSelection,
data: { points: simplified },
};
});
} else {
setSelection((prevSelection) => {
if (prevSelection?.type !== "rectangle") {
return prevSelection;
}
return {
...prevSelection,
data: getUpdatedShapeData(
"rectangle",
prevSelection.data,
brushPosition,
gridCellNormalizedSize,
mapWidth,
mapHeight
) as RectData,
};
});
}
}
}
function handleBrushUp() {
setSelection(null);
setIsBrushDown(false);
}
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);
};
});
function renderSelection(selection: Selection) {
const strokeWidth = gridStrokeWidth / stageScale;
const defaultProps = {
stroke: colors.primary,
strokeWidth: strokeWidth,
dash: [strokeWidth / 2, strokeWidth * 2],
};
if (selection.type === "path") {
return (
<Line
points={selection.data.points.reduce(
(acc: number[], point) => [
...acc,
point.x * mapWidth,
point.y * mapHeight,
],
[]
)}
tension={0.5}
closed={false}
lineCap="round"
lineJoin="round"
{...defaultProps}
/>
);
} else if (selection.type === "rectangle") {
return (
<Rect
x={selection.data.x * mapWidth}
y={selection.data.y * mapHeight}
width={selection.data.width * mapWidth}
height={selection.data.height * mapHeight}
lineCap="round"
lineJoin="round"
{...defaultProps}
/>
);
}
}
return <Group>{selection && renderSelection(selection)}</Group>;
}
export default MapSelect;

View File

@ -35,7 +35,7 @@ function SelectToolSettings({
const tools = [
{
id: "lasso",
id: "path",
title: "Lasso (L)",
isSelected: settings.type === "path",
icon: <PathIcon />,

View File

@ -76,7 +76,7 @@ const shortcuts: Record<string, Shortcut> = {
fogFinishPolygon: ({ key }) => key === "Enter",
fogCancelPolygon: ({ key }) => key === "Escape",
// Select tool
selectTool: (event) => singleKey(event, "s"),
selectTool: (event) => singleKey(event, "l"),
selectPath: (event) => singleKey(event, "l"),
selectRect: (event) => singleKey(event, "r"),
// Stage interaction