Fix Ctrl+V to paste from system clipboard when internal clipboard is empty

This commit is contained in:
diodiogod
2026-02-02 18:22:54 -03:00
parent 9b04729561
commit ce4d332987
2 changed files with 74 additions and 78 deletions

View File

@@ -575,10 +575,8 @@ export class CanvasInteractions {
} }
break; break;
case 'v': case 'v':
// Paste layers from internal clipboard // Paste from internal clipboard or system clipboard
if (this.canvas.canvasLayers.internalClipboard.length > 0) { this.canvas.canvasLayers.handlePaste('mouse');
this.canvas.canvasLayers.pasteLayers();
}
break; break;
default: default:
handled = false; handled = false;

View File

@@ -132,16 +132,16 @@ export class CanvasInteractions {
const mouseBufferY = (worldCoords.y - this.canvas.viewport.y) * this.canvas.viewport.zoom; const mouseBufferY = (worldCoords.y - this.canvas.viewport.y) * this.canvas.viewport.zoom;
const newZoom = Math.max(0.1, Math.min(10, this.canvas.viewport.zoom * zoomFactor)); const newZoom = Math.max(0.1, Math.min(10, this.canvas.viewport.zoom * zoomFactor));
this.canvas.viewport.zoom = newZoom; this.canvas.viewport.zoom = newZoom;
this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom); this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom);
this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom); this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom);
// Update stroke overlay if mask tool is drawing during zoom // Update stroke overlay if mask tool is drawing during zoom
if (this.canvas.maskTool.isDrawing) { if (this.canvas.maskTool.isDrawing) {
this.canvas.maskTool.handleViewportChange(); this.canvas.maskTool.handleViewportChange();
} }
this.canvas.onViewportChange?.(); this.canvas.onViewportChange?.();
} }
@@ -234,7 +234,7 @@ export class CanvasInteractions {
const rotatedY = dx * Math.sin(rad) + dy * Math.cos(rad); const rotatedY = dx * Math.sin(rad) + dy * Math.cos(rad);
// Sprawdź czy punkt jest wewnątrz prostokąta layera // Sprawdź czy punkt jest wewnątrz prostokąta layera
if (Math.abs(rotatedX) <= layer.width / 2 && if (Math.abs(rotatedX) <= layer.width / 2 &&
Math.abs(rotatedY) <= layer.height / 2) { Math.abs(rotatedY) <= layer.height / 2) {
return true; return true;
} }
@@ -331,11 +331,11 @@ export class CanvasInteractions {
this.startCanvasResize(coords.world); this.startCanvasResize(coords.world);
return; return;
} }
// 2. Inne przyciski myszy // 2. Inne przyciski myszy
if (e.button === 2) { // Prawy przycisk myszy if (e.button === 2) { // Prawy przycisk myszy
this.preventEventDefaults(e); this.preventEventDefaults(e);
// Sprawdź czy kliknięto w obszarze któregokolwiek z zaznaczonych layerów (niezależnie od przykrycia) // Sprawdź czy kliknięto w obszarze któregokolwiek z zaznaczonych layerów (niezależnie od przykrycia)
if (this.isPointInSelectedLayers(coords.world.x, coords.world.y)) { if (this.isPointInSelectedLayers(coords.world.x, coords.world.y)) {
// Nowa logika przekazuje tylko współrzędne świata, menu pozycjonuje się samo // Nowa logika przekazuje tylko współrzędne świata, menu pozycjonuje się samo
@@ -360,7 +360,7 @@ export class CanvasInteractions {
if (grabIconLayer) { if (grabIconLayer) {
// Start dragging the selected layer(s) without changing selection // Start dragging the selected layer(s) without changing selection
this.interaction.mode = 'potential-drag'; this.interaction.mode = 'potential-drag';
this.interaction.dragStart = {...coords.world}; this.interaction.dragStart = { ...coords.world };
return; return;
} }
@@ -369,7 +369,7 @@ export class CanvasInteractions {
this.prepareForDrag(clickedLayerResult.layer, coords.world); this.prepareForDrag(clickedLayerResult.layer, coords.world);
return; return;
} }
// 4. Domyślna akcja na tle (lewy przycisk bez modyfikatorów) // 4. Domyślna akcja na tle (lewy przycisk bez modyfikatorów)
this.startPanning(e, true); // clearSelection = true this.startPanning(e, true); // clearSelection = true
} }
@@ -377,7 +377,7 @@ export class CanvasInteractions {
handleMouseMove(e: MouseEvent): void { handleMouseMove(e: MouseEvent): void {
const coords = this.getMouseCoordinates(e); const coords = this.getMouseCoordinates(e);
this.canvas.lastMousePosition = coords.world; // Zawsze aktualizuj ostatnią pozycję myszy this.canvas.lastMousePosition = coords.world; // Zawsze aktualizuj ostatnią pozycję myszy
// Sprawdź, czy rozpocząć przeciąganie // Sprawdź, czy rozpocząć przeciąganie
if (this.interaction.mode === 'potential-drag') { if (this.interaction.mode === 'potential-drag') {
const dx = coords.world.x - this.interaction.dragStart.x; const dx = coords.world.x - this.interaction.dragStart.x;
@@ -390,7 +390,7 @@ export class CanvasInteractions {
}); });
} }
} }
switch (this.interaction.mode) { switch (this.interaction.mode) {
case 'drawingMask': case 'drawingMask':
this.canvas.maskTool.handleMouseMove(coords.world, coords.view); this.canvas.maskTool.handleMouseMove(coords.world, coords.view);
@@ -425,12 +425,12 @@ export class CanvasInteractions {
// Check if hovering over grab icon // Check if hovering over grab icon
const wasHovering = this.interaction.hoveringGrabIcon; const wasHovering = this.interaction.hoveringGrabIcon;
this.interaction.hoveringGrabIcon = this.getGrabIconAtPosition(coords.world.x, coords.world.y) !== null; this.interaction.hoveringGrabIcon = this.getGrabIconAtPosition(coords.world.x, coords.world.y) !== null;
// Re-render if hover state changed to show/hide grab icon // Re-render if hover state changed to show/hide grab icon
if (wasHovering !== this.interaction.hoveringGrabIcon) { if (wasHovering !== this.interaction.hoveringGrabIcon) {
this.canvas.render(); this.canvas.render();
} }
this.updateCursor(coords.world); this.updateCursor(coords.world);
// Update brush cursor on overlay if mask tool is active // Update brush cursor on overlay if mask tool is active
if (this.canvas.maskTool.isActive) { if (this.canvas.maskTool.isActive) {
@@ -447,7 +447,7 @@ export class CanvasInteractions {
handleMouseUp(e: MouseEvent): void { handleMouseUp(e: MouseEvent): void {
const coords = this.getMouseCoordinates(e); const coords = this.getMouseCoordinates(e);
if (this.interaction.mode === 'drawingMask') { if (this.interaction.mode === 'drawingMask') {
this.canvas.maskTool.handleMouseUp(coords.view); this.canvas.maskTool.handleMouseUp(coords.view);
// Render only once after drawing is complete // Render only once after drawing is complete
@@ -476,7 +476,7 @@ export class CanvasInteractions {
if (this.interaction.mode === 'resizing' && this.interaction.transformingLayer?.cropMode) { if (this.interaction.mode === 'resizing' && this.interaction.transformingLayer?.cropMode) {
this.canvas.canvasLayers.handleCropBoundsTransformEnd(this.interaction.transformingLayer); this.canvas.canvasLayers.handleCropBoundsTransformEnd(this.interaction.transformingLayer);
} }
// Handle end of scale transformation (normal transform mode) before resetting interaction state // Handle end of scale transformation (normal transform mode) before resetting interaction state
if (this.interaction.mode === 'resizing' && this.interaction.transformingLayer && !this.interaction.transformingLayer.cropMode) { if (this.interaction.mode === 'resizing' && this.interaction.transformingLayer && !this.interaction.transformingLayer.cropMode) {
this.canvas.canvasLayers.handleScaleTransformEnd(this.interaction.transformingLayer); this.canvas.canvasLayers.handleScaleTransformEnd(this.interaction.transformingLayer);
@@ -500,7 +500,7 @@ export class CanvasInteractions {
log.info(`Mouse position: world(${coords.world.x.toFixed(1)}, ${coords.world.y.toFixed(1)}) view(${coords.view.x.toFixed(1)}, ${coords.view.y.toFixed(1)})`); log.info(`Mouse position: world(${coords.world.x.toFixed(1)}, ${coords.world.y.toFixed(1)}) view(${coords.view.x.toFixed(1)}, ${coords.view.y.toFixed(1)})`);
log.info(`Output Area Bounds: x=${bounds.x}, y=${bounds.y}, w=${bounds.width}, h=${bounds.height}`); log.info(`Output Area Bounds: x=${bounds.x}, y=${bounds.y}, w=${bounds.width}, h=${bounds.height}`);
log.info(`Viewport: x=${this.canvas.viewport.x.toFixed(1)}, y=${this.canvas.viewport.y.toFixed(1)}, zoom=${this.canvas.viewport.zoom.toFixed(2)}`); log.info(`Viewport: x=${this.canvas.viewport.x.toFixed(1)}, y=${this.canvas.viewport.y.toFixed(1)}, zoom=${this.canvas.viewport.zoom.toFixed(2)}`);
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer, index: number) => { this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer, index: number) => {
const relativeToOutput = { const relativeToOutput = {
x: layer.x - bounds.x, x: layer.x - bounds.x,
@@ -547,7 +547,7 @@ export class CanvasInteractions {
handleWheel(e: WheelEvent): void { handleWheel(e: WheelEvent): void {
this.preventEventDefaults(e); this.preventEventDefaults(e);
const coords = this.getMouseCoordinates(e); const coords = this.getMouseCoordinates(e);
if (this.canvas.maskTool.isActive || this.canvas.canvasSelection.selectedLayers.length === 0) { if (this.canvas.maskTool.isActive || this.canvas.canvasSelection.selectedLayers.length === 0) {
// Zoom operation for mask tool or when no layers selected // Zoom operation for mask tool or when no layers selected
const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1; const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
@@ -555,7 +555,7 @@ export class CanvasInteractions {
} else { } else {
// Check if mouse is over any selected layer // Check if mouse is over any selected layer
const isOverSelectedLayer = this.isPointInSelectedLayers(coords.world.x, coords.world.y); const isOverSelectedLayer = this.isPointInSelectedLayers(coords.world.x, coords.world.y);
if (isOverSelectedLayer) { if (isOverSelectedLayer) {
// Layer transformation when layers are selected and mouse is over selected layer // Layer transformation when layers are selected and mouse is over selected layer
this.handleLayerWheelTransformation(e); this.handleLayerWheelTransformation(e);
@@ -565,7 +565,7 @@ export class CanvasInteractions {
this.performZoomOperation(coords.world, zoomFactor); this.performZoomOperation(coords.world, zoomFactor);
} }
} }
this.canvas.render(); this.canvas.render();
if (!this.canvas.maskTool.isActive) { if (!this.canvas.maskTool.isActive) {
this.canvas.requestSaveState(); this.canvas.requestSaveState();
@@ -621,7 +621,7 @@ export class CanvasInteractions {
layer.height *= scaleFactor; layer.height *= scaleFactor;
layer.x += (oldWidth - layer.width) / 2; layer.x += (oldWidth - layer.width) / 2;
layer.y += (oldHeight - layer.height) / 2; layer.y += (oldHeight - layer.height) / 2;
// Handle wheel scaling end for layers with blend area // Handle wheel scaling end for layers with blend area
this.canvas.canvasLayers.handleWheelScalingEnd(layer); this.canvas.canvasLayers.handleWheelScalingEnd(layer);
} }
@@ -637,11 +637,11 @@ export class CanvasInteractions {
} else { } else {
targetHeight = (Math.ceil(oldHeight / gridSize) - 1) * gridSize; targetHeight = (Math.ceil(oldHeight / gridSize) - 1) * gridSize;
} }
if (targetHeight < gridSize / 2) { if (targetHeight < gridSize / 2) {
targetHeight = gridSize / 2; targetHeight = gridSize / 2;
} }
if (Math.abs(oldHeight - targetHeight) < 1) { if (Math.abs(oldHeight - targetHeight) < 1) {
if (direction > 0) targetHeight += gridSize; if (direction > 0) targetHeight += gridSize;
else targetHeight -= gridSize; else targetHeight -= gridSize;
@@ -704,10 +704,8 @@ export class CanvasInteractions {
} }
break; break;
case 'v': case 'v':
// Paste layers from internal clipboard // Paste from internal clipboard or system clipboard
if (this.canvas.canvasLayers.internalClipboard.length > 0) { this.canvas.canvasLayers.handlePaste('mouse');
this.canvas.canvasLayers.pasteLayers();
}
break; break;
default: default:
handled = false; handled = false;
@@ -724,7 +722,7 @@ export class CanvasInteractions {
if (this.canvas.canvasSelection.selectedLayers.length > 0) { if (this.canvas.canvasSelection.selectedLayers.length > 0) {
const step = mods.shift ? 10 : 1; const step = mods.shift ? 10 : 1;
let needsRender = false; let needsRender = false;
// Używamy e.code dla spójności i niezależności od układu klawiatury // Używamy e.code dla spójności i niezależności od układu klawiatury
const movementKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BracketLeft', 'BracketRight']; const movementKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BracketLeft', 'BracketRight'];
if (movementKeys.includes(e.code)) { if (movementKeys.includes(e.code)) {
@@ -748,7 +746,7 @@ export class CanvasInteractions {
this.canvas.canvasSelection.removeSelectedLayers(); this.canvas.canvasSelection.removeSelectedLayers();
return; return;
} }
if (needsRender) { if (needsRender) {
this.canvas.render(); this.canvas.render();
} }
@@ -794,7 +792,7 @@ export class CanvasInteractions {
this.canvas.saveState(); this.canvas.saveState();
this.canvas.canvasState.saveStateToDB(); this.canvas.canvasState.saveStateToDB();
} }
// Reset interaction mode if it's something that can get "stuck" // Reset interaction mode if it's something that can get "stuck"
if (this.interaction.mode !== 'none' && this.interaction.mode !== 'drawingMask') { if (this.interaction.mode !== 'none' && this.interaction.mode !== 'drawingMask') {
this.resetInteractionState(); this.resetInteractionState();
@@ -844,7 +842,7 @@ export class CanvasInteractions {
originalHeight: layer.originalHeight, originalHeight: layer.originalHeight,
cropBounds: layer.cropBounds ? { ...layer.cropBounds } : undefined cropBounds: layer.cropBounds ? { ...layer.cropBounds } : undefined
}; };
this.interaction.dragStart = {...worldCoords}; this.interaction.dragStart = { ...worldCoords };
if (handle === 'rot') { if (handle === 'rot') {
this.interaction.mode = 'rotating'; this.interaction.mode = 'rotating';
@@ -878,9 +876,9 @@ export class CanvasInteractions {
this.canvas.canvasSelection.updateSelection([layer]); this.canvas.canvasSelection.updateSelection([layer]);
} }
} }
this.interaction.mode = 'potential-drag'; this.interaction.mode = 'potential-drag';
this.interaction.dragStart = {...worldCoords}; this.interaction.dragStart = { ...worldCoords };
} }
startPanning(e: MouseEvent, clearSelection: boolean = true): void { startPanning(e: MouseEvent, clearSelection: boolean = true): void {
@@ -896,8 +894,8 @@ export class CanvasInteractions {
this.interaction.mode = 'resizingCanvas'; this.interaction.mode = 'resizingCanvas';
const startX = snapToGrid(worldCoords.x); const startX = snapToGrid(worldCoords.x);
const startY = snapToGrid(worldCoords.y); const startY = snapToGrid(worldCoords.y);
this.interaction.canvasResizeStart = {x: startX, y: startY}; this.interaction.canvasResizeStart = { x: startX, y: startY };
this.interaction.canvasResizeRect = {x: startX, y: startY, width: 0, height: 0}; this.interaction.canvasResizeRect = { x: startX, y: startY, width: 0, height: 0 };
this.canvas.render(); this.canvas.render();
} }
@@ -911,7 +909,7 @@ export class CanvasInteractions {
updateCanvasMove(worldCoords: Point): void { updateCanvasMove(worldCoords: Point): void {
const dx = worldCoords.x - this.interaction.dragStart.x; const dx = worldCoords.x - this.interaction.dragStart.x;
const dy = worldCoords.y - this.interaction.dragStart.y; const dy = worldCoords.y - this.interaction.dragStart.y;
// Po prostu przesuwamy outputAreaBounds // Po prostu przesuwamy outputAreaBounds
const bounds = this.canvas.outputAreaBounds; const bounds = this.canvas.outputAreaBounds;
this.interaction.canvasMoveRect = { this.interaction.canvasMoveRect = {
@@ -935,11 +933,11 @@ export class CanvasInteractions {
width: moveRect.width, width: moveRect.width,
height: moveRect.height height: moveRect.height
}; };
// Update mask canvas to ensure it covers the new output area position // Update mask canvas to ensure it covers the new output area position
this.canvas.maskTool.updateMaskCanvasForOutputArea(); this.canvas.maskTool.updateMaskCanvasForOutputArea();
} }
this.canvas.render(); this.canvas.render();
this.canvas.saveState(); this.canvas.saveState();
} }
@@ -949,13 +947,13 @@ export class CanvasInteractions {
const dy = e.clientY - this.interaction.panStart.y; const dy = e.clientY - this.interaction.panStart.y;
this.canvas.viewport.x -= dx / this.canvas.viewport.zoom; this.canvas.viewport.x -= dx / this.canvas.viewport.zoom;
this.canvas.viewport.y -= dy / this.canvas.viewport.zoom; this.canvas.viewport.y -= dy / this.canvas.viewport.zoom;
this.interaction.panStart = {x: e.clientX, y: e.clientY}; this.interaction.panStart = { x: e.clientX, y: e.clientY };
// Update stroke overlay if mask tool is drawing during pan // Update stroke overlay if mask tool is drawing during pan
if (this.canvas.maskTool.isDrawing) { if (this.canvas.maskTool.isDrawing) {
this.canvas.maskTool.handleViewportChange(); this.canvas.maskTool.handleViewportChange();
} }
this.canvas.render(); this.canvas.render();
this.canvas.onViewportChange?.(); this.canvas.onViewportChange?.();
} }
@@ -1018,7 +1016,7 @@ export class CanvasInteractions {
const o = this.interaction.transformOrigin; const o = this.interaction.transformOrigin;
if (!o) return; if (!o) return;
const handle = this.interaction.resizeHandle; const handle = this.interaction.resizeHandle;
const anchor = this.interaction.resizeAnchor; const anchor = this.interaction.resizeAnchor;
const rad = o.rotation * Math.PI / 180; const rad = o.rotation * Math.PI / 180;
@@ -1036,7 +1034,7 @@ export class CanvasInteractions {
// Determine sign based on handle // Determine sign based on handle
const signX = handle?.includes('e') ? 1 : (handle?.includes('w') ? -1 : 0); const signX = handle?.includes('e') ? 1 : (handle?.includes('w') ? -1 : 0);
const signY = handle?.includes('s') ? 1 : (handle?.includes('n') ? -1 : 0); const signY = handle?.includes('s') ? 1 : (handle?.includes('n') ? -1 : 0);
localVecX *= signX; localVecX *= signX;
localVecY *= signY; localVecY *= signY;
@@ -1046,13 +1044,13 @@ export class CanvasInteractions {
if (layer.cropMode && o.cropBounds && o.originalWidth && o.originalHeight) { if (layer.cropMode && o.cropBounds && o.originalWidth && o.originalHeight) {
// CROP MODE: Calculate delta based on mouse movement and apply to cropBounds. // CROP MODE: Calculate delta based on mouse movement and apply to cropBounds.
// Calculate mouse movement since drag start, in the layer's local coordinate system. // Calculate mouse movement since drag start, in the layer's local coordinate system.
const dragStartX_local = this.interaction.dragStart.x - (o.centerX ?? 0); const dragStartX_local = this.interaction.dragStart.x - (o.centerX ?? 0);
const dragStartY_local = this.interaction.dragStart.y - (o.centerY ?? 0); const dragStartY_local = this.interaction.dragStart.y - (o.centerY ?? 0);
const mouseX_local = mouseX - (o.centerX ?? 0); const mouseX_local = mouseX - (o.centerX ?? 0);
const mouseY_local = mouseY - (o.centerY ?? 0); const mouseY_local = mouseY - (o.centerY ?? 0);
// Rotate mouse delta into the layer's unrotated frame // Rotate mouse delta into the layer's unrotated frame
const deltaX_world = mouseX_local - dragStartX_local; const deltaX_world = mouseX_local - dragStartX_local;
const deltaY_world = mouseY_local - dragStartY_local; const deltaY_world = mouseY_local - dragStartY_local;
@@ -1065,20 +1063,20 @@ export class CanvasInteractions {
if (layer.flipV) { if (layer.flipV) {
mouseDeltaY_local *= -1; mouseDeltaY_local *= -1;
} }
// Convert the on-screen mouse delta to an image-space delta. // Convert the on-screen mouse delta to an image-space delta.
const screenToImageScaleX = o.originalWidth / o.width; const screenToImageScaleX = o.originalWidth / o.width;
const screenToImageScaleY = o.originalHeight / o.height; const screenToImageScaleY = o.originalHeight / o.height;
const delta_image_x = mouseDeltaX_local * screenToImageScaleX; const delta_image_x = mouseDeltaX_local * screenToImageScaleX;
const delta_image_y = mouseDeltaY_local * screenToImageScaleY; const delta_image_y = mouseDeltaY_local * screenToImageScaleY;
let newCropBounds = { ...o.cropBounds }; // Start with the bounds from the beginning of the drag let newCropBounds = { ...o.cropBounds }; // Start with the bounds from the beginning of the drag
// Apply the image-space delta to the appropriate edges of the crop bounds // Apply the image-space delta to the appropriate edges of the crop bounds
const isFlippedH = layer.flipH; const isFlippedH = layer.flipH;
const isFlippedV = layer.flipV; const isFlippedV = layer.flipV;
if (handle?.includes('w')) { if (handle?.includes('w')) {
if (isFlippedH) newCropBounds.width += delta_image_x; if (isFlippedH) newCropBounds.width += delta_image_x;
else { else {
@@ -1105,10 +1103,10 @@ export class CanvasInteractions {
newCropBounds.height -= delta_image_y; newCropBounds.height -= delta_image_y;
} else newCropBounds.height += delta_image_y; } else newCropBounds.height += delta_image_y;
} }
// Clamp crop bounds to stay within the original image and maintain minimum size // Clamp crop bounds to stay within the original image and maintain minimum size
if (newCropBounds.width < 1) { if (newCropBounds.width < 1) {
if (handle?.includes('w')) newCropBounds.x = o.cropBounds.x + o.cropBounds.width -1; if (handle?.includes('w')) newCropBounds.x = o.cropBounds.x + o.cropBounds.width - 1;
newCropBounds.width = 1; newCropBounds.width = 1;
} }
if (newCropBounds.height < 1) { if (newCropBounds.height < 1) {
@@ -1136,7 +1134,7 @@ export class CanvasInteractions {
// TRANSFORM MODE: Resize the layer's main transform frame // TRANSFORM MODE: Resize the layer's main transform frame
let newWidth = localVecX; let newWidth = localVecX;
let newHeight = localVecY; let newHeight = localVecY;
if (isShiftPressed) { if (isShiftPressed) {
const originalAspectRatio = o.width / o.height; const originalAspectRatio = o.width / o.height;
if (Math.abs(newWidth) > Math.abs(newHeight) * originalAspectRatio) { if (Math.abs(newWidth) > Math.abs(newHeight) * originalAspectRatio) {
@@ -1148,10 +1146,10 @@ export class CanvasInteractions {
if (newWidth < 10) newWidth = 10; if (newWidth < 10) newWidth = 10;
if (newHeight < 10) newHeight = 10; if (newHeight < 10) newHeight = 10;
layer.width = newWidth; layer.width = newWidth;
layer.height = newHeight; layer.height = newHeight;
// Update position to keep anchor point fixed // Update position to keep anchor point fixed
const deltaW = layer.width - o.width; const deltaW = layer.width - o.width;
const deltaH = layer.height - o.height; const deltaH = layer.height - o.height;
@@ -1217,7 +1215,7 @@ export class CanvasInteractions {
this.canvas.updateOutputAreaSize(newWidth, newHeight); this.canvas.updateOutputAreaSize(newWidth, newHeight);
} }
this.canvas.render(); this.canvas.render();
this.canvas.saveState(); this.canvas.saveState();
} }
@@ -1315,7 +1313,7 @@ export class CanvasInteractions {
// Store the original canvas size for extension calculations // Store the original canvas size for extension calculations
this.canvas.originalCanvasSize = { width: newWidth, height: newHeight }; this.canvas.originalCanvasSize = { width: newWidth, height: newHeight };
// Store the original position where custom shape was drawn for extension calculations // Store the original position where custom shape was drawn for extension calculations
this.canvas.originalOutputAreaPosition = { x: newX, y: newY }; this.canvas.originalOutputAreaPosition = { x: newX, y: newY };
@@ -1324,10 +1322,10 @@ export class CanvasInteractions {
const ext = this.canvas.outputAreaExtensions; const ext = this.canvas.outputAreaExtensions;
const extendedWidth = newWidth + ext.left + ext.right; const extendedWidth = newWidth + ext.left + ext.right;
const extendedHeight = newHeight + ext.top + ext.bottom; const extendedHeight = newHeight + ext.top + ext.bottom;
// Update canvas size with extensions // Update canvas size with extensions
this.canvas.updateOutputAreaSize(extendedWidth, extendedHeight, false); this.canvas.updateOutputAreaSize(extendedWidth, extendedHeight, false);
// Set outputAreaBounds accounting for extensions // Set outputAreaBounds accounting for extensions
this.canvas.outputAreaBounds = { this.canvas.outputAreaBounds = {
x: newX - ext.left, // Adjust position by left extension x: newX - ext.left, // Adjust position by left extension
@@ -1335,19 +1333,19 @@ export class CanvasInteractions {
width: extendedWidth, width: extendedWidth,
height: extendedHeight height: extendedHeight
}; };
log.info(`New custom shape with extensions: original(${newX}, ${newY}) extended(${newX - ext.left}, ${newY - ext.top}) size(${extendedWidth}x${extendedHeight})`); log.info(`New custom shape with extensions: original(${newX}, ${newY}) extended(${newX - ext.left}, ${newY - ext.top}) size(${extendedWidth}x${extendedHeight})`);
} else { } else {
// No extensions - use original size and position // No extensions - use original size and position
this.canvas.updateOutputAreaSize(newWidth, newHeight, false); this.canvas.updateOutputAreaSize(newWidth, newHeight, false);
this.canvas.outputAreaBounds = { this.canvas.outputAreaBounds = {
x: newX, x: newX,
y: newY, y: newY,
width: newWidth, width: newWidth,
height: newHeight height: newHeight
}; };
log.info(`New custom shape without extensions: position(${newX}, ${newY}) size(${newWidth}x${newHeight})`); log.info(`New custom shape without extensions: position(${newX}, ${newY}) size(${newWidth}x${newHeight})`);
} }
@@ -1383,7 +1381,7 @@ export class CanvasInteractions {
log.info("Paste event detected, checking clipboard preference"); log.info("Paste event detected, checking clipboard preference");
const preference = this.canvas.canvasLayers.clipboardPreference; const preference = this.canvas.canvasLayers.clipboardPreference;
if (preference === 'clipspace') { if (preference === 'clipspace') {
log.info("Clipboard preference is clipspace, delegating to ClipboardManager"); log.info("Clipboard preference is clipspace, delegating to ClipboardManager");
@@ -1427,7 +1425,7 @@ export class CanvasInteractions {
public activateOutputAreaTransform(): void { public activateOutputAreaTransform(): void {
// Clear any existing interaction state before starting transform // Clear any existing interaction state before starting transform
this.resetInteractionState(); this.resetInteractionState();
// Deactivate any active tools that might conflict // Deactivate any active tools that might conflict
if (this.canvas.shapeTool.isActive) { if (this.canvas.shapeTool.isActive) {
this.canvas.shapeTool.deactivate(); this.canvas.shapeTool.deactivate();
@@ -1435,10 +1433,10 @@ export class CanvasInteractions {
if (this.canvas.maskTool.isActive) { if (this.canvas.maskTool.isActive) {
this.canvas.maskTool.deactivate(); this.canvas.maskTool.deactivate();
} }
// Clear selection to avoid confusion // Clear selection to avoid confusion
this.canvas.canvasSelection.updateSelection([]); this.canvas.canvasSelection.updateSelection([]);
// Set transform mode // Set transform mode
this.interaction.mode = 'transformingOutputArea'; this.interaction.mode = 'transformingOutputArea';
this.canvas.render(); this.canvas.render();
@@ -1447,7 +1445,7 @@ export class CanvasInteractions {
private getOutputAreaHandle(worldCoords: Point): string | null { private getOutputAreaHandle(worldCoords: Point): string | null {
const bounds = this.canvas.outputAreaBounds; const bounds = this.canvas.outputAreaBounds;
const threshold = 10 / this.canvas.viewport.zoom; const threshold = 10 / this.canvas.viewport.zoom;
// Define handle positions // Define handle positions
const handles = { const handles = {
'nw': { x: bounds.x, y: bounds.y }, 'nw': { x: bounds.x, y: bounds.y },
@@ -1474,7 +1472,7 @@ export class CanvasInteractions {
private startOutputAreaTransform(handle: string, worldCoords: Point): void { private startOutputAreaTransform(handle: string, worldCoords: Point): void {
this.interaction.outputAreaTransformHandle = handle; this.interaction.outputAreaTransformHandle = handle;
this.interaction.dragStart = { ...worldCoords }; this.interaction.dragStart = { ...worldCoords };
const bounds = this.canvas.outputAreaBounds; const bounds = this.canvas.outputAreaBounds;
this.interaction.transformOrigin = { this.interaction.transformOrigin = {
x: bounds.x, x: bounds.x,
@@ -1497,17 +1495,17 @@ export class CanvasInteractions {
'sw': { x: bounds.x + bounds.width, y: bounds.y }, 'sw': { x: bounds.x + bounds.width, y: bounds.y },
'w': { x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2 }, 'w': { x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2 },
}; };
this.interaction.outputAreaTransformAnchor = anchorMap[handle]; this.interaction.outputAreaTransformAnchor = anchorMap[handle];
} }
private resizeOutputAreaFromHandle(worldCoords: Point, isShiftPressed: boolean): void { private resizeOutputAreaFromHandle(worldCoords: Point, isShiftPressed: boolean): void {
const o = this.interaction.transformOrigin; const o = this.interaction.transformOrigin;
if (!o) return; if (!o) return;
const handle = this.interaction.outputAreaTransformHandle; const handle = this.interaction.outputAreaTransformHandle;
const anchor = this.interaction.outputAreaTransformAnchor; const anchor = this.interaction.outputAreaTransformAnchor;
let newX = o.x; let newX = o.x;
let newY = o.y; let newY = o.y;
let newWidth = o.width; let newWidth = o.width;
@@ -1578,7 +1576,7 @@ export class CanvasInteractions {
private updateOutputAreaTransformCursor(worldCoords: Point): void { private updateOutputAreaTransformCursor(worldCoords: Point): void {
const handle = this.getOutputAreaHandle(worldCoords); const handle = this.getOutputAreaHandle(worldCoords);
if (handle) { if (handle) {
const cursorMap: { [key: string]: string } = { const cursorMap: { [key: string]: string } = {
'n': 'ns-resize', 's': 'ns-resize', 'n': 'ns-resize', 's': 'ns-resize',
@@ -1594,16 +1592,16 @@ export class CanvasInteractions {
private finalizeOutputAreaTransform(): void { private finalizeOutputAreaTransform(): void {
const bounds = this.canvas.outputAreaBounds; const bounds = this.canvas.outputAreaBounds;
// Update canvas size and mask tool // Update canvas size and mask tool
this.canvas.updateOutputAreaSize(bounds.width, bounds.height); this.canvas.updateOutputAreaSize(bounds.width, bounds.height);
// Update mask canvas for new output area // Update mask canvas for new output area
this.canvas.maskTool.updateMaskCanvasForOutputArea(); this.canvas.maskTool.updateMaskCanvasForOutputArea();
// Save state // Save state
this.canvas.saveState(); this.canvas.saveState();
// Reset transform handle but keep transform mode active // Reset transform handle but keep transform mode active
this.interaction.outputAreaTransformHandle = null; this.interaction.outputAreaTransformHandle = null;
} }