mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
Refactor CanvasInteractions for code reuse and clarity
Introduces helper methods to reduce code duplication and improve readability in CanvasInteractions. Mouse coordinate extraction, event prevention, zoom operations, drag-and-drop styling, and layer wheel transformations are now handled by dedicated methods. This refactor centralizes logic, making the codebase easier to maintain and extend.
This commit is contained in:
@@ -25,6 +25,43 @@ export class CanvasInteractions {
|
|||||||
};
|
};
|
||||||
this.originalLayerPositions = new Map();
|
this.originalLayerPositions = new Map();
|
||||||
}
|
}
|
||||||
|
// Helper functions to eliminate code duplication
|
||||||
|
getMouseCoordinates(e) {
|
||||||
|
return {
|
||||||
|
world: this.canvas.getMouseWorldCoordinates(e),
|
||||||
|
view: this.canvas.getMouseViewCoordinates(e)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
preventEventDefaults(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
performZoomOperation(worldCoords, zoomFactor) {
|
||||||
|
const rect = this.canvas.canvas.getBoundingClientRect();
|
||||||
|
const mouseBufferX = (worldCoords.x - this.canvas.viewport.x) * 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));
|
||||||
|
this.canvas.viewport.zoom = newZoom;
|
||||||
|
this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom);
|
||||||
|
this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom);
|
||||||
|
}
|
||||||
|
renderAndSave(shouldSave = false) {
|
||||||
|
this.canvas.render();
|
||||||
|
if (shouldSave) {
|
||||||
|
this.canvas.saveState();
|
||||||
|
this.canvas.canvasState.saveStateToDB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setDragDropStyling(active) {
|
||||||
|
if (active) {
|
||||||
|
this.canvas.canvas.style.backgroundColor = 'rgba(45, 90, 160, 0.1)';
|
||||||
|
this.canvas.canvas.style.border = '2px dashed #2d5aa0';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.canvas.canvas.style.backgroundColor = '';
|
||||||
|
this.canvas.canvas.style.border = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
this.canvas.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
|
this.canvas.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
|
||||||
this.canvas.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
this.canvas.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
||||||
@@ -62,21 +99,20 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
handleMouseDown(e) {
|
handleMouseDown(e) {
|
||||||
this.canvas.canvas.focus();
|
this.canvas.canvas.focus();
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
|
||||||
if (this.interaction.mode === 'drawingMask') {
|
if (this.interaction.mode === 'drawingMask') {
|
||||||
this.canvas.maskTool.handleMouseDown(worldCoords, viewCoords);
|
this.canvas.maskTool.handleMouseDown(coords.world, coords.view);
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.canvas.shapeTool.isActive) {
|
if (this.canvas.shapeTool.isActive) {
|
||||||
this.canvas.shapeTool.addPoint(worldCoords);
|
this.canvas.shapeTool.addPoint(coords.world);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// --- Ostateczna, poprawna kolejność sprawdzania ---
|
// --- Ostateczna, poprawna kolejność sprawdzania ---
|
||||||
// 1. Akcje globalne z modyfikatorami (mają najwyższy priorytet)
|
// 1. Akcje globalne z modyfikatorami (mają najwyższy priorytet)
|
||||||
if (e.shiftKey && e.ctrlKey) {
|
if (e.shiftKey && e.ctrlKey) {
|
||||||
this.startCanvasMove(worldCoords);
|
this.startCanvasMove(coords.world);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
@@ -85,16 +121,15 @@ export class CanvasInteractions {
|
|||||||
this.canvas.outputAreaShape = null;
|
this.canvas.outputAreaShape = null;
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
}
|
}
|
||||||
this.startCanvasResize(worldCoords);
|
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
|
||||||
e.preventDefault(); // Always prevent right-click default behavior
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // Stop event propagation
|
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(coords.world.x, coords.world.y);
|
||||||
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y);
|
|
||||||
if (clickedLayerResult && this.canvas.canvasSelection.selectedLayers.includes(clickedLayerResult.layer)) {
|
if (clickedLayerResult && this.canvas.canvasSelection.selectedLayers.includes(clickedLayerResult.layer)) {
|
||||||
this.canvas.canvasLayers.showBlendModeMenu(viewCoords.x, viewCoords.y);
|
this.canvas.canvasLayers.showBlendModeMenu(coords.view.x, coords.view.y);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -103,27 +138,26 @@ export class CanvasInteractions {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 3. Interakcje z elementami na płótnie (lewy przycisk)
|
// 3. Interakcje z elementami na płótnie (lewy przycisk)
|
||||||
const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(worldCoords.x, worldCoords.y);
|
const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(coords.world.x, coords.world.y);
|
||||||
if (transformTarget) {
|
if (transformTarget) {
|
||||||
this.startLayerTransform(transformTarget.layer, transformTarget.handle, worldCoords);
|
this.startLayerTransform(transformTarget.layer, transformTarget.handle, coords.world);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y);
|
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(coords.world.x, coords.world.y);
|
||||||
if (clickedLayerResult) {
|
if (clickedLayerResult) {
|
||||||
this.prepareForDrag(clickedLayerResult.layer, worldCoords);
|
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.startPanningOrClearSelection(e);
|
this.startPanningOrClearSelection(e);
|
||||||
}
|
}
|
||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
this.canvas.lastMousePosition = coords.world; // Zawsze aktualizuj ostatnią pozycję myszy
|
||||||
this.canvas.lastMousePosition = worldCoords; // 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 = worldCoords.x - this.interaction.dragStart.x;
|
const dx = coords.world.x - this.interaction.dragStart.x;
|
||||||
const dy = worldCoords.y - this.interaction.dragStart.y;
|
const dy = coords.world.y - this.interaction.dragStart.y;
|
||||||
if (Math.sqrt(dx * dx + dy * dy) > 3) { // Próg 3 pikseli
|
if (Math.sqrt(dx * dx + dy * dy) > 3) { // Próg 3 pikseli
|
||||||
this.interaction.mode = 'dragging';
|
this.interaction.mode = 'dragging';
|
||||||
this.originalLayerPositions.clear();
|
this.originalLayerPositions.clear();
|
||||||
@@ -134,37 +168,36 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
switch (this.interaction.mode) {
|
switch (this.interaction.mode) {
|
||||||
case 'drawingMask':
|
case 'drawingMask':
|
||||||
this.canvas.maskTool.handleMouseMove(worldCoords, viewCoords);
|
this.canvas.maskTool.handleMouseMove(coords.world, coords.view);
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
break;
|
break;
|
||||||
case 'panning':
|
case 'panning':
|
||||||
this.panViewport(e);
|
this.panViewport(e);
|
||||||
break;
|
break;
|
||||||
case 'dragging':
|
case 'dragging':
|
||||||
this.dragLayers(worldCoords);
|
this.dragLayers(coords.world);
|
||||||
break;
|
break;
|
||||||
case 'resizing':
|
case 'resizing':
|
||||||
this.resizeLayerFromHandle(worldCoords, e.shiftKey);
|
this.resizeLayerFromHandle(coords.world, e.shiftKey);
|
||||||
break;
|
break;
|
||||||
case 'rotating':
|
case 'rotating':
|
||||||
this.rotateLayerFromHandle(worldCoords, e.shiftKey);
|
this.rotateLayerFromHandle(coords.world, e.shiftKey);
|
||||||
break;
|
break;
|
||||||
case 'resizingCanvas':
|
case 'resizingCanvas':
|
||||||
this.updateCanvasResize(worldCoords);
|
this.updateCanvasResize(coords.world);
|
||||||
break;
|
break;
|
||||||
case 'movingCanvas':
|
case 'movingCanvas':
|
||||||
this.updateCanvasMove(worldCoords);
|
this.updateCanvasMove(coords.world);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.updateCursor(worldCoords);
|
this.updateCursor(coords.world);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
|
||||||
if (this.interaction.mode === 'drawingMask') {
|
if (this.interaction.mode === 'drawingMask') {
|
||||||
this.canvas.maskTool.handleMouseUp(viewCoords);
|
this.canvas.maskTool.handleMouseUp(coords.view);
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -176,36 +209,38 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
// Log layer positions when dragging ends
|
// Log layer positions when dragging ends
|
||||||
if (this.interaction.mode === 'dragging' && this.canvas.canvasSelection.selectedLayers.length > 0) {
|
if (this.interaction.mode === 'dragging' && this.canvas.canvasSelection.selectedLayers.length > 0) {
|
||||||
const bounds = this.canvas.outputAreaBounds;
|
this.logDragCompletion(coords);
|
||||||
log.info("=== LAYER DRAG COMPLETED ===");
|
|
||||||
log.info(`Mouse position: world(${worldCoords.x.toFixed(1)}, ${worldCoords.y.toFixed(1)}) view(${viewCoords.x.toFixed(1)}, ${viewCoords.y.toFixed(1)})`);
|
|
||||||
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)}`);
|
|
||||||
this.canvas.canvasSelection.selectedLayers.forEach((layer, index) => {
|
|
||||||
const relativeToOutput = {
|
|
||||||
x: layer.x - bounds.x,
|
|
||||||
y: layer.y - bounds.y
|
|
||||||
};
|
|
||||||
log.info(`Layer ${index + 1} "${layer.name}": world(${layer.x.toFixed(1)}, ${layer.y.toFixed(1)}) relative_to_output(${relativeToOutput.x.toFixed(1)}, ${relativeToOutput.y.toFixed(1)}) size(${layer.width.toFixed(1)}x${layer.height.toFixed(1)})`);
|
|
||||||
});
|
|
||||||
log.info("=== END LAYER DRAG ===");
|
|
||||||
}
|
}
|
||||||
// Zapisz stan tylko, jeśli faktycznie doszło do zmiany (przeciąganie, transformacja, duplikacja)
|
// Zapisz stan tylko, jeśli faktycznie doszło do zmiany (przeciąganie, transformacja, duplikacja)
|
||||||
const stateChangingInteraction = ['dragging', 'resizing', 'rotating'].includes(this.interaction.mode);
|
const stateChangingInteraction = ['dragging', 'resizing', 'rotating'].includes(this.interaction.mode);
|
||||||
const duplicatedInDrag = this.interaction.hasClonedInDrag;
|
const duplicatedInDrag = this.interaction.hasClonedInDrag;
|
||||||
if (stateChangingInteraction || duplicatedInDrag) {
|
if (stateChangingInteraction || duplicatedInDrag) {
|
||||||
this.canvas.saveState();
|
this.renderAndSave(true);
|
||||||
this.canvas.canvasState.saveStateToDB();
|
|
||||||
}
|
}
|
||||||
this.resetInteractionState();
|
this.resetInteractionState();
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
}
|
}
|
||||||
|
logDragCompletion(coords) {
|
||||||
|
const bounds = this.canvas.outputAreaBounds;
|
||||||
|
log.info("=== LAYER DRAG COMPLETED ===");
|
||||||
|
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(`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, index) => {
|
||||||
|
const relativeToOutput = {
|
||||||
|
x: layer.x - bounds.x,
|
||||||
|
y: layer.y - bounds.y
|
||||||
|
};
|
||||||
|
log.info(`Layer ${index + 1} "${layer.name}": world(${layer.x.toFixed(1)}, ${layer.y.toFixed(1)}) relative_to_output(${relativeToOutput.x.toFixed(1)}, ${relativeToOutput.y.toFixed(1)}) size(${layer.width.toFixed(1)}x${layer.height.toFixed(1)})`);
|
||||||
|
});
|
||||||
|
log.info("=== END LAYER DRAG ===");
|
||||||
|
}
|
||||||
handleMouseLeave(e) {
|
handleMouseLeave(e) {
|
||||||
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
if (this.canvas.maskTool.isActive) {
|
if (this.canvas.maskTool.isActive) {
|
||||||
this.canvas.maskTool.handleMouseLeave();
|
this.canvas.maskTool.handleMouseLeave();
|
||||||
if (this.canvas.maskTool.isDrawing) {
|
if (this.canvas.maskTool.isDrawing) {
|
||||||
this.canvas.maskTool.handleMouseUp(viewCoords);
|
this.canvas.maskTool.handleMouseUp(coords.view);
|
||||||
}
|
}
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
@@ -230,99 +265,95 @@ export class CanvasInteractions {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
handleWheel(e) {
|
handleWheel(e) {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
if (this.canvas.maskTool.isActive) {
|
const coords = this.getMouseCoordinates(e);
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
if (this.canvas.maskTool.isActive || this.canvas.canvasSelection.selectedLayers.length === 0) {
|
||||||
const rect = this.canvas.canvas.getBoundingClientRect();
|
// Zoom operation for mask tool or when no layers selected
|
||||||
const mouseBufferX = (e.clientX - rect.left) * (this.canvas.offscreenCanvas.width / rect.width);
|
|
||||||
const mouseBufferY = (e.clientY - rect.top) * (this.canvas.offscreenCanvas.height / rect.height);
|
|
||||||
const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
|
const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
|
||||||
const newZoom = this.canvas.viewport.zoom * zoomFactor;
|
this.performZoomOperation(coords.world, zoomFactor);
|
||||||
this.canvas.viewport.zoom = Math.max(0.1, Math.min(10, newZoom));
|
|
||||||
this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom);
|
|
||||||
this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom);
|
|
||||||
}
|
|
||||||
else if (this.canvas.canvasSelection.selectedLayers.length > 0) {
|
|
||||||
const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1);
|
|
||||||
const direction = e.deltaY < 0 ? 1 : -1; // 1 = up/right, -1 = down/left
|
|
||||||
this.canvas.canvasSelection.selectedLayers.forEach((layer) => {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
// Nowy skrót: Shift + Ctrl + Kółko do przyciągania do absolutnych wartości
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
const snapAngle = 5;
|
|
||||||
if (direction > 0) { // Obrót w górę/prawo
|
|
||||||
layer.rotation = Math.ceil((layer.rotation + 0.1) / snapAngle) * snapAngle;
|
|
||||||
}
|
|
||||||
else { // Obrót w dół/lewo
|
|
||||||
layer.rotation = Math.floor((layer.rotation - 0.1) / snapAngle) * snapAngle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Stara funkcjonalność: Shift + Kółko obraca o stały krok
|
|
||||||
layer.rotation += rotationStep;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const oldWidth = layer.width;
|
|
||||||
const oldHeight = layer.height;
|
|
||||||
let scaleFactor;
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
const direction = e.deltaY > 0 ? -1 : 1;
|
|
||||||
const baseDimension = Math.max(layer.width, layer.height);
|
|
||||||
const newBaseDimension = baseDimension + direction;
|
|
||||||
if (newBaseDimension < 10) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scaleFactor = newBaseDimension / baseDimension;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const gridSize = 64;
|
|
||||||
const direction = e.deltaY > 0 ? -1 : 1;
|
|
||||||
let targetHeight;
|
|
||||||
if (direction > 0) {
|
|
||||||
targetHeight = (Math.floor(oldHeight / gridSize) + 1) * gridSize;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
targetHeight = (Math.ceil(oldHeight / gridSize) - 1) * gridSize;
|
|
||||||
}
|
|
||||||
if (targetHeight < gridSize / 2) {
|
|
||||||
targetHeight = gridSize / 2;
|
|
||||||
}
|
|
||||||
if (Math.abs(oldHeight - targetHeight) < 1) {
|
|
||||||
if (direction > 0)
|
|
||||||
targetHeight += gridSize;
|
|
||||||
else
|
|
||||||
targetHeight -= gridSize;
|
|
||||||
if (targetHeight < gridSize / 2)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scaleFactor = targetHeight / oldHeight;
|
|
||||||
}
|
|
||||||
if (scaleFactor && isFinite(scaleFactor)) {
|
|
||||||
layer.width *= scaleFactor;
|
|
||||||
layer.height *= scaleFactor;
|
|
||||||
layer.x += (oldWidth - layer.width) / 2;
|
|
||||||
layer.y += (oldHeight - layer.height) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
// Layer transformation when layers are selected
|
||||||
const rect = this.canvas.canvas.getBoundingClientRect();
|
this.handleLayerWheelTransformation(e);
|
||||||
const mouseBufferX = (e.clientX - rect.left) * (this.canvas.offscreenCanvas.width / rect.width);
|
|
||||||
const mouseBufferY = (e.clientY - rect.top) * (this.canvas.offscreenCanvas.height / rect.height);
|
|
||||||
const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
|
|
||||||
const newZoom = this.canvas.viewport.zoom * zoomFactor;
|
|
||||||
this.canvas.viewport.zoom = Math.max(0.1, Math.min(10, newZoom));
|
|
||||||
this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom);
|
|
||||||
this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom);
|
|
||||||
}
|
}
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
if (!this.canvas.maskTool.isActive) {
|
if (!this.canvas.maskTool.isActive) {
|
||||||
this.canvas.requestSaveState(); // Użyj opóźnionego zapisu
|
this.canvas.requestSaveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
handleLayerWheelTransformation(e) {
|
||||||
|
const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1);
|
||||||
|
const direction = e.deltaY < 0 ? 1 : -1;
|
||||||
|
this.canvas.canvasSelection.selectedLayers.forEach((layer) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this.handleLayerRotation(layer, e.ctrlKey, direction, rotationStep);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.handleLayerScaling(layer, e.ctrlKey, e.deltaY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleLayerRotation(layer, isCtrlPressed, direction, rotationStep) {
|
||||||
|
if (isCtrlPressed) {
|
||||||
|
// Snap to absolute values
|
||||||
|
const snapAngle = 5;
|
||||||
|
if (direction > 0) {
|
||||||
|
layer.rotation = Math.ceil((layer.rotation + 0.1) / snapAngle) * snapAngle;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
layer.rotation = Math.floor((layer.rotation - 0.1) / snapAngle) * snapAngle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Fixed step rotation
|
||||||
|
layer.rotation += rotationStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleLayerScaling(layer, isCtrlPressed, deltaY) {
|
||||||
|
const oldWidth = layer.width;
|
||||||
|
const oldHeight = layer.height;
|
||||||
|
let scaleFactor;
|
||||||
|
if (isCtrlPressed) {
|
||||||
|
const direction = deltaY > 0 ? -1 : 1;
|
||||||
|
const baseDimension = Math.max(layer.width, layer.height);
|
||||||
|
const newBaseDimension = baseDimension + direction;
|
||||||
|
if (newBaseDimension < 10)
|
||||||
|
return;
|
||||||
|
scaleFactor = newBaseDimension / baseDimension;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
scaleFactor = this.calculateGridBasedScaling(oldHeight, deltaY);
|
||||||
|
}
|
||||||
|
if (scaleFactor && isFinite(scaleFactor)) {
|
||||||
|
layer.width *= scaleFactor;
|
||||||
|
layer.height *= scaleFactor;
|
||||||
|
layer.x += (oldWidth - layer.width) / 2;
|
||||||
|
layer.y += (oldHeight - layer.height) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calculateGridBasedScaling(oldHeight, deltaY) {
|
||||||
|
const gridSize = 64;
|
||||||
|
const direction = deltaY > 0 ? -1 : 1;
|
||||||
|
let targetHeight;
|
||||||
|
if (direction > 0) {
|
||||||
|
targetHeight = (Math.floor(oldHeight / gridSize) + 1) * gridSize;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
targetHeight = (Math.ceil(oldHeight / gridSize) - 1) * gridSize;
|
||||||
|
}
|
||||||
|
if (targetHeight < gridSize / 2) {
|
||||||
|
targetHeight = gridSize / 2;
|
||||||
|
}
|
||||||
|
if (Math.abs(oldHeight - targetHeight) < 1) {
|
||||||
|
if (direction > 0)
|
||||||
|
targetHeight += gridSize;
|
||||||
|
else
|
||||||
|
targetHeight -= gridSize;
|
||||||
|
if (targetHeight < gridSize / 2)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return targetHeight / oldHeight;
|
||||||
|
}
|
||||||
handleKeyDown(e) {
|
handleKeyDown(e) {
|
||||||
if (e.key === 'Control')
|
if (e.key === 'Control')
|
||||||
this.interaction.isCtrlPressed = true;
|
this.interaction.isCtrlPressed = true;
|
||||||
@@ -728,40 +759,33 @@ export class CanvasInteractions {
|
|||||||
this.canvas.saveState();
|
this.canvas.saveState();
|
||||||
}
|
}
|
||||||
handleDragOver(e) {
|
handleDragOver(e) {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
|
||||||
if (e.dataTransfer)
|
if (e.dataTransfer)
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
}
|
}
|
||||||
handleDragEnter(e) {
|
handleDragEnter(e) {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
this.setDragDropStyling(true);
|
||||||
this.canvas.canvas.style.backgroundColor = 'rgba(45, 90, 160, 0.1)';
|
|
||||||
this.canvas.canvas.style.border = '2px dashed #2d5aa0';
|
|
||||||
}
|
}
|
||||||
handleDragLeave(e) {
|
handleDragLeave(e) {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
|
||||||
if (!this.canvas.canvas.contains(e.relatedTarget)) {
|
if (!this.canvas.canvas.contains(e.relatedTarget)) {
|
||||||
this.canvas.canvas.style.backgroundColor = '';
|
this.setDragDropStyling(false);
|
||||||
this.canvas.canvas.style.border = '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async handleDrop(e) {
|
async handleDrop(e) {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // CRITICAL: Prevent ComfyUI from handling this event and loading workflow
|
|
||||||
log.info("Canvas drag & drop event intercepted - preventing ComfyUI workflow loading");
|
log.info("Canvas drag & drop event intercepted - preventing ComfyUI workflow loading");
|
||||||
this.canvas.canvas.style.backgroundColor = '';
|
this.setDragDropStyling(false);
|
||||||
this.canvas.canvas.style.border = '';
|
|
||||||
if (!e.dataTransfer)
|
if (!e.dataTransfer)
|
||||||
return;
|
return;
|
||||||
const files = Array.from(e.dataTransfer.files);
|
const files = Array.from(e.dataTransfer.files);
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
log.info(`Dropped ${files.length} file(s) onto canvas at position (${worldCoords.x}, ${worldCoords.y})`);
|
log.info(`Dropped ${files.length} file(s) onto canvas at position (${coords.world.x}, ${coords.world.y})`);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.type.startsWith('image/')) {
|
if (file.type.startsWith('image/')) {
|
||||||
try {
|
try {
|
||||||
await this.loadDroppedImageFile(file, worldCoords);
|
await this.loadDroppedImageFile(file, coords.world);
|
||||||
log.info(`Successfully loaded dropped image: ${file.name}`);
|
log.info(`Successfully loaded dropped image: ${file.name}`);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
@@ -365,7 +365,6 @@ export class CanvasLayers {
|
|||||||
// Check if we need to apply blend area effect
|
// Check if we need to apply blend area effect
|
||||||
const blendArea = layer.blendArea ?? 0;
|
const blendArea = layer.blendArea ?? 0;
|
||||||
const needsBlendAreaEffect = blendArea > 0;
|
const needsBlendAreaEffect = blendArea > 0;
|
||||||
log.info(`Drawing layer ${layer.id}: blendArea=${blendArea}, needsBlendAreaEffect=${needsBlendAreaEffect}`);
|
|
||||||
if (needsBlendAreaEffect) {
|
if (needsBlendAreaEffect) {
|
||||||
log.info(`Applying blend area effect for layer ${layer.id}`);
|
log.info(`Applying blend area effect for layer ${layer.id}`);
|
||||||
// Get or create distance field mask
|
// Get or create distance field mask
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createModuleLogger } from "./utils/LoggerUtils.js";
|
|||||||
const log = createModuleLogger('Mask_tool');
|
const log = createModuleLogger('Mask_tool');
|
||||||
export class MaskTool {
|
export class MaskTool {
|
||||||
constructor(canvasInstance, callbacks = {}) {
|
constructor(canvasInstance, callbacks = {}) {
|
||||||
|
this.ACTIVE_MASK_UPDATE_DELAY = 16; // ~60fps throttling
|
||||||
this.canvasInstance = canvasInstance;
|
this.canvasInstance = canvasInstance;
|
||||||
this.mainCanvas = canvasInstance.canvas;
|
this.mainCanvas = canvasInstance.canvas;
|
||||||
this.onStateChange = callbacks.onStateChange || null;
|
this.onStateChange = callbacks.onStateChange || null;
|
||||||
@@ -42,6 +43,9 @@ export class MaskTool {
|
|||||||
this.shapePreviewCtx = shapePreviewCtx;
|
this.shapePreviewCtx = shapePreviewCtx;
|
||||||
this.shapePreviewVisible = false;
|
this.shapePreviewVisible = false;
|
||||||
this.isPreviewMode = false;
|
this.isPreviewMode = false;
|
||||||
|
// Initialize performance optimization flags
|
||||||
|
this.activeMaskNeedsUpdate = false;
|
||||||
|
this.activeMaskUpdateTimeout = null;
|
||||||
this.initMaskCanvas();
|
this.initMaskCanvas();
|
||||||
}
|
}
|
||||||
// Temporary compatibility getters - will be replaced with chunked system
|
// Temporary compatibility getters - will be replaced with chunked system
|
||||||
@@ -120,7 +124,6 @@ export class MaskTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info(`Updated active mask canvas to show ALL chunks: ${canvasWidth}x${canvasHeight} at (${canvasLeft}, ${canvasTop}), chunks: ${chunkBounds.minX},${chunkBounds.minY} to ${chunkBounds.maxX},${chunkBounds.maxY}`);
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Finds the bounds of all chunks that contain mask data
|
* Finds the bounds of all chunks that contain mask data
|
||||||
@@ -338,8 +341,8 @@ export class MaskTool {
|
|||||||
return true; // For now, always draw - more precise intersection can be added later
|
return true; // For now, always draw - more precise intersection can be added later
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Updates active canvas when drawing affects chunks
|
* Updates active canvas when drawing affects chunks with throttling to prevent lag
|
||||||
* Now always updates when new chunks are created to ensure immediate visibility
|
* Uses throttling to limit updates to ~60fps during drawing operations
|
||||||
*/
|
*/
|
||||||
updateActiveCanvasIfNeeded(startWorld, endWorld) {
|
updateActiveCanvasIfNeeded(startWorld, endWorld) {
|
||||||
// Calculate which chunks were affected by this drawing operation
|
// Calculate which chunks were affected by this drawing operation
|
||||||
@@ -364,16 +367,37 @@ export class MaskTool {
|
|||||||
affectedChunkMaxY > this.activeChunkBounds.maxY;
|
affectedChunkMaxY > this.activeChunkBounds.maxY;
|
||||||
}
|
}
|
||||||
if (drewOnNewChunks) {
|
if (drewOnNewChunks) {
|
||||||
// Drawing extended beyond current active bounds - do full update to include new chunks
|
// Drawing extended beyond current active bounds - immediate update required
|
||||||
this.updateActiveMaskCanvas();
|
this.updateActiveMaskCanvas();
|
||||||
log.debug("Drew on new chunks - performed full active canvas update");
|
log.debug("Drew on new chunks - performed immediate full active canvas update");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Drawing within existing bounds - do partial update for performance
|
// Drawing within existing bounds - use throttled update for performance
|
||||||
this.updateActiveCanvasPartial(affectedChunkMinX, affectedChunkMinY, affectedChunkMaxX, affectedChunkMaxY);
|
this.scheduleThrottledActiveMaskUpdate(affectedChunkMinX, affectedChunkMinY, affectedChunkMaxX, affectedChunkMaxY);
|
||||||
log.debug("Drew within existing bounds - performed partial update");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Schedules a throttled update of the active mask canvas to prevent excessive redraws
|
||||||
|
* Only updates at most once per ACTIVE_MASK_UPDATE_DELAY milliseconds
|
||||||
|
*/
|
||||||
|
scheduleThrottledActiveMaskUpdate(chunkMinX, chunkMinY, chunkMaxX, chunkMaxY) {
|
||||||
|
// Mark that an update is needed
|
||||||
|
this.activeMaskNeedsUpdate = true;
|
||||||
|
// If there's already a pending update, don't schedule another one
|
||||||
|
if (this.activeMaskUpdateTimeout !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Schedule the update with throttling
|
||||||
|
this.activeMaskUpdateTimeout = window.setTimeout(() => {
|
||||||
|
if (this.activeMaskNeedsUpdate) {
|
||||||
|
// Perform partial update for the affected chunks
|
||||||
|
this.updateActiveCanvasPartial(chunkMinX, chunkMinY, chunkMaxX, chunkMaxY);
|
||||||
|
this.activeMaskNeedsUpdate = false;
|
||||||
|
log.debug("Performed throttled partial active canvas update");
|
||||||
|
}
|
||||||
|
this.activeMaskUpdateTimeout = null;
|
||||||
|
}, this.ACTIVE_MASK_UPDATE_DELAY);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Partially updates the active canvas by redrawing only specific chunks
|
* Partially updates the active canvas by redrawing only specific chunks
|
||||||
* Much faster than full recomposition during drawing
|
* Much faster than full recomposition during drawing
|
||||||
@@ -773,9 +797,12 @@ export class MaskTool {
|
|||||||
log.info("Cleared all mask data from all chunks");
|
log.info("Cleared all mask data from all chunks");
|
||||||
}
|
}
|
||||||
getMask() {
|
getMask() {
|
||||||
// Always return the current active mask canvas which shows all chunks
|
// Return the current active mask canvas which shows all chunks
|
||||||
// Make sure it's up to date before returning
|
// Only update if there are pending changes to avoid unnecessary redraws
|
||||||
this.updateActiveMaskCanvas();
|
if (this.activeMaskNeedsUpdate) {
|
||||||
|
this.updateActiveMaskCanvas();
|
||||||
|
this.activeMaskNeedsUpdate = false;
|
||||||
|
}
|
||||||
return this.activeMaskCanvas;
|
return this.activeMaskCanvas;
|
||||||
}
|
}
|
||||||
resize(width, height) {
|
resize(width, height) {
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import type { Layer, Point } from './types';
|
|||||||
|
|
||||||
const log = createModuleLogger('CanvasInteractions');
|
const log = createModuleLogger('CanvasInteractions');
|
||||||
|
|
||||||
|
interface MouseCoordinates {
|
||||||
|
world: Point;
|
||||||
|
view: Point;
|
||||||
|
}
|
||||||
|
|
||||||
interface InteractionState {
|
interface InteractionState {
|
||||||
mode: 'none' | 'panning' | 'dragging' | 'resizing' | 'rotating' | 'drawingMask' | 'resizingCanvas' | 'movingCanvas' | 'potential-drag' | 'drawingShape';
|
mode: 'none' | 'panning' | 'dragging' | 'resizing' | 'rotating' | 'drawingMask' | 'resizingCanvas' | 'movingCanvas' | 'potential-drag' | 'drawingShape';
|
||||||
panStart: Point;
|
panStart: Point;
|
||||||
@@ -54,6 +59,50 @@ export class CanvasInteractions {
|
|||||||
this.originalLayerPositions = new Map();
|
this.originalLayerPositions = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper functions to eliminate code duplication
|
||||||
|
|
||||||
|
private getMouseCoordinates(e: MouseEvent | WheelEvent): MouseCoordinates {
|
||||||
|
return {
|
||||||
|
world: this.canvas.getMouseWorldCoordinates(e),
|
||||||
|
view: this.canvas.getMouseViewCoordinates(e)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private preventEventDefaults(e: Event): void {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private performZoomOperation(worldCoords: Point, zoomFactor: number): void {
|
||||||
|
const rect = this.canvas.canvas.getBoundingClientRect();
|
||||||
|
const mouseBufferX = (worldCoords.x - this.canvas.viewport.x) * 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));
|
||||||
|
|
||||||
|
this.canvas.viewport.zoom = newZoom;
|
||||||
|
this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom);
|
||||||
|
this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderAndSave(shouldSave: boolean = false): void {
|
||||||
|
this.canvas.render();
|
||||||
|
if (shouldSave) {
|
||||||
|
this.canvas.saveState();
|
||||||
|
this.canvas.canvasState.saveStateToDB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setDragDropStyling(active: boolean): void {
|
||||||
|
if (active) {
|
||||||
|
this.canvas.canvas.style.backgroundColor = 'rgba(45, 90, 160, 0.1)';
|
||||||
|
this.canvas.canvas.style.border = '2px dashed #2d5aa0';
|
||||||
|
} else {
|
||||||
|
this.canvas.canvas.style.backgroundColor = '';
|
||||||
|
this.canvas.canvas.style.border = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupEventListeners(): void {
|
setupEventListeners(): void {
|
||||||
this.canvas.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this) as EventListener);
|
||||||
this.canvas.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this) as EventListener);
|
||||||
@@ -98,18 +147,16 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
handleMouseDown(e: MouseEvent): void {
|
handleMouseDown(e: MouseEvent): void {
|
||||||
this.canvas.canvas.focus();
|
this.canvas.canvas.focus();
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
|
||||||
|
|
||||||
|
|
||||||
if (this.interaction.mode === 'drawingMask') {
|
if (this.interaction.mode === 'drawingMask') {
|
||||||
this.canvas.maskTool.handleMouseDown(worldCoords, viewCoords);
|
this.canvas.maskTool.handleMouseDown(coords.world, coords.view);
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.canvas.shapeTool.isActive) {
|
if (this.canvas.shapeTool.isActive) {
|
||||||
this.canvas.shapeTool.addPoint(worldCoords);
|
this.canvas.shapeTool.addPoint(coords.world);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +164,7 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
// 1. Akcje globalne z modyfikatorami (mają najwyższy priorytet)
|
// 1. Akcje globalne z modyfikatorami (mają najwyższy priorytet)
|
||||||
if (e.shiftKey && e.ctrlKey) {
|
if (e.shiftKey && e.ctrlKey) {
|
||||||
this.startCanvasMove(worldCoords);
|
this.startCanvasMove(coords.world);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
@@ -126,18 +173,17 @@ export class CanvasInteractions {
|
|||||||
this.canvas.outputAreaShape = null;
|
this.canvas.outputAreaShape = null;
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
}
|
}
|
||||||
this.startCanvasResize(worldCoords);
|
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
|
||||||
e.preventDefault(); // Always prevent right-click default behavior
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // Stop event propagation
|
|
||||||
|
|
||||||
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y);
|
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(coords.world.x, coords.world.y);
|
||||||
if (clickedLayerResult && this.canvas.canvasSelection.selectedLayers.includes(clickedLayerResult.layer)) {
|
if (clickedLayerResult && this.canvas.canvasSelection.selectedLayers.includes(clickedLayerResult.layer)) {
|
||||||
this.canvas.canvasLayers.showBlendModeMenu(viewCoords.x, viewCoords.y);
|
this.canvas.canvasLayers.showBlendModeMenu(coords.view.x, coords.view.y);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,15 +193,15 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Interakcje z elementami na płótnie (lewy przycisk)
|
// 3. Interakcje z elementami na płótnie (lewy przycisk)
|
||||||
const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(worldCoords.x, worldCoords.y);
|
const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(coords.world.x, coords.world.y);
|
||||||
if (transformTarget) {
|
if (transformTarget) {
|
||||||
this.startLayerTransform(transformTarget.layer, transformTarget.handle, worldCoords);
|
this.startLayerTransform(transformTarget.layer, transformTarget.handle, coords.world);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y);
|
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(coords.world.x, coords.world.y);
|
||||||
if (clickedLayerResult) {
|
if (clickedLayerResult) {
|
||||||
this.prepareForDrag(clickedLayerResult.layer, worldCoords);
|
this.prepareForDrag(clickedLayerResult.layer, coords.world);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,14 +210,13 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove(e: MouseEvent): void {
|
handleMouseMove(e: MouseEvent): void {
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
this.canvas.lastMousePosition = coords.world; // Zawsze aktualizuj ostatnią pozycję myszy
|
||||||
this.canvas.lastMousePosition = worldCoords; // 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 = worldCoords.x - this.interaction.dragStart.x;
|
const dx = coords.world.x - this.interaction.dragStart.x;
|
||||||
const dy = worldCoords.y - this.interaction.dragStart.y;
|
const dy = coords.world.y - this.interaction.dragStart.y;
|
||||||
if (Math.sqrt(dx * dx + dy * dy) > 3) { // Próg 3 pikseli
|
if (Math.sqrt(dx * dx + dy * dy) > 3) { // Próg 3 pikseli
|
||||||
this.interaction.mode = 'dragging';
|
this.interaction.mode = 'dragging';
|
||||||
this.originalLayerPositions.clear();
|
this.originalLayerPositions.clear();
|
||||||
@@ -183,39 +228,38 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
switch (this.interaction.mode) {
|
switch (this.interaction.mode) {
|
||||||
case 'drawingMask':
|
case 'drawingMask':
|
||||||
this.canvas.maskTool.handleMouseMove(worldCoords, viewCoords);
|
this.canvas.maskTool.handleMouseMove(coords.world, coords.view);
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
break;
|
break;
|
||||||
case 'panning':
|
case 'panning':
|
||||||
this.panViewport(e);
|
this.panViewport(e);
|
||||||
break;
|
break;
|
||||||
case 'dragging':
|
case 'dragging':
|
||||||
this.dragLayers(worldCoords);
|
this.dragLayers(coords.world);
|
||||||
break;
|
break;
|
||||||
case 'resizing':
|
case 'resizing':
|
||||||
this.resizeLayerFromHandle(worldCoords, e.shiftKey);
|
this.resizeLayerFromHandle(coords.world, e.shiftKey);
|
||||||
break;
|
break;
|
||||||
case 'rotating':
|
case 'rotating':
|
||||||
this.rotateLayerFromHandle(worldCoords, e.shiftKey);
|
this.rotateLayerFromHandle(coords.world, e.shiftKey);
|
||||||
break;
|
break;
|
||||||
case 'resizingCanvas':
|
case 'resizingCanvas':
|
||||||
this.updateCanvasResize(worldCoords);
|
this.updateCanvasResize(coords.world);
|
||||||
break;
|
break;
|
||||||
case 'movingCanvas':
|
case 'movingCanvas':
|
||||||
this.updateCanvasMove(worldCoords);
|
this.updateCanvasMove(coords.world);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.updateCursor(worldCoords);
|
this.updateCursor(coords.world);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp(e: MouseEvent): void {
|
handleMouseUp(e: MouseEvent): void {
|
||||||
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
|
||||||
|
|
||||||
if (this.interaction.mode === 'drawingMask') {
|
if (this.interaction.mode === 'drawingMask') {
|
||||||
this.canvas.maskTool.handleMouseUp(viewCoords);
|
this.canvas.maskTool.handleMouseUp(coords.view);
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -229,20 +273,7 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
// Log layer positions when dragging ends
|
// Log layer positions when dragging ends
|
||||||
if (this.interaction.mode === 'dragging' && this.canvas.canvasSelection.selectedLayers.length > 0) {
|
if (this.interaction.mode === 'dragging' && this.canvas.canvasSelection.selectedLayers.length > 0) {
|
||||||
const bounds = this.canvas.outputAreaBounds;
|
this.logDragCompletion(coords);
|
||||||
log.info("=== LAYER DRAG COMPLETED ===");
|
|
||||||
log.info(`Mouse position: world(${worldCoords.x.toFixed(1)}, ${worldCoords.y.toFixed(1)}) view(${viewCoords.x.toFixed(1)}, ${viewCoords.y.toFixed(1)})`);
|
|
||||||
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)}`);
|
|
||||||
|
|
||||||
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer, index: number) => {
|
|
||||||
const relativeToOutput = {
|
|
||||||
x: layer.x - bounds.x,
|
|
||||||
y: layer.y - bounds.y
|
|
||||||
};
|
|
||||||
log.info(`Layer ${index + 1} "${layer.name}": world(${layer.x.toFixed(1)}, ${layer.y.toFixed(1)}) relative_to_output(${relativeToOutput.x.toFixed(1)}, ${relativeToOutput.y.toFixed(1)}) size(${layer.width.toFixed(1)}x${layer.height.toFixed(1)})`);
|
|
||||||
});
|
|
||||||
log.info("=== END LAYER DRAG ===");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zapisz stan tylko, jeśli faktycznie doszło do zmiany (przeciąganie, transformacja, duplikacja)
|
// Zapisz stan tylko, jeśli faktycznie doszło do zmiany (przeciąganie, transformacja, duplikacja)
|
||||||
@@ -250,20 +281,36 @@ export class CanvasInteractions {
|
|||||||
const duplicatedInDrag = this.interaction.hasClonedInDrag;
|
const duplicatedInDrag = this.interaction.hasClonedInDrag;
|
||||||
|
|
||||||
if (stateChangingInteraction || duplicatedInDrag) {
|
if (stateChangingInteraction || duplicatedInDrag) {
|
||||||
this.canvas.saveState();
|
this.renderAndSave(true);
|
||||||
this.canvas.canvasState.saveStateToDB();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resetInteractionState();
|
this.resetInteractionState();
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private logDragCompletion(coords: MouseCoordinates): void {
|
||||||
|
const bounds = this.canvas.outputAreaBounds;
|
||||||
|
log.info("=== LAYER DRAG COMPLETED ===");
|
||||||
|
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(`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) => {
|
||||||
|
const relativeToOutput = {
|
||||||
|
x: layer.x - bounds.x,
|
||||||
|
y: layer.y - bounds.y
|
||||||
|
};
|
||||||
|
log.info(`Layer ${index + 1} "${layer.name}": world(${layer.x.toFixed(1)}, ${layer.y.toFixed(1)}) relative_to_output(${relativeToOutput.x.toFixed(1)}, ${relativeToOutput.y.toFixed(1)}) size(${layer.width.toFixed(1)}x${layer.height.toFixed(1)})`);
|
||||||
|
});
|
||||||
|
log.info("=== END LAYER DRAG ===");
|
||||||
|
}
|
||||||
|
|
||||||
handleMouseLeave(e: MouseEvent): void {
|
handleMouseLeave(e: MouseEvent): void {
|
||||||
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
if (this.canvas.maskTool.isActive) {
|
if (this.canvas.maskTool.isActive) {
|
||||||
this.canvas.maskTool.handleMouseLeave();
|
this.canvas.maskTool.handleMouseLeave();
|
||||||
if (this.canvas.maskTool.isDrawing) {
|
if (this.canvas.maskTool.isDrawing) {
|
||||||
this.canvas.maskTool.handleMouseUp(viewCoords);
|
this.canvas.maskTool.handleMouseUp(coords.view);
|
||||||
}
|
}
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
@@ -292,99 +339,99 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleWheel(e: WheelEvent): void {
|
handleWheel(e: WheelEvent): void {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
if (this.canvas.maskTool.isActive) {
|
const coords = this.getMouseCoordinates(e);
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
|
||||||
const rect = this.canvas.canvas.getBoundingClientRect();
|
if (this.canvas.maskTool.isActive || this.canvas.canvasSelection.selectedLayers.length === 0) {
|
||||||
const mouseBufferX = (e.clientX - rect.left) * (this.canvas.offscreenCanvas.width / rect.width);
|
// Zoom operation for mask tool or when no layers selected
|
||||||
const mouseBufferY = (e.clientY - rect.top) * (this.canvas.offscreenCanvas.height / rect.height);
|
|
||||||
|
|
||||||
const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
|
const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
|
||||||
const newZoom = this.canvas.viewport.zoom * zoomFactor;
|
this.performZoomOperation(coords.world, zoomFactor);
|
||||||
|
|
||||||
this.canvas.viewport.zoom = Math.max(0.1, Math.min(10, newZoom));
|
|
||||||
this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom);
|
|
||||||
this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom);
|
|
||||||
} else if (this.canvas.canvasSelection.selectedLayers.length > 0) {
|
|
||||||
const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1);
|
|
||||||
const direction = e.deltaY < 0 ? 1 : -1; // 1 = up/right, -1 = down/left
|
|
||||||
|
|
||||||
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
// Nowy skrót: Shift + Ctrl + Kółko do przyciągania do absolutnych wartości
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
const snapAngle = 5;
|
|
||||||
if (direction > 0) { // Obrót w górę/prawo
|
|
||||||
layer.rotation = Math.ceil((layer.rotation + 0.1) / snapAngle) * snapAngle;
|
|
||||||
} else { // Obrót w dół/lewo
|
|
||||||
layer.rotation = Math.floor((layer.rotation - 0.1) / snapAngle) * snapAngle;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Stara funkcjonalność: Shift + Kółko obraca o stały krok
|
|
||||||
layer.rotation += rotationStep;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const oldWidth = layer.width;
|
|
||||||
const oldHeight = layer.height;
|
|
||||||
let scaleFactor;
|
|
||||||
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
const direction = e.deltaY > 0 ? -1 : 1;
|
|
||||||
const baseDimension = Math.max(layer.width, layer.height);
|
|
||||||
const newBaseDimension = baseDimension + direction;
|
|
||||||
if (newBaseDimension < 10) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scaleFactor = newBaseDimension / baseDimension;
|
|
||||||
} else {
|
|
||||||
const gridSize = 64;
|
|
||||||
const direction = e.deltaY > 0 ? -1 : 1;
|
|
||||||
let targetHeight;
|
|
||||||
|
|
||||||
if (direction > 0) {
|
|
||||||
targetHeight = (Math.floor(oldHeight / gridSize) + 1) * gridSize;
|
|
||||||
} else {
|
|
||||||
targetHeight = (Math.ceil(oldHeight / gridSize) - 1) * gridSize;
|
|
||||||
}
|
|
||||||
if (targetHeight < gridSize / 2) {
|
|
||||||
targetHeight = gridSize / 2;
|
|
||||||
}
|
|
||||||
if (Math.abs(oldHeight - targetHeight) < 1) {
|
|
||||||
if (direction > 0) targetHeight += gridSize;
|
|
||||||
else targetHeight -= gridSize;
|
|
||||||
|
|
||||||
if (targetHeight < gridSize / 2) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scaleFactor = targetHeight / oldHeight;
|
|
||||||
}
|
|
||||||
if (scaleFactor && isFinite(scaleFactor)) {
|
|
||||||
layer.width *= scaleFactor;
|
|
||||||
layer.height *= scaleFactor;
|
|
||||||
layer.x += (oldWidth - layer.width) / 2;
|
|
||||||
layer.y += (oldHeight - layer.height) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
// Layer transformation when layers are selected
|
||||||
const rect = this.canvas.canvas.getBoundingClientRect();
|
this.handleLayerWheelTransformation(e);
|
||||||
const mouseBufferX = (e.clientX - rect.left) * (this.canvas.offscreenCanvas.width / rect.width);
|
|
||||||
const mouseBufferY = (e.clientY - rect.top) * (this.canvas.offscreenCanvas.height / rect.height);
|
|
||||||
|
|
||||||
const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
|
|
||||||
const newZoom = this.canvas.viewport.zoom * zoomFactor;
|
|
||||||
|
|
||||||
this.canvas.viewport.zoom = Math.max(0.1, Math.min(10, newZoom));
|
|
||||||
this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom);
|
|
||||||
this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
if (!this.canvas.maskTool.isActive) {
|
if (!this.canvas.maskTool.isActive) {
|
||||||
this.canvas.requestSaveState(); // Użyj opóźnionego zapisu
|
this.canvas.requestSaveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleLayerWheelTransformation(e: WheelEvent): void {
|
||||||
|
const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1);
|
||||||
|
const direction = e.deltaY < 0 ? 1 : -1;
|
||||||
|
|
||||||
|
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this.handleLayerRotation(layer, e.ctrlKey, direction, rotationStep);
|
||||||
|
} else {
|
||||||
|
this.handleLayerScaling(layer, e.ctrlKey, e.deltaY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleLayerRotation(layer: Layer, isCtrlPressed: boolean, direction: number, rotationStep: number): void {
|
||||||
|
if (isCtrlPressed) {
|
||||||
|
// Snap to absolute values
|
||||||
|
const snapAngle = 5;
|
||||||
|
if (direction > 0) {
|
||||||
|
layer.rotation = Math.ceil((layer.rotation + 0.1) / snapAngle) * snapAngle;
|
||||||
|
} else {
|
||||||
|
layer.rotation = Math.floor((layer.rotation - 0.1) / snapAngle) * snapAngle;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fixed step rotation
|
||||||
|
layer.rotation += rotationStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleLayerScaling(layer: Layer, isCtrlPressed: boolean, deltaY: number): void {
|
||||||
|
const oldWidth = layer.width;
|
||||||
|
const oldHeight = layer.height;
|
||||||
|
let scaleFactor;
|
||||||
|
|
||||||
|
if (isCtrlPressed) {
|
||||||
|
const direction = deltaY > 0 ? -1 : 1;
|
||||||
|
const baseDimension = Math.max(layer.width, layer.height);
|
||||||
|
const newBaseDimension = baseDimension + direction;
|
||||||
|
if (newBaseDimension < 10) return;
|
||||||
|
scaleFactor = newBaseDimension / baseDimension;
|
||||||
|
} else {
|
||||||
|
scaleFactor = this.calculateGridBasedScaling(oldHeight, deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scaleFactor && isFinite(scaleFactor)) {
|
||||||
|
layer.width *= scaleFactor;
|
||||||
|
layer.height *= scaleFactor;
|
||||||
|
layer.x += (oldWidth - layer.width) / 2;
|
||||||
|
layer.y += (oldHeight - layer.height) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateGridBasedScaling(oldHeight: number, deltaY: number): number {
|
||||||
|
const gridSize = 64;
|
||||||
|
const direction = deltaY > 0 ? -1 : 1;
|
||||||
|
let targetHeight;
|
||||||
|
|
||||||
|
if (direction > 0) {
|
||||||
|
targetHeight = (Math.floor(oldHeight / gridSize) + 1) * gridSize;
|
||||||
|
} else {
|
||||||
|
targetHeight = (Math.ceil(oldHeight / gridSize) - 1) * gridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetHeight < gridSize / 2) {
|
||||||
|
targetHeight = gridSize / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(oldHeight - targetHeight) < 1) {
|
||||||
|
if (direction > 0) targetHeight += gridSize;
|
||||||
|
else targetHeight -= gridSize;
|
||||||
|
if (targetHeight < gridSize / 2) return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetHeight / oldHeight;
|
||||||
|
}
|
||||||
|
|
||||||
handleKeyDown(e: KeyboardEvent): void {
|
handleKeyDown(e: KeyboardEvent): void {
|
||||||
if (e.key === 'Control') this.interaction.isCtrlPressed = true;
|
if (e.key === 'Control') this.interaction.isCtrlPressed = true;
|
||||||
if (e.key === 'Shift') this.interaction.isShiftPressed = true;
|
if (e.key === 'Shift') this.interaction.isShiftPressed = true;
|
||||||
@@ -827,47 +874,38 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDragOver(e: DragEvent): void {
|
handleDragOver(e: DragEvent): void {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
|
||||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragEnter(e: DragEvent): void {
|
handleDragEnter(e: DragEvent): void {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
this.setDragDropStyling(true);
|
||||||
this.canvas.canvas.style.backgroundColor = 'rgba(45, 90, 160, 0.1)';
|
|
||||||
this.canvas.canvas.style.border = '2px dashed #2d5aa0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragLeave(e: DragEvent): void {
|
handleDragLeave(e: DragEvent): void {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
|
||||||
|
|
||||||
if (!this.canvas.canvas.contains(e.relatedTarget as Node)) {
|
if (!this.canvas.canvas.contains(e.relatedTarget as Node)) {
|
||||||
this.canvas.canvas.style.backgroundColor = '';
|
this.setDragDropStyling(false);
|
||||||
this.canvas.canvas.style.border = '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDrop(e: DragEvent): Promise<void> {
|
async handleDrop(e: DragEvent): Promise<void> {
|
||||||
e.preventDefault();
|
this.preventEventDefaults(e);
|
||||||
e.stopPropagation(); // CRITICAL: Prevent ComfyUI from handling this event and loading workflow
|
|
||||||
|
|
||||||
log.info("Canvas drag & drop event intercepted - preventing ComfyUI workflow loading");
|
log.info("Canvas drag & drop event intercepted - preventing ComfyUI workflow loading");
|
||||||
|
|
||||||
this.canvas.canvas.style.backgroundColor = '';
|
this.setDragDropStyling(false);
|
||||||
this.canvas.canvas.style.border = '';
|
|
||||||
|
|
||||||
if (!e.dataTransfer) return;
|
if (!e.dataTransfer) return;
|
||||||
const files = Array.from(e.dataTransfer.files);
|
const files = Array.from(e.dataTransfer.files);
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
|
|
||||||
log.info(`Dropped ${files.length} file(s) onto canvas at position (${worldCoords.x}, ${worldCoords.y})`);
|
log.info(`Dropped ${files.length} file(s) onto canvas at position (${coords.world.x}, ${coords.world.y})`);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.type.startsWith('image/')) {
|
if (file.type.startsWith('image/')) {
|
||||||
try {
|
try {
|
||||||
await this.loadDroppedImageFile(file, worldCoords);
|
await this.loadDroppedImageFile(file, coords.world);
|
||||||
log.info(`Successfully loaded dropped image: ${file.name}`);
|
log.info(`Successfully loaded dropped image: ${file.name}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Failed to load dropped image ${file.name}:`, error);
|
log.error(`Failed to load dropped image ${file.name}:`, error);
|
||||||
|
|||||||
@@ -424,8 +424,6 @@ export class CanvasLayers {
|
|||||||
// Check if we need to apply blend area effect
|
// Check if we need to apply blend area effect
|
||||||
const blendArea = layer.blendArea ?? 0;
|
const blendArea = layer.blendArea ?? 0;
|
||||||
const needsBlendAreaEffect = blendArea > 0;
|
const needsBlendAreaEffect = blendArea > 0;
|
||||||
|
|
||||||
log.info(`Drawing layer ${layer.id}: blendArea=${blendArea}, needsBlendAreaEffect=${needsBlendAreaEffect}`);
|
|
||||||
|
|
||||||
if (needsBlendAreaEffect) {
|
if (needsBlendAreaEffect) {
|
||||||
log.info(`Applying blend area effect for layer ${layer.id}`);
|
log.info(`Applying blend area effect for layer ${layer.id}`);
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ export class MaskTool {
|
|||||||
public shapePreviewVisible: boolean;
|
public shapePreviewVisible: boolean;
|
||||||
private isPreviewMode: boolean;
|
private isPreviewMode: boolean;
|
||||||
|
|
||||||
|
// Performance optimization for active canvas updates
|
||||||
|
private activeMaskNeedsUpdate: boolean;
|
||||||
|
private activeMaskUpdateTimeout: number | null;
|
||||||
|
private readonly ACTIVE_MASK_UPDATE_DELAY = 16; // ~60fps throttling
|
||||||
|
|
||||||
constructor(canvasInstance: Canvas & { canvasState: CanvasState, width: number, height: number }, callbacks: MaskToolCallbacks = {}) {
|
constructor(canvasInstance: Canvas & { canvasState: CanvasState, width: number, height: number }, callbacks: MaskToolCallbacks = {}) {
|
||||||
this.canvasInstance = canvasInstance;
|
this.canvasInstance = canvasInstance;
|
||||||
this.mainCanvas = canvasInstance.canvas;
|
this.mainCanvas = canvasInstance.canvas;
|
||||||
@@ -97,6 +102,10 @@ export class MaskTool {
|
|||||||
this.shapePreviewVisible = false;
|
this.shapePreviewVisible = false;
|
||||||
this.isPreviewMode = false;
|
this.isPreviewMode = false;
|
||||||
|
|
||||||
|
// Initialize performance optimization flags
|
||||||
|
this.activeMaskNeedsUpdate = false;
|
||||||
|
this.activeMaskUpdateTimeout = null;
|
||||||
|
|
||||||
this.initMaskCanvas();
|
this.initMaskCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,9 +199,7 @@ export class MaskTool {
|
|||||||
this.activeMaskCtx.drawImage(chunk.canvas, destX, destY);
|
this.activeMaskCtx.drawImage(chunk.canvas, destX, destY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`Updated active mask canvas to show ALL chunks: ${canvasWidth}x${canvasHeight} at (${canvasLeft}, ${canvasTop}), chunks: ${chunkBounds.minX},${chunkBounds.minY} to ${chunkBounds.maxX},${chunkBounds.maxY}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -449,8 +456,8 @@ export class MaskTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates active canvas when drawing affects chunks
|
* Updates active canvas when drawing affects chunks with throttling to prevent lag
|
||||||
* Now always updates when new chunks are created to ensure immediate visibility
|
* Uses throttling to limit updates to ~60fps during drawing operations
|
||||||
*/
|
*/
|
||||||
private updateActiveCanvasIfNeeded(startWorld: Point, endWorld: Point): void {
|
private updateActiveCanvasIfNeeded(startWorld: Point, endWorld: Point): void {
|
||||||
// Calculate which chunks were affected by this drawing operation
|
// Calculate which chunks were affected by this drawing operation
|
||||||
@@ -477,16 +484,40 @@ export class MaskTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (drewOnNewChunks) {
|
if (drewOnNewChunks) {
|
||||||
// Drawing extended beyond current active bounds - do full update to include new chunks
|
// Drawing extended beyond current active bounds - immediate update required
|
||||||
this.updateActiveMaskCanvas();
|
this.updateActiveMaskCanvas();
|
||||||
log.debug("Drew on new chunks - performed full active canvas update");
|
log.debug("Drew on new chunks - performed immediate full active canvas update");
|
||||||
} else {
|
} else {
|
||||||
// Drawing within existing bounds - do partial update for performance
|
// Drawing within existing bounds - use throttled update for performance
|
||||||
this.updateActiveCanvasPartial(affectedChunkMinX, affectedChunkMinY, affectedChunkMaxX, affectedChunkMaxY);
|
this.scheduleThrottledActiveMaskUpdate(affectedChunkMinX, affectedChunkMinY, affectedChunkMaxX, affectedChunkMaxY);
|
||||||
log.debug("Drew within existing bounds - performed partial update");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules a throttled update of the active mask canvas to prevent excessive redraws
|
||||||
|
* Only updates at most once per ACTIVE_MASK_UPDATE_DELAY milliseconds
|
||||||
|
*/
|
||||||
|
private scheduleThrottledActiveMaskUpdate(chunkMinX: number, chunkMinY: number, chunkMaxX: number, chunkMaxY: number): void {
|
||||||
|
// Mark that an update is needed
|
||||||
|
this.activeMaskNeedsUpdate = true;
|
||||||
|
|
||||||
|
// If there's already a pending update, don't schedule another one
|
||||||
|
if (this.activeMaskUpdateTimeout !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule the update with throttling
|
||||||
|
this.activeMaskUpdateTimeout = window.setTimeout(() => {
|
||||||
|
if (this.activeMaskNeedsUpdate) {
|
||||||
|
// Perform partial update for the affected chunks
|
||||||
|
this.updateActiveCanvasPartial(chunkMinX, chunkMinY, chunkMaxX, chunkMaxY);
|
||||||
|
this.activeMaskNeedsUpdate = false;
|
||||||
|
log.debug("Performed throttled partial active canvas update");
|
||||||
|
}
|
||||||
|
this.activeMaskUpdateTimeout = null;
|
||||||
|
}, this.ACTIVE_MASK_UPDATE_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partially updates the active canvas by redrawing only specific chunks
|
* Partially updates the active canvas by redrawing only specific chunks
|
||||||
* Much faster than full recomposition during drawing
|
* Much faster than full recomposition during drawing
|
||||||
@@ -940,9 +971,12 @@ export class MaskTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMask(): HTMLCanvasElement {
|
getMask(): HTMLCanvasElement {
|
||||||
// Always return the current active mask canvas which shows all chunks
|
// Return the current active mask canvas which shows all chunks
|
||||||
// Make sure it's up to date before returning
|
// Only update if there are pending changes to avoid unnecessary redraws
|
||||||
this.updateActiveMaskCanvas();
|
if (this.activeMaskNeedsUpdate) {
|
||||||
|
this.updateActiveMaskCanvas();
|
||||||
|
this.activeMaskNeedsUpdate = false;
|
||||||
|
}
|
||||||
return this.activeMaskCanvas;
|
return this.activeMaskCanvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user