Skip to content

custom_widget

chrisgoringe edited this page Oct 15, 2023 · 2 revisions

Widgets that are just for show

Here's the code for a node that just shows some html...

import { app } from "../../../scripts/app.js";
import { $el } from "../../../scripts/ui.js";

/* 
A method that returns the required style for the html 
*/
function get_position_style(ctx, widget_width, y, node_height) {
    const MARGIN = 4;  // the margin around the html element

/* Create a transform that deals with all the scrolling and zooming */
    const elRect = ctx.canvas.getBoundingClientRect();
    const transform = new DOMMatrix()
        .scaleSelf(elRect.width / ctx.canvas.width, elRect.height / ctx.canvas.height)
        .multiplySelf(ctx.getTransform())
        .translateSelf(MARGIN, MARGIN + y);

    return {
        transformOrigin: '0 0',
        transform: transform,
        left: `0px`, 
        top: `0px`,
        position: "absolute",
        maxWidth: `${widget_width - MARGIN*2}px`,
        maxHeight: `${node_height - MARGIN*2}px`,    // we're assuming we have the whole height of the node
        width: `auto`,
        height: `auto`,
    }
}

app.registerExtension({
    name: "the.unique,name",

    async beforeRegisterNodeDef(nodeType, nodeData, app) {
        if (nodeType.comfyClass=="HtmlNode") {

            /* 
            Hijack the onNodeCreated call to add our widget
            */
            const orig_nodeCreated = nodeType.prototype.onNodeCreated;
            nodeType.prototype.onNodeCreated = function () {
                orig_nodeCreated?.apply(this, arguments);

                const widget = {
                    type: "HTML",   // whatever
                    name: "flying", // whatever
                    draw(ctx, node, widget_width, y, widget_height) { 
                        Object.assign(this.inputEl.style, get_position_style(ctx, widget_width, y, node.size[1])); // assign the required style when we are drawn
                    },
                };

                /*
                Create an html element and add it to the document.  
                Look at $el in ui.js for all the options here
                */
                widget.inputEl = $el("img", { src: "http://127.0.0.1:8188/view?filename=misc-stained+glass_00001_.png&subfolder=2023-10-16&type=output" });
                document.body.appendChild(widget.inputEl);

                /*
                Add the widget, make sure we clean up nicely, and we do not want to be serialized!
                */
                this.addCustomWidget(widget);
                this.onRemoved = function () { widget.inputEl.remove(); };
                this.serialize_widgets = false;

            }
        }
    },
})
class HtmlNode:
    CATEGORY = "quicknodes"
    @classmethod    
    def INPUT_TYPES(s):
        return { "required":{} }
    RETURN_TYPES = ()
    RETURN_NAMES = ()
    FUNCTION = "func"
    def func(self):
        return ()

Widgets that handle input and output

A node plugin can provide a method getCustomWidgets to declare a widget that handles the datatype CHEESE

  getCustomWidgets(app) {
    return {
      CHEESE(node, inputName, inputData, app) { // We return an object containing a field CHEESE which has a function (taking node, name, data, app)
         const widget = /* see below */;        // that creates a widget
         widget.something = something;          // maybe adds stuff to it
         node.addCustomWidget(widget);          // adds it to the node
         return  widget;                        // and returns it.
      } 
    }
  },

We can then use CHEESE in inputs and outputs.

    "required": { "slice": ("CHEESE",),     

A custom widget can probably have lots of fields, but here are some to start with:

const widget = {
    type: inputData[0],       // the type, CHEESE
    name: inputName,          // the name, slice
    size: [128,128],          // a default size
    draw(ctx, node, width, y) {
                              // a method to draw the widget (ctx is a CanvasRenderingContext2D)
    },
    computeSize(...args) {    
       return [128,128];      // a method to compute the current size of the widget
    },
    async serializeValue(nodeId,widgetIndex) {
       return "Data That Goes to the Python Side";
    }
}

Looking at litegraph.js will give you more info on node widgets.

Clone this wiki locally