From 176b9d03ac707a842b4a4ba5bbdafd944009c43b Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Fri, 8 Aug 2025 13:50:13 +0200 Subject: [PATCH] unify modifier key handling in CanvasInteractions Implemented centralized modifier state management with ModifierState interface and getModifierState() method. This eliminates inconsistencies between event-based and state-based modifier checking across mouse, wheel, and keyboard interactions. --- js/CanvasInteractions.js | 30 +++++++++++++++++++++--------- src/CanvasInteractions.ts | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index 7442b7a..96f72b1 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -49,6 +49,14 @@ export class CanvasInteractions { view: this.canvas.getMouseViewCoordinates(e) }; } + getModifierState(e) { + return { + ctrl: this.interaction.isCtrlPressed || e?.ctrlKey || false, + shift: this.interaction.isShiftPressed || e?.shiftKey || false, + alt: this.interaction.isAltPressed || e?.altKey || false, + meta: this.interaction.isMetaPressed || e?.metaKey || false, + }; + } preventEventDefaults(e) { e.preventDefault(); e.stopPropagation(); @@ -150,6 +158,7 @@ export class CanvasInteractions { handleMouseDown(e) { this.canvas.canvas.focus(); const coords = this.getMouseCoordinates(e); + const mods = this.getModifierState(e); if (this.interaction.mode === 'drawingMask') { this.canvas.maskTool.handleMouseDown(coords.world, coords.view); this.canvas.render(); @@ -161,11 +170,11 @@ export class CanvasInteractions { } // --- Ostateczna, poprawna kolejność sprawdzania --- // 1. Akcje globalne z modyfikatorami (mają najwyższy priorytet) - if (e.shiftKey && e.ctrlKey) { + if (mods.shift && mods.ctrl) { this.startCanvasMove(coords.world); return; } - if (e.shiftKey) { + if (mods.shift) { // Clear custom shape when starting canvas resize if (this.canvas.outputAreaShape) { // If auto-apply shape mask is enabled, remove the mask before clearing the shape @@ -350,14 +359,15 @@ export class CanvasInteractions { } } handleLayerWheelTransformation(e) { + const mods = this.getModifierState(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); + if (mods.shift) { + this.handleLayerRotation(layer, mods.ctrl, direction, rotationStep); } else { - this.handleLayerScaling(layer, e.ctrlKey, e.deltaY); + this.handleLayerScaling(layer, mods.ctrl, e.deltaY); } }); } @@ -446,11 +456,12 @@ export class CanvasInteractions { return; } // Globalne skróty (Undo/Redo/Copy/Paste) - if (e.ctrlKey || e.metaKey) { + const mods = this.getModifierState(e); + if (mods.ctrl || mods.meta) { let handled = true; switch (e.key.toLowerCase()) { case 'z': - if (e.shiftKey) { + if (mods.shift) { this.canvas.redo(); } else { @@ -477,7 +488,7 @@ export class CanvasInteractions { } // Skróty kontekstowe (zależne od zaznaczenia) if (this.canvas.canvasSelection.selectedLayers.length > 0) { - const step = e.shiftKey ? 10 : 1; + const step = mods.shift ? 10 : 1; let needsRender = false; // Używamy e.code dla spójności i niezależności od układu klawiatury const movementKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BracketLeft', 'BracketRight']; @@ -609,7 +620,8 @@ export class CanvasInteractions { prepareForDrag(layer, worldCoords) { // Zaktualizuj zaznaczenie, ale nie zapisuj stanu // Support both Ctrl (Windows/Linux) and Cmd (macOS) for multi-selection - if (this.interaction.isCtrlPressed || this.interaction.isMetaPressed) { + const mods = this.getModifierState(); + if (mods.ctrl || mods.meta) { const index = this.canvas.canvasSelection.selectedLayers.indexOf(layer); if (index === -1) { this.canvas.canvasSelection.updateSelection([...this.canvas.canvasSelection.selectedLayers, layer]); diff --git a/src/CanvasInteractions.ts b/src/CanvasInteractions.ts index 68fca65..8002c94 100644 --- a/src/CanvasInteractions.ts +++ b/src/CanvasInteractions.ts @@ -10,6 +10,13 @@ interface MouseCoordinates { view: Point; } +interface ModifierState { + ctrl: boolean; + shift: boolean; + alt: boolean; + meta: boolean; +} + interface TransformOrigin { x: number; y: number; @@ -100,6 +107,15 @@ export class CanvasInteractions { }; } + private getModifierState(e?: MouseEvent | WheelEvent | KeyboardEvent): ModifierState { + return { + ctrl: this.interaction.isCtrlPressed || (e as any)?.ctrlKey || false, + shift: this.interaction.isShiftPressed || (e as any)?.shiftKey || false, + alt: this.interaction.isAltPressed || (e as any)?.altKey || false, + meta: this.interaction.isMetaPressed || (e as any)?.metaKey || false, + }; + } + private preventEventDefaults(e: Event): void { e.preventDefault(); e.stopPropagation(); @@ -223,6 +239,7 @@ export class CanvasInteractions { handleMouseDown(e: MouseEvent): void { this.canvas.canvas.focus(); const coords = this.getMouseCoordinates(e); + const mods = this.getModifierState(e); if (this.interaction.mode === 'drawingMask') { this.canvas.maskTool.handleMouseDown(coords.world, coords.view); @@ -238,11 +255,11 @@ export class CanvasInteractions { // --- Ostateczna, poprawna kolejność sprawdzania --- // 1. Akcje globalne z modyfikatorami (mają najwyższy priorytet) - if (e.shiftKey && e.ctrlKey) { + if (mods.shift && mods.ctrl) { this.startCanvasMove(coords.world); return; } - if (e.shiftKey) { + if (mods.shift) { // Clear custom shape when starting canvas resize if (this.canvas.outputAreaShape) { // If auto-apply shape mask is enabled, remove the mask before clearing the shape @@ -454,14 +471,15 @@ export class CanvasInteractions { } private handleLayerWheelTransformation(e: WheelEvent): void { + const mods = this.getModifierState(e); 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); + if (mods.shift) { + this.handleLayerRotation(layer, mods.ctrl, direction, rotationStep); } else { - this.handleLayerScaling(layer, e.ctrlKey, e.deltaY); + this.handleLayerScaling(layer, mods.ctrl, e.deltaY); } }); } @@ -552,11 +570,12 @@ export class CanvasInteractions { } // Globalne skróty (Undo/Redo/Copy/Paste) - if (e.ctrlKey || e.metaKey) { + const mods = this.getModifierState(e); + if (mods.ctrl || mods.meta) { let handled = true; switch (e.key.toLowerCase()) { case 'z': - if (e.shiftKey) { + if (mods.shift) { this.canvas.redo(); } else { this.canvas.undo(); @@ -583,7 +602,7 @@ export class CanvasInteractions { // Skróty kontekstowe (zależne od zaznaczenia) if (this.canvas.canvasSelection.selectedLayers.length > 0) { - const step = e.shiftKey ? 10 : 1; + const step = mods.shift ? 10 : 1; let needsRender = false; // Używamy e.code dla spójności i niezależności od układu klawiatury @@ -719,7 +738,8 @@ export class CanvasInteractions { prepareForDrag(layer: Layer, worldCoords: Point): void { // Zaktualizuj zaznaczenie, ale nie zapisuj stanu // Support both Ctrl (Windows/Linux) and Cmd (macOS) for multi-selection - if (this.interaction.isCtrlPressed || this.interaction.isMetaPressed) { + const mods = this.getModifierState(); + if (mods.ctrl || mods.meta) { const index = this.canvas.canvasSelection.selectedLayers.indexOf(layer); if (index === -1) { this.canvas.canvasSelection.updateSelection([...this.canvas.canvasSelection.selectedLayers, layer]);