From 3957aa0f61cef9ca6b55a370151c64e8e646e1c2 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sat, 28 Jun 2025 02:26:06 +0200 Subject: [PATCH] Refactor canvas state change handling and layer removal Replaces the onInteractionEnd callback with onStateChange for more consistent state change notifications. Adds a removeSelectedLayers method to Canvas for cleaner layer removal logic. Updates UI event handlers to use the new methods and callbacks, and ensures state is saved after relevant operations. Cleans up redundant updateOutput calls and streamlines output update logic. --- js/Canvas.js | 21 ++++++++++++++- js/CanvasIO.js | 2 +- js/CanvasInteractions.js | 3 --- js/CanvasLayers.js | 1 + js/CanvasView.js | 55 ++++++++-------------------------------- 5 files changed, 32 insertions(+), 50 deletions(-) diff --git a/js/Canvas.js b/js/Canvas.js index 6c346f1..a92735c 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -22,7 +22,7 @@ export class Canvas { this.selectedLayer = null; this.selectedLayers = []; this.onSelectionChange = null; - this.onInteractionEnd = callbacks.onInteractionEnd || null; + this.onStateChange = callbacks.onStateChange || null; this.lastMousePosition = {x: 0, y: 0}; this.viewport = { @@ -78,19 +78,28 @@ export class Canvas { this.render(); } + _notifyStateChange() { + if (this.onStateChange) { + this.onStateChange(); + } + } + saveState(replaceLast = false) { this.canvasState.saveState(replaceLast); this.incrementOperationCount(); + this._notifyStateChange(); } undo() { this.canvasState.undo(); this.incrementOperationCount(); + this._notifyStateChange(); } redo() { this.canvasState.redo(); this.incrementOperationCount(); + this._notifyStateChange(); } updateSelectionAfterHistory() { @@ -210,6 +219,16 @@ export class Canvas { } } + removeSelectedLayers() { + if (this.selectedLayers.length > 0) { + this.saveState(); + this.layers = this.layers.filter(l => !this.selectedLayers.includes(l)); + this.updateSelection([]); + this.render(); + this.saveState(); + } + } + getMouseWorldCoordinates(e) { const rect = this.canvas.getBoundingClientRect(); diff --git a/js/CanvasIO.js b/js/CanvasIO.js index c029b0a..05d06c1 100644 --- a/js/CanvasIO.js +++ b/js/CanvasIO.js @@ -727,7 +727,7 @@ export class CanvasIO { this.canvas.layers.push(layer); this.canvas.selectedLayer = layer; this.canvas.render(); - + this.canvas.saveState(); } catch (error) { log.error('Error importing image:', error); } diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index e730ac8..6132964 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -172,9 +172,6 @@ export class CanvasInteractions { if (interactionEnded) { this.canvas.saveState(); this.canvas.saveStateToDB(true); - if (this.canvas.onInteractionEnd) { - this.canvas.onInteractionEnd(); - } } } diff --git a/js/CanvasLayers.js b/js/CanvasLayers.js index 77714c3..072394c 100644 --- a/js/CanvasLayers.js +++ b/js/CanvasLayers.js @@ -158,6 +158,7 @@ export class CanvasLayers { this.canvasLayers.layers.splice(index, 1); this.canvasLayers.selectedLayer = this.canvasLayers.layers[this.canvasLayers.layers.length - 1] || null; this.canvasLayers.render(); + this.canvasLayers.saveState(); } } diff --git a/js/CanvasView.js b/js/CanvasView.js index 3358786..2c8dac0 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -12,7 +12,7 @@ const log = createModuleLogger('Canvas_view'); async function createCanvasWidget(node, widget, app) { const canvas = new Canvas(node, widget, { - onInteractionEnd: () => updateOutput() + onStateChange: () => updateOutput() }); const imageCache = new ImageCache(); @@ -375,11 +375,10 @@ async function createCanvasWidget(node, widget, app) { input.onchange = async (e) => { for (const file of e.target.files) { const reader = new FileReader(); - reader.onload = async (event) => { + reader.onload = (event) => { const img = new Image(); - img.onload = async () => { + img.onload = () => { canvas.addLayer(img); - await updateOutput(); }; img.src = event.target.result; }; @@ -391,11 +390,7 @@ async function createCanvasWidget(node, widget, app) { }), $el("button.painter-button.primary", { textContent: "Import Input", - onclick: async () => { - if (await canvas.importLatestImage()) { - await updateOutput(); - } - } + onclick: () => canvas.importLatestImage() }), $el("button.painter-button.primary", { textContent: "Paste Image", @@ -481,6 +476,7 @@ async function createCanvasWidget(node, widget, app) { const height = parseInt(document.getElementById('canvas-height').value) || canvas.height; canvas.updateOutputAreaSize(width, height); document.body.removeChild(dialog); + // updateOutput is triggered by saveState in updateOutputAreaSize }; document.getElementById('cancel-size').onclick = () => { @@ -490,27 +486,15 @@ async function createCanvasWidget(node, widget, app) { }), $el("button.painter-button.requires-selection", { textContent: "Remove Layer", - onclick: () => { - if (canvas.selectedLayers.length > 0) { - canvas.saveState(); - canvas.layers = canvas.layers.filter(l => !canvas.selectedLayers.includes(l)); - canvas.updateSelection([]); - canvas.render(); - canvas.saveState(); - } - } + onclick: () => canvas.removeSelectedLayers() }), $el("button.painter-button.requires-selection", { textContent: "Layer Up", - onclick: async () => { - canvas.moveLayerUp(); - } + onclick: () => canvas.moveLayerUp() }), $el("button.painter-button.requires-selection", { textContent: "Layer Down", - onclick: async () => { - canvas.moveLayerDown(); - } + onclick: () => canvas.moveLayerDown() }), ]), @@ -574,7 +558,6 @@ async function createCanvasWidget(node, widget, app) { canvas.updateSelection([newLayer]); canvas.render(); canvas.saveState(); - await updateOutput(); } catch (error) { log.error("Matting error:", error); alert(`Error during matting process: ${error.message}`); @@ -743,28 +726,11 @@ async function createCanvasWidget(node, widget, app) { const triggerWidget = node.widgets.find(w => w.name === "trigger"); - const updateOutput = async () => { - - + const updateOutput = () => { triggerWidget.value = (triggerWidget.value + 1) % 99999999; - app.graph.runStep(); + // app.graph.runStep(); // Potentially not needed if we just want to mark dirty }; - const addUpdateToButton = (button) => { - if (button.textContent === "Undo" || button.textContent === "Redo" || button.title === "Open in Editor") { - return; - } - const origClick = button.onclick; - button.onclick = async (...args) => { - if (origClick) { - await origClick(...args); - } - await updateOutput(); - }; - }; - - controlPanel.querySelectorAll('button').forEach(addUpdateToButton); - const canvasContainer = $el("div.painterCanvasContainer.painter-container", { style: { position: "absolute", @@ -833,7 +799,6 @@ async function createCanvasWidget(node, widget, app) { canvas.render(); canvas.saveState(); log.info("Dropped layer added and state saved."); - await updateOutput(); }; img.src = event.target.result; };