diff --git a/src/components/Canvas.tsx b/src/components/canvas/Canvas.tsx similarity index 81% rename from src/components/Canvas.tsx rename to src/components/canvas/Canvas.tsx index ab2c1c0..5f7a582 100644 --- a/src/components/Canvas.tsx +++ b/src/components/canvas/Canvas.tsx @@ -37,6 +37,7 @@ function Canvas() { const [blocks, setBlocks] = useState([]); + const [holdingAlt, setHoldingAlt] = useState(false); const [oldTool, setOldTool] = useState("hand"); const updatedBlocks = useMemo(() => { @@ -64,6 +65,25 @@ function Canvas() { }; }, [blocks]); + const zoomToMousePosition = useCallback( + (newScale: number) => { + setCoords({ + x: mousePosition.x - ((mousePosition.x - coords.x) / scale) * newScale, + y: mousePosition.y - ((mousePosition.y - coords.y) / scale) * newScale, + }); + }, + [coords, mousePosition, scale] + ); + + const updateCssCursor = useCallback(() => { + const cursorMapping: Partial> = { + hand: dragging ? "grabbing" : "grab", + zoom: holdingAlt ? "zoom-out" : "zoom-in", + }; + + setCssCursor(cursorMapping[tool] || "pointer"); + }, [dragging, holdingAlt, tool, setCssCursor]); + const onToolUse = useCallback(() => { switch (tool) { case "pencil": { @@ -116,30 +136,34 @@ function Canvas() { const onMouseDown = useCallback(() => { setDragging(true); onToolUse(); - setCssCursor(tool === "hand" ? "grabbing" : ""); - }, [onToolUse, tool, setCssCursor]); + updateCssCursor(); + }, [onToolUse, updateCssCursor]); - const onMouseUp = () => { + const onMouseUp = useCallback(() => { setDragging(false); - setCssCursor(tool === "hand" ? "grab" : ""); - }; + updateCssCursor(); + }, [updateCssCursor]); const onWheel = useCallback( (e: React.WheelEvent) => { e.preventDefault(); - const scaleChange = e.deltaY > 0 ? -0.1 : 0.1; const newScale = Math.min(Math.max(scale + scaleChange * scale, 0.1), 32); - setScale(newScale); - setCoords({ - x: mousePosition.x - ((mousePosition.x - coords.x) / scale) * newScale, - y: mousePosition.y - ((mousePosition.y - coords.y) / scale) * newScale, - }); + zoomToMousePosition(newScale); }, - [scale, coords, mousePosition] + [scale, zoomToMousePosition] ); + const onClick = useCallback(() => { + if (tool === "zoom") { + const scaleChange = holdingAlt ? -0.1 : 0.1; + const newScale = Math.min(Math.max(scale + scaleChange * scale, 0.1), 32); + setScale(newScale); + zoomToMousePosition(newScale); + } + }, [tool, holdingAlt, scale, zoomToMousePosition]); + const onKeyDown = (e: KeyboardEvent) => { switch (e.key) { case " ": // Space @@ -157,15 +181,27 @@ function Canvas() { case "3": setTool("eraser"); break; + case "4": + setTool("zoom"); + break; + case "Alt": + setHoldingAlt(true); + setCssCursor("zoom-out"); + break; } }; const onKeyUp = (e: KeyboardEvent) => { - if (e.key == " ") { - // Space - setDragging(false); - setCssCursor("grab"); - setTool(oldTool); + switch (e.key) { + case " ": // Space + setDragging(false); + setCssCursor("grab"); + setTool(oldTool); + break; + case "Alt": + setHoldingAlt(false); + setCssCursor("zoom-in"); + break; } }; @@ -203,6 +239,7 @@ function Canvas() { onMouseDown={onMouseDown} onMouseUp={onMouseUp} onWheel={onWheel} + onClick={onClick} > diff --git a/src/context/ToolContext.tsx b/src/context/ToolContext.tsx index 6898954..269d337 100644 --- a/src/context/ToolContext.tsx +++ b/src/context/ToolContext.tsx @@ -19,7 +19,18 @@ export const ToolProvider = ({ children }: Props) => { const [cssCursor, setCssCursor] = useState("pointer"); useEffect(() => { - setCssCursor(tool === "hand" ? "grab" : "pointer"); + switch (tool) { + case "hand": + setCssCursor("grab"); + break; + case "zoom": + setCssCursor("zoom-in"); + break; + + default: + setCssCursor("pointer"); + break; + } }, [tool]); return {children}; diff --git a/src/types.d.ts b/src/types.d.ts index a05b2d1..ebaafef 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -21,7 +21,7 @@ interface Block extends Position { name: string; } -type Tool = "hand" | "pencil" | "eraser"; +type Tool = "hand" | "pencil" | "eraser" | "zoom"; interface Settings { grid: boolean;