Added basic selection visual
This commit is contained in:
parent
07058db9c2
commit
fa2190dd7d
@ -43,6 +43,7 @@ import Action from "../../actions/Action";
|
|||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import { TokenDraggingOptions, TokenMenuOptions } from "../../types/Token";
|
import { TokenDraggingOptions, TokenMenuOptions } from "../../types/Token";
|
||||||
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note";
|
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note";
|
||||||
|
import MapSelect from "./MapSelect";
|
||||||
|
|
||||||
type MapProps = {
|
type MapProps = {
|
||||||
map: MapType | null;
|
map: MapType | null;
|
||||||
@ -385,6 +386,13 @@ function Map({
|
|||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
const mapSelect = (
|
||||||
|
<MapSelect
|
||||||
|
active={selectedToolId === "select"}
|
||||||
|
toolSettings={settings.select}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<MapInteraction
|
<MapInteraction
|
||||||
@ -410,6 +418,7 @@ function Map({
|
|||||||
{mapFog}
|
{mapFog}
|
||||||
{mapPointer}
|
{mapPointer}
|
||||||
{mapMeasure}
|
{mapMeasure}
|
||||||
|
{mapSelect}
|
||||||
</MapInteraction>
|
</MapInteraction>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -75,7 +75,7 @@ function MapContols({
|
|||||||
select: {
|
select: {
|
||||||
id: "select",
|
id: "select",
|
||||||
icon: <SelectToolIcon />,
|
icon: <SelectToolIcon />,
|
||||||
title: "Select Tool (S)",
|
title: "Select Tool (L)",
|
||||||
SettingsComponent: SelectToolSettings,
|
SettingsComponent: SelectToolSettings,
|
||||||
},
|
},
|
||||||
fog: {
|
fog: {
|
||||||
|
195
src/components/map/MapSelect.tsx
Normal file
195
src/components/map/MapSelect.tsx
Normal 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;
|
@ -35,7 +35,7 @@ function SelectToolSettings({
|
|||||||
|
|
||||||
const tools = [
|
const tools = [
|
||||||
{
|
{
|
||||||
id: "lasso",
|
id: "path",
|
||||||
title: "Lasso (L)",
|
title: "Lasso (L)",
|
||||||
isSelected: settings.type === "path",
|
isSelected: settings.type === "path",
|
||||||
icon: <PathIcon />,
|
icon: <PathIcon />,
|
||||||
|
@ -76,7 +76,7 @@ const shortcuts: Record<string, Shortcut> = {
|
|||||||
fogFinishPolygon: ({ key }) => key === "Enter",
|
fogFinishPolygon: ({ key }) => key === "Enter",
|
||||||
fogCancelPolygon: ({ key }) => key === "Escape",
|
fogCancelPolygon: ({ key }) => key === "Escape",
|
||||||
// Select tool
|
// Select tool
|
||||||
selectTool: (event) => singleKey(event, "s"),
|
selectTool: (event) => singleKey(event, "l"),
|
||||||
selectPath: (event) => singleKey(event, "l"),
|
selectPath: (event) => singleKey(event, "l"),
|
||||||
selectRect: (event) => singleKey(event, "r"),
|
selectRect: (event) => singleKey(event, "r"),
|
||||||
// Stage interaction
|
// Stage interaction
|
||||||
|
Loading…
Reference in New Issue
Block a user