Skip to content

Commit 108cfaa

Browse files
authored
Support display of multitype slots (#7457)
Example with forcibly modified types for testing <img width="736" height="425" alt="image" src="https://github.com/user-attachments/assets/e885a7d0-5946-41be-b9b4-b9b195f50c92" /> Vue mode doesn't currently seem to display optional inputs, but the SVGs here include support for being made hollow with `--shape: url(#hollow)` <img width="765" height="360" alt="image" src="https://github.com/user-attachments/assets/0ea57179-99a4-4001-aa18-856e172287c0" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7457-Support-display-of-multitype-slots-2c86d73d3650818594afd988e73827e3) by [Unito](https://www.unito.io)
1 parent 5d745c9 commit 108cfaa

File tree

6 files changed

+136
-37
lines changed

6 files changed

+136
-37
lines changed
Lines changed: 19 additions & 0 deletions
Loading
Lines changed: 20 additions & 0 deletions
Loading

src/lib/litegraph/src/node/NodeSlot.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export interface IDrawOptions {
3030
highlight?: boolean
3131
}
3232

33+
const ROTATION_OFFSET = -Math.PI / 2
34+
3335
/** Shared base class for {@link LGraphNode} input and output slots. */
3436
export abstract class NodeSlot extends SlotBase implements INodeSlot {
3537
pos?: Point
@@ -130,6 +132,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
130132
slot_type === SlotType.Array ? SlotShape.Grid : this.shape
131133
) as SlotShape
132134

135+
ctx.save()
133136
ctx.beginPath()
134137
let doFill = true
135138

@@ -163,23 +166,60 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
163166
if (lowQuality) {
164167
ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8)
165168
} else {
166-
let radius: number
167169
if (slot_shape === SlotShape.HollowCircle) {
170+
const path = new Path2D()
171+
path.arc(pos[0], pos[1], 10, 0, Math.PI * 2)
172+
path.arc(pos[0], pos[1], highlight ? 2.5 : 1.5, 0, Math.PI * 2)
173+
ctx.clip(path, 'evenodd')
174+
}
175+
const radius = highlight ? 5 : 4
176+
const typesSet = new Set(
177+
`${this.type}`
178+
.split(',')
179+
.map(
180+
this.isConnected
181+
? (type) => colorContext.getConnectedColor(type)
182+
: (type) => colorContext.getDisconnectedColor(type)
183+
)
184+
)
185+
const types = [...typesSet].slice(0, 3)
186+
if (types.length > 1) {
168187
doFill = false
169-
doStroke = true
170-
ctx.lineWidth = 3
171-
ctx.strokeStyle = ctx.fillStyle
172-
radius = highlight ? 4 : 3
173-
} else {
174-
// Normal circle
175-
radius = highlight ? 5 : 4
188+
const arcLen = (Math.PI * 2) / types.length
189+
types.forEach((type, idx) => {
190+
ctx.moveTo(pos[0], pos[1])
191+
ctx.fillStyle = type
192+
ctx.arc(
193+
pos[0],
194+
pos[1],
195+
radius,
196+
arcLen * idx + ROTATION_OFFSET,
197+
Math.PI * 2 + ROTATION_OFFSET
198+
)
199+
ctx.fill()
200+
ctx.beginPath()
201+
})
202+
//add stroke dividers
203+
ctx.save()
204+
ctx.strokeStyle = 'black'
205+
ctx.lineWidth = 0.5
206+
types.forEach((_, idx) => {
207+
ctx.moveTo(pos[0], pos[1])
208+
const xOffset = Math.cos(arcLen * idx + ROTATION_OFFSET) * radius
209+
const yOffset = Math.sin(arcLen * idx + ROTATION_OFFSET) * radius
210+
ctx.lineTo(pos[0] + xOffset, pos[1] + yOffset)
211+
})
212+
ctx.stroke()
213+
ctx.restore()
214+
ctx.beginPath()
176215
}
177216
ctx.arc(pos[0], pos[1], radius, 0, Math.PI * 2)
178217
}
179218
}
180219

181220
if (doFill) ctx.fill()
182221
if (!lowQuality && doStroke) ctx.stroke()
222+
ctx.restore()
183223

184224
// render slot label
185225
const hideLabel = lowQuality || this.isWidgetInputSlot

