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.
This commit is contained in:
Dariusz L
2025-08-08 13:50:13 +02:00
parent e4f44c10e8
commit 176b9d03ac
2 changed files with 50 additions and 18 deletions

View File

@@ -49,6 +49,14 @@ export class CanvasInteractions {
view: this.canvas.getMouseViewCoordinates(e) 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) { preventEventDefaults(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -150,6 +158,7 @@ export class CanvasInteractions {
handleMouseDown(e) { handleMouseDown(e) {
this.canvas.canvas.focus(); this.canvas.canvas.focus();
const coords = this.getMouseCoordinates(e); const coords = this.getMouseCoordinates(e);
const mods = this.getModifierState(e);
if (this.interaction.mode === 'drawingMask') { if (this.interaction.mode === 'drawingMask') {
this.canvas.maskTool.handleMouseDown(coords.world, coords.view); this.canvas.maskTool.handleMouseDown(coords.world, coords.view);
this.canvas.render(); this.canvas.render();
@@ -161,11 +170,11 @@ export class CanvasInteractions {
} }
// --- 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 (mods.shift && mods.ctrl) {
this.startCanvasMove(coords.world); this.startCanvasMove(coords.world);
return; return;
} }
if (e.shiftKey) { if (mods.shift) {
// Clear custom shape when starting canvas resize // Clear custom shape when starting canvas resize
if (this.canvas.outputAreaShape) { if (this.canvas.outputAreaShape) {
// If auto-apply shape mask is enabled, remove the mask before clearing the shape // If auto-apply shape mask is enabled, remove the mask before clearing the shape
@@ -350,14 +359,15 @@ export class CanvasInteractions {
} }
} }
handleLayerWheelTransformation(e) { handleLayerWheelTransformation(e) {
const mods = this.getModifierState(e);
const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1); const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1);
const direction = e.deltaY < 0 ? 1 : -1; const direction = e.deltaY < 0 ? 1 : -1;
this.canvas.canvasSelection.selectedLayers.forEach((layer) => { this.canvas.canvasSelection.selectedLayers.forEach((layer) => {
if (e.shiftKey) { if (mods.shift) {
this.handleLayerRotation(layer, e.ctrlKey, direction, rotationStep); this.handleLayerRotation(layer, mods.ctrl, direction, rotationStep);
} }
else { else {
this.handleLayerScaling(layer, e.ctrlKey, e.deltaY); this.handleLayerScaling(layer, mods.ctrl, e.deltaY);
} }
}); });
} }
@@ -446,11 +456,12 @@ export class CanvasInteractions {
return; return;
} }
// Globalne skróty (Undo/Redo/Copy/Paste) // 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; let handled = true;
switch (e.key.toLowerCase()) { switch (e.key.toLowerCase()) {
case 'z': case 'z':
if (e.shiftKey) { if (mods.shift) {
this.canvas.redo(); this.canvas.redo();
} }
else { else {
@@ -477,7 +488,7 @@ export class CanvasInteractions {
} }
// Skróty kontekstowe (zależne od zaznaczenia) // Skróty kontekstowe (zależne od zaznaczenia)
if (this.canvas.canvasSelection.selectedLayers.length > 0) { if (this.canvas.canvasSelection.selectedLayers.length > 0) {
const step = e.shiftKey ? 10 : 1; const step = mods.shift ? 10 : 1;
let needsRender = false; let needsRender = false;
// Używamy e.code dla spójności i niezależności od układu klawiatury // Używamy e.code dla spójności i niezależności od układu klawiatury
const movementKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BracketLeft', 'BracketRight']; const movementKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BracketLeft', 'BracketRight'];
@@ -609,7 +620,8 @@ export class CanvasInteractions {
prepareForDrag(layer, worldCoords) { prepareForDrag(layer, worldCoords) {
// Zaktualizuj zaznaczenie, ale nie zapisuj stanu // Zaktualizuj zaznaczenie, ale nie zapisuj stanu
// Support both Ctrl (Windows/Linux) and Cmd (macOS) for multi-selection // 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); 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]);

View File

@@ -10,6 +10,13 @@ interface MouseCoordinates {
view: Point; view: Point;
} }
interface ModifierState {
ctrl: boolean;
shift: boolean;
alt: boolean;
meta: boolean;
}
interface TransformOrigin { interface TransformOrigin {
x: number; x: number;
y: 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 { private preventEventDefaults(e: Event): void {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -223,6 +239,7 @@ export class CanvasInteractions {
handleMouseDown(e: MouseEvent): void { handleMouseDown(e: MouseEvent): void {
this.canvas.canvas.focus(); this.canvas.canvas.focus();
const coords = this.getMouseCoordinates(e); const coords = this.getMouseCoordinates(e);
const mods = this.getModifierState(e);
if (this.interaction.mode === 'drawingMask') { if (this.interaction.mode === 'drawingMask') {
this.canvas.maskTool.handleMouseDown(coords.world, coords.view); this.canvas.maskTool.handleMouseDown(coords.world, coords.view);
@@ -238,11 +255,11 @@ export class CanvasInteractions {
// --- 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 (mods.shift && mods.ctrl) {
this.startCanvasMove(coords.world); this.startCanvasMove(coords.world);
return; return;
} }
if (e.shiftKey) { if (mods.shift) {
// Clear custom shape when starting canvas resize // Clear custom shape when starting canvas resize
if (this.canvas.outputAreaShape) { if (this.canvas.outputAreaShape) {
// If auto-apply shape mask is enabled, remove the mask before clearing the shape // 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 { private handleLayerWheelTransformation(e: WheelEvent): void {
const mods = this.getModifierState(e);
const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1); const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1);
const direction = e.deltaY < 0 ? 1 : -1; const direction = e.deltaY < 0 ? 1 : -1;
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => { this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => {
if (e.shiftKey) { if (mods.shift) {
this.handleLayerRotation(layer, e.ctrlKey, direction, rotationStep); this.handleLayerRotation(layer, mods.ctrl, direction, rotationStep);
} else { } 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) // 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; let handled = true;
switch (e.key.toLowerCase()) { switch (e.key.toLowerCase()) {
case 'z': case 'z':
if (e.shiftKey) { if (mods.shift) {
this.canvas.redo(); this.canvas.redo();
} else { } else {
this.canvas.undo(); this.canvas.undo();
@@ -583,7 +602,7 @@ export class CanvasInteractions {
// Skróty kontekstowe (zależne od zaznaczenia) // Skróty kontekstowe (zależne od zaznaczenia)
if (this.canvas.canvasSelection.selectedLayers.length > 0) { if (this.canvas.canvasSelection.selectedLayers.length > 0) {
const step = e.shiftKey ? 10 : 1; const step = mods.shift ? 10 : 1;
let needsRender = false; let needsRender = false;
// Używamy e.code dla spójności i niezależności od układu klawiatury // Używamy e.code dla spójności i niezależności od układu klawiatury
@@ -719,7 +738,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
// Support both Ctrl (Windows/Linux) and Cmd (macOS) for multi-selection // 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); 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]);