Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/renderer/core/canvas/links/linkDropOrchestrator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { LGraph } from '@/lib/litegraph/src/LGraph'
import type { LinkConnectorAdapter } from '@/renderer/core/canvas/links/linkConnectorAdapter'
import type { SlotDropCandidate } from '@/renderer/core/canvas/links/slotLinkDragState'
import {
type SlotDropCandidate,
useSlotLinkDragState
} from '@/renderer/core/canvas/links/slotLinkDragState'
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import type { SlotLinkDragSession } from '@/renderer/extensions/vueNodes/composables/slotLinkDragSession'
Expand All @@ -13,8 +16,9 @@ interface DropResolutionContext {

export const resolveSlotTargetCandidate = (
target: EventTarget | null,
{ adapter, graph, session }: DropResolutionContext
{ adapter, graph }: DropResolutionContext
): SlotDropCandidate | null => {
const { state: dragState, setCompatibleForKey } = useSlotLinkDragState()
if (!(target instanceof HTMLElement)) return null

const elWithKey = target.closest<HTMLElement>('[data-slot-key]')
Expand All @@ -27,7 +31,7 @@ export const resolveSlotTargetCandidate = (
const candidate: SlotDropCandidate = { layout, compatible: false }

if (adapter && graph) {
const cached = session.compatCache.get(key)
const cached = dragState.compatible.get(key)
if (cached != null) {
candidate.compatible = cached
} else {
Expand All @@ -37,7 +41,7 @@ export const resolveSlotTargetCandidate = (
? adapter.isInputValidDrop(nodeId, layout.index)
: adapter.isOutputValidDrop(nodeId, layout.index)

session.compatCache.set(key, compatible)
setCompatibleForKey(key, compatible)
candidate.compatible = compatible
}
}
Expand All @@ -49,6 +53,7 @@ export const resolveNodeSurfaceCandidate = (
target: EventTarget | null,
{ adapter, graph, session }: DropResolutionContext
): SlotDropCandidate | null => {
const { setCompatibleForKey } = useSlotLinkDragState()
if (!(target instanceof HTMLElement)) return null

const elWithNode = target.closest<HTMLElement>('[data-node-id]')
Expand Down Expand Up @@ -99,7 +104,7 @@ export const resolveNodeSurfaceCandidate = (
? adapter.isInputValidDrop(nodeId, index)
: adapter.isOutputValidDrop(nodeId, index)

session.compatCache.set(key, compatible)
setCompatibleForKey(key, compatible)

if (!compatible) {
session.nodePreferred.set(nodeId, null)
Expand Down
16 changes: 14 additions & 2 deletions src/renderer/core/canvas/links/slotLinkDragState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface SlotDragState {
source: SlotDragSource | null
pointer: PointerPosition
candidate: SlotDropCandidate | null
compatible: Map<string, boolean>
}

const state = reactive<SlotDragState>({
Expand All @@ -43,7 +44,8 @@ const state = reactive<SlotDragState>({
client: { x: 0, y: 0 },
canvas: { x: 0, y: 0 }
},
candidate: null
candidate: null,
compatible: new Map<string, boolean>()
})

function updatePointerPosition(
Expand All @@ -67,6 +69,7 @@ function beginDrag(source: SlotDragSource, pointerId: number) {
state.source = source
state.pointerId = pointerId
state.candidate = null
state.compatible.clear()
}

function endDrag() {
Expand All @@ -78,6 +81,7 @@ function endDrag() {
state.pointer.canvas.x = 0
state.pointer.canvas.y = 0
state.candidate = null
state.compatible.clear()
}

function getSlotLayout(nodeId: string, slotIndex: number, isInput: boolean) {
Expand All @@ -92,6 +96,14 @@ export function useSlotLinkDragState() {
endDrag,
updatePointerPosition,
setCandidate,
getSlotLayout
getSlotLayout,
setCompatibleMap: (entries: Iterable<[string, boolean]>) => {
state.compatible.clear()
for (const [key, value] of entries) state.compatible.set(key, value)
},
setCompatibleForKey: (key: string, value: boolean) => {
state.compatible.set(key, value)
},
clearCompatible: () => state.compatible.clear()
}
}
8 changes: 8 additions & 0 deletions src/renderer/core/layout/store/layoutStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,14 @@ class LayoutStoreImpl implements LayoutStore {
return this.rerouteLayouts.get(rerouteId) || null
}

/**
* Returns all slot layout keys currently tracked by the store.
* Useful for global passes without relying on spatial queries.
*/
getAllSlotKeys(): string[] {
return Array.from(this.slotLayouts.keys())
}

/**
* Update link segment layout data
*/
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/core/layout/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ export interface LayoutStore {
getSlotLayout(key: string): SlotLayout | null
getRerouteLayout(rerouteId: RerouteId): RerouteLayout | null

// Returns all slot layout keys currently tracked by the store
getAllSlotKeys(): string[]

// Direct mutation API (CRDT-ready)
applyOperation(operation: LayoutOperation): void

Expand Down
14 changes: 13 additions & 1 deletion src/renderer/extensions/vueNodes/components/InputSlot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
import { useErrorHandling } from '@/composables/useErrorHandling'
import { getSlotColor } from '@/constants/slotColors'
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
import { useSlotLinkDragState } from '@/renderer/core/canvas/links/slotLinkDragState'
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking'
import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction'
Expand Down Expand Up @@ -113,6 +115,15 @@ const slotColor = computed(() => {
return getSlotColor(props.slotData.type)
})

const { state: dragState } = useSlotLinkDragState()
const slotKey = computed(() =>
getSlotKey(props.nodeId ?? '', props.index, true)
)
const shouldDim = computed(() => {
if (!dragState.active) return false
return !dragState.compatible.get(slotKey.value)
})

const slotWrapperClass = computed(() =>
cn(
'lg-slot lg-slot--input flex items-center group rounded-r-lg h-6',
Expand All @@ -122,7 +133,8 @@ const slotWrapperClass = computed(() =>
: 'pr-6 hover:bg-black/5 hover:dark:bg-white/5',
{
'lg-slot--connected': props.connected,
'lg-slot--compatible': props.compatible
'lg-slot--compatible': props.compatible,
'opacity-40': shouldDim.value
}
)
)
Expand Down
14 changes: 13 additions & 1 deletion src/renderer/extensions/vueNodes/components/OutputSlot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
import { useErrorHandling } from '@/composables/useErrorHandling'
import { getSlotColor } from '@/constants/slotColors'
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
import { useSlotLinkDragState } from '@/renderer/core/canvas/links/slotLinkDragState'
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking'
import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction'
Expand Down Expand Up @@ -83,6 +85,15 @@ onErrorCaptured((error) => {
// Get slot color based on type
const slotColor = computed(() => getSlotColor(props.slotData.type))

const { state: dragState } = useSlotLinkDragState()
const slotKey = computed(() =>
getSlotKey(props.nodeId ?? '', props.index, false)
)
const shouldDim = computed(() => {
if (!dragState.active) return false
return !dragState.compatible.get(slotKey.value)
})

const slotWrapperClass = computed(() =>
cn(
'lg-slot lg-slot--output flex items-center justify-end group rounded-l-lg h-6',
Expand All @@ -92,7 +103,8 @@ const slotWrapperClass = computed(() =>
: 'pl-6 hover:bg-black/5 hover:dark:bg-white/5',
{
'lg-slot--connected': props.connected,
'lg-slot--compatible': props.compatible
'lg-slot--compatible': props.compatible,
'opacity-40': shouldDim.value
}
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ interface PendingMoveData {
}

export interface SlotLinkDragSession {
compatCache: Map<string, boolean>
nodePreferred: Map<
number,
{ index: number; key: string; layout: SlotLayout } | null
Expand All @@ -22,14 +21,12 @@ export interface SlotLinkDragSession {

export function createSlotLinkDragSession(): SlotLinkDragSession {
const state: SlotLinkDragSession = {
compatCache: new Map(),
nodePreferred: new Map(),
lastHoverSlotKey: null,
lastHoverNodeId: null,
lastCandidateKey: null,
pendingMove: null,
reset: () => {
state.compatCache = new Map()
state.nodePreferred = new Map()
state.lastHoverSlotKey = null
state.lastHoverNodeId = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,15 @@ export function useSlotLinkInteraction({
index,
type
}: SlotInteractionOptions): SlotInteractionHandlers {
const { state, beginDrag, endDrag, updatePointerPosition, setCandidate } =
useSlotLinkDragState()
const {
state,
beginDrag,
endDrag,
updatePointerPosition,
setCandidate,
setCompatibleForKey,
clearCompatible
} = useSlotLinkDragState()
const conversion = useSharedCanvasPositionConversion()
const pointerSession = createPointerSession()
let activeAdapter: LinkConnectorAdapter | null = null
Expand Down Expand Up @@ -262,6 +269,7 @@ export function useSlotLinkInteraction({
activeAdapter = null
raf.cancel()
dragSession.dispose()
clearCompatible()
}

const updatePointerState = (event: PointerEvent) => {
Expand Down Expand Up @@ -319,6 +327,22 @@ export function useSlotLinkInteraction({
candidate = slotCandidate ?? nodeCandidate
dragSession.lastHoverSlotKey = hoveredSlotKey
dragSession.lastHoverNodeId = hoveredNodeId

if (slotCandidate) {
const key = getSlotKey(
slotCandidate.layout.nodeId,
slotCandidate.layout.index,
slotCandidate.layout.type === 'input'
)
setCompatibleForKey(key, !!slotCandidate.compatible)
} else if (nodeCandidate) {
const key = getSlotKey(
nodeCandidate.layout.nodeId,
nodeCandidate.layout.index,
nodeCandidate.layout.type === 'input'
)
setCompatibleForKey(key, !!nodeCandidate.compatible)
}
}

const newCandidate = candidate?.compatible ? candidate : null
Expand Down Expand Up @@ -637,6 +661,20 @@ export function useSlotLinkInteraction({
capture: true
})
)
const targetType: 'input' | 'output' = type === 'input' ? 'output' : 'input'
const allKeys = layoutStore.getAllSlotKeys()
clearCompatible()
for (const key of allKeys) {
const slotLayout = layoutStore.getSlotLayout(key)
if (!slotLayout) continue
if (slotLayout.type !== targetType) continue
const idx = slotLayout.index
const ok =
targetType === 'input'
? activeAdapter.isInputValidDrop(slotLayout.nodeId, idx)
: activeAdapter.isOutputValidDrop(slotLayout.nodeId, idx)
setCompatibleForKey(key, ok)
}
app.canvas?.setDirty(true, true)
event.preventDefault()
event.stopPropagation()
Expand Down
Loading