mirror of
https://github.com/YuzuZensai/Pien-Studio.git
synced 2026-09-02 14:18:35 +00:00
✨ feat: layer effects, bucket fill, simple brush
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import type { Layer } from "@pien-studio/types";
|
||||
import { getToolDefinition } from "@pien-studio/editor-core";
|
||||
|
||||
type Viewport = {
|
||||
x: number;
|
||||
@@ -10,7 +11,7 @@ type Viewport = {
|
||||
type UseCanvasInteractionsOptions = {
|
||||
canvasWidth: number;
|
||||
canvasHeight: number;
|
||||
tool: "pointer" | "hand" | "face";
|
||||
tool: string;
|
||||
onSelectLayer: (id: string | null) => void;
|
||||
onMoveLayer: (id: string, x: number, y: number) => void;
|
||||
onMoveLayerEnd?: (id: string, x: number, y: number) => void;
|
||||
@@ -21,6 +22,10 @@ type UseCanvasInteractionsOptions = {
|
||||
onInteractionStart?: () => void;
|
||||
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;
|
||||
onBrushStrokeMove?: (layerId: string, x: number, y: number) => void;
|
||||
onBrushStrokeEnd?: (layerId: string) => void;
|
||||
};
|
||||
|
||||
const MIN_SCALE = 0.1;
|
||||
@@ -43,6 +48,10 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
onInteractionStart,
|
||||
onInteractionEnd,
|
||||
onContextMenu,
|
||||
onFillLayer,
|
||||
onBrushStrokeStart,
|
||||
onBrushStrokeMove,
|
||||
onBrushStrokeEnd,
|
||||
} = options;
|
||||
const [viewport, setViewport] = React.useState<Viewport>({ x: 0, y: 0, scale: 1 });
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
@@ -86,6 +95,8 @@ 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 pinchRef = React.useRef<{
|
||||
active: boolean;
|
||||
initialPinchPx: number;
|
||||
@@ -237,10 +248,18 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
return;
|
||||
}
|
||||
if (e.button !== 0) return;
|
||||
if (tool !== "hand" && tool !== "face" && !isSpacePan) return;
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
isPanning.current = true;
|
||||
lastPos.current = { x: e.clientX, y: e.clientY };
|
||||
const toolDef = getToolDefinition(tool);
|
||||
if (toolDef?.interactionMode !== "select" && !isSpacePan) {
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
isPanning.current = true;
|
||||
lastPos.current = { x: e.clientX, y: e.clientY };
|
||||
return;
|
||||
}
|
||||
if (isSpacePan) {
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
isPanning.current = true;
|
||||
lastPos.current = { x: e.clientX, y: e.clientY };
|
||||
}
|
||||
}
|
||||
|
||||
function onContainerPointerMove(e: React.PointerEvent<HTMLDivElement>) {
|
||||
@@ -251,7 +270,17 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
setViewport((vp) => ({ ...vp, x: vp.x + dx, y: vp.y + dy }));
|
||||
return;
|
||||
}
|
||||
if (tool === "pointer" && rotateRef.current && onRotateLayer) {
|
||||
if (brushRef.current && onBrushStrokeMove) {
|
||||
const { rect } = brushRef.current;
|
||||
const localX = (e.clientX - rect.left) / viewport.scale;
|
||||
const localY = (e.clientY - rect.top) / viewport.scale;
|
||||
brushRef.current.lastX = localX;
|
||||
brushRef.current.lastY = localY;
|
||||
onBrushStrokeMove(brushRef.current.id, localX, localY);
|
||||
return;
|
||||
}
|
||||
const toolDef = getToolDefinition(tool);
|
||||
if (toolDef?.allowsLayerRotate && rotateRef.current && onRotateLayer) {
|
||||
const { centerX, centerY, startAngle, startRotation, id } = rotateRef.current;
|
||||
const currentAngle = Math.atan2(e.clientY - centerY, e.clientX - centerX);
|
||||
const delta = currentAngle - startAngle;
|
||||
@@ -260,7 +289,7 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
onRotateLayer(id, nextRotation);
|
||||
return;
|
||||
}
|
||||
if (tool === "pointer" && resizeRef.current && onResizeLayer) {
|
||||
if (toolDef?.allowsLayerResize && resizeRef.current && onResizeLayer) {
|
||||
const dx = (e.clientX - resizeRef.current.startX) / viewport.scale;
|
||||
const dy = (e.clientY - resizeRef.current.startY) / viewport.scale;
|
||||
const corner = resizeRef.current.corner;
|
||||
@@ -315,7 +344,7 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (tool === "pointer" && dragRef.current) {
|
||||
if (toolDef?.allowsLayerDrag && dragRef.current) {
|
||||
const dx = (e.clientX - dragRef.current.startEventX) / viewport.scale;
|
||||
const dy = (e.clientY - dragRef.current.startEventY) / viewport.scale;
|
||||
const nextX = dragRef.current.startLayerX + dx;
|
||||
@@ -362,6 +391,10 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
const { id, lastRotation } = rotateRef.current;
|
||||
if (typeof lastRotation === "number") onRotateLayerEnd(id, lastRotation);
|
||||
}
|
||||
if (brushRef.current && onBrushStrokeEnd) {
|
||||
onBrushStrokeEnd(brushRef.current.id);
|
||||
brushRef.current = null;
|
||||
}
|
||||
dragRef.current = null;
|
||||
resizeRef.current = null;
|
||||
resizeMovePendingRef.current = null;
|
||||
@@ -372,6 +405,24 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
function onLayerPointerDown(e: React.PointerEvent<HTMLDivElement>, layer: Layer) {
|
||||
if (isSpacePan) return;
|
||||
e.stopPropagation();
|
||||
const toolDef = getToolDefinition(tool);
|
||||
if (toolDef?.interactionMode === "paint") {
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const localX = (e.clientX - rect.left) / viewport.scale;
|
||||
const localY = (e.clientY - rect.top) / viewport.scale;
|
||||
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);
|
||||
onBrushStrokeStart(layer.id, localX, localY, lw, lh);
|
||||
beginInteraction();
|
||||
} else if (onFillLayer) {
|
||||
onFillLayer(layer.id, localX, localY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
dragRef.current = {
|
||||
id: layer.id,
|
||||
@@ -387,7 +438,7 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
}
|
||||
|
||||
function onResizeHandleDown(e: React.PointerEvent<HTMLButtonElement>, layer: Layer, corner: string) {
|
||||
if (tool !== "pointer" || !onResizeLayer) return;
|
||||
if (!getToolDefinition(tool)?.allowsLayerResize || !onResizeLayer) return;
|
||||
e.stopPropagation();
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
rotateRef.current = null;
|
||||
@@ -411,7 +462,7 @@ export function useCanvasInteractions(options: UseCanvasInteractionsOptions) {
|
||||
}
|
||||
|
||||
function onRotateHandleDown(e: React.PointerEvent<HTMLButtonElement>, layer: Layer) {
|
||||
if (tool !== "pointer" || !onRotateLayer) return;
|
||||
if (!getToolDefinition(tool)?.allowsLayerRotate || !onRotateLayer) return;
|
||||
e.stopPropagation();
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
resizeRef.current = null;
|
||||
|
||||
Reference in New Issue
Block a user