feat: pinch zooming
This commit is contained in:
parent
5bc42e5574
commit
c04f62ac2c
4 changed files with 64 additions and 26 deletions
|
|
@ -28,6 +28,7 @@
|
||||||
"@radix-ui/react-toggle-group": "^1.1.2",
|
"@radix-ui/react-toggle-group": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@uiw/react-color": "^2.3.4",
|
"@uiw/react-color": "^2.3.4",
|
||||||
|
"@use-gesture/react": "^10.3.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "1.0.0",
|
"cmdk": "1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,9 @@ importers:
|
||||||
'@uiw/react-color':
|
'@uiw/react-color':
|
||||||
specifier: ^2.3.4
|
specifier: ^2.3.4
|
||||||
version: 2.3.4(@babel/runtime@7.26.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 2.3.4(@babel/runtime@7.26.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@use-gesture/react':
|
||||||
|
specifier: ^10.3.1
|
||||||
|
version: 10.3.1(react@18.3.1)
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
|
|
@ -1873,6 +1876,14 @@ packages:
|
||||||
react: '>=16.9.0'
|
react: '>=16.9.0'
|
||||||
react-dom: '>=16.9.0'
|
react-dom: '>=16.9.0'
|
||||||
|
|
||||||
|
'@use-gesture/core@10.3.1':
|
||||||
|
resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==}
|
||||||
|
|
||||||
|
'@use-gesture/react@10.3.1':
|
||||||
|
resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 16.8.0'
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.3.4':
|
'@vitejs/plugin-react@4.3.4':
|
||||||
resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==}
|
resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
|
@ -4769,6 +4780,13 @@ snapshots:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
|
'@use-gesture/core@10.3.1': {}
|
||||||
|
|
||||||
|
'@use-gesture/react@10.3.1(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@use-gesture/core': 10.3.1
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@1.21.7)(yaml@2.7.0))':
|
'@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@1.21.7)(yaml@2.7.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.26.8
|
'@babel/core': 7.26.8
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { useGesture } from "@use-gesture/react";
|
||||||
|
|
||||||
import * as PIXI from "pixi.js";
|
import * as PIXI from "pixi.js";
|
||||||
import { Container, Stage } from "@pixi/react";
|
import { Container, Stage } from "@pixi/react";
|
||||||
|
|
@ -68,17 +69,6 @@ function Canvas() {
|
||||||
|
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
const zoom = useCallback(
|
|
||||||
(newScale: number) => {
|
|
||||||
setScale(newScale);
|
|
||||||
setCoords({
|
|
||||||
x: mousePosition.x - ((mousePosition.x - coords.x) / scale) * newScale,
|
|
||||||
y: mousePosition.y - ((mousePosition.y - coords.y) / scale) * newScale,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[coords, mousePosition, scale, setCoords, setScale]
|
|
||||||
);
|
|
||||||
|
|
||||||
const moveTool = useMoveTool(mouseMovementRef.current);
|
const moveTool = useMoveTool(mouseMovementRef.current);
|
||||||
const rectangleSelectTool = useRectangleSelectTool(mouseCoords, dragStartCoordsRef.current, holdingShiftRef.current);
|
const rectangleSelectTool = useRectangleSelectTool(mouseCoords, dragStartCoordsRef.current, holdingShiftRef.current);
|
||||||
const lassoTool = useLassoTool(mouseCoords, holdingAltRef.current);
|
const lassoTool = useLassoTool(mouseCoords, holdingAltRef.current);
|
||||||
|
|
@ -88,7 +78,7 @@ function Canvas() {
|
||||||
const paintBucketTool = usePaintBucketTool(mouseCoords);
|
const paintBucketTool = usePaintBucketTool(mouseCoords);
|
||||||
const shapeTool = useShapeTool(mouseCoords, dragStartCoordsRef.current, holdingShiftRef.current);
|
const shapeTool = useShapeTool(mouseCoords, dragStartCoordsRef.current, holdingShiftRef.current);
|
||||||
const eyedropperTool = useEyedropperTool(mouseCoords);
|
const eyedropperTool = useEyedropperTool(mouseCoords);
|
||||||
const zoomTool = useZoomTool(zoom, holdingAltRef.current);
|
const zoomTool = useZoomTool(mousePosition, holdingAltRef.current);
|
||||||
|
|
||||||
const visibleArea = useMemo(() => {
|
const visibleArea = useMemo(() => {
|
||||||
const blockSize = 16 * scale;
|
const blockSize = 16 * scale;
|
||||||
|
|
@ -225,11 +215,16 @@ function Canvas() {
|
||||||
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 zoomFactor = 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 + zoomFactor * scale, 0.1), 32);
|
||||||
zoom(newScale);
|
|
||||||
|
setScale(newScale);
|
||||||
|
setCoords({
|
||||||
|
x: mousePosition.x - ((mousePosition.x - coords.x) / scale) * newScale,
|
||||||
|
y: mousePosition.y - ((mousePosition.y - coords.y) / scale) * newScale,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[scale, zoom]
|
[scale, setScale, setCoords, mousePosition, coords]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
|
|
@ -452,20 +447,39 @@ function Canvas() {
|
||||||
};
|
};
|
||||||
}, [onKeyDown, onKeyUp]);
|
}, [onKeyDown, onKeyUp]);
|
||||||
|
|
||||||
|
useGesture(
|
||||||
|
{
|
||||||
|
onPinch: ({ offset: [d] }) => {
|
||||||
|
setScale(d);
|
||||||
|
setCoords({
|
||||||
|
x: mousePosition.x - ((mousePosition.x - coords.x) / scale) * d,
|
||||||
|
y: mousePosition.y - ((mousePosition.y - coords.y) / scale) * d,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: stageContainerRef,
|
||||||
|
pinch: { threshold: 0.1, scaleBounds: { min: 0.1, max: 32 }, pinchOnWheel: false },
|
||||||
|
eventOptions: { passive: false },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={stageContainerRef} style={{ cursor: cssCursor }} className="relative w-full h-full bg-zinc-200 dark:bg-black">
|
<div ref={stageContainerRef} className="relative">
|
||||||
<Stage
|
<Stage
|
||||||
width={stageSize.width}
|
width={stageSize.width}
|
||||||
height={stageSize.height}
|
height={stageSize.height}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
onClick={onClick}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
onKeyUp={onKeyUp}
|
onKeyUp={onKeyUp}
|
||||||
onMouseMove={onMouseMove}
|
onPointerDown={onMouseDown}
|
||||||
onMouseDown={onMouseDown}
|
onPointerMove={onMouseMove}
|
||||||
onMouseUp={onMouseUp}
|
onPointerUp={onMouseUp}
|
||||||
onWheel={onWheel}
|
onWheel={onWheel}
|
||||||
onClick={onClick}
|
|
||||||
options={{ backgroundAlpha: 0 }}
|
options={{ backgroundAlpha: 0 }}
|
||||||
|
style={{ cursor: cssCursor }}
|
||||||
|
className="w-full h-full bg-zinc-100 dark:bg-black touch-none select-none"
|
||||||
>
|
>
|
||||||
<Blocks blocks={visibleBlocks} missingTexture={missingTexture} textures={textures} coords={coords} scale={scale} version={version} />
|
<Blocks blocks={visibleBlocks} missingTexture={missingTexture} textures={textures} coords={coords} scale={scale} version={version} />
|
||||||
{/* Selection layer */}
|
{/* Selection layer */}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,18 @@ import { useContext } from "react";
|
||||||
|
|
||||||
import { CanvasContext } from "@/context/Canvas";
|
import { CanvasContext } from "@/context/Canvas";
|
||||||
|
|
||||||
export function useZoomTool(zoom: (newScale: number) => void, holdingAlt: boolean) {
|
export function useZoomTool(mousePosition: Position, holdingAlt: boolean) {
|
||||||
const { scale } = useContext(CanvasContext);
|
const { coords, scale, setScale, setCoords } = useContext(CanvasContext);
|
||||||
|
|
||||||
const use = () => {
|
const use = () => {
|
||||||
const scaleChange = holdingAlt ? -0.1 : 0.1;
|
const zoomFactor = holdingAlt ? -0.1 : 0.1;
|
||||||
const newScale = Math.min(Math.max(scale + scaleChange * scale, 0.1), 32);
|
const newScale = Math.min(Math.max(scale + zoomFactor * scale, 0.1), 32);
|
||||||
zoom(newScale);
|
|
||||||
|
setScale(newScale);
|
||||||
|
setCoords({
|
||||||
|
x: mousePosition.x - ((mousePosition.x - coords.x) / scale) * newScale,
|
||||||
|
y: mousePosition.y - ((mousePosition.y - coords.y) / scale) * newScale,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return { use };
|
return { use };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue