| | import { app, ANIM_PREVIEW_WIDGET } from "./app.js"; |
| |
|
| | const SIZE = Symbol(); |
| |
|
| | function intersect(a, b) { |
| | const x = Math.max(a.x, b.x); |
| | const num1 = Math.min(a.x + a.width, b.x + b.width); |
| | const y = Math.max(a.y, b.y); |
| | const num2 = Math.min(a.y + a.height, b.y + b.height); |
| | if (num1 >= x && num2 >= y) return [x, y, num1 - x, num2 - y]; |
| | else return null; |
| | } |
| |
|
| | function getClipPath(node, element, elRect) { |
| | const selectedNode = Object.values(app.canvas.selected_nodes)[0]; |
| | if (selectedNode && selectedNode !== node) { |
| | const MARGIN = 7; |
| | const scale = app.canvas.ds.scale; |
| |
|
| | const bounding = selectedNode.getBounding(); |
| | const intersection = intersect( |
| | { x: elRect.x / scale, y: elRect.y / scale, width: elRect.width / scale, height: elRect.height / scale }, |
| | { |
| | x: selectedNode.pos[0] + app.canvas.ds.offset[0] - MARGIN, |
| | y: selectedNode.pos[1] + app.canvas.ds.offset[1] - LiteGraph.NODE_TITLE_HEIGHT - MARGIN, |
| | width: bounding[2] + MARGIN + MARGIN, |
| | height: bounding[3] + MARGIN + MARGIN, |
| | } |
| | ); |
| |
|
| | if (!intersection) { |
| | return ""; |
| | } |
| |
|
| | const widgetRect = element.getBoundingClientRect(); |
| | const clipX = intersection[0] - widgetRect.x / scale + "px"; |
| | const clipY = intersection[1] - widgetRect.y / scale + "px"; |
| | const clipWidth = intersection[2] + "px"; |
| | const clipHeight = intersection[3] + "px"; |
| | const path = `polygon(0% 0%, 0% 100%, ${clipX} 100%, ${clipX} ${clipY}, calc(${clipX} + ${clipWidth}) ${clipY}, calc(${clipX} + ${clipWidth}) calc(${clipY} + ${clipHeight}), ${clipX} calc(${clipY} + ${clipHeight}), ${clipX} 100%, 100% 100%, 100% 0%)`; |
| | return path; |
| | } |
| | return ""; |
| | } |
| |
|
| | function computeSize(size) { |
| | if (this.widgets?.[0]?.last_y == null) return; |
| |
|
| | let y = this.widgets[0].last_y; |
| | let freeSpace = size[1] - y; |
| |
|
| | let widgetHeight = 0; |
| | let dom = []; |
| | for (const w of this.widgets) { |
| | if (w.type === "converted-widget") { |
| | |
| | delete w.computedHeight; |
| | } else if (w.computeSize) { |
| | widgetHeight += w.computeSize()[1] + 4; |
| | } else if (w.element) { |
| | |
| | const styles = getComputedStyle(w.element); |
| | let minHeight = w.options.getMinHeight?.() ?? parseInt(styles.getPropertyValue("--comfy-widget-min-height")); |
| | let maxHeight = w.options.getMaxHeight?.() ?? parseInt(styles.getPropertyValue("--comfy-widget-max-height")); |
| |
|
| | let prefHeight = w.options.getHeight?.() ?? styles.getPropertyValue("--comfy-widget-height"); |
| | if (prefHeight.endsWith?.("%")) { |
| | prefHeight = size[1] * (parseFloat(prefHeight.substring(0, prefHeight.length - 1)) / 100); |
| | } else { |
| | prefHeight = parseInt(prefHeight); |
| | if (isNaN(minHeight)) { |
| | minHeight = prefHeight; |
| | } |
| | } |
| | if (isNaN(minHeight)) { |
| | minHeight = 50; |
| | } |
| | if (!isNaN(maxHeight)) { |
| | if (!isNaN(prefHeight)) { |
| | prefHeight = Math.min(prefHeight, maxHeight); |
| | } else { |
| | prefHeight = maxHeight; |
| | } |
| | } |
| | dom.push({ |
| | minHeight, |
| | prefHeight, |
| | w, |
| | }); |
| | } else { |
| | widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 4; |
| | } |
| | } |
| |
|
| | freeSpace -= widgetHeight; |
| |
|
| | |
| | const prefGrow = []; |
| | const canGrow = []; |
| | let growBy = 0; |
| | for (const d of dom) { |
| | freeSpace -= d.minHeight; |
| | if (isNaN(d.prefHeight)) { |
| | canGrow.push(d); |
| | d.w.computedHeight = d.minHeight; |
| | } else { |
| | const diff = d.prefHeight - d.minHeight; |
| | if (diff > 0) { |
| | prefGrow.push(d); |
| | growBy += diff; |
| | d.diff = diff; |
| | } else { |
| | d.w.computedHeight = d.minHeight; |
| | } |
| | } |
| | } |
| |
|
| | if (this.imgs && !this.widgets.find((w) => w.name === ANIM_PREVIEW_WIDGET)) { |
| | |
| | freeSpace -= 220; |
| | } |
| |
|
| | this.freeWidgetSpace = freeSpace; |
| |
|
| | if (freeSpace < 0) { |
| | |
| | size[1] -= freeSpace; |
| | this.graph.setDirtyCanvas(true); |
| | } else { |
| | |
| | const growDiff = freeSpace - growBy; |
| | if (growDiff > 0) { |
| | |
| | freeSpace = growDiff; |
| | for (const d of prefGrow) { |
| | d.w.computedHeight = d.prefHeight; |
| | } |
| | } else { |
| | |
| | const shared = -growDiff / prefGrow.length; |
| | for (const d of prefGrow) { |
| | d.w.computedHeight = d.prefHeight - shared; |
| | } |
| | freeSpace = 0; |
| | } |
| |
|
| | if (freeSpace > 0 && canGrow.length) { |
| | |
| | const shared = freeSpace / canGrow.length; |
| | for (const d of canGrow) { |
| | d.w.computedHeight += shared; |
| | } |
| | } |
| | } |
| |
|
| | |
| | for (const w of this.widgets) { |
| | w.y = y; |
| | if (w.computedHeight) { |
| | y += w.computedHeight; |
| | } else if (w.computeSize) { |
| | y += w.computeSize()[1] + 4; |
| | } else { |
| | y += LiteGraph.NODE_WIDGET_HEIGHT + 4; |
| | } |
| | } |
| | } |
| |
|
| | |
| | const elementWidgets = new Set(); |
| | const computeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes; |
| | LGraphCanvas.prototype.computeVisibleNodes = function () { |
| | const visibleNodes = computeVisibleNodes.apply(this, arguments); |
| | for (const node of app.graph._nodes) { |
| | if (elementWidgets.has(node)) { |
| | const hidden = visibleNodes.indexOf(node) === -1; |
| | for (const w of node.widgets) { |
| | if (w.element) { |
| | w.element.hidden = hidden; |
| | w.element.style.display = hidden ? "none" : undefined; |
| | if (hidden) { |
| | w.options.onHide?.(w); |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|
| | return visibleNodes; |
| | }; |
| |
|
| | let enableDomClipping = true; |
| |
|
| | export function addDomClippingSetting() { |
| | app.ui.settings.addSetting({ |
| | id: "Comfy.DOMClippingEnabled", |
| | name: "Enable DOM element clipping (enabling may reduce performance)", |
| | type: "boolean", |
| | defaultValue: enableDomClipping, |
| | onChange(value) { |
| | enableDomClipping = !!value; |
| | }, |
| | }); |
| | } |
| |
|
| | LGraphNode.prototype.addDOMWidget = function (name, type, element, options) { |
| | options = { hideOnZoom: true, selectOn: ["focus", "click"], ...options }; |
| |
|
| | if (!element.parentElement) { |
| | document.body.append(element); |
| | } |
| |
|
| | let mouseDownHandler; |
| | if (element.blur) { |
| | mouseDownHandler = (event) => { |
| | if (!element.contains(event.target)) { |
| | element.blur(); |
| | } |
| | }; |
| | document.addEventListener("mousedown", mouseDownHandler); |
| | } |
| |
|
| | const widget = { |
| | type, |
| | name, |
| | get value() { |
| | return options.getValue?.() ?? undefined; |
| | }, |
| | set value(v) { |
| | options.setValue?.(v); |
| | widget.callback?.(widget.value); |
| | }, |
| | draw: function (ctx, node, widgetWidth, y, widgetHeight) { |
| | if (widget.computedHeight == null) { |
| | computeSize.call(node, node.size); |
| | } |
| |
|
| | const hidden = |
| | node.flags?.collapsed || |
| | (!!options.hideOnZoom && app.canvas.ds.scale < 0.5) || |
| | widget.computedHeight <= 0 || |
| | widget.type === "converted-widget"|| |
| | widget.type === "hidden"; |
| | element.hidden = hidden; |
| | element.style.display = hidden ? "none" : null; |
| | if (hidden) { |
| | widget.options.onHide?.(widget); |
| | return; |
| | } |
| |
|
| | const margin = 10; |
| | 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); |
| |
|
| | const scale = new DOMMatrix().scaleSelf(transform.a, transform.d); |
| |
|
| | Object.assign(element.style, { |
| | transformOrigin: "0 0", |
| | transform: scale, |
| | left: `${transform.a + transform.e}px`, |
| | top: `${transform.d + transform.f}px`, |
| | width: `${widgetWidth - margin * 2}px`, |
| | height: `${(widget.computedHeight ?? 50) - margin * 2}px`, |
| | position: "absolute", |
| | zIndex: app.graph._nodes.indexOf(node), |
| | }); |
| |
|
| | if (enableDomClipping) { |
| | element.style.clipPath = getClipPath(node, element, elRect); |
| | element.style.willChange = "clip-path"; |
| | } |
| |
|
| | this.options.onDraw?.(widget); |
| | }, |
| | element, |
| | options, |
| | onRemove() { |
| | if (mouseDownHandler) { |
| | document.removeEventListener("mousedown", mouseDownHandler); |
| | } |
| | element.remove(); |
| | }, |
| | }; |
| |
|
| | for (const evt of options.selectOn) { |
| | element.addEventListener(evt, () => { |
| | app.canvas.selectNode(this); |
| | app.canvas.bringToFront(this); |
| | }); |
| | } |
| |
|
| | this.addCustomWidget(widget); |
| | elementWidgets.add(this); |
| |
|
| | const collapse = this.collapse; |
| | this.collapse = function() { |
| | collapse.apply(this, arguments); |
| | if(this.flags?.collapsed) { |
| | element.hidden = true; |
| | element.style.display = "none"; |
| | } |
| | } |
| |
|
| | const onRemoved = this.onRemoved; |
| | this.onRemoved = function () { |
| | element.remove(); |
| | elementWidgets.delete(this); |
| | onRemoved?.apply(this, arguments); |
| | }; |
| |
|
| | if (!this[SIZE]) { |
| | this[SIZE] = true; |
| | const onResize = this.onResize; |
| | this.onResize = function (size) { |
| | options.beforeResize?.call(widget, this); |
| | computeSize.call(this, size); |
| | onResize?.apply(this, arguments); |
| | options.afterResize?.call(widget, this); |
| | }; |
| | } |
| |
|
| | return widget; |
| | }; |
| |
|