Optimize the node graph panel while panning
This commit is contained in:
parent
83d03ad67d
commit
7cd5531730
|
|
@ -20,13 +20,17 @@
|
||||||
|
|
||||||
const editor = getContext<EditorWrapper>("editor");
|
const editor = getContext<EditorWrapper>("editor");
|
||||||
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
|
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
|
||||||
|
const nodeGraphTransform = nodeGraph.transformStore;
|
||||||
|
const nodeGraphImportsExports = nodeGraph.importsExportsStore;
|
||||||
|
const visibleNodes = nodeGraph.visibleNodesStore;
|
||||||
|
const nodeGraphWires = nodeGraph.wiresStore;
|
||||||
const documentState = getContext<DocumentStore>("document");
|
const documentState = getContext<DocumentStore>("document");
|
||||||
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||||
|
|
||||||
let graph: HTMLDivElement | undefined;
|
let graph: HTMLDivElement | undefined;
|
||||||
|
|
||||||
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
$: gridSpacing = calculateGridSpacing($nodeGraphTransform.scale);
|
||||||
$: gridDotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
$: gridDotRadius = 1 + Math.floor($nodeGraphTransform.scale - 0.5 + 0.001) / 2;
|
||||||
|
|
||||||
// Close the context menu when the graph view overlay is closed
|
// Close the context menu when the graph view overlay is closed
|
||||||
$: if (!$documentState.graphViewOverlayOpen) closeContextMenu();
|
$: if (!$documentState.graphViewOverlayOpen) closeContextMenu();
|
||||||
|
|
@ -215,23 +219,22 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="graph" bind:this={graph} data-node-graph>
|
||||||
class="graph"
|
<div
|
||||||
bind:this={graph}
|
class="grid-background"
|
||||||
style:--grid-spacing={`${gridSpacing}px`}
|
style:--grid-spacing={`${gridSpacing}px`}
|
||||||
style:--grid-offset-x={`${$nodeGraph.transform.x}px`}
|
style:--grid-offset-x={`${$nodeGraphTransform.x}px`}
|
||||||
style:--grid-offset-y={`${$nodeGraph.transform.y}px`}
|
style:--grid-offset-y={`${$nodeGraphTransform.y}px`}
|
||||||
style:--grid-dot-radius={`${gridDotRadius}px`}
|
style:--grid-dot-radius={`${gridDotRadius}px`}
|
||||||
data-node-graph
|
></div>
|
||||||
>
|
|
||||||
<!-- Right click menu for adding nodes -->
|
<!-- Right click menu for adding nodes -->
|
||||||
{#if $nodeGraph.contextMenuInformation}
|
{#if $nodeGraph.contextMenuInformation}
|
||||||
<FloatingMenu
|
<FloatingMenu
|
||||||
class="context-menu"
|
class="context-menu"
|
||||||
data-context-menu
|
data-context-menu
|
||||||
styles={{
|
styles={{
|
||||||
left: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates[0] * $nodeGraph.transform.scale + $nodeGraph.transform.x}px`,
|
left: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates[0] * $nodeGraphTransform.scale + $nodeGraphTransform.x}px`,
|
||||||
top: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates[1] * $nodeGraph.transform.scale + $nodeGraph.transform.y}px`,
|
top: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates[1] * $nodeGraphTransform.scale + $nodeGraphTransform.y}px`,
|
||||||
}}
|
}}
|
||||||
open={true}
|
open={true}
|
||||||
type="Popover"
|
type="Popover"
|
||||||
|
|
@ -283,7 +286,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $nodeGraph.error}
|
{#if $nodeGraph.error}
|
||||||
<div class="node-error-container" style:transform-origin="0 0" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
<div class="node-error-container" style:transform-origin="0 0" style:transform={`translate(${$nodeGraphTransform.x}px, ${$nodeGraphTransform.y}px) scale(${$nodeGraphTransform.scale})`}>
|
||||||
<span class="node-error faded" style:left={`${$nodeGraph.error.position[0]}px`} style:top={`${$nodeGraph.error.position[1]}px`} transition:fade={FADE_TRANSITION}>
|
<span class="node-error faded" style:left={`${$nodeGraph.error.position[0]}px`} style:top={`${$nodeGraph.error.position[1]}px`} transition:fade={FADE_TRANSITION}>
|
||||||
{$nodeGraph.error.error}
|
{$nodeGraph.error.error}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -295,7 +298,7 @@
|
||||||
|
|
||||||
<!-- Click target debug visualizations -->
|
<!-- Click target debug visualizations -->
|
||||||
{#if $nodeGraph.clickTargets}
|
{#if $nodeGraph.clickTargets}
|
||||||
<div class="click-targets" style:transform-origin="0 0" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
<div class="click-targets" style:transform-origin="0 0" style:transform={`translate(${$nodeGraphTransform.x}px, ${$nodeGraphTransform.y}px) scale(${$nodeGraphTransform.scale})`}>
|
||||||
<svg>
|
<svg>
|
||||||
{#each $nodeGraph.clickTargets.nodeClickTargets as pathString}
|
{#each $nodeGraph.clickTargets.nodeClickTargets as pathString}
|
||||||
<path class="node" d={pathString} />
|
<path class="node" d={pathString} />
|
||||||
|
|
@ -318,9 +321,9 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Thick vertical layer connection wires -->
|
<!-- Thick vertical layer connection wires -->
|
||||||
<div class="wires" style:transform-origin="0 0" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
<div class="wires" style:transform-origin="0 0" style:transform={`translate(${$nodeGraphTransform.x}px, ${$nodeGraphTransform.y}px) scale(${$nodeGraphTransform.scale})`}>
|
||||||
<svg>
|
<svg>
|
||||||
{#each $nodeGraph.wires.values() as map}
|
{#each $nodeGraphWires.values() as map}
|
||||||
{#each map.values() as { pathString, dataType, thick, dashed }}
|
{#each map.values() as { pathString, dataType, thick, dashed }}
|
||||||
{#if thick}
|
{#if thick}
|
||||||
<path
|
<path
|
||||||
|
|
@ -337,9 +340,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Import and Export connectors -->
|
<!-- Import and Export connectors -->
|
||||||
<div class="imports-and-exports" style:transform-origin="0 0" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
<div class="imports-and-exports" style:transform-origin="0 0" style:transform={`translate(${$nodeGraphTransform.x}px, ${$nodeGraphTransform.y}px) scale(${$nodeGraphTransform.scale})`}>
|
||||||
{#if $nodeGraph.updateImportsExports}
|
{#if $nodeGraphImportsExports}
|
||||||
{#each $nodeGraph.updateImportsExports.imports as frontendOutput, index}
|
{#each $nodeGraphImportsExports.imports as frontendOutput, index}
|
||||||
{#if frontendOutput}
|
{#if frontendOutput}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -351,8 +354,8 @@
|
||||||
data-datatype={frontendOutput.dataType}
|
data-datatype={frontendOutput.dataType}
|
||||||
style:--data-color={`var(--color-data-${frontendOutput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${frontendOutput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${frontendOutput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${frontendOutput.dataType.toLowerCase()}-dim)`}
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 8) / 24}
|
style:--offset-left={($nodeGraphImportsExports.importPosition[0] - 8) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 8) / 24 + index}
|
style:--offset-top={($nodeGraphImportsExports.importPosition[1] - 8) / 24 + index}
|
||||||
>
|
>
|
||||||
{#if frontendOutput.connectedTo.length > 0}
|
{#if frontendOutput.connectedTo.length > 0}
|
||||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||||
|
|
@ -365,10 +368,10 @@
|
||||||
on:pointerenter={() => (hoveringImportIndex = index)}
|
on:pointerenter={() => (hoveringImportIndex = index)}
|
||||||
on:pointerleave={() => (hoveringImportIndex = undefined)}
|
on:pointerleave={() => (hoveringImportIndex = undefined)}
|
||||||
class="edit-import-export import"
|
class="edit-import-export import"
|
||||||
class:separator-bottom={index === 0 && $nodeGraph.updateImportsExports.addImportExport}
|
class:separator-bottom={index === 0 && $nodeGraphImportsExports.addImportExport}
|
||||||
class:separator-top={index === 1 && $nodeGraph.updateImportsExports.addImportExport}
|
class:separator-top={index === 1 && $nodeGraphImportsExports.addImportExport}
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 8) / 24}
|
style:--offset-left={($nodeGraphImportsExports.importPosition[0] - 8) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 8) / 24 + index}
|
style:--offset-top={($nodeGraphImportsExports.importPosition[1] - 8) / 24 + index}
|
||||||
>
|
>
|
||||||
{#if editingNameImportIndex === index}
|
{#if editingNameImportIndex === index}
|
||||||
<input
|
<input
|
||||||
|
|
@ -385,7 +388,7 @@
|
||||||
{frontendOutput.name}
|
{frontendOutput.name}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if (hoveringImportIndex === index || editingNameImportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}
|
{#if (hoveringImportIndex === index || editingNameImportIndex === index) && $nodeGraphImportsExports.addImportExport}
|
||||||
<IconButton
|
<IconButton
|
||||||
size={16}
|
size={16}
|
||||||
icon="Remove"
|
icon="Remove"
|
||||||
|
|
@ -402,17 +405,13 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div class="plus" style:--offset-top={($nodeGraphImportsExports.importPosition[1] - 12) / 24} style:--offset-left={($nodeGraphImportsExports.importPosition[0] - 12) / 24}>
|
||||||
class="plus"
|
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24}
|
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 12) / 24}
|
|
||||||
>
|
|
||||||
<IconButton size={24} icon="Add" action={() => editor.addPrimaryImport()} />
|
<IconButton size={24} icon="Add" action={() => editor.addPrimaryImport()} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#each $nodeGraph.updateImportsExports.exports as frontendInput, index}
|
{#each $nodeGraphImportsExports.exports as frontendInput, index}
|
||||||
{#if frontendInput}
|
{#if frontendInput}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -424,8 +423,8 @@
|
||||||
data-datatype={frontendInput.dataType}
|
data-datatype={frontendInput.dataType}
|
||||||
style:--data-color={`var(--color-data-${frontendInput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${frontendInput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${frontendInput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${frontendInput.dataType.toLowerCase()}-dim)`}
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 8) / 24}
|
style:--offset-left={($nodeGraphImportsExports.exportPosition[0] - 8) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 8) / 24 + index}
|
style:--offset-top={($nodeGraphImportsExports.exportPosition[1] - 8) / 24 + index}
|
||||||
>
|
>
|
||||||
{#if frontendInput.connectedTo !== "Connected to nothing."}
|
{#if frontendInput.connectedTo !== "Connected to nothing."}
|
||||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
||||||
|
|
@ -437,12 +436,12 @@
|
||||||
on:pointerenter={() => (hoveringExportIndex = index)}
|
on:pointerenter={() => (hoveringExportIndex = index)}
|
||||||
on:pointerleave={() => (hoveringExportIndex = undefined)}
|
on:pointerleave={() => (hoveringExportIndex = undefined)}
|
||||||
class="edit-import-export export"
|
class="edit-import-export export"
|
||||||
class:separator-bottom={index === 0 && $nodeGraph.updateImportsExports.addImportExport}
|
class:separator-bottom={index === 0 && $nodeGraphImportsExports.addImportExport}
|
||||||
class:separator-top={index === 1 && $nodeGraph.updateImportsExports.addImportExport}
|
class:separator-top={index === 1 && $nodeGraphImportsExports.addImportExport}
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 8) / 24}
|
style:--offset-left={($nodeGraphImportsExports.exportPosition[0] - 8) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 8) / 24 + index}
|
style:--offset-top={($nodeGraphImportsExports.exportPosition[1] - 8) / 24 + index}
|
||||||
>
|
>
|
||||||
{#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}
|
{#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraphImportsExports.addImportExport}
|
||||||
{#if index > 0}
|
{#if index > 0}
|
||||||
<div class="reorder-drag-grip" data-tooltip-description="Reorder this export"></div>
|
<div class="reorder-drag-grip" data-tooltip-description="Reorder this export"></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -473,28 +472,24 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div class="plus" style:--offset-left={($nodeGraphImportsExports.exportPosition[0] - 12) / 24} style:--offset-top={($nodeGraphImportsExports.exportPosition[1] - 12) / 24}>
|
||||||
class="plus"
|
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}
|
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 12) / 24}
|
|
||||||
>
|
|
||||||
<IconButton size={24} icon="Add" action={() => editor.addPrimaryExport()} />
|
<IconButton size={24} icon="Add" action={() => editor.addPrimaryExport()} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if $nodeGraph.updateImportsExports.addImportExport}
|
{#if $nodeGraphImportsExports.addImportExport}
|
||||||
<div
|
<div
|
||||||
class="plus"
|
class="plus"
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 12) / 24}
|
style:--offset-left={($nodeGraphImportsExports.importPosition[0] - 12) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24 + $nodeGraph.updateImportsExports.imports.length}
|
style:--offset-top={($nodeGraphImportsExports.importPosition[1] - 12) / 24 + $nodeGraphImportsExports.imports.length}
|
||||||
>
|
>
|
||||||
<IconButton size={24} icon="Add" action={() => editor.addSecondaryImport()} />
|
<IconButton size={24} icon="Add" action={() => editor.addSecondaryImport()} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="plus"
|
class="plus"
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}
|
style:--offset-left={($nodeGraphImportsExports.exportPosition[0] - 12) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 12) / 24 + $nodeGraph.updateImportsExports.exports.length}
|
style:--offset-top={($nodeGraphImportsExports.exportPosition[1] - 12) / 24 + $nodeGraphImportsExports.exports.length}
|
||||||
>
|
>
|
||||||
<IconButton size={24} icon="Add" action={() => editor.addSecondaryExport()} />
|
<IconButton size={24} icon="Add" action={() => editor.addSecondaryExport()} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -502,16 +497,16 @@
|
||||||
|
|
||||||
{#if $nodeGraph.reorderImportIndex !== undefined}
|
{#if $nodeGraph.reorderImportIndex !== undefined}
|
||||||
{@const position = {
|
{@const position = {
|
||||||
x: Number($nodeGraph.updateImportsExports.importPosition[0]),
|
x: Number($nodeGraphImportsExports.importPosition[0]),
|
||||||
y: Number($nodeGraph.updateImportsExports.importPosition[1]) + Number($nodeGraph.reorderImportIndex) * 24,
|
y: Number($nodeGraphImportsExports.importPosition[1]) + Number($nodeGraph.reorderImportIndex) * 24,
|
||||||
}}
|
}}
|
||||||
<div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 12) / 24}></div>
|
<div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 12) / 24}></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $nodeGraph.reorderExportIndex !== undefined}
|
{#if $nodeGraph.reorderExportIndex !== undefined}
|
||||||
{@const position = {
|
{@const position = {
|
||||||
x: Number($nodeGraph.updateImportsExports.exportPosition[0]),
|
x: Number($nodeGraphImportsExports.exportPosition[0]),
|
||||||
y: Number($nodeGraph.updateImportsExports.exportPosition[1]) + Number($nodeGraph.reorderExportIndex) * 24,
|
y: Number($nodeGraphImportsExports.exportPosition[1]) + Number($nodeGraph.reorderExportIndex) * 24,
|
||||||
}}
|
}}
|
||||||
<div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 12) / 24}></div>
|
<div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 12) / 24}></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -519,11 +514,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Layers and nodes -->
|
<!-- Layers and nodes -->
|
||||||
<div class="layers-and-nodes" style:transform-origin="0 0" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
<div class="layers-and-nodes" style:transform-origin="0 0" style:transform={`translate(${$nodeGraphTransform.x}px, ${$nodeGraphTransform.y}px) scale(${$nodeGraphTransform.scale})`}>
|
||||||
<!-- Layers -->
|
<!-- Layers -->
|
||||||
{#each Array.from($nodeGraph.nodes)
|
{#each Array.from($nodeGraph.nodes)
|
||||||
.filter(([nodeId, node]) => node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
|
.filter(([nodeId, node]) => node.isLayer && $visibleNodes.has(nodeId))
|
||||||
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
|
.map(([_, node]) => node) as node (node.id)}
|
||||||
{@const clipPathId = String(Math.random()).substring(2)}
|
{@const clipPathId = String(Math.random()).substring(2)}
|
||||||
{@const stackDataInput = node.exposedInputs[0]}
|
{@const stackDataInput = node.exposedInputs[0]}
|
||||||
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
|
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
|
||||||
|
|
@ -687,7 +682,7 @@
|
||||||
<!-- Node connection wires -->
|
<!-- Node connection wires -->
|
||||||
<div class="wires">
|
<div class="wires">
|
||||||
<svg>
|
<svg>
|
||||||
{#each $nodeGraph.wires.values() as map}
|
{#each $nodeGraphWires.values() as map}
|
||||||
{#each map.values() as { pathString, dataType, thick, dashed }}
|
{#each map.values() as { pathString, dataType, thick, dashed }}
|
||||||
{#if !thick}
|
{#if !thick}
|
||||||
<path
|
<path
|
||||||
|
|
@ -714,8 +709,8 @@
|
||||||
|
|
||||||
<!-- Nodes -->
|
<!-- Nodes -->
|
||||||
{#each Array.from($nodeGraph.nodes)
|
{#each Array.from($nodeGraph.nodes)
|
||||||
.filter(([nodeId, node]) => !node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
|
.filter(([nodeId, node]) => !node.isLayer && $visibleNodes.has(nodeId))
|
||||||
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
|
.map(([_, node]) => node) as node (node.id)}
|
||||||
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
|
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
|
||||||
{@const clipPathId = String(Math.random()).substring(2)}
|
{@const clipPathId = String(Math.random()).substring(2)}
|
||||||
{@const description = node.reference ? $nodeGraph.nodeDescriptions.get(node.reference) : undefined}
|
{@const description = node.reference ? $nodeGraph.nodeDescriptions.get(node.reference) : undefined}
|
||||||
|
|
@ -870,18 +865,25 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
// We're displaying the dotted grid in a pseudo-element because `image-rendering` is an inherited property and we don't want it to apply to child elements
|
.grid-background {
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-size: var(--grid-spacing) var(--grid-spacing);
|
pointer-events: none;
|
||||||
background-position: calc(var(--grid-offset-x) - var(--grid-dot-radius)) calc(var(--grid-offset-y) - var(--grid-dot-radius));
|
|
||||||
background-image: radial-gradient(circle at var(--grid-dot-radius) var(--grid-dot-radius), var(--color-3-darkgray) var(--grid-dot-radius), transparent 0);
|
// We're displaying the dotted grid in a pseudo-element because `image-rendering` is an inherited property and we don't want it to apply to child elements
|
||||||
background-repeat: repeat;
|
&::before {
|
||||||
image-rendering: pixelated;
|
content: "";
|
||||||
mix-blend-mode: screen;
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-size: var(--grid-spacing) var(--grid-spacing);
|
||||||
|
background-position: calc(var(--grid-offset-x) - var(--grid-dot-radius)) calc(var(--grid-offset-y) - var(--grid-dot-radius));
|
||||||
|
background-image: radial-gradient(circle at var(--grid-dot-radius) var(--grid-dot-radius), var(--color-3-darkgray) var(--grid-dot-radius), transparent 0);
|
||||||
|
background-repeat: repeat;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> img {
|
> img {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import type { NodeGraphErrorDiagnostic, BoxSelection, FrontendClickTargets, Cont
|
||||||
|
|
||||||
export type NodeGraphStore = ReturnType<typeof createNodeGraphStore>;
|
export type NodeGraphStore = ReturnType<typeof createNodeGraphStore>;
|
||||||
|
|
||||||
|
export type NodeGraphTransform = { scale: number; x: number; y: number };
|
||||||
|
|
||||||
type NodeGraphStoreState = {
|
type NodeGraphStoreState = {
|
||||||
box: BoxSelection | undefined;
|
box: BoxSelection | undefined;
|
||||||
clickTargets: FrontendClickTargets | undefined;
|
clickTargets: FrontendClickTargets | undefined;
|
||||||
|
|
@ -14,17 +16,12 @@ type NodeGraphStoreState = {
|
||||||
layerWidths: Map<bigint, number>;
|
layerWidths: Map<bigint, number>;
|
||||||
chainWidths: Map<bigint, number>;
|
chainWidths: Map<bigint, number>;
|
||||||
hasLeftInputWire: Map<bigint, boolean>;
|
hasLeftInputWire: Map<bigint, boolean>;
|
||||||
updateImportsExports: MessageBody<"UpdateImportsExports"> | undefined;
|
|
||||||
nodes: Map<bigint, FrontendNode>;
|
nodes: Map<bigint, FrontendNode>;
|
||||||
visibleNodes: Set<bigint>;
|
|
||||||
/// The index is the exposed input index. The exports have a first key value of u32::MAX.
|
|
||||||
wires: Map<bigint, Map<number, WirePath>>;
|
|
||||||
wirePathInProgress: WirePath | undefined;
|
wirePathInProgress: WirePath | undefined;
|
||||||
nodeDescriptions: Map<string, string>;
|
nodeDescriptions: Map<string, string>;
|
||||||
nodeTypes: FrontendNodeType[];
|
nodeTypes: FrontendNodeType[];
|
||||||
thumbnails: Map<bigint, string>;
|
thumbnails: Map<bigint, string>;
|
||||||
selected: bigint[];
|
selected: bigint[];
|
||||||
transform: { scale: number; x: number; y: number };
|
|
||||||
inSelectedNetwork: boolean;
|
inSelectedNetwork: boolean;
|
||||||
reorderImportIndex: number | undefined;
|
reorderImportIndex: number | undefined;
|
||||||
reorderExportIndex: number | undefined;
|
reorderExportIndex: number | undefined;
|
||||||
|
|
@ -37,16 +34,12 @@ const initialState: NodeGraphStoreState = {
|
||||||
layerWidths: new Map(),
|
layerWidths: new Map(),
|
||||||
chainWidths: new Map(),
|
chainWidths: new Map(),
|
||||||
hasLeftInputWire: new Map(),
|
hasLeftInputWire: new Map(),
|
||||||
updateImportsExports: undefined,
|
|
||||||
nodes: new Map(),
|
nodes: new Map(),
|
||||||
visibleNodes: new Set(),
|
|
||||||
wires: new Map(),
|
|
||||||
wirePathInProgress: undefined,
|
wirePathInProgress: undefined,
|
||||||
nodeDescriptions: new Map(),
|
nodeDescriptions: new Map(),
|
||||||
nodeTypes: [],
|
nodeTypes: [],
|
||||||
thumbnails: new Map(),
|
thumbnails: new Map(),
|
||||||
selected: [],
|
selected: [],
|
||||||
transform: { scale: 1, x: 0, y: 0 },
|
|
||||||
inSelectedNetwork: true,
|
inSelectedNetwork: true,
|
||||||
reorderImportIndex: undefined,
|
reorderImportIndex: undefined,
|
||||||
reorderExportIndex: undefined,
|
reorderExportIndex: undefined,
|
||||||
|
|
@ -59,6 +52,22 @@ const store: Writable<NodeGraphStoreState> = import.meta.hot?.data?.store || wri
|
||||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||||
const { subscribe, update } = store;
|
const { subscribe, update } = store;
|
||||||
|
|
||||||
|
// Separate transform store so pan/zoom updates don't trigger re-rendering the entire node graph
|
||||||
|
const transformStore: Writable<NodeGraphTransform> = import.meta.hot?.data?.transformStore || writable<NodeGraphTransform>({ scale: 1, x: 0, y: 0 });
|
||||||
|
if (import.meta.hot) import.meta.hot.data.transformStore = transformStore;
|
||||||
|
|
||||||
|
// Separate imports/exports store so viewport-anchored position updates don't trigger node re-renders
|
||||||
|
const importsExportsStore: Writable<MessageBody<"UpdateImportsExports"> | undefined> = import.meta.hot?.data?.importsExportsStore || writable(undefined);
|
||||||
|
if (import.meta.hot) import.meta.hot.data.importsExportsStore = importsExportsStore;
|
||||||
|
|
||||||
|
// Separate visible nodes store so viewport culling changes don't trigger full node re-renders
|
||||||
|
const visibleNodesStore: Writable<Set<bigint>> = import.meta.hot?.data?.visibleNodesStore || writable(new Set());
|
||||||
|
if (import.meta.hot) import.meta.hot.data.visibleNodesStore = visibleNodesStore;
|
||||||
|
|
||||||
|
// Separate wires store so wire path updates (e.g. export connector movement during pan) don't trigger node re-renders
|
||||||
|
const wiresStore: Writable<Map<bigint, Map<number, WirePath>>> = import.meta.hot?.data?.wiresStore || writable(new Map());
|
||||||
|
if (import.meta.hot) import.meta.hot.data.wiresStore = wiresStore;
|
||||||
|
|
||||||
export function createNodeGraphStore(subscriptions: SubscriptionsRouter) {
|
export function createNodeGraphStore(subscriptions: SubscriptionsRouter) {
|
||||||
destroyNodeGraphStore();
|
destroyNodeGraphStore();
|
||||||
|
|
||||||
|
|
@ -108,10 +117,7 @@ export function createNodeGraphStore(subscriptions: SubscriptionsRouter) {
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.subscribeFrontendMessage("UpdateImportsExports", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateImportsExports", (data) => {
|
||||||
update((state) => {
|
importsExportsStore.set(data);
|
||||||
state.updateImportsExports = data;
|
|
||||||
return state;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.subscribeFrontendMessage("UpdateInSelectedNetwork", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateInSelectedNetwork", (data) => {
|
||||||
|
|
@ -148,20 +154,35 @@ export function createNodeGraphStore(subscriptions: SubscriptionsRouter) {
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.subscribeFrontendMessage("UpdateVisibleNodes", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateVisibleNodes", (data) => {
|
||||||
update((state) => {
|
const newNodes = new Set<bigint>(data.nodes);
|
||||||
state.visibleNodes = new Set<bigint>(data.nodes);
|
|
||||||
return state;
|
// Short-circuit when the visible set hasn't changed to avoid unnecessary re-renders
|
||||||
|
let changed = false;
|
||||||
|
const unsubscribe = visibleNodesStore.subscribe((current) => {
|
||||||
|
if (current.size !== newNodes.size) {
|
||||||
|
changed = true;
|
||||||
|
} else {
|
||||||
|
newNodes.forEach((node) => {
|
||||||
|
if (!current.has(node)) changed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
unsubscribe();
|
||||||
|
|
||||||
|
if (!changed) return;
|
||||||
|
|
||||||
|
visibleNodesStore.set(newNodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.subscribeFrontendMessage("UpdateNodeGraphWires", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeGraphWires", (data) => {
|
||||||
update((state) => {
|
if (data.wires.length === 0) return;
|
||||||
|
|
||||||
|
wiresStore.update((wires) => {
|
||||||
data.wires.forEach((wireUpdate) => {
|
data.wires.forEach((wireUpdate) => {
|
||||||
let inputMap = state.wires.get(wireUpdate.id);
|
let inputMap = wires.get(wireUpdate.id);
|
||||||
// If it doesn't exist, create it and set it in the outer map
|
|
||||||
if (!inputMap) {
|
if (!inputMap) {
|
||||||
inputMap = new Map();
|
inputMap = new Map();
|
||||||
state.wires.set(wireUpdate.id, inputMap);
|
wires.set(wireUpdate.id, inputMap);
|
||||||
}
|
}
|
||||||
if (wireUpdate.wirePathUpdate !== undefined) {
|
if (wireUpdate.wirePathUpdate !== undefined) {
|
||||||
inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate);
|
inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate);
|
||||||
|
|
@ -169,15 +190,12 @@ export function createNodeGraphStore(subscriptions: SubscriptionsRouter) {
|
||||||
inputMap.delete(wireUpdate.inputIndex);
|
inputMap.delete(wireUpdate.inputIndex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return state;
|
return wires;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.subscribeFrontendMessage("ClearAllNodeGraphWires", () => {
|
subscriptions.subscribeFrontendMessage("ClearAllNodeGraphWires", () => {
|
||||||
update((state) => {
|
wiresStore.set(new Map());
|
||||||
state.wires.clear();
|
|
||||||
return state;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.subscribeFrontendMessage("UpdateNodeGraphSelection", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeGraphSelection", (data) => {
|
||||||
|
|
@ -188,10 +206,7 @@ export function createNodeGraphStore(subscriptions: SubscriptionsRouter) {
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.subscribeFrontendMessage("UpdateNodeGraphTransform", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeGraphTransform", (data) => {
|
||||||
update((state) => {
|
transformStore.set({ scale: data.scale, x: data.translation[0], y: data.translation[1] });
|
||||||
state.transform = { scale: data.scale, x: data.translation[0], y: data.translation[1] };
|
|
||||||
return state;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriptions.subscribeFrontendMessage("UpdateNodeThumbnail", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeThumbnail", (data) => {
|
||||||
|
|
@ -208,7 +223,7 @@ export function createNodeGraphStore(subscriptions: SubscriptionsRouter) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return { subscribe };
|
return { subscribe, transformStore, importsExportsStore, visibleNodesStore, wiresStore };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function destroyNodeGraphStore() {
|
export function destroyNodeGraphStore() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue