feat: zoom tool

This commit is contained in:
trafficlunar 2024-12-15 13:46:28 +00:00
parent ceb87c2088
commit 57064e71c7
3 changed files with 67 additions and 19 deletions

View file

@ -37,6 +37,7 @@ function Canvas() {
const [blocks, setBlocks] = useState<Block[]>([]); const [blocks, setBlocks] = useState<Block[]>([]);
const [holdingAlt, setHoldingAlt] = useState(false);
const [oldTool, setOldTool] = useState<Tool>("hand"); const [oldTool, setOldTool] = useState<Tool>("hand");
const updatedBlocks = useMemo(() => { const updatedBlocks = useMemo(() => {
@ -64,6 +65,25 @@ function Canvas() {
}; };
}, [blocks]); }, [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<Record<Tool, string>> = {
hand: dragging ? "grabbing" : "grab",
zoom: holdingAlt ? "zoom-out" : "zoom-in",
};
setCssCursor(cursorMapping[tool] || "pointer");
}, [dragging, holdingAlt, tool, setCssCursor]);
const onToolUse = useCallback(() => { const onToolUse = useCallback(() => {
switch (tool) { switch (tool) {
case "pencil": { case "pencil": {
@ -116,30 +136,34 @@ function Canvas() {
const onMouseDown = useCallback(() => { const onMouseDown = useCallback(() => {
setDragging(true); setDragging(true);
onToolUse(); onToolUse();
setCssCursor(tool === "hand" ? "grabbing" : ""); updateCssCursor();
}, [onToolUse, tool, setCssCursor]); }, [onToolUse, updateCssCursor]);
const onMouseUp = () => { const onMouseUp = useCallback(() => {
setDragging(false); setDragging(false);
setCssCursor(tool === "hand" ? "grab" : ""); updateCssCursor();
}; }, [updateCssCursor]);
const onWheel = useCallback( const onWheel = useCallback(
(e: React.WheelEvent) => { (e: React.WheelEvent) => {
e.preventDefault(); e.preventDefault();
const scaleChange = e.deltaY > 0 ? -0.1 : 0.1; const scaleChange = e.deltaY > 0 ? -0.1 : 0.1;
const newScale = Math.min(Math.max(scale + scaleChange * scale, 0.1), 32); const newScale = Math.min(Math.max(scale + scaleChange * scale, 0.1), 32);
setScale(newScale); setScale(newScale);
setCoords({ zoomToMousePosition(newScale);
x: mousePosition.x - ((mousePosition.x - coords.x) / scale) * newScale,
y: mousePosition.y - ((mousePosition.y - coords.y) / scale) * 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) => { const onKeyDown = (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
case " ": // Space case " ": // Space
@ -157,15 +181,27 @@ function Canvas() {
case "3": case "3":
setTool("eraser"); setTool("eraser");
break; break;
case "4":
setTool("zoom");
break;
case "Alt":
setHoldingAlt(true);
setCssCursor("zoom-out");
break;
} }
}; };
const onKeyUp = (e: KeyboardEvent) => { const onKeyUp = (e: KeyboardEvent) => {
if (e.key == " ") { switch (e.key) {
// Space case " ": // Space
setDragging(false); setDragging(false);
setCssCursor("grab"); setCssCursor("grab");
setTool(oldTool); setTool(oldTool);
break;
case "Alt":
setHoldingAlt(false);
setCssCursor("zoom-in");
break;
} }
}; };
@ -203,6 +239,7 @@ function Canvas() {
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
onMouseUp={onMouseUp} onMouseUp={onMouseUp}
onWheel={onWheel} onWheel={onWheel}
onClick={onClick}
> >
<Container x={coords.x} y={coords.y} scale={scale}> <Container x={coords.x} y={coords.y} scale={scale}>
<Blocks blocks={blocks} setBlocks={setBlocks} textures={textures} image={image} imageDimensions={imageDimensions} /> <Blocks blocks={blocks} setBlocks={setBlocks} textures={textures} image={image} imageDimensions={imageDimensions} />

View file

@ -19,7 +19,18 @@ export const ToolProvider = ({ children }: Props) => {
const [cssCursor, setCssCursor] = useState("pointer"); const [cssCursor, setCssCursor] = useState("pointer");
useEffect(() => { useEffect(() => {
setCssCursor(tool === "hand" ? "grab" : "pointer"); switch (tool) {
case "hand":
setCssCursor("grab");
break;
case "zoom":
setCssCursor("zoom-in");
break;
default:
setCssCursor("pointer");
break;
}
}, [tool]); }, [tool]);
return <ToolContext.Provider value={{ tool, selectedBlock, cssCursor, setTool, setSelectedBlock, setCssCursor }}>{children}</ToolContext.Provider>; return <ToolContext.Provider value={{ tool, selectedBlock, cssCursor, setTool, setSelectedBlock, setCssCursor }}>{children}</ToolContext.Provider>;

2
src/types.d.ts vendored
View file

@ -21,7 +21,7 @@ interface Block extends Position {
name: string; name: string;
} }
type Tool = "hand" | "pencil" | "eraser"; type Tool = "hand" | "pencil" | "eraser" | "zoom";
interface Settings { interface Settings {
grid: boolean; grid: boolean;