From fa2190dd7d20d7c4dd84ec4fd8ce6221e662bb3c Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Mon, 19 Jul 2021 11:37:41 +1000 Subject: [PATCH] Added basic selection visual --- src/components/map/Map.tsx | 9 + src/components/map/MapControls.tsx | 2 +- src/components/map/MapSelect.tsx | 195 ++++++++++++++++++ .../map/controls/SelectToolSettings.tsx | 2 +- src/shortcuts.ts | 2 +- 5 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 src/components/map/MapSelect.tsx diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 4acdfb9..f4d3bc4 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -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 = ( + + ); + return ( ); diff --git a/src/components/map/MapControls.tsx b/src/components/map/MapControls.tsx index b8b8296..71c40dd 100644 --- a/src/components/map/MapControls.tsx +++ b/src/components/map/MapControls.tsx @@ -75,7 +75,7 @@ function MapContols({ select: { id: "select", icon: , - title: "Select Tool (S)", + title: "Select Tool (L)", SettingsComponent: SelectToolSettings, }, fog: { diff --git a/src/components/map/MapSelect.tsx b/src/components/map/MapSelect.tsx new file mode 100644 index 0000000..fcac63f --- /dev/null +++ b/src/components/map/MapSelect.tsx @@ -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(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 ( + [ + ...acc, + point.x * mapWidth, + point.y * mapHeight, + ], + [] + )} + tension={0.5} + closed={false} + lineCap="round" + lineJoin="round" + {...defaultProps} + /> + ); + } else if (selection.type === "rectangle") { + return ( + + ); + } + } + + return {selection && renderSelection(selection)}; +} + +export default MapSelect; diff --git a/src/components/map/controls/SelectToolSettings.tsx b/src/components/map/controls/SelectToolSettings.tsx index 3f9ad45..f07afee 100644 --- a/src/components/map/controls/SelectToolSettings.tsx +++ b/src/components/map/controls/SelectToolSettings.tsx @@ -35,7 +35,7 @@ function SelectToolSettings({ const tools = [ { - id: "lasso", + id: "path", title: "Lasso (L)", isSelected: settings.type === "path", icon: , diff --git a/src/shortcuts.ts b/src/shortcuts.ts index 925c159..3a84e21 100644 --- a/src/shortcuts.ts +++ b/src/shortcuts.ts @@ -76,7 +76,7 @@ const shortcuts: Record = { 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