Refactor layer selection and movement logic

Centralizes layer movement logic in CanvasLayers with a new moveLayers function, supporting both up/down and drag-and-drop reordering. Updates selection logic in Canvas to only trigger updates when selection changes, and improves event handling in CanvasLayersPanel for more responsive selection and drag operations. Removes redundant moveLayersToPosition method in favor of the new unified approach.
This commit is contained in:
Dariusz L
2025-07-02 08:41:18 +02:00
parent a73a3dcf96
commit b3d1206f3f
3 changed files with 98 additions and 57 deletions

View File

@@ -373,6 +373,7 @@ export class Canvas {
*/
updateSelectionLogic(layer, isCtrlPressed, isShiftPressed, index) {
let newSelection = [...this.selectedLayers];
let selectionChanged = false;
if (isShiftPressed && this.canvasLayersPanel.lastSelectedIndex !== -1) {
const sortedLayers = [...this.layers].sort((a, b) => b.zIndex - a.zIndex);
@@ -385,6 +386,7 @@ export class Canvas {
newSelection.push(sortedLayers[i]);
}
}
selectionChanged = true;
} else if (isCtrlPressed) {
const layerIndex = newSelection.indexOf(layer);
if (layerIndex === -1) {
@@ -392,12 +394,24 @@ export class Canvas {
} else {
newSelection.splice(layerIndex, 1);
}
this.canvasLayersPanel.lastSelectedIndex = index;
selectionChanged = true;
} else {
newSelection = [layer];
// Jeśli kliknięta warstwa nie jest częścią obecnego zaznaczenia,
// wyczyść zaznaczenie i zaznacz tylko ją.
if (!this.selectedLayers.includes(layer)) {
newSelection = [layer];
selectionChanged = true;
}
// Jeśli kliknięta warstwa JEST już zaznaczona (potencjalnie z innymi),
// NIE rób nic, aby umożliwić przeciąganie całej grupy.
this.canvasLayersPanel.lastSelectedIndex = index;
}
this.updateSelection(newSelection);
// Aktualizuj zaznaczenie tylko jeśli faktycznie się zmieniło
if (selectionChanged) {
this.updateSelection(newSelection);
}
}
/**

View File

@@ -200,40 +200,93 @@ export class CanvasLayers {
return this.addLayerWithImage(image);
}
moveLayerUp() {
if (this.canvas.selectedLayers.length === 0) return;
const selectedIndicesSet = new Set(this.canvas.selectedLayers.map(layer => this.canvas.layers.indexOf(layer)));
/**
* Centralna funkcja do przesuwania warstw.
* @param {Array} layersToMove - Tablica warstw do przesunięcia.
* @param {Object} options - Opcje przesunięcia, np. { direction: 'up' } lub { toIndex: 3 }
*/
moveLayers(layersToMove, options = {}) {
if (!layersToMove || layersToMove.length === 0) return;
const sortedIndices = Array.from(selectedIndicesSet).sort((a, b) => b - a);
let finalLayers;
sortedIndices.forEach(index => {
const targetIndex = index + 1;
if (targetIndex < this.canvas.layers.length && !selectedIndicesSet.has(targetIndex)) {
[this.canvas.layers[index], this.canvas.layers[targetIndex]] = [this.canvas.layers[targetIndex], this.canvas.layers[index]];
if (options.direction) {
// Logika dla 'up' i 'down'
const allLayers = [...this.canvas.layers];
const selectedIndices = new Set(layersToMove.map(l => allLayers.indexOf(l)));
if (options.direction === 'up') {
const sorted = Array.from(selectedIndices).sort((a, b) => b - a);
sorted.forEach(index => {
const targetIndex = index + 1;
if (targetIndex < allLayers.length && !selectedIndices.has(targetIndex)) {
[allLayers[index], allLayers[targetIndex]] = [allLayers[targetIndex], allLayers[index]];
}
});
} else if (options.direction === 'down') {
const sorted = Array.from(selectedIndices).sort((a, b) => a - b);
sorted.forEach(index => {
const targetIndex = index - 1;
if (targetIndex >= 0 && !selectedIndices.has(targetIndex)) {
[allLayers[index], allLayers[targetIndex]] = [allLayers[targetIndex], allLayers[index]];
}
});
}
finalLayers = allLayers;
} else if (options.toIndex !== undefined) {
// Logika dla przeciągania i upuszczania (z panelu)
const displayedLayers = [...this.canvas.layers].sort((a, b) => b.zIndex - a.zIndex);
const reorderedFinal = [];
let inserted = false;
for (let i = 0; i < displayedLayers.length; i++) {
if (i === options.toIndex) {
reorderedFinal.push(...layersToMove);
inserted = true;
}
const currentLayer = displayedLayers[i];
if (!layersToMove.includes(currentLayer)) {
reorderedFinal.push(currentLayer);
}
}
if (!inserted) {
reorderedFinal.push(...layersToMove);
}
finalLayers = reorderedFinal;
} else {
log.warn("Invalid options for moveLayers", options);
return;
}
// Zunifikowana końcówka: aktualizacja zIndex i stanu aplikacji
const totalLayers = finalLayers.length;
finalLayers.forEach((layer, index) => {
// Jeśli przyszły z panelu, zIndex jest odwrócony
const zIndex = (options.toIndex !== undefined) ? (totalLayers - 1 - index) : index;
layer.zIndex = zIndex;
});
this.canvas.layers.forEach((layer, i) => layer.zIndex = i);
this.canvas.layers = finalLayers;
this.canvas.layers.sort((a, b) => a.zIndex - b.zIndex);
if (this.canvas.canvasLayersPanel) {
this.canvas.canvasLayersPanel.onLayersChanged();
}
this.canvas.render();
this.canvas.saveState();
log.info(`Moved ${layersToMove.length} layer(s).`);
}
moveLayerUp() {
if (this.canvas.selectedLayers.length === 0) return;
this.moveLayers(this.canvas.selectedLayers, { direction: 'up' });
}
moveLayerDown() {
if (this.canvas.selectedLayers.length === 0) return;
const selectedIndicesSet = new Set(this.canvas.selectedLayers.map(layer => this.canvas.layers.indexOf(layer)));
const sortedIndices = Array.from(selectedIndicesSet).sort((a, b) => a - b);
sortedIndices.forEach(index => {
const targetIndex = index - 1;
if (targetIndex >= 0 && !selectedIndicesSet.has(targetIndex)) {
[this.canvas.layers[index], this.canvas.layers[targetIndex]] = [this.canvas.layers[targetIndex], this.canvas.layers[index]];
}
});
this.canvas.layers.forEach((layer, i) => layer.zIndex = i);
this.canvas.render();
this.canvas.saveState();
this.moveLayers(this.canvas.selectedLayers, { direction: 'down' });
}
/**

View File

@@ -365,12 +365,11 @@ export class CanvasLayersPanel {
* Konfiguruje event listenery dla elementu warstwy
*/
setupLayerEventListeners(layerRow, layer, index) {
// Click handler - natychmiastowe zaznaczanie
layerRow.addEventListener('click', (e) => {
// Mousedown handler - zaznaczanie w momencie wciśnięcia przycisku
layerRow.addEventListener('mousedown', (e) => {
// Ignoruj, jeśli edytujemy nazwę
const nameElement = layerRow.querySelector('.layer-name');
if (nameElement && nameElement.classList.contains('editing')) {
e.preventDefault();
e.stopPropagation();
return;
}
this.handleLayerClick(e, layer, index);
@@ -395,9 +394,6 @@ export class CanvasLayersPanel {
* Obsługuje kliknięcie na warstwę, aktualizując stan bez pełnego renderowania.
*/
handleLayerClick(e, layer, index) {
// Zatrzymujemy, bo dblclick też wywołałby click
e.preventDefault();
const isCtrlPressed = e.ctrlKey || e.metaKey;
const isShiftPressed = e.shiftKey;
@@ -599,7 +595,8 @@ export class CanvasLayersPanel {
insertIndex = targetIndex + 1;
}
this.moveLayersToPosition(this.draggedElements, insertIndex);
// Użyj nowej, centralnej funkcji do przesuwania warstw
this.canvas.canvasLayers.moveLayers(this.draggedElements, { toIndex: insertIndex });
log.info(`Dropped ${this.draggedElements.length} layers at position ${insertIndex}`);
}
@@ -618,29 +615,6 @@ export class CanvasLayersPanel {
this.draggedElements = [];
}
/**
* Przenosi warstwy na nową pozycję
*/
moveLayersToPosition(layers, insertIndex) {
const sortedLayers = [...this.canvas.layers].sort((a, b) => a.zIndex - b.zIndex);
// Usuń przeciągane warstwy z listy
const filteredLayers = sortedLayers.filter(layer => !layers.includes(layer));
// Wstaw warstwy w nowej pozycji (odwróć kolejność bo renderujemy od góry)
const reverseInsertIndex = filteredLayers.length - insertIndex;
filteredLayers.splice(reverseInsertIndex, 0, ...layers);
// Zaktualizuj zIndex dla wszystkich warstw
filteredLayers.forEach((layer, index) => {
layer.zIndex = index;
});
this.canvas.layers = filteredLayers;
this.canvas.render();
this.renderLayers();
this.canvas.saveState();
}
/**
* Aktualizuje panel gdy zmienią się warstwy