mirror of
https://github.com/YuzuZensai/Pien-Studio.git
synced 2026-09-02 14:18:35 +00:00
♻️ refactor!: massive refactor
This commit is contained in:
@@ -23,7 +23,13 @@ type UseCanvasInteractionsOptions = {
|
||||
onInteractionEnd?: () => void;
|
||||
onContextMenu?: (x: number, y: number) => void;
|
||||
onFillLayer?: (layerId: string, x: number, y: number) => void;
|
||||
onBrushStrokeStart?: (layerId: string, x: number, y: number, layerWidth: number, layerHeight: number) => void;
|
||||
onBrushStrokeStart?: (
|
||||
layerId: string,
|
||||
x: number,
|
||||
y: number,
|
||||
layerWidth: number,
|
||||
layerHeight: number,
|
||||
) => void;
|
||||
onBrushStrokeMove?: (layerId: string, x: number, y: number) => void;
|
||||
onBrushStrokeEnd?: (layerId: string) => void;
|
||||
};
|
||||
@@ -53,7 +59,11 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
onBrushStrokeMove,
|
||||
onBrushStrokeEnd,
|
||||
} = options;
|
||||
const [viewport, setViewport] = React.useState<Viewport>({ x: 0, y: 0, scale: 1 });
|
||||
const [viewport, setViewport] = React.useState<Viewport>({
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
});
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const isPanning = React.useRef(false);
|
||||
const lastPos = React.useRef({ x: 0, y: 0 });
|
||||
@@ -64,7 +74,12 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
const interactionActiveRef = React.useRef(false);
|
||||
const dragMoveRafRef = React.useRef<number | null>(null);
|
||||
const resizeMoveRafRef = React.useRef<number | null>(null);
|
||||
const resizeMovePendingRef = React.useRef<{ width: number; height: number; x: number; y: number } | null>(null);
|
||||
const resizeMovePendingRef = React.useRef<{
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
} | null>(null);
|
||||
const dragRef = React.useRef<{
|
||||
id: string;
|
||||
startLayerX: number;
|
||||
@@ -95,7 +110,12 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
startRotation: number;
|
||||
lastRotation: number;
|
||||
} | null>(null);
|
||||
const brushRef = React.useRef<{ id: string; lastX: number; lastY: number; rect: DOMRect } | null>(null);
|
||||
const brushRef = React.useRef<{
|
||||
id: string;
|
||||
lastX: number;
|
||||
lastY: number;
|
||||
rect: DOMRect;
|
||||
} | null>(null);
|
||||
|
||||
const pinchRef = React.useRef<{
|
||||
active: boolean;
|
||||
@@ -119,7 +139,10 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
onInteractionEnd?.();
|
||||
}
|
||||
|
||||
function clientDist(t0: Pick<Touch, "clientX" | "clientY">, t1: Pick<Touch, "clientX" | "clientY">) {
|
||||
function clientDist(
|
||||
t0: Pick<Touch, "clientX" | "clientY">,
|
||||
t1: Pick<Touch, "clientX" | "clientY">,
|
||||
) {
|
||||
const dx = t0.clientX - t1.clientX;
|
||||
const dy = t0.clientY - t1.clientY;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
@@ -130,12 +153,16 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
e.stopPropagation();
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
const factor = e.ctrlKey || e.metaKey ? 1 - e.deltaY * 0.01 : 1 - e.deltaY * ZOOM_FACTOR;
|
||||
const factor =
|
||||
e.ctrlKey || e.metaKey ? 1 - e.deltaY * 0.01 : 1 - e.deltaY * ZOOM_FACTOR;
|
||||
if (!Number.isFinite(factor) || factor === 0) return;
|
||||
const pivotX = e.clientX - rect.left;
|
||||
const pivotY = e.clientY - rect.top;
|
||||
setViewport((vp) => {
|
||||
const nextScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, vp.scale * factor));
|
||||
const nextScale = Math.max(
|
||||
MIN_SCALE,
|
||||
Math.min(MAX_SCALE, vp.scale * factor),
|
||||
);
|
||||
const scaleChange = nextScale / vp.scale;
|
||||
return {
|
||||
x: pivotX - (pivotX - vp.x) * scaleChange,
|
||||
@@ -147,7 +174,10 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
|
||||
function zoomBy(factor: number, pivotX: number, pivotY: number) {
|
||||
setViewport((vp) => {
|
||||
const newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, vp.scale * factor));
|
||||
const newScale = Math.max(
|
||||
MIN_SCALE,
|
||||
Math.min(MAX_SCALE, vp.scale * factor),
|
||||
);
|
||||
const scaleChange = newScale / vp.scale;
|
||||
return {
|
||||
x: pivotX - (pivotX - vp.x) * scaleChange,
|
||||
@@ -193,13 +223,21 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
React.useEffect(() => {
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.code === "Space") {
|
||||
if (!(e.target instanceof HTMLElement) || /^(input|textarea|select)$/i.test(e.target.tagName)) return;
|
||||
if (
|
||||
!(e.target instanceof HTMLElement) ||
|
||||
/^(input|textarea|select)$/i.test(e.target.tagName)
|
||||
)
|
||||
return;
|
||||
if (!isSpacePan) setIsSpacePan(true);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.key === "Shift") {
|
||||
if (!(e.target instanceof HTMLElement) || /^(input|textarea|select)$/i.test(e.target.tagName)) return;
|
||||
if (
|
||||
!(e.target instanceof HTMLElement) ||
|
||||
/^(input|textarea|select)$/i.test(e.target.tagName)
|
||||
)
|
||||
return;
|
||||
setIsShiftPressed(true);
|
||||
return;
|
||||
}
|
||||
@@ -231,11 +269,16 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
}
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("keyup", handleKeyUp);
|
||||
window.addEventListener("wheel", handleWheelCaptured, { capture: true, passive: false });
|
||||
window.addEventListener("wheel", handleWheelCaptured, {
|
||||
capture: true,
|
||||
passive: false,
|
||||
});
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
window.removeEventListener("keyup", handleKeyUp);
|
||||
window.removeEventListener("wheel", handleWheelCaptured, { capture: true });
|
||||
window.removeEventListener("wheel", handleWheelCaptured, {
|
||||
capture: true,
|
||||
});
|
||||
};
|
||||
}, [isSpacePan]);
|
||||
|
||||
@@ -281,7 +324,8 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
}
|
||||
const toolDef = getToolDefinition(tool);
|
||||
if (toolDef?.allowsLayerRotate && rotateRef.current && onRotateLayer) {
|
||||
const { centerX, centerY, startAngle, startRotation, id } = rotateRef.current;
|
||||
const { centerX, centerY, startAngle, startRotation, id } =
|
||||
rotateRef.current;
|
||||
const currentAngle = Math.atan2(e.clientY - centerY, e.clientX - centerX);
|
||||
const delta = currentAngle - startAngle;
|
||||
const nextRotation = startRotation + (delta * 180) / Math.PI;
|
||||
@@ -312,7 +356,13 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
nextHeight = Math.max(8, resizeRef.current.startHeight - dy);
|
||||
}
|
||||
|
||||
if (!e.shiftKey && (corner === "br" || corner === "bl" || corner === "tr" || corner === "tl")) {
|
||||
if (
|
||||
!e.shiftKey &&
|
||||
(corner === "br" ||
|
||||
corner === "bl" ||
|
||||
corner === "tr" ||
|
||||
corner === "tl")
|
||||
) {
|
||||
const aspect = resizeRef.current.aspect || 1;
|
||||
if (Math.abs(dx) > Math.abs(dy)) {
|
||||
nextHeight = Math.max(8, nextWidth / aspect);
|
||||
@@ -321,8 +371,10 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
if (corner === "bl" || corner === "tl") offsetX = resizeRef.current.startWidth - nextWidth;
|
||||
if (corner === "tr" || corner === "tl") offsetY = resizeRef.current.startHeight - nextHeight;
|
||||
if (corner === "bl" || corner === "tl")
|
||||
offsetX = resizeRef.current.startWidth - nextWidth;
|
||||
if (corner === "tr" || corner === "tl")
|
||||
offsetY = resizeRef.current.startHeight - nextHeight;
|
||||
|
||||
resizeRef.current.lastWidth = nextWidth;
|
||||
resizeRef.current.lastHeight = nextHeight;
|
||||
@@ -338,7 +390,11 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
if (!resizeRef.current || !resizeMovePendingRef.current) return;
|
||||
const pending = resizeMovePendingRef.current;
|
||||
onResizeLayer(resizeRef.current.id, pending.width, pending.height);
|
||||
if ((pending.x !== resizeRef.current.startLayerX || pending.y !== resizeRef.current.startLayerY) && onMoveLayer) {
|
||||
if (
|
||||
(pending.x !== resizeRef.current.startLayerX ||
|
||||
pending.y !== resizeRef.current.startLayerY) &&
|
||||
onMoveLayer
|
||||
) {
|
||||
onMoveLayer(resizeRef.current.id, pending.x, pending.y);
|
||||
}
|
||||
});
|
||||
@@ -349,14 +405,19 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
const dy = (e.clientY - dragRef.current.startEventY) / viewport.scale;
|
||||
const nextX = dragRef.current.startLayerX + dx;
|
||||
const nextY = dragRef.current.startLayerY + dy;
|
||||
if (dragRef.current.lastX === nextX && dragRef.current.lastY === nextY) return;
|
||||
if (dragRef.current.lastX === nextX && dragRef.current.lastY === nextY)
|
||||
return;
|
||||
dragRef.current.lastX = nextX;
|
||||
dragRef.current.lastY = nextY;
|
||||
if (dragMoveRafRef.current !== null) return;
|
||||
dragMoveRafRef.current = window.requestAnimationFrame(() => {
|
||||
dragMoveRafRef.current = null;
|
||||
if (!dragRef.current) return;
|
||||
onMoveLayer(dragRef.current.id, dragRef.current.lastX, dragRef.current.lastY);
|
||||
onMoveLayer(
|
||||
dragRef.current.id,
|
||||
dragRef.current.lastX,
|
||||
dragRef.current.lastY,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -372,7 +433,11 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
if (resizeRef.current && resizeMovePendingRef.current && onResizeLayer) {
|
||||
const pending = resizeMovePendingRef.current;
|
||||
onResizeLayer(resizeRef.current.id, pending.width, pending.height);
|
||||
if ((pending.x !== resizeRef.current.startLayerX || pending.y !== resizeRef.current.startLayerY) && onMoveLayer) {
|
||||
if (
|
||||
(pending.x !== resizeRef.current.startLayerX ||
|
||||
pending.y !== resizeRef.current.startLayerY) &&
|
||||
onMoveLayer
|
||||
) {
|
||||
onMoveLayer(resizeRef.current.id, pending.x, pending.y);
|
||||
}
|
||||
}
|
||||
@@ -381,11 +446,13 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
isMiddleMousePan.current = false;
|
||||
if (dragRef.current && onMoveLayerEnd) {
|
||||
const { id, lastX, lastY } = dragRef.current;
|
||||
if (typeof lastX === "number" && typeof lastY === "number") onMoveLayerEnd(id, lastX, lastY);
|
||||
if (typeof lastX === "number" && typeof lastY === "number")
|
||||
onMoveLayerEnd(id, lastX, lastY);
|
||||
}
|
||||
if (resizeRef.current && onResizeLayerEnd) {
|
||||
const { id, lastWidth, lastHeight } = resizeRef.current;
|
||||
if (typeof lastWidth === "number" && typeof lastHeight === "number") onResizeLayerEnd(id, lastWidth, lastHeight);
|
||||
if (typeof lastWidth === "number" && typeof lastHeight === "number")
|
||||
onResizeLayerEnd(id, lastWidth, lastHeight);
|
||||
}
|
||||
if (rotateRef.current && onRotateLayerEnd) {
|
||||
const { id, lastRotation } = rotateRef.current;
|
||||
@@ -402,7 +469,10 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
endInteraction();
|
||||
}
|
||||
|
||||
function onLayerPointerDown(e: React.PointerEvent<HTMLDivElement>, layer: Layer) {
|
||||
function onLayerPointerDown(
|
||||
e: React.PointerEvent<HTMLDivElement>,
|
||||
layer: Layer,
|
||||
) {
|
||||
if (isSpacePan) return;
|
||||
e.stopPropagation();
|
||||
const toolDef = getToolDefinition(tool);
|
||||
@@ -413,9 +483,14 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
onSelectLayer(layer.id);
|
||||
if (tool === "brush" && onBrushStrokeStart) {
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
brushRef.current = { id: layer.id, lastX: localX, lastY: localY, rect: e.currentTarget.getBoundingClientRect() };
|
||||
const lw = layer.width ?? Math.round(200 * layer.scale);
|
||||
const lh = layer.height ?? Math.round(150 * layer.scale);
|
||||
brushRef.current = {
|
||||
id: layer.id,
|
||||
lastX: localX,
|
||||
lastY: localY,
|
||||
rect: e.currentTarget.getBoundingClientRect(),
|
||||
};
|
||||
const lw = layer.width;
|
||||
const lh = layer.height;
|
||||
onBrushStrokeStart(layer.id, localX, localY, lw, lh);
|
||||
beginInteraction();
|
||||
} else if (onFillLayer) {
|
||||
@@ -437,13 +512,17 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
onSelectLayer(layer.id);
|
||||
}
|
||||
|
||||
function onResizeHandleDown(e: React.PointerEvent<HTMLButtonElement>, layer: Layer, corner: string) {
|
||||
function onResizeHandleDown(
|
||||
e: React.PointerEvent<HTMLButtonElement>,
|
||||
layer: Layer,
|
||||
corner: string,
|
||||
) {
|
||||
if (!getToolDefinition(tool)?.allowsLayerResize || !onResizeLayer) return;
|
||||
e.stopPropagation();
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
rotateRef.current = null;
|
||||
const width = layer.width ?? Math.round(200 * layer.scale);
|
||||
const height = layer.height ?? Math.round(150 * layer.scale);
|
||||
const width = layer.width;
|
||||
const height = layer.height;
|
||||
resizeRef.current = {
|
||||
id: layer.id,
|
||||
startWidth: width,
|
||||
@@ -461,16 +540,27 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
onSelectLayer(layer.id);
|
||||
}
|
||||
|
||||
function onRotateHandleDown(e: React.PointerEvent<HTMLButtonElement>, layer: Layer) {
|
||||
function onRotateHandleDown(
|
||||
e: React.PointerEvent<HTMLButtonElement>,
|
||||
layer: Layer,
|
||||
) {
|
||||
if (!getToolDefinition(tool)?.allowsLayerRotate || !onRotateLayer) return;
|
||||
e.stopPropagation();
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
resizeRef.current = null;
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
const width = layer.width ?? Math.round(200 * layer.scale);
|
||||
const height = layer.height ?? Math.round(150 * layer.scale);
|
||||
const centerX = (rect?.left ?? 0) + viewport.x + layer.x * viewport.scale + (width * viewport.scale) / 2;
|
||||
const centerY = (rect?.top ?? 0) + viewport.y + layer.y * viewport.scale + (height * viewport.scale) / 2;
|
||||
const width = layer.width;
|
||||
const height = layer.height;
|
||||
const centerX =
|
||||
(rect?.left ?? 0) +
|
||||
viewport.x +
|
||||
layer.x * viewport.scale +
|
||||
(width * viewport.scale) / 2;
|
||||
const centerY =
|
||||
(rect?.top ?? 0) +
|
||||
viewport.y +
|
||||
layer.y * viewport.scale +
|
||||
(height * viewport.scale) / 2;
|
||||
const startAngle = Math.atan2(e.clientY - centerY, e.clientX - centerX);
|
||||
rotateRef.current = {
|
||||
id: layer.id,
|
||||
@@ -488,7 +578,8 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
if (e.touches.length === 2) {
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2 - rect.left;
|
||||
const midX =
|
||||
(e.touches[0].clientX + e.touches[1].clientX) / 2 - rect.left;
|
||||
const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2 - rect.top;
|
||||
pinchRef.current = {
|
||||
active: true,
|
||||
@@ -507,7 +598,10 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
e.preventDefault();
|
||||
const px = clientDist(e.touches[0], e.touches[1]);
|
||||
const ratio = px / pinchRef.current.initialPinchPx;
|
||||
const nextScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, pinchRef.current.initialScale * ratio));
|
||||
const nextScale = Math.max(
|
||||
MIN_SCALE,
|
||||
Math.min(MAX_SCALE, pinchRef.current.initialScale * ratio),
|
||||
);
|
||||
const scaleChange = nextScale / pinchRef.current.initialScale;
|
||||
const { pivotX, pivotY, initialX, initialY } = pinchRef.current;
|
||||
setViewport({
|
||||
|
||||
Reference in New Issue
Block a user