mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 17:30:07 +00:00
Upstream widgets layout to litegraph (#2557)
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -11,7 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||||
"@comfyorg/comfyui-electron-types": "^0.4.16",
|
"@comfyorg/comfyui-electron-types": "^0.4.16",
|
||||||
"@comfyorg/litegraph": "^0.8.79",
|
"@comfyorg/litegraph": "^0.8.80",
|
||||||
"@primevue/forms": "^4.2.5",
|
"@primevue/forms": "^4.2.5",
|
||||||
"@primevue/themes": "^4.2.5",
|
"@primevue/themes": "^4.2.5",
|
||||||
"@sentry/vue": "^8.48.0",
|
"@sentry/vue": "^8.48.0",
|
||||||
@@ -1944,9 +1944,9 @@
|
|||||||
"license": "GPL-3.0-only"
|
"license": "GPL-3.0-only"
|
||||||
},
|
},
|
||||||
"node_modules/@comfyorg/litegraph": {
|
"node_modules/@comfyorg/litegraph": {
|
||||||
"version": "0.8.79",
|
"version": "0.8.80",
|
||||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.79.tgz",
|
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.80.tgz",
|
||||||
"integrity": "sha512-KLdAu1x/amz/MU6CZmYF/WOyQIt1XdacOkzMzKfVk4C9s7viTIZI+fCFPyoXQdAdjkwL/I/afloyFy2zVpO0ew==",
|
"integrity": "sha512-JpdhONCepi335moA1XEGOykgwt1AB5EIUf6VHi0VI9M9lwDGo8sTnVX8D9FhC/iAt+lcQ3bI2+HNBFeu9w2pGA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||||
"@comfyorg/comfyui-electron-types": "^0.4.16",
|
"@comfyorg/comfyui-electron-types": "^0.4.16",
|
||||||
"@comfyorg/litegraph": "^0.8.79",
|
"@comfyorg/litegraph": "^0.8.80",
|
||||||
"@primevue/forms": "^4.2.5",
|
"@primevue/forms": "^4.2.5",
|
||||||
"@primevue/themes": "^4.2.5",
|
"@primevue/themes": "^4.2.5",
|
||||||
"@sentry/vue": "^8.48.0",
|
"@sentry/vue": "^8.48.0",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import type {
|
|||||||
} from '@comfyorg/litegraph/dist/types/widgets'
|
} from '@comfyorg/litegraph/dist/types/widgets'
|
||||||
|
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
import { useSettingStore } from '@/stores/settingStore'
|
||||||
import { distributeSpace } from '@/utils/spaceDistribution'
|
|
||||||
|
|
||||||
import { ANIM_PREVIEW_WIDGET, app } from './app'
|
import { ANIM_PREVIEW_WIDGET, app } from './app'
|
||||||
|
|
||||||
@@ -128,79 +127,6 @@ function getClipPath(
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeSize(this: LGraphNode, size: Size): void {
|
|
||||||
if (!this.widgets?.[0]?.last_y) return
|
|
||||||
|
|
||||||
let y = this.widgets[0].last_y
|
|
||||||
let freeSpace = size[1] - y
|
|
||||||
|
|
||||||
// Collect fixed height widgets first
|
|
||||||
let fixedWidgetHeight = 0
|
|
||||||
const layoutWidgets: SizeInfo[] = []
|
|
||||||
|
|
||||||
for (const w of this.widgets) {
|
|
||||||
// @ts-expect-error custom widget type
|
|
||||||
if (w.type === 'converted-widget') {
|
|
||||||
// Ignore
|
|
||||||
// @ts-expect-error custom widget type
|
|
||||||
delete w.computedHeight
|
|
||||||
} else if (w.computeLayoutSize) {
|
|
||||||
const { minHeight, maxHeight } = w.computeLayoutSize(this)
|
|
||||||
layoutWidgets.push({
|
|
||||||
minHeight,
|
|
||||||
prefHeight: maxHeight,
|
|
||||||
w
|
|
||||||
})
|
|
||||||
} else if (w.computeSize) {
|
|
||||||
fixedWidgetHeight += w.computeSize()[1] + 4
|
|
||||||
} else {
|
|
||||||
fixedWidgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.imgs && !this.widgets?.find((w) => w.name === ANIM_PREVIEW_WIDGET)) {
|
|
||||||
fixedWidgetHeight += 220
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate remaining space for DOM widgets
|
|
||||||
freeSpace -= fixedWidgetHeight
|
|
||||||
this.freeWidgetSpace = freeSpace
|
|
||||||
|
|
||||||
// Prepare space requests for distribution
|
|
||||||
const spaceRequests = layoutWidgets.map((d) => ({
|
|
||||||
minSize: d.minHeight,
|
|
||||||
maxSize: d.prefHeight
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Distribute space among DOM widgets
|
|
||||||
const allocations = distributeSpace(Math.max(0, freeSpace), spaceRequests)
|
|
||||||
|
|
||||||
// Apply computed heights
|
|
||||||
layoutWidgets.forEach((d, i) => {
|
|
||||||
d.w.computedHeight = allocations[i]
|
|
||||||
})
|
|
||||||
|
|
||||||
// If we need more space, grow the node
|
|
||||||
const totalNeeded =
|
|
||||||
fixedWidgetHeight + allocations.reduce((sum, h) => sum + h, 0)
|
|
||||||
if (totalNeeded > size[1] - this.widgets[0].last_y) {
|
|
||||||
size[1] = totalNeeded + this.widgets[0].last_y
|
|
||||||
this.graph?.setDirtyCanvas(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position widgets
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override the compute visible nodes function to allow us to hide/show DOM elements when the node goes offscreen
|
// Override the compute visible nodes function to allow us to hide/show DOM elements when the node goes offscreen
|
||||||
const elementWidgets = new Set<LGraphNode>()
|
const elementWidgets = new Set<LGraphNode>()
|
||||||
const computeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes
|
const computeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes
|
||||||
@@ -315,10 +241,6 @@ export class DOMWidgetImpl<T extends HTMLElement, V extends object | string>
|
|||||||
widgetWidth: number,
|
widgetWidth: number,
|
||||||
y: number
|
y: number
|
||||||
): void {
|
): void {
|
||||||
if (this.computedHeight == null) {
|
|
||||||
computeSize.call(node, node.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { offset, scale } = app.canvas.ds
|
const { offset, scale } = app.canvas.ds
|
||||||
const hidden =
|
const hidden =
|
||||||
(!!this.options.hideOnZoom && app.canvas.low_quality) ||
|
(!!this.options.hideOnZoom && app.canvas.low_quality) ||
|
||||||
@@ -461,7 +383,6 @@ LGraphNode.prototype.addDOMWidget = function <
|
|||||||
const onResize = this.onResize
|
const onResize = this.onResize
|
||||||
this.onResize = function (this: LGraphNode, size: Size) {
|
this.onResize = function (this: LGraphNode, size: Size) {
|
||||||
options.beforeResize?.call(widget, this)
|
options.beforeResize?.call(widget, this)
|
||||||
computeSize.call(this, size)
|
|
||||||
onResize?.call(this, size)
|
onResize?.call(this, size)
|
||||||
options.afterResize?.call(widget, this)
|
options.afterResize?.call(widget, this)
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/types/litegraph-augmentation.d.ts
vendored
21
src/types/litegraph-augmentation.d.ts
vendored
@@ -24,25 +24,6 @@ declare module '@comfyorg/litegraph/dist/types/widgets' {
|
|||||||
* See extensions/core/dynamicPrompts.ts
|
* See extensions/core/dynamicPrompts.ts
|
||||||
*/
|
*/
|
||||||
dynamicPrompts?: boolean
|
dynamicPrompts?: boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* The computed height of the widget. Used by customized node resize logic.
|
|
||||||
* See scripts/domWidget.ts for more details.
|
|
||||||
*/
|
|
||||||
computedHeight?: number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the layout size of the widget. Overrides {@link IBaseWidget.computeSize}.
|
|
||||||
*/
|
|
||||||
computeLayoutSize?: (
|
|
||||||
this: IBaseWidget,
|
|
||||||
node: LGraphNode
|
|
||||||
) => {
|
|
||||||
minHeight: number
|
|
||||||
maxHeight?: number
|
|
||||||
minWidth: number
|
|
||||||
maxWidth?: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,8 +107,6 @@ declare module '@comfyorg/litegraph' {
|
|||||||
|
|
||||||
/** @deprecated Unused */
|
/** @deprecated Unused */
|
||||||
imageOffset?: number
|
imageOffset?: number
|
||||||
/** Set by DOM widgets */
|
|
||||||
freeWidgetSpace?: number
|
|
||||||
/** Callback for pasting an image file into the node */
|
/** Callback for pasting an image file into the node */
|
||||||
pasteFile?(file: File): void
|
pasteFile?(file: File): void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
export interface SpaceRequest {
|
|
||||||
minSize: number
|
|
||||||
maxSize?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Distributes available space among items with min/max size constraints
|
|
||||||
* @param totalSpace Total space available to distribute
|
|
||||||
* @param requests Array of space requests with size constraints
|
|
||||||
* @returns Array of space allocations
|
|
||||||
*/
|
|
||||||
export function distributeSpace(
|
|
||||||
totalSpace: number,
|
|
||||||
requests: SpaceRequest[]
|
|
||||||
): number[] {
|
|
||||||
// Handle edge cases
|
|
||||||
if (requests.length === 0) return []
|
|
||||||
|
|
||||||
// Calculate total minimum space needed
|
|
||||||
const totalMinSize = requests.reduce((sum, req) => sum + req.minSize, 0)
|
|
||||||
|
|
||||||
// If we can't meet minimum requirements, return the minimum sizes
|
|
||||||
if (totalSpace < totalMinSize) {
|
|
||||||
return requests.map((req) => req.minSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize allocations with minimum sizes
|
|
||||||
let allocations = requests.map((req) => ({
|
|
||||||
computedSize: req.minSize,
|
|
||||||
maxSize: req.maxSize ?? Infinity,
|
|
||||||
remaining: (req.maxSize ?? Infinity) - req.minSize
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Calculate remaining space to distribute
|
|
||||||
let remainingSpace = totalSpace - totalMinSize
|
|
||||||
|
|
||||||
// Distribute remaining space iteratively
|
|
||||||
while (
|
|
||||||
remainingSpace > 0 &&
|
|
||||||
allocations.some((alloc) => alloc.remaining > 0)
|
|
||||||
) {
|
|
||||||
// Count items that can still grow
|
|
||||||
const growableItems = allocations.filter(
|
|
||||||
(alloc) => alloc.remaining > 0
|
|
||||||
).length
|
|
||||||
|
|
||||||
if (growableItems === 0) break
|
|
||||||
|
|
||||||
// Calculate fair share per item
|
|
||||||
const sharePerItem = remainingSpace / growableItems
|
|
||||||
|
|
||||||
// Track how much space was actually used in this iteration
|
|
||||||
let spaceUsedThisRound = 0
|
|
||||||
|
|
||||||
// Distribute space
|
|
||||||
allocations = allocations.map((alloc) => {
|
|
||||||
if (alloc.remaining <= 0) return alloc
|
|
||||||
|
|
||||||
const growth = Math.min(sharePerItem, alloc.remaining)
|
|
||||||
spaceUsedThisRound += growth
|
|
||||||
|
|
||||||
return {
|
|
||||||
...alloc,
|
|
||||||
computedSize: alloc.computedSize + growth,
|
|
||||||
remaining: alloc.remaining - growth
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
remainingSpace -= spaceUsedThisRound
|
|
||||||
|
|
||||||
// Break if we couldn't distribute any more space
|
|
||||||
if (spaceUsedThisRound === 0) break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return only the computed sizes
|
|
||||||
return allocations.map(({ computedSize }) => computedSize)
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { type SpaceRequest, distributeSpace } from '@/utils/spaceDistribution'
|
|
||||||
|
|
||||||
describe('distributeSpace', () => {
|
|
||||||
it('should distribute space according to minimum sizes when space is limited', () => {
|
|
||||||
const requests: SpaceRequest[] = [
|
|
||||||
{ minSize: 100 },
|
|
||||||
{ minSize: 100 },
|
|
||||||
{ minSize: 100 }
|
|
||||||
]
|
|
||||||
expect(distributeSpace(300, requests)).toEqual([100, 100, 100])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should distribute extra space equally when no maxSize', () => {
|
|
||||||
const requests: SpaceRequest[] = [{ minSize: 100 }, { minSize: 100 }]
|
|
||||||
expect(distributeSpace(400, requests)).toEqual([200, 200])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should respect maximum sizes', () => {
|
|
||||||
const requests: SpaceRequest[] = [
|
|
||||||
{ minSize: 100, maxSize: 150 },
|
|
||||||
{ minSize: 100 }
|
|
||||||
]
|
|
||||||
expect(distributeSpace(400, requests)).toEqual([150, 250])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle empty requests array', () => {
|
|
||||||
expect(distributeSpace(1000, [])).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle negative total space', () => {
|
|
||||||
const requests: SpaceRequest[] = [{ minSize: 100 }, { minSize: 100 }]
|
|
||||||
expect(distributeSpace(-100, requests)).toEqual([100, 100])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle total space smaller than minimum sizes', () => {
|
|
||||||
const requests: SpaceRequest[] = [{ minSize: 100 }, { minSize: 100 }]
|
|
||||||
expect(distributeSpace(100, requests)).toEqual([100, 100])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user