Files
2024-07-25 16:38:04 -03:00

1055 lines
33 KiB
JavaScript

import { app, ComfyApp, ANIM_PREVIEW_WIDGET } from "../../scripts/app.js";
import { $el } from "../../scripts/ui.js";
import { api } from "../../scripts/api.js";
import { createImageHost, calculateImageGrid } from "../../scripts/ui/imagePreview.js";
function patchLiteGraph() {
var temp_vec2 = new Float32Array(2);
LGraphCanvas.prototype.drawNode = function(node, ctx) {
var glow = false;
this.current_node = node;
var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR;
var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;
//shadow and glow
if (node.mouseOver) {
glow = true;
}
var low_quality = this.ds.scale < 0.6; //zoomed out
//only render if it forces it to do it
if (this.live_mode) {
if (!node.flags.collapsed) {
ctx.shadowColor = "transparent";
if (node.onDrawForeground) {
node.onDrawForeground(ctx, this, this.canvas);
}
}
return;
}
var editor_alpha = this.editor_alpha;
ctx.globalAlpha = editor_alpha;
if (this.render_shadows && !low_quality) {
ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;
ctx.shadowOffsetX = 2 * this.ds.scale;
ctx.shadowOffsetY = 2 * this.ds.scale;
ctx.shadowBlur = 3 * this.ds.scale;
} else {
ctx.shadowColor = "transparent";
}
//custom draw collapsed method (draw after shadows because they are affected)
if (
node.flags.collapsed &&
node.onDrawCollapsed &&
node.onDrawCollapsed(ctx, this) == true
) {
return;
}
//clip if required (mask)
var shape = node._shape || LiteGraph.BOX_SHAPE;
var size = temp_vec2;
temp_vec2.set(node.size);
var horizontal = node.horizontal; // || node.flags.horizontal;
if (node.flags.collapsed) {
ctx.font = this.inner_text_font;
var title = node.getTitle ? node.getTitle() : node.title;
if (title != null) {
node._collapsed_width = Math.min(
node.size[0],
ctx.measureText(title).width +
LiteGraph.NODE_TITLE_HEIGHT * 2
); //LiteGraph.NODE_COLLAPSED_WIDTH;
size[0] = node._collapsed_width;
size[1] = 0;
}
}
if (node.clip_area) {
//Start clipping
ctx.save();
ctx.beginPath();
if (shape == LiteGraph.BOX_SHAPE) {
ctx.rect(0, 0, size[0], size[1]);
} else if (shape == LiteGraph.ROUND_SHAPE) {
ctx.roundRect(0, 0, size[0], size[1], [10]);
} else if (shape == LiteGraph.CIRCLE_SHAPE) {
ctx.arc(
size[0] * 0.5,
size[1] * 0.5,
size[0] * 0.5,
0,
Math.PI * 2
);
}
ctx.clip();
}
//draw shape
if (node.has_errors) {
bgcolor = "red";
}
this.drawNodeShape(
node,
ctx,
size,
color,
bgcolor,
node.is_selected,
node.mouseOver
);
ctx.shadowColor = "transparent";
//draw foreground
if (node.onDrawForeground) {
node.onDrawForeground(ctx, this, this.canvas);
}
//connection slots
ctx.textAlign = horizontal ? "center" : "left";
ctx.font = this.inner_text_font;
var render_text = !low_quality;
var out_slot = this.connecting_output;
var in_slot = this.connecting_input;
ctx.lineWidth = 1;
var max_y = 0;
var slot_pos = new Float32Array(2); //to reuse
//render inputs and outputs
if (!node.flags.collapsed) {
//input connection slots
if (node.inputs) {
for (var i = 0; i < node.inputs.length; i++) {
var slot = node.inputs[i];
var slot_type = slot.type;
var slot_shape = slot.shape;
ctx.globalAlpha = editor_alpha;
//change opacity of incompatible slots when dragging a connection
if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) {
ctx.globalAlpha = 0.4 * editor_alpha;
}
ctx.fillStyle =
slot.link != null
? slot.color_on ||
this.default_connection_color_byType[slot_type] ||
this.default_connection_color.input_on
: slot.color_off ||
this.default_connection_color_byTypeOff[slot_type] ||
this.default_connection_color_byType[slot_type] ||
this.default_connection_color.input_off;
var pos = node.getConnectionPos(true, i, slot_pos);
pos[0] -= node.pos[0];
pos[1] -= node.pos[1];
if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {
max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;
}
ctx.beginPath();
if (slot_type == "array"){
slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead?
}
var doStroke = true;
if (
slot.type === LiteGraph.EVENT ||
slot.shape === LiteGraph.BOX_SHAPE
) {
if (horizontal) {
ctx.rect(
pos[0] - 5 + 0.5,
pos[1] - 8 + 0.5,
10,
14
);
} else {
ctx.rect(
pos[0] - 6 + 0.5,
pos[1] - 5 + 0.5,
14,
10
);
}
} else if (slot_shape === LiteGraph.ARROW_SHAPE) {
ctx.moveTo(pos[0] + 8, pos[1] + 0.5);
ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);
ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);
ctx.closePath();
} else if (slot_shape === LiteGraph.GRID_SHAPE) {
ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);
ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);
ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);
ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);
ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);
ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);
ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);
ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);
ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);
doStroke = false;
} else {
if(low_quality)
ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster
else
ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);
}
ctx.fill();
//render name
if (render_text) {
var text = slot.label != null ? slot.label : slot.name;
if (text) {
ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;
if (horizontal || slot.dir == LiteGraph.UP) {
ctx.fillText(text, pos[0], pos[1] - 10);
} else {
ctx.fillText(text, pos[0] + 10, pos[1] + 5);
}
}
}
}
}
//output connection slots
ctx.textAlign = horizontal ? "center" : "right";
ctx.strokeStyle = "black";
if (node.outputs) {
for (var i = 0; i < node.outputs.length; i++) {
var slot = node.outputs[i];
var slot_type = slot.type;
var slot_shape = slot.shape;
//change opacity of incompatible slots when dragging a connection
if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) {
ctx.globalAlpha = 0.4 * editor_alpha;
}
var pos = node.getConnectionPos(false, i, slot_pos);
pos[0] -= node.pos[0];
pos[1] -= node.pos[1];
if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {
max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;
}
ctx.fillStyle =
slot.links && slot.links.length
? slot.color_on ||
this.default_connection_color_byType[slot_type] ||
this.default_connection_color.output_on
: slot.color_off ||
this.default_connection_color_byTypeOff[slot_type] ||
this.default_connection_color_byType[slot_type] ||
this.default_connection_color.output_off;
ctx.beginPath();
//ctx.rect( node.size[0] - 14,i*14,10,10);
if (slot_type == "array"){
slot_shape = LiteGraph.GRID_SHAPE;
}
var doStroke = true;
if (
slot_type === LiteGraph.EVENT ||
slot_shape === LiteGraph.BOX_SHAPE
) {
if (horizontal) {
ctx.rect(
pos[0] - 5 + 0.5,
pos[1] - 8 + 0.5,
10,
14
);
} else {
ctx.rect(
pos[0] - 6 + 0.5,
pos[1] - 5 + 0.5,
14,
10
);
}
} else if (slot_shape === LiteGraph.ARROW_SHAPE) {
ctx.moveTo(pos[0] + 8, pos[1] + 0.5);
ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);
ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);
ctx.closePath();
} else if (slot_shape === LiteGraph.GRID_SHAPE) {
ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);
ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);
ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);
ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);
ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);
ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);
ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);
ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);
ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);
doStroke = false;
} else {
if(low_quality)
ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 );
else
ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);
}
//trigger
//if(slot.node_id != null && slot.slot == -1)
// ctx.fillStyle = "#F85";
//if(slot.links != null && slot.links.length)
ctx.fill();
// if(!low_quality && doStroke)
// ctx.stroke();
//render output name
if (render_text) {
var text = slot.label != null ? slot.label : slot.name;
if (text) {
ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;
if (horizontal || slot.dir == LiteGraph.DOWN) {
ctx.fillText(text, pos[0], pos[1] - 8);
} else {
ctx.fillText(text, pos[0] - 10, pos[1] + 5);
}
}
}
}
}
ctx.textAlign = "left";
ctx.globalAlpha = 1;
if (node.widgets) {
var widgets_y = max_y;
if (horizontal || node.widgets_up) {
widgets_y = 2;
}
if( node.widgets_start_y != null )
widgets_y = node.widgets_start_y;
this.drawNodeWidgets(
node,
widgets_y,
ctx,
this.node_widget && this.node_widget[0] == node
? this.node_widget[1]
: null
);
}
} else if (this.render_collapsed_slots) {
//if collapsed
var input_slot = null;
var output_slot = null;
//get first connected slot to render
if (node.inputs) {
for (var i = 0; i < node.inputs.length; i++) {
var slot = node.inputs[i];
if (slot.link == null) {
continue;
}
input_slot = slot;
break;
}
}
if (node.outputs) {
for (var i = 0; i < node.outputs.length; i++) {
var slot = node.outputs[i];
if (!slot.links || !slot.links.length) {
continue;
}
output_slot = slot;
}
}
if (input_slot) {
var x = 0;
var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center
if (horizontal) {
x = node._collapsed_width * 0.5;
y = -LiteGraph.NODE_TITLE_HEIGHT;
}
ctx.fillStyle = "#686";
ctx.beginPath();
if (
slot.type === LiteGraph.EVENT ||
slot.shape === LiteGraph.BOX_SHAPE
) {
ctx.rect(x - 7 + 0.5, y - 4, 14, 8);
} else if (slot.shape === LiteGraph.ARROW_SHAPE) {
ctx.moveTo(x + 8, y);
ctx.lineTo(x + -4, y - 4);
ctx.lineTo(x + -4, y + 4);
ctx.closePath();
} else {
ctx.arc(x, y, 4, 0, Math.PI * 2);
}
ctx.fill();
}
if (output_slot) {
var x = node._collapsed_width;
var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center
if (horizontal) {
x = node._collapsed_width * 0.5;
y = 0;
}
ctx.fillStyle = "#686";
ctx.strokeStyle = "black";
ctx.beginPath();
if (
slot.type === LiteGraph.EVENT ||
slot.shape === LiteGraph.BOX_SHAPE
) {
ctx.rect(x - 7 + 0.5, y - 4, 14, 8);
} else if (slot.shape === LiteGraph.ARROW_SHAPE) {
ctx.moveTo(x + 6, y);
ctx.lineTo(x - 6, y - 4);
ctx.lineTo(x - 6, y + 4);
ctx.closePath();
} else {
ctx.arc(x, y, 4, 0, Math.PI * 2);
}
ctx.fill();
//ctx.stroke();
}
}
if (node.clip_area) {
ctx.restore();
}
ctx.globalAlpha = 1.0;
}
}
function patchApp() {
app._invokeExtensionsAsync = async function(method, ...args) {
return await Promise.all(
this.extensions.map(async (ext) => {
if (method in ext) {
try {
return await ext[method](...args, this);
} catch (error) {
console.error(
`Error calling extension '${ext.name}' method '${method}'`,
{ error },
{ extension: ext },
{ args }
);
}
}
})
);
}
app._addNodeContextMenuHandler = function(node) {
function getCopyImageOption(img) {
if (typeof window.ClipboardItem === "undefined") return [];
return [
{
content: "Copy Image",
callback: async () => {
const url = new URL(img.src);
url.searchParams.delete("preview");
const writeImage = async (blob) => {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
};
try {
const data = await fetch(url);
const blob = await data.blob();
try {
await writeImage(blob);
} catch (error) {
// Chrome seems to only support PNG on write, convert and try again
if (blob.type !== "image/png") {
const canvas = $el("canvas", {
width: img.naturalWidth,
height: img.naturalHeight,
});
const ctx = canvas.getContext("2d");
let image;
if (typeof window.createImageBitmap === "undefined") {
image = new Image();
const p = new Promise((resolve, reject) => {
image.onload = resolve;
image.onerror = reject;
}).finally(() => {
URL.revokeObjectURL(image.src);
});
image.src = URL.createObjectURL(blob);
await p;
} else {
image = await createImageBitmap(blob);
}
try {
ctx.drawImage(image, 0, 0);
canvas.toBlob(writeImage, "image/png");
} finally {
if (typeof image.close === "function") {
image.close();
}
}
return;
}
throw error;
}
} catch (error) {
alert("Error copying image: " + (error.message ?? error));
}
},
},
];
}
node.prototype.getExtraMenuOptions = function (_, options) {
if (this.imgs) {
// If this node has images then we add an open in new tab item
let img;
if (this.imageIndex != null) {
// An image is selected so select that
img = this.imgs[this.imageIndex];
} else if (this.overIndex != null) {
// No image is selected but one is hovered
img = this.imgs[this.overIndex];
}
if (img) {
options.unshift(
{
content: "Open Image",
callback: () => {
let url = new URL(img.src);
url.searchParams.delete("preview");
window.open(url, "_blank");
},
},
...getCopyImageOption(img),
{
content: "Save Image",
callback: () => {
const a = document.createElement("a");
let url = new URL(img.src);
url.searchParams.delete("preview");
a.href = url;
a.setAttribute("download", new URLSearchParams(url.search).get("filename"));
document.body.append(a);
a.click();
requestAnimationFrame(() => a.remove());
},
}
);
}
}
options.push({
content: "Bypass",
callback: (obj) => {
if (this.mode === 4) this.mode = 0;
else this.mode = 4;
this.graph.change();
},
});
// prevent conflict of clipspace content
if (!ComfyApp.clipspace_return_node) {
options.push({
content: "Copy (Clipspace)",
callback: (obj) => {
ComfyApp.copyToClipspace(this);
},
});
if (ComfyApp.clipspace != null) {
options.push({
content: "Paste (Clipspace)",
callback: () => {
ComfyApp.pasteFromClipspace(this);
},
});
}
if (ComfyApp.isImageNode(this)) {
options.push({
content: "Open in MaskEditor",
callback: (obj) => {
ComfyApp.copyToClipspace(this);
ComfyApp.clipspace_return_node = this;
ComfyApp.open_maskeditor();
},
});
}
}
};
}
app._addDrawBackgroundHandler = function(node) {
const app = this;
function getImageTop(node) {
let shiftY;
if (node.imageOffset != null) {
shiftY = node.imageOffset;
} else {
if (node.widgets?.length) {
const w = node.widgets[node.widgets.length - 1];
shiftY = w.last_y;
if (w.computeSize) {
shiftY += w.computeSize()[1] + 4;
}
else if(w.computedHeight) {
shiftY += w.computedHeight;
}
else {
shiftY += LiteGraph.NODE_WIDGET_HEIGHT + 4;
}
} else {
shiftY = node.computeSize()[1];
}
}
return shiftY;
}
node.prototype.setSizeForImage = function (force) {
if(!force && this.animatedImages) return;
if (this.inputHeight || this.freeWidgetSpace > 210) {
this.setSize(this.size);
return;
}
const minHeight = getImageTop(this) + 220;
if (this.size[1] < minHeight) {
this.setSize([this.size[0], minHeight]);
}
};
node.prototype.onDrawBackground = function (ctx) {
if (!this.flags.collapsed) {
let imgURLs = []
let imagesChanged = false
const output = app.nodeOutputs[this.id + ""];
if (output?.images) {
this.animatedImages = output?.animated?.find(Boolean);
if (this.images !== output.images) {
this.images = output.images;
imagesChanged = true;
imgURLs = imgURLs.concat(
output.images.map((params) => {
return api.apiURL(
"/view?" +
new URLSearchParams(params).toString() +
(this.animatedImages ? "" : app.getPreviewFormatParam()) + app.getRandParam()
);
})
);
}
}
const preview = app.nodePreviewImages[this.id + ""]
if (this.preview !== preview) {
this.preview = preview
imagesChanged = true;
if (preview != null) {
imgURLs.push(preview);
}
}
if (imagesChanged) {
this.imageIndex = null;
if (imgURLs.length > 0) {
Promise.all(
imgURLs.map((src) => {
return new Promise((r) => {
const img = new Image();
img.onload = () => r(img);
img.onerror = () => r(null);
img.src = src
});
})
).then((imgs) => {
if ((!output || this.images === output.images) && (!preview || this.preview === preview)) {
this.imgs = imgs.filter(Boolean);
this.setSizeForImage?.();
app.graph.setDirtyCanvas(true);
}
});
}
else {
this.imgs = null;
}
}
function calculateGrid(w, h, n) {
let columns, rows, cellsize;
if (w > h) {
cellsize = h;
columns = Math.ceil(w / cellsize);
rows = Math.ceil(n / columns);
} else {
cellsize = w;
rows = Math.ceil(h / cellsize);
columns = Math.ceil(n / rows);
}
while (columns * rows < n) {
cellsize++;
if (w >= h) {
columns = Math.ceil(w / cellsize);
rows = Math.ceil(n / columns);
} else {
rows = Math.ceil(h / cellsize);
columns = Math.ceil(n / rows);
}
}
const cell_size = Math.min(w/columns, h/rows);
return {cell_size, columns, rows};
}
function is_all_same_aspect_ratio(imgs) {
// assume: imgs.length >= 2
let ratio = imgs[0].naturalWidth/imgs[0].naturalHeight;
for(let i=1; i<imgs.length; i++) {
let this_ratio = imgs[i].naturalWidth/imgs[i].naturalHeight;
if(ratio != this_ratio)
return false;
}
return true;
}
if (this.imgs?.length) {
const widgetIdx = this.widgets?.findIndex((w) => w.name === ANIM_PREVIEW_WIDGET);
if(this.animatedImages) {
// Instead of using the canvas we'll use a IMG
if(widgetIdx > -1) {
// Replace content
const widget = this.widgets[widgetIdx];
widget.options.host.updateImages(this.imgs);
} else {
const host = createImageHost(this);
this.setSizeForImage(true);
const widget = this.addDOMWidget(ANIM_PREVIEW_WIDGET, "img", host.el, {
host,
getHeight: host.getHeight,
onDraw: host.onDraw,
hideOnZoom: false
});
widget.serializeValue = () => undefined;
widget.options.host.updateImages(this.imgs);
}
return;
}
if (widgetIdx > -1) {
this.widgets[widgetIdx].onRemove?.();
this.widgets.splice(widgetIdx, 1);
}
const canvas = app.graph.list_of_graphcanvas[0];
const mouse = canvas.graph_mouse;
if (!canvas.pointer_is_down && this.pointerDown) {
if (mouse[0] === this.pointerDown.pos[0] && mouse[1] === this.pointerDown.pos[1]) {
this.imageIndex = this.pointerDown.index;
}
this.pointerDown = null;
}
let imageIndex = this.imageIndex;
const numImages = this.imgs.length;
if (numImages === 1 && !imageIndex) {
this.imageIndex = imageIndex = 0;
}
const top = getImageTop(this);
var shiftY = top;
let dw = this.size[0];
let dh = this.size[1];
dh -= shiftY;
if (imageIndex == null) {
var cellWidth, cellHeight, shiftX, cell_padding, cols;
const compact_mode = is_all_same_aspect_ratio(this.imgs);
if(!compact_mode) {
// use rectangle cell style and border line
cell_padding = 2;
const { cell_size, columns, rows } = calculateGrid(dw, dh, numImages);
cols = columns;
cellWidth = cell_size;
cellHeight = cell_size;
shiftX = (dw-cell_size*cols)/2;
shiftY = (dh-cell_size*rows)/2 + top;
}
else {
cell_padding = 0;
({ cellWidth, cellHeight, cols, shiftX } = calculateImageGrid(this.imgs, dw, dh));
}
let anyHovered = false;
this.imageRects = [];
for (let i = 0; i < numImages; i++) {
const img = this.imgs[i];
const row = Math.floor(i / cols);
const col = i % cols;
const x = col * cellWidth + shiftX;
const y = row * cellHeight + shiftY;
if (!anyHovered) {
anyHovered = LiteGraph.isInsideRectangle(
mouse[0],
mouse[1],
x + this.pos[0],
y + this.pos[1],
cellWidth,
cellHeight
);
if (anyHovered) {
this.overIndex = i;
let value = 110;
if (canvas.pointer_is_down) {
if (!this.pointerDown || this.pointerDown.index !== i) {
this.pointerDown = { index: i, pos: [...mouse] };
}
value = 125;
}
ctx.filter = `contrast(${value}%) brightness(${value}%)`;
canvas.canvas.style.cursor = "pointer";
}
}
this.imageRects.push([x, y, cellWidth, cellHeight]);
let wratio = cellWidth/img.width;
let hratio = cellHeight/img.height;
var ratio = Math.min(wratio, hratio);
let imgHeight = ratio * img.height;
let imgY = row * cellHeight + shiftY + (cellHeight - imgHeight)/2;
let imgWidth = ratio * img.width;
let imgX = col * cellWidth + shiftX + (cellWidth - imgWidth)/2;
ctx.drawImage(img, imgX+cell_padding, imgY+cell_padding, imgWidth-cell_padding*2, imgHeight-cell_padding*2);
if(!compact_mode) {
// rectangle cell and border line style
ctx.strokeStyle = "#8F8F8F";
ctx.lineWidth = 1;
ctx.strokeRect(x+cell_padding, y+cell_padding, cellWidth-cell_padding*2, cellHeight-cell_padding*2);
}
ctx.filter = "none";
}
if (!anyHovered) {
this.pointerDown = null;
this.overIndex = null;
}
} else {
// Draw individual
let w = this.imgs[imageIndex].naturalWidth;
let h = this.imgs[imageIndex].naturalHeight;
const scaleX = dw / w;
const scaleY = dh / h;
const scale = Math.min(scaleX, scaleY, 1);
w *= scale;
h *= scale;
let x = (dw - w) / 2;
let y = (dh - h) / 2 + shiftY;
ctx.drawImage(this.imgs[imageIndex], x, y, w, h);
const drawButton = (x, y, sz, text) => {
const hovered = LiteGraph.isInsideRectangle(mouse[0], mouse[1], x + this.pos[0], y + this.pos[1], sz, sz);
let fill = "#333";
let textFill = "#fff";
let isClicking = false;
if (hovered) {
canvas.canvas.style.cursor = "pointer";
if (canvas.pointer_is_down) {
fill = "#1e90ff";
isClicking = true;
} else {
fill = "#eee";
textFill = "#000";
}
} else {
this.pointerWasDown = null;
}
ctx.fillStyle = fill;
ctx.beginPath();
ctx.roundRect(x, y, sz, sz, [4]);
ctx.fill();
ctx.fillStyle = textFill;
ctx.font = "12px Arial";
ctx.textAlign = "center";
ctx.fillText(text, x + 15, y + 20);
return isClicking;
};
if (numImages > 1) {
if (drawButton(dw - 40, dh + top - 40, 30, `${this.imageIndex + 1}/${numImages}`)) {
let i = this.imageIndex + 1 >= numImages ? 0 : this.imageIndex + 1;
if (!this.pointerDown || !this.pointerDown.index === i) {
this.pointerDown = { index: i, pos: [...mouse] };
}
}
if (drawButton(dw - 40, top + 10, 30, `x`)) {
if (!this.pointerDown || !this.pointerDown.index === null) {
this.pointerDown = { index: null, pos: [...mouse] };
}
}
}
}
}
}
};
}
app._addNodeKeyHandler = function(node) {
const app = this;
const origNodeOnKeyDown = node.prototype.onKeyDown;
node.prototype.onKeyDown = function(e) {
if (origNodeOnKeyDown && origNodeOnKeyDown.apply(this, e) === false) {
return false;
}
if (this.flags.collapsed || !this.imgs || this.imageIndex === null) {
return;
}
let handled = false;
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
if (e.key === "ArrowLeft") {
this.imageIndex -= 1;
} else if (e.key === "ArrowRight") {
this.imageIndex += 1;
}
this.imageIndex %= this.imgs.length;
if (this.imageIndex < 0) {
this.imageIndex = this.imgs.length + this.imageIndex;
}
handled = true;
} else if (e.key === "Escape") {
this.imageIndex = null;
handled = true;
}
if (handled === true) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
}
}
app.registerNodeDef = async function(nodeId, nodeData) {
const self = this;
const node = Object.assign(
function ComfyNode() {
var inputs = nodeData["input"]["required"];
if (nodeData["input"]["optional"] != undefined) {
inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"]);
}
const config = { minWidth: 1, minHeight: 1 };
for (const inputName in inputs) {
const inputData = inputs[inputName];
const type = inputData[0];
const extraInfo = {};
let widgetCreated = true;
const widgetType = self.getWidgetType(inputData, inputName);
if(widgetType) {
if(widgetType === "COMBO") {
Object.assign(config, self.widgets.COMBO(this, inputName, inputData, app) || {});
} else {
Object.assign(config, self.widgets[widgetType](this, inputName, inputData, app) || {});
}
} else {
// Node connection inputs
if (inputData[1]?.multiple) {
extraInfo.multiple = true;
extraInfo.shape = LiteGraph.GRID_SHAPE;
}
this.addInput(inputName, type, extraInfo);
widgetCreated = false;
}
if(widgetCreated && inputData[1]?.forceInput && config?.widget) {
if (!config.widget.options) config.widget.options = {};
config.widget.options.forceInput = inputData[1].forceInput;
}
if(widgetCreated && inputData[1]?.defaultInput && config?.widget) {
if (!config.widget.options) config.widget.options = {};
config.widget.options.defaultInput = inputData[1].defaultInput;
}
}
for (const o in nodeData["output"]) {
let output = nodeData["output"][o];
if(output instanceof Array) output = "COMBO";
const outputName = nodeData["output_name"][o] || output;
const outputShape = nodeData["output_is_list"][o] ? LiteGraph.GRID_SHAPE : LiteGraph.CIRCLE_SHAPE ;
this.addOutput(outputName, output, { shape: outputShape });
}
const s = this.computeSize();
s[0] = Math.max(config.minWidth, s[0] * 1.5);
s[1] = Math.max(config.minHeight, s[1]);
this.size = s;
this.serialize_widgets = true;
app._invokeExtensionsAsync("nodeCreated", this);
},
{
title: nodeData.display_name || nodeData.name,
comfyClass: nodeData.name,
nodeData
}
);
node.prototype.comfyClass = nodeData.name;
app._addNodeContextMenuHandler(node);
app._addDrawBackgroundHandler(node, app);
app._addNodeKeyHandler(node);
await app._invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
LiteGraph.registerNodeType(nodeId, node);
node.category = nodeData.category;
}
}
patchLiteGraph()
patchApp()