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.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();

View File

@@ -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);
}

View File

@@ -172,9 +172,6 @@ export class CanvasInteractions {
if (interactionEnded) {
this.canvas.saveState();
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.selectedLayer = this.canvasLayers.layers[this.canvasLayers.layers.length - 1] || null;
this.canvasLayers.render();
this.canvasLayers.saveState();
}
}

View File

@@ -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;
};