diff --git a/src/components/boundingbox/WidgetBoundingBox.vue b/src/components/boundingbox/WidgetBoundingBox.vue index 581ec8eaa4..cc2a0bd815 100644 --- a/src/components/boundingbox/WidgetBoundingBox.vue +++ b/src/components/boundingbox/WidgetBoundingBox.vue @@ -3,19 +3,19 @@ - + - + - + - + @@ -25,6 +25,10 @@ import { computed } from 'vue' import ScrubableNumberInput from '@/components/common/ScrubableNumberInput.vue' import type { Bounds } from '@/renderer/core/layout/types' +const { disabled = false } = defineProps<{ + disabled?: boolean +}>() + const modelValue = defineModel({ default: () => ({ x: 0, y: 0, width: 512, height: 512 }) }) diff --git a/src/components/curve/CurveEditor.vue b/src/components/curve/CurveEditor.vue index 85a64bc5b2..bcebacc56e 100644 --- a/src/components/curve/CurveEditor.vue +++ b/src/components/curve/CurveEditor.vue @@ -3,8 +3,13 @@ ref="svgRef" viewBox="-0.04 -0.04 1.08 1.08" preserveAspectRatio="xMidYMid meet" - class="aspect-square w-full cursor-crosshair rounded-[5px] bg-node-component-surface" - @pointerdown.stop="handleSvgPointerDown" + :class=" + cn( + 'aspect-square w-full rounded-[5px] bg-node-component-surface', + disabled ? 'cursor-default' : 'cursor-crosshair' + ) + " + @pointerdown.stop="onSvgPointerDown" @contextmenu.prevent.stop > - + @@ -77,14 +85,20 @@ import { computed, useTemplateRef } from 'vue' import { useCurveEditor } from '@/composables/useCurveEditor' +import { cn } from '@/utils/tailwindUtil' import type { CurvePoint } from './types' import { histogramToPath } from './curveUtils' -const { curveColor = 'white', histogram } = defineProps<{ +const { + curveColor = 'white', + histogram, + disabled = false +} = defineProps<{ curveColor?: string histogram?: Uint32Array | null + disabled?: boolean }>() const modelValue = defineModel({ @@ -98,6 +112,10 @@ const { curvePath, handleSvgPointerDown, startDrag } = useCurveEditor({ modelValue }) +function onSvgPointerDown(e: PointerEvent) { + if (!disabled) handleSvgPointerDown(e) +} + const histogramPath = computed(() => histogram ? histogramToPath(histogram) : '' ) diff --git a/src/components/curve/WidgetCurve.vue b/src/components/curve/WidgetCurve.vue index 85a16adc64..77443a23a3 100644 --- a/src/components/curve/WidgetCurve.vue +++ b/src/components/curve/WidgetCurve.vue @@ -1,11 +1,27 @@ diff --git a/src/components/curve/curveUtils.ts b/src/components/curve/curveUtils.ts index f46f0c92b5..f8f359c82c 100644 --- a/src/components/curve/curveUtils.ts +++ b/src/components/curve/curveUtils.ts @@ -1,5 +1,18 @@ import type { CurvePoint } from './types' +export function isCurvePointArray(value: unknown): value is CurvePoint[] { + return ( + Array.isArray(value) && + value.every( + (p) => + Array.isArray(p) && + p.length === 2 && + typeof p[0] === 'number' && + typeof p[1] === 'number' + ) + ) +} + /** * Monotone cubic Hermite interpolation. * Produces a smooth curve that passes through all control points diff --git a/src/components/imagecrop/WidgetImageCrop.vue b/src/components/imagecrop/WidgetImageCrop.vue index 5d55ddb8b9..2c99762551 100644 --- a/src/components/imagecrop/WidgetImageCrop.vue +++ b/src/components/imagecrop/WidgetImageCrop.vue @@ -36,26 +36,37 @@
-
+
-
+
@@ -90,12 +101,16 @@
- +
diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index c40fe95e49..4c87b68355 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -41,6 +41,8 @@ import { getExecutionIdByNode } from '@/utils/graphTraversalUtil' export interface WidgetSlotMetadata { index: number linked: boolean + originNodeId?: string + originOutputName?: string } /** @@ -355,6 +357,36 @@ function safeWidgetMapper( } } +function buildSlotMetadata( + inputs: INodeInputSlot[] | undefined, + graphRef: LGraph | null | undefined +): Map { + const metadata = new Map() + inputs?.forEach((input, index) => { + let originNodeId: string | undefined + let originOutputName: string | undefined + + if (input.link != null && graphRef) { + const link = graphRef.getLink(input.link) + if (link) { + originNodeId = String(link.origin_id) + const originNode = graphRef.getNodeById(link.origin_id) + originOutputName = originNode?.outputs?.[link.origin_slot]?.name + } + } + + const slotInfo: WidgetSlotMetadata = { + index, + linked: input.link != null, + originNodeId, + originOutputName + } + if (input.name) metadata.set(input.name, slotInfo) + if (input.widget?.name) metadata.set(input.widget.name, slotInfo) + }) + return metadata +} + // Extract safe data from LiteGraph node for Vue consumption export function extractVueNodeData(node: LGraphNode): VueNodeData { // Determine subgraph ID - null for root graph, string for subgraphs @@ -427,15 +459,11 @@ export function extractVueNodeData(node: LGraphNode): VueNodeData { const safeWidgets = reactiveComputed(() => { const widgetsSnapshot = node.widgets ?? [] + const freshMetadata = buildSlotMetadata(node.inputs, node.graph) slotMetadata.clear() - node.inputs?.forEach((input, index) => { - const slotInfo = { - index, - linked: input.link != null - } - if (input.name) slotMetadata.set(input.name, slotInfo) - if (input.widget?.name) slotMetadata.set(input.widget.name, slotInfo) - }) + for (const [key, value] of freshMetadata) { + slotMetadata.set(key, value) + } return widgetsSnapshot.map(safeWidgetMapper(node, slotMetadata)) }) @@ -488,17 +516,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { if (!nodeRef || !currentData) return - // Only extract slot-related data instead of full node re-extraction - const slotMetadata = new Map() - - nodeRef.inputs?.forEach((input, index) => { - const slotInfo = { - index, - linked: input.link != null - } - if (input.name) slotMetadata.set(input.name, slotInfo) - if (input.widget?.name) slotMetadata.set(input.widget.name, slotInfo) - }) + const slotMetadata = buildSlotMetadata(nodeRef.inputs, graph) // Update only widgets with new slot metadata, keeping other widget data intact for (const widget of currentData.widgets ?? []) { diff --git a/src/composables/useUpstreamValue.ts b/src/composables/useUpstreamValue.ts new file mode 100644 index 0000000000..de578cee4d --- /dev/null +++ b/src/composables/useUpstreamValue.ts @@ -0,0 +1,83 @@ +import { computed } from 'vue' + +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' +import { useWidgetValueStore } from '@/stores/widgetValueStore' +import type { LinkedUpstreamInfo } from '@/types/simplifiedWidget' + +interface UpstreamWidget { + name: string + type: string + value?: unknown +} + +type ValueExtractor = ( + widgets: UpstreamWidget[], + outputName: string | undefined +) => unknown | undefined + +export function useUpstreamValue( + getLinkedUpstream: () => LinkedUpstreamInfo | undefined, + extractValue: ValueExtractor +) { + const canvasStore = useCanvasStore() + const widgetValueStore = useWidgetValueStore() + + return computed(() => { + const upstream = getLinkedUpstream() + if (!upstream) return undefined + const graphId = canvasStore.canvas?.graph?.rootGraph.id + if (!graphId) return undefined + const widgets = widgetValueStore.getNodeWidgets(graphId, upstream.nodeId) + return extractValue(widgets, upstream.outputName) + }) +} + +export function singleValueExtractor( + isValid: (value: unknown) => boolean +): ValueExtractor { + return (widgets, outputName) => { + if (outputName) { + const matched = widgets.find((w) => w.name === outputName) + if (matched && isValid(matched.value)) return matched.value + } + const valid = widgets.filter((w) => isValid(w.value)) + return valid.length === 1 ? valid[0].value : undefined + } +} + +function isBoundsObject(value: unknown): boolean { + if (typeof value !== 'object' || value === null) return false + const v = value as Record + return ( + typeof v.x === 'number' && + typeof v.y === 'number' && + typeof v.width === 'number' && + typeof v.height === 'number' + ) +} + +export function boundsExtractor(): ValueExtractor { + const single = singleValueExtractor(isBoundsObject) + return (widgets, outputName) => { + const singleResult = single(widgets, outputName) + if (singleResult) return singleResult + + const getNum = (name: string): number | undefined => { + const w = widgets.find((w) => w.name === name) + return typeof w?.value === 'number' ? w.value : undefined + } + const x = getNum('x') + const y = getNum('y') + const width = getNum('width') + const height = getNum('height') + if ( + x !== undefined && + y !== undefined && + width !== undefined && + height !== undefined + ) { + return { x, y, width, height } + } + return undefined + } +} diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue index c9e73669ff..d643af4264 100644 --- a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue +++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue @@ -120,7 +120,11 @@ import { import { usePromotionStore } from '@/stores/promotionStore' import { useMissingModelStore } from '@/platform/missingModel/missingModelStore' import { useExecutionErrorStore } from '@/stores/executionErrorStore' -import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget' +import type { + LinkedUpstreamInfo, + SimplifiedWidget, + WidgetValue +} from '@/types/simplifiedWidget' import { cn } from '@/utils/tailwindUtil' import { getExecutionIdFromNodeData } from '@/utils/graphTraversalUtil' import { app } from '@/scripts/app' @@ -297,6 +301,14 @@ const processedWidgets = computed((): ProcessedWidget[] => { ? 'ring ring-component-node-widget-advanced' : undefined + const linkedUpstream: LinkedUpstreamInfo | undefined = + slotMetadata?.linked && slotMetadata.originNodeId + ? { + nodeId: slotMetadata.originNodeId, + outputName: slotMetadata.originOutputName + } + : undefined + const simplified: SimplifiedWidget = { name: widget.name, type: widget.type, @@ -305,6 +317,7 @@ const processedWidgets = computed((): ProcessedWidget[] => { callback: widget.callback, controlWidget: widget.controlWidget, label: widgetState?.label, + linkedUpstream, options: widgetOptions, spec: widget.spec } diff --git a/src/types/simplifiedWidget.ts b/src/types/simplifiedWidget.ts index 10ba258ce9..7703e15c1f 100644 --- a/src/types/simplifiedWidget.ts +++ b/src/types/simplifiedWidget.ts @@ -38,6 +38,11 @@ export type SafeControlWidget = { update: (value: WidgetValue) => void } +export interface LinkedUpstreamInfo { + nodeId: string + outputName?: string +} + export interface SimplifiedWidget< T extends WidgetValue = WidgetValue, O extends IWidgetOptions = IWidgetOptions @@ -77,6 +82,8 @@ export interface SimplifiedWidget< tooltip?: string controlWidget?: SafeControlWidget + + linkedUpstream?: LinkedUpstreamInfo } export interface SimplifiedControlWidget<