fix: integrate crop tool with canvas component
The crop tool was previously not connected to the canvas component, preventing it from receiving pointer events and displaying its overlay. Changes: - Added cropOverlayNeedsUpdate state to trigger re-renders when crop changes - Added crop tool overlay rendering in main canvas render effect - Added crop tool pointer handlers in handlePointerDown, handlePointerMove, and handlePointerUp - Crop tool now creates temporary canvas contexts for state management while drawing overlay on display canvas - Crop overlay is properly transformed with zoom and pan transformations The crop tool now works correctly: it displays the crop rectangle with handles, responds to dragging to define/move/resize the crop area, and updates in real-time. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,7 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
|||||||
prevY: 0,
|
prevY: 0,
|
||||||
pressure: 1,
|
pressure: 1,
|
||||||
});
|
});
|
||||||
|
const [cropOverlayNeedsUpdate, setCropOverlayNeedsUpdate] = useState(0);
|
||||||
|
|
||||||
// Touch gesture support for mobile
|
// Touch gesture support for mobile
|
||||||
useTouchGestures(containerRef, {
|
useTouchGestures(containerRef, {
|
||||||
@@ -216,7 +217,30 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
|||||||
|
|
||||||
// Restore context state
|
// Restore context state
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}, [layers, width, height, zoom, offsetX, offsetY, showGrid, gridSize, backgroundColor, selection, pointer, activeSelection, isMarching, marchingOffset, textObjects, editingTextId]);
|
|
||||||
|
// Draw crop tool overlay if crop tool is active
|
||||||
|
if (activeTool === 'crop') {
|
||||||
|
const cropTool = toolsCache.current['crop'];
|
||||||
|
if (cropTool && typeof (cropTool as any).drawCropOverlay === 'function') {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(offsetX + canvas.width / 2, offsetY + canvas.height / 2);
|
||||||
|
ctx.scale(zoom, zoom);
|
||||||
|
ctx.translate(-width / 2, -height / 2);
|
||||||
|
|
||||||
|
// Create a temporary canvas context that matches the full canvas size
|
||||||
|
const tempCanvas = document.createElement('canvas');
|
||||||
|
tempCanvas.width = width;
|
||||||
|
tempCanvas.height = height;
|
||||||
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
if (tempCtx) {
|
||||||
|
(cropTool as any).drawCropOverlay(tempCtx);
|
||||||
|
ctx.drawImage(tempCanvas, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [layers, width, height, zoom, offsetX, offsetY, showGrid, gridSize, backgroundColor, selection, pointer, activeSelection, isMarching, marchingOffset, textObjects, editingTextId, activeTool, cropOverlayNeedsUpdate]);
|
||||||
|
|
||||||
// Marching ants animation
|
// Marching ants animation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -283,6 +307,38 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Crop tool
|
||||||
|
if (e.button === 0 && !e.shiftKey && activeTool === 'crop') {
|
||||||
|
const cropTool = toolsCache.current['crop'];
|
||||||
|
if (!cropTool) return; // Tool not loaded yet
|
||||||
|
|
||||||
|
const newPointer: PointerState = {
|
||||||
|
isDown: true,
|
||||||
|
x: canvasPos.x,
|
||||||
|
y: canvasPos.y,
|
||||||
|
prevX: canvasPos.x,
|
||||||
|
prevY: canvasPos.y,
|
||||||
|
pressure: e.pressure || 1,
|
||||||
|
altKey: e.altKey,
|
||||||
|
ctrlKey: e.ctrlKey,
|
||||||
|
shiftKey: e.shiftKey,
|
||||||
|
metaKey: e.metaKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
setPointer(newPointer);
|
||||||
|
|
||||||
|
// Create a temporary canvas for the crop tool
|
||||||
|
const tempCanvas = document.createElement('canvas');
|
||||||
|
tempCanvas.width = width;
|
||||||
|
tempCanvas.height = height;
|
||||||
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
if (tempCtx) {
|
||||||
|
cropTool.onPointerDown(newPointer, tempCtx, settings);
|
||||||
|
setCropOverlayNeedsUpdate(prev => prev + 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Text tool - only handle if editor is not already active
|
// Text tool - only handle if editor is not already active
|
||||||
if (activeTool === 'text') {
|
if (activeTool === 'text') {
|
||||||
// If editor is active, let it handle its own events (selection, dragging, click-outside)
|
// If editor is active, let it handle its own events (selection, dragging, click-outside)
|
||||||
@@ -394,6 +450,35 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Crop tool
|
||||||
|
if (pointer.isDown && activeTool === 'crop') {
|
||||||
|
const cropTool = toolsCache.current['crop'];
|
||||||
|
if (!cropTool) return;
|
||||||
|
|
||||||
|
const newPointer: PointerState = {
|
||||||
|
...pointer,
|
||||||
|
x: canvasPos.x,
|
||||||
|
y: canvasPos.y,
|
||||||
|
pressure: e.pressure || 1,
|
||||||
|
altKey: e.altKey,
|
||||||
|
ctrlKey: e.ctrlKey,
|
||||||
|
shiftKey: e.shiftKey,
|
||||||
|
metaKey: e.metaKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
setPointer(newPointer);
|
||||||
|
|
||||||
|
const tempCanvas = document.createElement('canvas');
|
||||||
|
tempCanvas.width = width;
|
||||||
|
tempCanvas.height = height;
|
||||||
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
if (tempCtx) {
|
||||||
|
cropTool.onPointerMove(newPointer, tempCtx, settings);
|
||||||
|
setCropOverlayNeedsUpdate(prev => prev + 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Drawing
|
// Drawing
|
||||||
if (pointer.isDown && ['pencil', 'brush', 'eraser', 'eyedropper', 'shape', 'clone', 'smudge', 'dodge'].includes(activeTool)) {
|
if (pointer.isDown && ['pencil', 'brush', 'eraser', 'eyedropper', 'shape', 'clone', 'smudge', 'dodge'].includes(activeTool)) {
|
||||||
const activeLayer = getActiveLayer();
|
const activeLayer = getActiveLayer();
|
||||||
@@ -429,6 +514,23 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Crop tool
|
||||||
|
if (pointer.isDown && activeTool === 'crop') {
|
||||||
|
const cropTool = toolsCache.current['crop'];
|
||||||
|
if (cropTool) {
|
||||||
|
const tempCanvas = document.createElement('canvas');
|
||||||
|
tempCanvas.width = width;
|
||||||
|
tempCanvas.height = height;
|
||||||
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
if (tempCtx) {
|
||||||
|
cropTool.onPointerUp(pointer, tempCtx, settings);
|
||||||
|
setCropOverlayNeedsUpdate(prev => prev + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setPointer({ ...pointer, isDown: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (pointer.isDown && ['pencil', 'brush', 'eraser', 'fill', 'eyedropper', 'shape'].includes(activeTool)) {
|
if (pointer.isDown && ['pencil', 'brush', 'eraser', 'fill', 'eyedropper', 'shape'].includes(activeTool)) {
|
||||||
const activeLayer = getActiveLayer();
|
const activeLayer = getActiveLayer();
|
||||||
if (!activeLayer || !activeLayer.canvas) return;
|
if (!activeLayer || !activeLayer.canvas) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user