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.
This commit is contained in:
Dariusz L
2025-06-28 02:26:06 +02:00
parent f8f7583c1c
commit 3957aa0f61
5 changed files with 32 additions and 50 deletions

View File

@@ -22,7 +22,7 @@ export class Canvas {
this.selectedLayer = null; this.selectedLayer = null;
this.selectedLayers = []; this.selectedLayers = [];
this.onSelectionChange = null; this.onSelectionChange = null;
this.onInteractionEnd = callbacks.onInteractionEnd || null; this.onStateChange = callbacks.onStateChange || null;
this.lastMousePosition = {x: 0, y: 0}; this.lastMousePosition = {x: 0, y: 0};
this.viewport = { this.viewport = {
@@ -78,19 +78,28 @@ export class Canvas {
this.render(); this.render();
} }
_notifyStateChange() {
if (this.onStateChange) {
this.onStateChange();
}
}
saveState(replaceLast = false) { saveState(replaceLast = false) {
this.canvasState.saveState(replaceLast); this.canvasState.saveState(replaceLast);
this.incrementOperationCount(); this.incrementOperationCount();
this._notifyStateChange();
} }
undo() { undo() {
this.canvasState.undo(); this.canvasState.undo();
this.incrementOperationCount(); this.incrementOperationCount();
this._notifyStateChange();
} }
redo() { redo() {
this.canvasState.redo(); this.canvasState.redo();
this.incrementOperationCount(); this.incrementOperationCount();
this._notifyStateChange();
} }
updateSelectionAfterHistory() { 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) { getMouseWorldCoordinates(e) {
const rect = this.canvas.getBoundingClientRect(); const rect = this.canvas.getBoundingClientRect();

View File

@@ -727,7 +727,7 @@ export class CanvasIO {
this.canvas.layers.push(layer); this.canvas.layers.push(layer);
this.canvas.selectedLayer = layer; this.canvas.selectedLayer = layer;
this.canvas.render(); this.canvas.render();
this.canvas.saveState();
} catch (error) { } catch (error) {
log.error('Error importing image:', error); log.error('Error importing image:', error);
} }

View File

@@ -172,9 +172,6 @@ export class CanvasInteractions {
if (interactionEnded) { if (interactionEnded) {
this.canvas.saveState(); this.canvas.saveState();
this.canvas.saveStateToDB(true); this.canvas.saveStateToDB(true);
if (this.canvas.onInteractionEnd) {
this.canvas.onInteractionEnd();
}
} }
} }

View File

@@ -158,6 +158,7 @@ export class CanvasLayers {
this.canvasLayers.layers.splice(index, 1); this.canvasLayers.layers.splice(index, 1);
this.canvasLayers.selectedLayer = this.canvasLayers.layers[this.canvasLayers.layers.length - 1] || null; this.canvasLayers.selectedLayer = this.canvasLayers.layers[this.canvasLayers.layers.length - 1] || null;
this.canvasLayers.render(); this.canvasLayers.render();
this.canvasLayers.saveState();
} }
} }

View File

@@ -12,7 +12,7 @@ const log = createModuleLogger('Canvas_view');
async function createCanvasWidget(node, widget, app) { async function createCanvasWidget(node, widget, app) {
const canvas = new Canvas(node, widget, { const canvas = new Canvas(node, widget, {
onInteractionEnd: () => updateOutput() onStateChange: () => updateOutput()
}); });
const imageCache = new ImageCache(); const imageCache = new ImageCache();
@@ -375,11 +375,10 @@ async function createCanvasWidget(node, widget, app) {
input.onchange = async (e) => { input.onchange = async (e) => {
for (const file of e.target.files) { for (const file of e.target.files) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async (event) => { reader.onload = (event) => {
const img = new Image(); const img = new Image();
img.onload = async () => { img.onload = () => {
canvas.addLayer(img); canvas.addLayer(img);
await updateOutput();
}; };
img.src = event.target.result; img.src = event.target.result;
}; };
@@ -391,11 +390,7 @@ async function createCanvasWidget(node, widget, app) {
}), }),
$el("button.painter-button.primary", { $el("button.painter-button.primary", {
textContent: "Import Input", textContent: "Import Input",
onclick: async () => { onclick: () => canvas.importLatestImage()
if (await canvas.importLatestImage()) {
await updateOutput();
}
}
}), }),
$el("button.painter-button.primary", { $el("button.painter-button.primary", {
textContent: "Paste Image", textContent: "Paste Image",
@@ -481,6 +476,7 @@ async function createCanvasWidget(node, widget, app) {
const height = parseInt(document.getElementById('canvas-height').value) || canvas.height; const height = parseInt(document.getElementById('canvas-height').value) || canvas.height;
canvas.updateOutputAreaSize(width, height); canvas.updateOutputAreaSize(width, height);
document.body.removeChild(dialog); document.body.removeChild(dialog);
// updateOutput is triggered by saveState in updateOutputAreaSize
}; };
document.getElementById('cancel-size').onclick = () => { document.getElementById('cancel-size').onclick = () => {
@@ -490,27 +486,15 @@ async function createCanvasWidget(node, widget, app) {
}), }),
$el("button.painter-button.requires-selection", { $el("button.painter-button.requires-selection", {
textContent: "Remove Layer", textContent: "Remove Layer",
onclick: () => { onclick: () => canvas.removeSelectedLayers()
if (canvas.selectedLayers.length > 0) {
canvas.saveState();
canvas.layers = canvas.layers.filter(l => !canvas.selectedLayers.includes(l));
canvas.updateSelection([]);
canvas.render();
canvas.saveState();
}
}
}), }),
$el("button.painter-button.requires-selection", { $el("button.painter-button.requires-selection", {
textContent: "Layer Up", textContent: "Layer Up",
onclick: async () => { onclick: () => canvas.moveLayerUp()
canvas.moveLayerUp();
}
}), }),
$el("button.painter-button.requires-selection", { $el("button.painter-button.requires-selection", {
textContent: "Layer Down", textContent: "Layer Down",
onclick: async () => { onclick: () => canvas.moveLayerDown()
canvas.moveLayerDown();
}
}), }),
]), ]),
@@ -574,7 +558,6 @@ async function createCanvasWidget(node, widget, app) {
canvas.updateSelection([newLayer]); canvas.updateSelection([newLayer]);
canvas.render(); canvas.render();
canvas.saveState(); canvas.saveState();
await updateOutput();
} catch (error) { } catch (error) {
log.error("Matting error:", error); log.error("Matting error:", error);
alert(`Error during matting process: ${error.message}`); 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 triggerWidget = node.widgets.find(w => w.name === "trigger");
const updateOutput = async () => { const updateOutput = () => {
triggerWidget.value = (triggerWidget.value + 1) % 99999999; 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", { const canvasContainer = $el("div.painterCanvasContainer.painter-container", {
style: { style: {
position: "absolute", position: "absolute",
@@ -833,7 +799,6 @@ async function createCanvasWidget(node, widget, app) {
canvas.render(); canvas.render();
canvas.saveState(); canvas.saveState();
log.info("Dropped layer added and state saved."); log.info("Dropped layer added and state saved.");
await updateOutput();
}; };
img.src = event.target.result; img.src = event.target.result;
}; };