src/renderer/extensions/vueNodes/components/InputSlot.vue

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
<!-- Connection Dot -->
2121
<SlotConnectionDot
2222
ref="connectionDotRef"
23-
:color="slotColor"
2423
:class="
2524
cn(
2625
'-translate-x-1/2 w-3',
2726
hasSlotError && 'ring-2 ring-error ring-offset-0 rounded-full'
2827
)
2928
"
29+
:slot-data
3030
@click="onClick"
3131
@dblclick="onDoubleClick"
3232
@pointerdown="onPointerDown"
@@ -54,7 +54,6 @@ import { computed, onErrorCaptured, ref, watchEffect } from 'vue'
5454
import type { ComponentPublicInstance } from 'vue'
5555
5656
import { useErrorHandling } from '@/composables/useErrorHandling'
57-
import { getSlotColor } from '@/constants/slotColors'
5857
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
5958
import { useSlotLinkDragUIState } from '@/renderer/core/canvas/links/slotLinkDragUIState'
6059
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
@@ -111,13 +110,6 @@ onErrorCaptured((error) => {
111110
return false
112111
})
113112
114-
const slotColor = computed(() => {
115-
if (hasSlotError.value) {
116-
return 'var(--color-error)'
117-
}
118-
return getSlotColor(props.slotData.type)
119-
})
120-
121113
const { state: dragState } = useSlotLinkDragUIState()
122114
const slotKey = computed(() =>
123115
getSlotKey(props.nodeId ?? '', props.index, true)

src/renderer/extensions/vueNodes/components/OutputSlot.vue

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
<!-- Connection Dot -->
1111
<SlotConnectionDot
1212
ref="connectionDotRef"
13-
:color="slotColor"
1413
class="w-3 translate-x-1/2"
14+
:slot-data
1515
@pointerdown="onPointerDown"
1616
/>
1717
</div>
@@ -22,7 +22,6 @@ import { computed, onErrorCaptured, ref, watchEffect } from 'vue'
2222
import type { ComponentPublicInstance } from 'vue'
2323
2424
import { useErrorHandling } from '@/composables/useErrorHandling'
25-
import { getSlotColor } from '@/constants/slotColors'
2625
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
2726
import { useSlotLinkDragUIState } from '@/renderer/core/canvas/links/slotLinkDragUIState'
2827
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
@@ -67,9 +66,6 @@ onErrorCaptured((error) => {
6766
return false
6867
})
6968
70-
// Get slot color based on type
71-
const slotColor = computed(() => getSlotColor(props.slotData.type))
72-
7369
const { state: dragState } = useSlotLinkDragUIState()
7470
const slotKey = computed(() =>
7571
getSlotKey(props.nodeId ?? '', props.index, false)
Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,45 @@
11
<script setup lang="ts">
2-
import { useTemplateRef } from 'vue'
2+
import { computed, useTemplateRef } from 'vue'
33
4+
import { getSlotColor } from '@/constants/slotColors'
5+
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
46
import { cn } from '@/utils/tailwindUtil'
57
import type { ClassValue } from '@/utils/tailwindUtil'
68
79
const props = defineProps<{
8-
color?: string
9-
multi?: boolean
10+
slotData?: INodeSlot
1011
class?: ClassValue
12+
hasError?: boolean
13+
multi?: boolean
1114
}>()
1215
1316
const slotElRef = useTemplateRef('slot-el')
1417
18+
function getTypes() {
19+
if (props.hasError) return ['var(--color-error)']
20+
//TODO Support connected/disconnected colors?
21+
if (!props.slotData) return [getSlotColor()]
22+
const typesSet = new Set(
23+
`${props.slotData.type}`.split(',').map(getSlotColor)
24+
)
25+
return [...typesSet].slice(0, 3)
26+
}
27+
const types = getTypes()
28+
1529
defineExpose({
1630
slotElRef
1731
})
32+
33+
const slotClass = computed(() =>
34+
cn(
35+
'bg-slate-300 rounded-full slot-dot',
36+
'transition-all duration-150',
37+
'border border-solid border-node-component-slot-dot-outline',
38+
props.multi
39+
? 'w-3 h-6'
40+
: 'size-3 cursor-crosshair group-hover/slot:[--node-component-slot-dot-outline-opacity-mult:5] group-hover/slot:scale-125'
41+
)
42+
)
1843
</script>
1944

2045
<template>
@@ -27,19 +52,26 @@ defineExpose({
2752
"
2853
>
2954
<div
55+
v-if="types.length === 1"
3056
ref="slot-el"
31-
class="slot-dot"
32-
:style="{ backgroundColor: color }"
33-
:class="
34-
cn(
35-
'bg-slate-300 rounded-full',
36-
'transition-all duration-150',
37-
'border border-solid border-node-component-slot-dot-outline',
38-
!multi &&
39-
'cursor-crosshair group-hover/slot:[--node-component-slot-dot-outline-opacity-mult:5] group-hover/slot:scale-125',
40-
multi ? 'w-3 h-6' : 'size-3'
41-
)
42-
"
57+
:style="{ backgroundColor: types[0] }"
58+
:class="slotClass"
4359
/>
60+
<div
61+
v-else
62+
ref="slot-el"
63+
:style="{
64+
'--type1': types[0],
65+
'--type2': types[1],
66+
'--type3': types[2]
67+
}"
68+
:class="slotClass"
69+
>
70+
<i-comfy:node-slot2
71+
v-if="types.length === 2"
72+
class="size-full -translate-y-1/2"
73+
/>
74+
<i-comfy:node-slot3 v-else class="size-full -translate-y-1/2" />
75+
</div>
4476
</div>
4577
</template>

0 commit comments

Comments
 (0)