mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-25 06:22:14 -03:00
resolve TypeScript errors and memory leaks
Fixed all TypeScript compilation errors by defining a dedicated TransformOrigin type and adding proper null checks. Implemented comprehensive event handler cleanup to prevent memory leaks and improved cross-platform support with Meta key handling for macOS users.
This commit is contained in:
@@ -3,16 +3,33 @@ import { snapToGrid, getSnapAdjustment } from "./utils/CommonUtils.js";
|
|||||||
const log = createModuleLogger('CanvasInteractions');
|
const log = createModuleLogger('CanvasInteractions');
|
||||||
export class CanvasInteractions {
|
export class CanvasInteractions {
|
||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
|
// Bound event handlers to enable proper removeEventListener and avoid leaks
|
||||||
|
this.onMouseDown = (e) => this.handleMouseDown(e);
|
||||||
|
this.onMouseMove = (e) => this.handleMouseMove(e);
|
||||||
|
this.onMouseUp = (e) => this.handleMouseUp(e);
|
||||||
|
this.onMouseEnter = (e) => { this.canvas.isMouseOver = true; this.handleMouseEnter(e); };
|
||||||
|
this.onMouseLeave = (e) => { this.canvas.isMouseOver = false; this.handleMouseLeave(e); };
|
||||||
|
this.onWheel = (e) => this.handleWheel(e);
|
||||||
|
this.onKeyDown = (e) => this.handleKeyDown(e);
|
||||||
|
this.onKeyUp = (e) => this.handleKeyUp(e);
|
||||||
|
this.onDragOver = (e) => this.handleDragOver(e);
|
||||||
|
this.onDragEnter = (e) => this.handleDragEnter(e);
|
||||||
|
this.onDragLeave = (e) => this.handleDragLeave(e);
|
||||||
|
this.onDrop = (e) => { this.handleDrop(e); };
|
||||||
|
this.onContextMenu = (e) => this.handleContextMenu(e);
|
||||||
|
this.onBlur = () => this.handleBlur();
|
||||||
|
this.onPaste = (e) => this.handlePasteEvent(e);
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.interaction = {
|
this.interaction = {
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
panStart: { x: 0, y: 0 },
|
panStart: { x: 0, y: 0 },
|
||||||
dragStart: { x: 0, y: 0 },
|
dragStart: { x: 0, y: 0 },
|
||||||
transformOrigin: {},
|
transformOrigin: null,
|
||||||
resizeHandle: null,
|
resizeHandle: null,
|
||||||
resizeAnchor: { x: 0, y: 0 },
|
resizeAnchor: { x: 0, y: 0 },
|
||||||
canvasResizeStart: { x: 0, y: 0 },
|
canvasResizeStart: { x: 0, y: 0 },
|
||||||
isCtrlPressed: false,
|
isCtrlPressed: false,
|
||||||
|
isMetaPressed: false,
|
||||||
isAltPressed: false,
|
isAltPressed: false,
|
||||||
isShiftPressed: false,
|
isShiftPressed: false,
|
||||||
isSPressed: false,
|
isSPressed: false,
|
||||||
@@ -37,7 +54,6 @@ export class CanvasInteractions {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
performZoomOperation(worldCoords, zoomFactor) {
|
performZoomOperation(worldCoords, zoomFactor) {
|
||||||
const rect = this.canvas.canvas.getBoundingClientRect();
|
|
||||||
const mouseBufferX = (worldCoords.x - this.canvas.viewport.x) * this.canvas.viewport.zoom;
|
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 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));
|
||||||
@@ -64,29 +80,39 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
this.canvas.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
|
this.canvas.canvas.addEventListener('mousedown', this.onMouseDown);
|
||||||
this.canvas.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
this.canvas.canvas.addEventListener('mousemove', this.onMouseMove);
|
||||||
this.canvas.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
|
this.canvas.canvas.addEventListener('mouseup', this.onMouseUp);
|
||||||
this.canvas.canvas.addEventListener('mouseleave', this.handleMouseLeave.bind(this));
|
this.canvas.canvas.addEventListener('wheel', this.onWheel, { passive: false });
|
||||||
this.canvas.canvas.addEventListener('wheel', this.handleWheel.bind(this), { passive: false });
|
this.canvas.canvas.addEventListener('keydown', this.onKeyDown);
|
||||||
this.canvas.canvas.addEventListener('keydown', this.handleKeyDown.bind(this));
|
this.canvas.canvas.addEventListener('keyup', this.onKeyUp);
|
||||||
this.canvas.canvas.addEventListener('keyup', this.handleKeyUp.bind(this));
|
|
||||||
// Add a blur event listener to the window to reset key states
|
// Add a blur event listener to the window to reset key states
|
||||||
window.addEventListener('blur', this.handleBlur.bind(this));
|
window.addEventListener('blur', this.onBlur);
|
||||||
document.addEventListener('paste', this.handlePasteEvent.bind(this));
|
document.addEventListener('paste', this.onPaste);
|
||||||
this.canvas.canvas.addEventListener('mouseenter', (e) => {
|
this.canvas.canvas.addEventListener('mouseenter', this.onMouseEnter);
|
||||||
this.canvas.isMouseOver = true;
|
this.canvas.canvas.addEventListener('mouseleave', this.onMouseLeave);
|
||||||
this.handleMouseEnter(e);
|
this.canvas.canvas.addEventListener('dragover', this.onDragOver);
|
||||||
});
|
this.canvas.canvas.addEventListener('dragenter', this.onDragEnter);
|
||||||
this.canvas.canvas.addEventListener('mouseleave', (e) => {
|
this.canvas.canvas.addEventListener('dragleave', this.onDragLeave);
|
||||||
this.canvas.isMouseOver = false;
|
this.canvas.canvas.addEventListener('drop', this.onDrop);
|
||||||
this.handleMouseLeave(e);
|
this.canvas.canvas.addEventListener('contextmenu', this.onContextMenu);
|
||||||
});
|
}
|
||||||
this.canvas.canvas.addEventListener('dragover', this.handleDragOver.bind(this));
|
teardownEventListeners() {
|
||||||
this.canvas.canvas.addEventListener('dragenter', this.handleDragEnter.bind(this));
|
this.canvas.canvas.removeEventListener('mousedown', this.onMouseDown);
|
||||||
this.canvas.canvas.addEventListener('dragleave', this.handleDragLeave.bind(this));
|
this.canvas.canvas.removeEventListener('mousemove', this.onMouseMove);
|
||||||
this.canvas.canvas.addEventListener('drop', this.handleDrop.bind(this));
|
this.canvas.canvas.removeEventListener('mouseup', this.onMouseUp);
|
||||||
this.canvas.canvas.addEventListener('contextmenu', this.handleContextMenu.bind(this));
|
this.canvas.canvas.removeEventListener('wheel', this.onWheel);
|
||||||
|
this.canvas.canvas.removeEventListener('keydown', this.onKeyDown);
|
||||||
|
this.canvas.canvas.removeEventListener('keyup', this.onKeyUp);
|
||||||
|
window.removeEventListener('blur', this.onBlur);
|
||||||
|
document.removeEventListener('paste', this.onPaste);
|
||||||
|
this.canvas.canvas.removeEventListener('mouseenter', this.onMouseEnter);
|
||||||
|
this.canvas.canvas.removeEventListener('mouseleave', this.onMouseLeave);
|
||||||
|
this.canvas.canvas.removeEventListener('dragover', this.onDragOver);
|
||||||
|
this.canvas.canvas.removeEventListener('dragenter', this.onDragEnter);
|
||||||
|
this.canvas.canvas.removeEventListener('dragleave', this.onDragLeave);
|
||||||
|
this.canvas.canvas.removeEventListener('drop', this.onDrop);
|
||||||
|
this.canvas.canvas.removeEventListener('contextmenu', this.onContextMenu);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sprawdza czy punkt znajduje się w obszarze któregokolwiek z zaznaczonych layerów
|
* Sprawdza czy punkt znajduje się w obszarze któregokolwiek z zaznaczonych layerów
|
||||||
@@ -163,7 +189,7 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.button !== 0) { // Środkowy przycisk
|
if (e.button === 1) { // Środkowy przycisk
|
||||||
this.startPanning(e);
|
this.startPanning(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -179,7 +205,7 @@ export class CanvasInteractions {
|
|||||||
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.startPanning(e, true); // clearSelection = true
|
||||||
}
|
}
|
||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
const coords = this.getMouseCoordinates(e);
|
const coords = this.getMouseCoordinates(e);
|
||||||
@@ -376,7 +402,7 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
calculateGridBasedScaling(oldHeight, deltaY) {
|
calculateGridBasedScaling(oldHeight, deltaY) {
|
||||||
const gridSize = 64;
|
const gridSize = 64; // Grid size - could be made configurable in the future
|
||||||
const direction = deltaY > 0 ? -1 : 1;
|
const direction = deltaY > 0 ? -1 : 1;
|
||||||
let targetHeight;
|
let targetHeight;
|
||||||
if (direction > 0) {
|
if (direction > 0) {
|
||||||
@@ -401,6 +427,8 @@ export class CanvasInteractions {
|
|||||||
handleKeyDown(e) {
|
handleKeyDown(e) {
|
||||||
if (e.key === 'Control')
|
if (e.key === 'Control')
|
||||||
this.interaction.isCtrlPressed = true;
|
this.interaction.isCtrlPressed = true;
|
||||||
|
if (e.key === 'Meta')
|
||||||
|
this.interaction.isMetaPressed = true;
|
||||||
if (e.key === 'Shift')
|
if (e.key === 'Shift')
|
||||||
this.interaction.isShiftPressed = true;
|
this.interaction.isShiftPressed = true;
|
||||||
if (e.key === 'Alt') {
|
if (e.key === 'Alt') {
|
||||||
@@ -485,6 +513,8 @@ export class CanvasInteractions {
|
|||||||
handleKeyUp(e) {
|
handleKeyUp(e) {
|
||||||
if (e.key === 'Control')
|
if (e.key === 'Control')
|
||||||
this.interaction.isCtrlPressed = false;
|
this.interaction.isCtrlPressed = false;
|
||||||
|
if (e.key === 'Meta')
|
||||||
|
this.interaction.isMetaPressed = false;
|
||||||
if (e.key === 'Shift')
|
if (e.key === 'Shift')
|
||||||
this.interaction.isShiftPressed = false;
|
this.interaction.isShiftPressed = false;
|
||||||
if (e.key === 'Alt')
|
if (e.key === 'Alt')
|
||||||
@@ -504,6 +534,7 @@ export class CanvasInteractions {
|
|||||||
handleBlur() {
|
handleBlur() {
|
||||||
log.debug('Window lost focus, resetting key states.');
|
log.debug('Window lost focus, resetting key states.');
|
||||||
this.interaction.isCtrlPressed = false;
|
this.interaction.isCtrlPressed = false;
|
||||||
|
this.interaction.isMetaPressed = false;
|
||||||
this.interaction.isAltPressed = false;
|
this.interaction.isAltPressed = false;
|
||||||
this.interaction.isShiftPressed = false;
|
this.interaction.isShiftPressed = false;
|
||||||
this.interaction.isSPressed = false;
|
this.interaction.isSPressed = false;
|
||||||
@@ -525,6 +556,11 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateCursor(worldCoords) {
|
updateCursor(worldCoords) {
|
||||||
|
// If actively rotating, show grabbing cursor
|
||||||
|
if (this.interaction.mode === 'rotating') {
|
||||||
|
this.canvas.canvas.style.cursor = 'grabbing';
|
||||||
|
return;
|
||||||
|
}
|
||||||
const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(worldCoords.x, worldCoords.y);
|
const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(worldCoords.x, worldCoords.y);
|
||||||
if (transformTarget) {
|
if (transformTarget) {
|
||||||
const handleName = transformTarget.handle;
|
const handleName = transformTarget.handle;
|
||||||
@@ -572,7 +608,8 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
prepareForDrag(layer, worldCoords) {
|
prepareForDrag(layer, worldCoords) {
|
||||||
// Zaktualizuj zaznaczenie, ale nie zapisuj stanu
|
// Zaktualizuj zaznaczenie, ale nie zapisuj stanu
|
||||||
if (this.interaction.isCtrlPressed) {
|
// Support both Ctrl (Windows/Linux) and Cmd (macOS) for multi-selection
|
||||||
|
if (this.interaction.isCtrlPressed || this.interaction.isMetaPressed) {
|
||||||
const index = this.canvas.canvasSelection.selectedLayers.indexOf(layer);
|
const index = this.canvas.canvasSelection.selectedLayers.indexOf(layer);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
this.canvas.canvasSelection.updateSelection([...this.canvas.canvasSelection.selectedLayers, layer]);
|
this.canvas.canvasSelection.updateSelection([...this.canvas.canvasSelection.selectedLayers, layer]);
|
||||||
@@ -590,10 +627,9 @@ export class CanvasInteractions {
|
|||||||
this.interaction.mode = 'potential-drag';
|
this.interaction.mode = 'potential-drag';
|
||||||
this.interaction.dragStart = { ...worldCoords };
|
this.interaction.dragStart = { ...worldCoords };
|
||||||
}
|
}
|
||||||
startPanningOrClearSelection(e) {
|
startPanning(e, clearSelection = true) {
|
||||||
// Ta funkcja jest teraz wywoływana tylko gdy kliknięto na tło bez modyfikatorów.
|
// Unified panning method - can optionally clear selection
|
||||||
// Domyślna akcja: wyczyść zaznaczenie i rozpocznij panoramowanie.
|
if (clearSelection && !this.interaction.isCtrlPressed) {
|
||||||
if (!this.interaction.isCtrlPressed) {
|
|
||||||
this.canvas.canvasSelection.updateSelection([]);
|
this.canvas.canvasSelection.updateSelection([]);
|
||||||
}
|
}
|
||||||
this.interaction.mode = 'panning';
|
this.interaction.mode = 'panning';
|
||||||
@@ -642,13 +678,6 @@ export class CanvasInteractions {
|
|||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
this.canvas.saveState();
|
this.canvas.saveState();
|
||||||
}
|
}
|
||||||
startPanning(e) {
|
|
||||||
if (!this.interaction.isCtrlPressed) {
|
|
||||||
this.canvas.canvasSelection.updateSelection([]);
|
|
||||||
}
|
|
||||||
this.interaction.mode = 'panning';
|
|
||||||
this.interaction.panStart = { x: e.clientX, y: e.clientY };
|
|
||||||
}
|
|
||||||
panViewport(e) {
|
panViewport(e) {
|
||||||
const dx = e.clientX - this.interaction.panStart.x;
|
const dx = e.clientX - this.interaction.panStart.x;
|
||||||
const dy = e.clientY - this.interaction.panStart.y;
|
const dy = e.clientY - this.interaction.panStart.y;
|
||||||
@@ -709,7 +738,7 @@ export class CanvasInteractions {
|
|||||||
mouseY = Math.abs(mouseY - snapToGrid(mouseY)) < snapThreshold ? snapToGrid(mouseY) : mouseY;
|
mouseY = Math.abs(mouseY - snapToGrid(mouseY)) < snapThreshold ? snapToGrid(mouseY) : mouseY;
|
||||||
}
|
}
|
||||||
const o = this.interaction.transformOrigin;
|
const o = this.interaction.transformOrigin;
|
||||||
if (o.rotation === undefined || o.width === undefined || o.height === undefined || o.centerX === undefined || o.centerY === undefined)
|
if (!o)
|
||||||
return;
|
return;
|
||||||
const handle = this.interaction.resizeHandle;
|
const handle = this.interaction.resizeHandle;
|
||||||
const anchor = this.interaction.resizeAnchor;
|
const anchor = this.interaction.resizeAnchor;
|
||||||
@@ -856,7 +885,7 @@ export class CanvasInteractions {
|
|||||||
if (!layer)
|
if (!layer)
|
||||||
return;
|
return;
|
||||||
const o = this.interaction.transformOrigin;
|
const o = this.interaction.transformOrigin;
|
||||||
if (o.rotation === undefined || o.centerX === undefined || o.centerY === undefined)
|
if (!o)
|
||||||
return;
|
return;
|
||||||
const startAngle = Math.atan2(this.interaction.dragStart.y - o.centerY, this.interaction.dragStart.x - o.centerX);
|
const startAngle = Math.atan2(this.interaction.dragStart.y - o.centerY, this.interaction.dragStart.x - o.centerX);
|
||||||
const currentAngle = Math.atan2(worldCoords.y - o.centerY, worldCoords.x - o.centerX);
|
const currentAngle = Math.atan2(worldCoords.y - o.centerY, worldCoords.x - o.centerX);
|
||||||
|
|||||||
@@ -10,15 +10,29 @@ interface MouseCoordinates {
|
|||||||
view: Point;
|
view: Point;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TransformOrigin {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
rotation: number;
|
||||||
|
centerX: number;
|
||||||
|
centerY: number;
|
||||||
|
originalWidth?: number;
|
||||||
|
originalHeight?: number;
|
||||||
|
cropBounds?: { x: number; y: number; width: number; height: number };
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
dragStart: Point;
|
dragStart: Point;
|
||||||
transformOrigin: Partial<Layer> & { centerX?: number, centerY?: number };
|
transformOrigin: TransformOrigin | null;
|
||||||
resizeHandle: string | null;
|
resizeHandle: string | null;
|
||||||
resizeAnchor: Point;
|
resizeAnchor: Point;
|
||||||
canvasResizeStart: Point;
|
canvasResizeStart: Point;
|
||||||
isCtrlPressed: boolean;
|
isCtrlPressed: boolean;
|
||||||
|
isMetaPressed: boolean;
|
||||||
isAltPressed: boolean;
|
isAltPressed: boolean;
|
||||||
isShiftPressed: boolean;
|
isShiftPressed: boolean;
|
||||||
isSPressed: boolean;
|
isSPressed: boolean;
|
||||||
@@ -35,17 +49,35 @@ export class CanvasInteractions {
|
|||||||
public interaction: InteractionState;
|
public interaction: InteractionState;
|
||||||
private originalLayerPositions: Map<Layer, Point>;
|
private originalLayerPositions: Map<Layer, Point>;
|
||||||
|
|
||||||
|
// Bound event handlers to enable proper removeEventListener and avoid leaks
|
||||||
|
private onMouseDown = (e: MouseEvent) => this.handleMouseDown(e);
|
||||||
|
private onMouseMove = (e: MouseEvent) => this.handleMouseMove(e);
|
||||||
|
private onMouseUp = (e: MouseEvent) => this.handleMouseUp(e);
|
||||||
|
private onMouseEnter = (e: MouseEvent) => { this.canvas.isMouseOver = true; this.handleMouseEnter(e); };
|
||||||
|
private onMouseLeave = (e: MouseEvent) => { this.canvas.isMouseOver = false; this.handleMouseLeave(e); };
|
||||||
|
private onWheel = (e: WheelEvent) => this.handleWheel(e);
|
||||||
|
private onKeyDown = (e: KeyboardEvent) => this.handleKeyDown(e);
|
||||||
|
private onKeyUp = (e: KeyboardEvent) => this.handleKeyUp(e);
|
||||||
|
private onDragOver = (e: DragEvent) => this.handleDragOver(e);
|
||||||
|
private onDragEnter = (e: DragEvent) => this.handleDragEnter(e);
|
||||||
|
private onDragLeave = (e: DragEvent) => this.handleDragLeave(e);
|
||||||
|
private onDrop = (e: DragEvent) => { this.handleDrop(e); };
|
||||||
|
private onContextMenu = (e: MouseEvent) => this.handleContextMenu(e);
|
||||||
|
private onBlur = () => this.handleBlur();
|
||||||
|
private onPaste = (e: ClipboardEvent) => this.handlePasteEvent(e);
|
||||||
|
|
||||||
constructor(canvas: Canvas) {
|
constructor(canvas: Canvas) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.interaction = {
|
this.interaction = {
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
panStart: { x: 0, y: 0 },
|
panStart: { x: 0, y: 0 },
|
||||||
dragStart: { x: 0, y: 0 },
|
dragStart: { x: 0, y: 0 },
|
||||||
transformOrigin: {},
|
transformOrigin: null,
|
||||||
resizeHandle: null,
|
resizeHandle: null,
|
||||||
resizeAnchor: { x: 0, y: 0 },
|
resizeAnchor: { x: 0, y: 0 },
|
||||||
canvasResizeStart: { x: 0, y: 0 },
|
canvasResizeStart: { x: 0, y: 0 },
|
||||||
isCtrlPressed: false,
|
isCtrlPressed: false,
|
||||||
|
isMetaPressed: false,
|
||||||
isAltPressed: false,
|
isAltPressed: false,
|
||||||
isShiftPressed: false,
|
isShiftPressed: false,
|
||||||
isSPressed: false,
|
isSPressed: false,
|
||||||
@@ -74,7 +106,6 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private performZoomOperation(worldCoords: Point, zoomFactor: number): void {
|
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 mouseBufferX = (worldCoords.x - this.canvas.viewport.x) * this.canvas.viewport.zoom;
|
||||||
const mouseBufferY = (worldCoords.y - this.canvas.viewport.y) * this.canvas.viewport.zoom;
|
const mouseBufferY = (worldCoords.y - this.canvas.viewport.y) * this.canvas.viewport.zoom;
|
||||||
|
|
||||||
@@ -106,34 +137,49 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners(): void {
|
setupEventListeners(): void {
|
||||||
this.canvas.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('mousedown', this.onMouseDown as EventListener);
|
||||||
this.canvas.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('mousemove', this.onMouseMove as EventListener);
|
||||||
this.canvas.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('mouseup', this.onMouseUp as EventListener);
|
||||||
this.canvas.canvas.addEventListener('mouseleave', this.handleMouseLeave.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('wheel', this.onWheel as EventListener, { passive: false });
|
||||||
this.canvas.canvas.addEventListener('wheel', this.handleWheel.bind(this) as EventListener, { passive: false });
|
this.canvas.canvas.addEventListener('keydown', this.onKeyDown as EventListener);
|
||||||
this.canvas.canvas.addEventListener('keydown', this.handleKeyDown.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('keyup', this.onKeyUp as EventListener);
|
||||||
this.canvas.canvas.addEventListener('keyup', this.handleKeyUp.bind(this) as EventListener);
|
|
||||||
|
|
||||||
// Add a blur event listener to the window to reset key states
|
// Add a blur event listener to the window to reset key states
|
||||||
window.addEventListener('blur', this.handleBlur.bind(this));
|
window.addEventListener('blur', this.onBlur);
|
||||||
|
|
||||||
document.addEventListener('paste', this.handlePasteEvent.bind(this));
|
document.addEventListener('paste', this.onPaste as unknown as EventListener);
|
||||||
|
|
||||||
this.canvas.canvas.addEventListener('mouseenter', (e: MouseEvent) => {
|
this.canvas.canvas.addEventListener('mouseenter', this.onMouseEnter as EventListener);
|
||||||
this.canvas.isMouseOver = true;
|
this.canvas.canvas.addEventListener('mouseleave', this.onMouseLeave as EventListener);
|
||||||
this.handleMouseEnter(e);
|
|
||||||
});
|
|
||||||
this.canvas.canvas.addEventListener('mouseleave', (e: MouseEvent) => {
|
|
||||||
this.canvas.isMouseOver = false;
|
|
||||||
this.handleMouseLeave(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.canvas.canvas.addEventListener('dragover', this.handleDragOver.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('dragover', this.onDragOver as EventListener);
|
||||||
this.canvas.canvas.addEventListener('dragenter', this.handleDragEnter.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('dragenter', this.onDragEnter as EventListener);
|
||||||
this.canvas.canvas.addEventListener('dragleave', this.handleDragLeave.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('dragleave', this.onDragLeave as EventListener);
|
||||||
this.canvas.canvas.addEventListener('drop', this.handleDrop.bind(this) as unknown as EventListener);
|
this.canvas.canvas.addEventListener('drop', this.onDrop as unknown as EventListener);
|
||||||
|
|
||||||
this.canvas.canvas.addEventListener('contextmenu', this.handleContextMenu.bind(this) as EventListener);
|
this.canvas.canvas.addEventListener('contextmenu', this.onContextMenu as EventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
teardownEventListeners(): void {
|
||||||
|
this.canvas.canvas.removeEventListener('mousedown', this.onMouseDown as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('mousemove', this.onMouseMove as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('mouseup', this.onMouseUp as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('wheel', this.onWheel as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('keydown', this.onKeyDown as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('keyup', this.onKeyUp as EventListener);
|
||||||
|
|
||||||
|
window.removeEventListener('blur', this.onBlur);
|
||||||
|
document.removeEventListener('paste', this.onPaste as unknown as EventListener);
|
||||||
|
|
||||||
|
this.canvas.canvas.removeEventListener('mouseenter', this.onMouseEnter as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('mouseleave', this.onMouseLeave as EventListener);
|
||||||
|
|
||||||
|
this.canvas.canvas.removeEventListener('dragover', this.onDragOver as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('dragenter', this.onDragEnter as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('dragleave', this.onDragLeave as EventListener);
|
||||||
|
this.canvas.canvas.removeEventListener('drop', this.onDrop as unknown as EventListener);
|
||||||
|
|
||||||
|
this.canvas.canvas.removeEventListener('contextmenu', this.onContextMenu as EventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,7 +268,7 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.button !== 0) { // Środkowy przycisk
|
if (e.button === 1) { // Środkowy przycisk
|
||||||
this.startPanning(e);
|
this.startPanning(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -241,7 +287,7 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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.startPanning(e, true); // clearSelection = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove(e: MouseEvent): void {
|
handleMouseMove(e: MouseEvent): void {
|
||||||
@@ -462,7 +508,7 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private calculateGridBasedScaling(oldHeight: number, deltaY: number): number {
|
private calculateGridBasedScaling(oldHeight: number, deltaY: number): number {
|
||||||
const gridSize = 64;
|
const gridSize = 64; // Grid size - could be made configurable in the future
|
||||||
const direction = deltaY > 0 ? -1 : 1;
|
const direction = deltaY > 0 ? -1 : 1;
|
||||||
let targetHeight;
|
let targetHeight;
|
||||||
|
|
||||||
@@ -487,6 +533,7 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
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 === 'Meta') this.interaction.isMetaPressed = true;
|
||||||
if (e.key === 'Shift') this.interaction.isShiftPressed = true;
|
if (e.key === 'Shift') this.interaction.isShiftPressed = true;
|
||||||
if (e.key === 'Alt') {
|
if (e.key === 'Alt') {
|
||||||
this.interaction.isAltPressed = true;
|
this.interaction.isAltPressed = true;
|
||||||
@@ -571,6 +618,7 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
handleKeyUp(e: KeyboardEvent): void {
|
handleKeyUp(e: KeyboardEvent): void {
|
||||||
if (e.key === 'Control') this.interaction.isCtrlPressed = false;
|
if (e.key === 'Control') this.interaction.isCtrlPressed = false;
|
||||||
|
if (e.key === 'Meta') this.interaction.isMetaPressed = false;
|
||||||
if (e.key === 'Shift') this.interaction.isShiftPressed = false;
|
if (e.key === 'Shift') this.interaction.isShiftPressed = false;
|
||||||
if (e.key === 'Alt') this.interaction.isAltPressed = false;
|
if (e.key === 'Alt') this.interaction.isAltPressed = false;
|
||||||
if (e.key.toLowerCase() === 's') this.interaction.isSPressed = false;
|
if (e.key.toLowerCase() === 's') this.interaction.isSPressed = false;
|
||||||
@@ -590,6 +638,7 @@ export class CanvasInteractions {
|
|||||||
handleBlur(): void {
|
handleBlur(): void {
|
||||||
log.debug('Window lost focus, resetting key states.');
|
log.debug('Window lost focus, resetting key states.');
|
||||||
this.interaction.isCtrlPressed = false;
|
this.interaction.isCtrlPressed = false;
|
||||||
|
this.interaction.isMetaPressed = false;
|
||||||
this.interaction.isAltPressed = false;
|
this.interaction.isAltPressed = false;
|
||||||
this.interaction.isShiftPressed = false;
|
this.interaction.isShiftPressed = false;
|
||||||
this.interaction.isSPressed = false;
|
this.interaction.isSPressed = false;
|
||||||
@@ -615,6 +664,12 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateCursor(worldCoords: Point): void {
|
updateCursor(worldCoords: Point): void {
|
||||||
|
// If actively rotating, show grabbing cursor
|
||||||
|
if (this.interaction.mode === 'rotating') {
|
||||||
|
this.canvas.canvas.style.cursor = 'grabbing';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(worldCoords.x, worldCoords.y);
|
const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(worldCoords.x, worldCoords.y);
|
||||||
|
|
||||||
if (transformTarget) {
|
if (transformTarget) {
|
||||||
@@ -663,7 +718,8 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
prepareForDrag(layer: Layer, worldCoords: Point): void {
|
prepareForDrag(layer: Layer, worldCoords: Point): void {
|
||||||
// Zaktualizuj zaznaczenie, ale nie zapisuj stanu
|
// Zaktualizuj zaznaczenie, ale nie zapisuj stanu
|
||||||
if (this.interaction.isCtrlPressed) {
|
// Support both Ctrl (Windows/Linux) and Cmd (macOS) for multi-selection
|
||||||
|
if (this.interaction.isCtrlPressed || this.interaction.isMetaPressed) {
|
||||||
const index = this.canvas.canvasSelection.selectedLayers.indexOf(layer);
|
const index = this.canvas.canvasSelection.selectedLayers.indexOf(layer);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
this.canvas.canvasSelection.updateSelection([...this.canvas.canvasSelection.selectedLayers, layer]);
|
this.canvas.canvasSelection.updateSelection([...this.canvas.canvasSelection.selectedLayers, layer]);
|
||||||
@@ -681,14 +737,13 @@ export class CanvasInteractions {
|
|||||||
this.interaction.dragStart = {...worldCoords};
|
this.interaction.dragStart = {...worldCoords};
|
||||||
}
|
}
|
||||||
|
|
||||||
startPanningOrClearSelection(e: MouseEvent): void {
|
startPanning(e: MouseEvent, clearSelection: boolean = true): void {
|
||||||
// Ta funkcja jest teraz wywoływana tylko gdy kliknięto na tło bez modyfikatorów.
|
// Unified panning method - can optionally clear selection
|
||||||
// Domyślna akcja: wyczyść zaznaczenie i rozpocznij panoramowanie.
|
if (clearSelection && !this.interaction.isCtrlPressed) {
|
||||||
if (!this.interaction.isCtrlPressed) {
|
|
||||||
this.canvas.canvasSelection.updateSelection([]);
|
this.canvas.canvasSelection.updateSelection([]);
|
||||||
}
|
}
|
||||||
this.interaction.mode = 'panning';
|
this.interaction.mode = 'panning';
|
||||||
this.interaction.panStart = {x: e.clientX, y: e.clientY};
|
this.interaction.panStart = { x: e.clientX, y: e.clientY };
|
||||||
}
|
}
|
||||||
|
|
||||||
startCanvasResize(worldCoords: Point): void {
|
startCanvasResize(worldCoords: Point): void {
|
||||||
@@ -743,14 +798,6 @@ export class CanvasInteractions {
|
|||||||
this.canvas.saveState();
|
this.canvas.saveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
startPanning(e: MouseEvent): void {
|
|
||||||
if (!this.interaction.isCtrlPressed) {
|
|
||||||
this.canvas.canvasSelection.updateSelection([]);
|
|
||||||
}
|
|
||||||
this.interaction.mode = 'panning';
|
|
||||||
this.interaction.panStart = { x: e.clientX, y: e.clientY };
|
|
||||||
}
|
|
||||||
|
|
||||||
panViewport(e: MouseEvent): void {
|
panViewport(e: MouseEvent): void {
|
||||||
const dx = e.clientX - this.interaction.panStart.x;
|
const dx = e.clientX - this.interaction.panStart.x;
|
||||||
const dy = e.clientY - this.interaction.panStart.y;
|
const dy = e.clientY - this.interaction.panStart.y;
|
||||||
@@ -818,7 +865,7 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const o = this.interaction.transformOrigin;
|
const o = this.interaction.transformOrigin;
|
||||||
if (o.rotation === undefined || o.width === undefined || o.height === undefined || o.centerX === undefined || o.centerY === undefined) return;
|
if (!o) return;
|
||||||
|
|
||||||
const handle = this.interaction.resizeHandle;
|
const handle = this.interaction.resizeHandle;
|
||||||
const anchor = this.interaction.resizeAnchor;
|
const anchor = this.interaction.resizeAnchor;
|
||||||
@@ -974,7 +1021,7 @@ export class CanvasInteractions {
|
|||||||
if (!layer) return;
|
if (!layer) return;
|
||||||
|
|
||||||
const o = this.interaction.transformOrigin;
|
const o = this.interaction.transformOrigin;
|
||||||
if (o.rotation === undefined || o.centerX === undefined || o.centerY === undefined) return;
|
if (!o) return;
|
||||||
const startAngle = Math.atan2(this.interaction.dragStart.y - o.centerY, this.interaction.dragStart.x - o.centerX);
|
const startAngle = Math.atan2(this.interaction.dragStart.y - o.centerY, this.interaction.dragStart.x - o.centerX);
|
||||||
const currentAngle = Math.atan2(worldCoords.y - o.centerY, worldCoords.x - o.centerX);
|
const currentAngle = Math.atan2(worldCoords.y - o.centerY, worldCoords.x - o.centerX);
|
||||||
let angleDiff = (currentAngle - startAngle) * 180 / Math.PI;
|
let angleDiff = (currentAngle - startAngle) * 180 / Math.PI;
|
||||||
|
|||||||
Reference in New Issue
Block a user