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,
|
||||
pressure: 1,
|
||||
});
|
||||
const [cropOverlayNeedsUpdate, setCropOverlayNeedsUpdate] = useState(0);
|
||||
|
||||
// Touch gesture support for mobile
|
||||
useTouchGestures(containerRef, {
|
||||
@@ -216,7 +217,30 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
||||
|
||||
// Restore context state
|
||||
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
|
||||
useEffect(() => {
|
||||
@@ -283,6 +307,38 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
||||
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
|
||||
if (activeTool === 'text') {
|
||||
// If editor is active, let it handle its own events (selection, dragging, click-outside)
|
||||
@@ -394,6 +450,35 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
||||
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
|
||||
if (pointer.isDown && ['pencil', 'brush', 'eraser', 'eyedropper', 'shape', 'clone', 'smudge', 'dodge'].includes(activeTool)) {
|
||||
const activeLayer = getActiveLayer();
|
||||
@@ -429,6 +514,23 @@ export function CanvasWithTools({ onCursorMove }: CanvasWithToolsProps = {}) {
|
||||
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)) {
|
||||
const activeLayer = getActiveLayer();
|
||||
if (!activeLayer || !activeLayer.canvas) return;
|
||||
|
||||
Reference in New Issue
Block a user