diff --git a/web/src/components/settings/MasksAndZones.tsx b/web/src/components/settings/MasksAndZones.tsx index 5e17a534f..2679cf5ca 100644 --- a/web/src/components/settings/MasksAndZones.tsx +++ b/web/src/components/settings/MasksAndZones.tsx @@ -629,6 +629,7 @@ export default function MasksAndZones({ scaledHeight && editingPolygons ? ( ; camera: string; width: number; height: number; @@ -18,6 +19,7 @@ type PolygonCanvasProps = { }; export function PolygonCanvas({ + containerRef, camera, width, height, @@ -55,10 +57,6 @@ export function PolygonCanvas({ }; }, [videoElement]); - const getMousePos = (stage: Konva.Stage) => { - return [stage.getPointerPosition()!.x, stage.getPointerPosition()!.y]; - }; - const addPointToPolygon = (polygon: Polygon, newPoint: number[]) => { const points = polygon.points; const pointsOrder = polygon.pointsOrder; @@ -99,37 +97,6 @@ export function PolygonCanvas({ return { updatedPoints, updatedPointsOrder }; }; - const isMouseOverFirstPoint = (polygon: Polygon, mousePos: number[]) => { - if (!polygon || !polygon.points || polygon.points.length < 1) { - return false; - } - const [firstPoint] = polygon.points; - const distance = Math.hypot( - mousePos[0] - firstPoint[0], - mousePos[1] - firstPoint[1], - ); - return distance < 10; - }; - - const isMouseOverAnyPoint = (polygon: Polygon, mousePos: number[]) => { - if (!polygon || !polygon.points || polygon.points.length === 0) { - return false; - } - - for (let i = 1; i < polygon.points.length; i++) { - const point = polygon.points[i]; - const distance = Math.hypot( - mousePos[0] - point[0], - mousePos[1] - point[1], - ); - if (distance < 10) { - return true; - } - } - - return false; - }; - const handleMouseDown = (e: KonvaEventObject) => { if (activePolygonIndex === undefined || !polygons) { return; @@ -138,11 +105,13 @@ export function PolygonCanvas({ const updatedPolygons = [...polygons]; const activePolygon = updatedPolygons[activePolygonIndex]; const stage = e.target.getStage()!; - const mousePos = getMousePos(stage); + const mousePos = stage.getPointerPosition() ?? { x: 0, y: 0 }; + const intersection = stage.getIntersection(mousePos); if ( activePolygon.points.length >= 3 && - isMouseOverFirstPoint(activePolygon, mousePos) + intersection?.getClassName() == "Circle" && + intersection?.name() == "point-0" ) { // Close the polygon updatedPolygons[activePolygonIndex] = { @@ -152,12 +121,13 @@ export function PolygonCanvas({ setPolygons(updatedPolygons); } else { if ( - !activePolygon.isFinished && - !isMouseOverAnyPoint(activePolygon, mousePos) + (!activePolygon.isFinished && + intersection?.getClassName() !== "Circle") || + (activePolygon.isFinished && intersection?.name() == "unfilled-line") ) { const { updatedPoints, updatedPointsOrder } = addPointToPolygon( activePolygon, - mousePos, + [mousePos.x, mousePos.y], ); updatedPolygons[activePolygonIndex] = { @@ -168,62 +138,6 @@ export function PolygonCanvas({ setPolygons(updatedPolygons); } } - // } - }; - - const handleMouseOverStartPoint = ( - e: KonvaEventObject, - ) => { - if (activePolygonIndex === undefined || !polygons) { - return; - } - - const activePolygon = polygons[activePolygonIndex]; - if (!activePolygon.isFinished && activePolygon.points.length >= 3) { - e.target.getStage()!.container().style.cursor = "default"; - e.currentTarget.scale({ x: 2, y: 2 }); - } - }; - - const handleMouseOutStartPoint = ( - e: KonvaEventObject, - ) => { - e.currentTarget.scale({ x: 1, y: 1 }); - - if (activePolygonIndex === undefined || !polygons) { - return; - } - - const activePolygon = polygons[activePolygonIndex]; - if ( - (!activePolygon.isFinished && activePolygon.points.length >= 3) || - activePolygon.isFinished - ) { - e.currentTarget.scale({ x: 1, y: 1 }); - } - }; - - const handleMouseOverAnyPoint = ( - e: KonvaEventObject, - ) => { - if (!polygons) { - return; - } - e.target.getStage()!.container().style.cursor = "move"; - }; - - const handleMouseOutAnyPoint = ( - e: KonvaEventObject, - ) => { - if (activePolygonIndex === undefined || !polygons) { - return; - } - const activePolygon = polygons[activePolygonIndex]; - if (activePolygon.isFinished) { - e.target.getStage()!.container().style.cursor = "default"; - } else { - e.target.getStage()!.container().style.cursor = "crosshair"; - } }; const handlePointDragMove = ( @@ -237,7 +151,8 @@ export function PolygonCanvas({ const activePolygon = updatedPolygons[activePolygonIndex]; const stage = e.target.getStage(); if (stage) { - const index = e.target.index - 1; + // we add an unfilled line for adding points when finished + const index = e.target.index - (activePolygon.isFinished ? 2 : 1); const pos = [e.target._lastPos!.x, e.target._lastPos!.y]; if (pos[0] < 0) pos[0] = 0; if (pos[1] < 0) pos[1] = 0; @@ -272,26 +187,17 @@ export function PolygonCanvas({ } }; - const handleStageMouseOver = ( - e: Konva.KonvaEventObject, - ) => { + const handleStageMouseOver = () => { if (activePolygonIndex === undefined || !polygons) { return; } const updatedPolygons = [...polygons]; const activePolygon = updatedPolygons[activePolygonIndex]; - const stage = e.target.getStage()!; - const mousePos = getMousePos(stage); - if ( - activePolygon.isFinished || - isMouseOverAnyPoint(activePolygon, mousePos) || - isMouseOverFirstPoint(activePolygon, mousePos) - ) - return; - - e.target.getStage()!.container().style.cursor = "crosshair"; + if (containerRef.current && !activePolygon.isFinished) { + containerRef.current.style.cursor = "crosshair"; + } }; useEffect(() => { @@ -336,6 +242,7 @@ export function PolygonCanvas({ selectedZoneMask.includes(polygon.type)) && index !== activePolygonIndex && ( ), )} @@ -356,6 +259,7 @@ export function PolygonCanvas({ (selectedZoneMask === undefined || selectedZoneMask.includes(polygons[activePolygonIndex].type)) && ( )} diff --git a/web/src/components/settings/PolygonDrawer.tsx b/web/src/components/settings/PolygonDrawer.tsx index a2209d35e..706cbea9d 100644 --- a/web/src/components/settings/PolygonDrawer.tsx +++ b/web/src/components/settings/PolygonDrawer.tsx @@ -1,4 +1,11 @@ -import { useCallback, useMemo, useRef, useState } from "react"; +import { + RefObject, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { Line, Circle, Group } from "react-konva"; import { minMax, @@ -9,9 +16,9 @@ import { import type { KonvaEventObject } from "konva/lib/Node"; import Konva from "konva"; import { Vector2d } from "konva/lib/types"; -import { isMobileOnly } from "react-device-detect"; type PolygonDrawerProps = { + stageRef: RefObject; points: number[][]; isActive: boolean; isHovered: boolean; @@ -19,21 +26,10 @@ type PolygonDrawerProps = { color: number[]; handlePointDragMove: (e: KonvaEventObject) => void; handleGroupDragEnd: (e: KonvaEventObject) => void; - handleMouseOverStartPoint: ( - e: KonvaEventObject, - ) => void; - handleMouseOutStartPoint: ( - e: KonvaEventObject, - ) => void; - handleMouseOverAnyPoint: ( - e: KonvaEventObject, - ) => void; - handleMouseOutAnyPoint: ( - e: KonvaEventObject, - ) => void; }; export default function PolygonDrawer({ + stageRef, points, isActive, isHovered, @@ -41,31 +37,41 @@ export default function PolygonDrawer({ color, handlePointDragMove, handleGroupDragEnd, - handleMouseOverStartPoint, - handleMouseOutStartPoint, - handleMouseOverAnyPoint, - handleMouseOutAnyPoint, }: PolygonDrawerProps) { - const vertexRadius = isMobileOnly ? 12 : 6; + const vertexRadius = 6; const flattenedPoints = useMemo(() => flattenPoints(points), [points]); - const [stage, setStage] = useState(); const [minMaxX, setMinMaxX] = useState([0, 0]); const [minMaxY, setMinMaxY] = useState([0, 0]); const groupRef = useRef(null); + const [cursor, setCursor] = useState("default"); - const handleGroupMouseOver = ( - e: Konva.KonvaEventObject, + const handleMouseOverPoint = ( + e: KonvaEventObject, ) => { - if (!isFinished) return; - e.target.getStage()!.container().style.cursor = "move"; - setStage(e.target.getStage()!); + if (!e.target) return; + + if (!isFinished && points.length >= 3 && e.target.name() === "point-0") { + e.target.scale({ x: 2, y: 2 }); + setCursor("crosshair"); + } else { + setCursor("move"); + } }; - const handleGroupMouseOut = ( - e: Konva.KonvaEventObject, + const handleMouseOutPoint = ( + e: KonvaEventObject, ) => { - if (!e.target || !isFinished) return; - e.target.getStage()!.container().style.cursor = "default"; + if (!e.target) return; + + if (isFinished) { + setCursor("default"); + } else { + setCursor("crosshair"); + } + + if (e.target.name() === "point-0") { + e.target.scale({ x: 1, y: 1 }); + } }; const handleGroupDragStart = () => { @@ -76,13 +82,13 @@ export default function PolygonDrawer({ }; const groupDragBound = (pos: Vector2d) => { - if (!stage) { + if (!stageRef.current) { return pos; } let { x, y } = pos; - const sw = stage.width(); - const sh = stage.height(); + const sw = stageRef.current.width(); + const sh = stageRef.current.height(); if (minMaxY[0] + y < 0) y = -1 * minMaxY[0]; if (minMaxX[0] + x < 0) x = -1 * minMaxX[0]; @@ -99,6 +105,14 @@ export default function PolygonDrawer({ [color], ); + useEffect(() => { + if (!stageRef.current) { + return; + } + + stageRef.current.container().style.cursor = cursor; + }, [stageRef, cursor]); + return ( + isFinished ? setCursor("move") : setCursor("crosshair") + } + onMouseOut={() => + isFinished ? setCursor("default") : setCursor("crosshair") + } /> + {isFinished && isActive && ( + setCursor("crosshair")} + onMouseOut={() => + isFinished ? setCursor("default") : setCursor("crosshair") + } + /> + )} {points.map((point, index) => { if (!isActive) { return; } const x = point[0]; const y = point[1]; - const startPointAttr = - index === 0 - ? { - hitStrokeWidth: 12, - onMouseOver: handleMouseOverStartPoint, - onMouseOut: handleMouseOutStartPoint, - } - : null; - const otherPointsAttr = - index !== 0 - ? { - onMouseOver: handleMouseOverAnyPoint, - onMouseOut: handleMouseOutAnyPoint, - } - : null; return ( { - if (stage) { + if (stageRef.current) { return dragBoundFunc( - stage.width(), - stage.height(), + stageRef.current.width(), + stageRef.current.height(), vertexRadius, pos, ); @@ -163,8 +184,6 @@ export default function PolygonDrawer({ return pos; } }} - {...startPointAttr} - {...otherPointsAttr} /> ); })} diff --git a/web/src/components/settings/PolygonEditControls.tsx b/web/src/components/settings/PolygonEditControls.tsx index 5aa4323b2..37a4cc695 100644 --- a/web/src/components/settings/PolygonEditControls.tsx +++ b/web/src/components/settings/PolygonEditControls.tsx @@ -41,7 +41,7 @@ export default function PolygonEditControls({ ...activePolygon.pointsOrder.slice(0, lastPointOrderIndex), ...activePolygon.pointsOrder.slice(lastPointOrderIndex + 1), ], - isFinished: false, + isFinished: activePolygon.isFinished && activePolygon.points.length > 3, }; setPolygons(updatedPolygons);