mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Complete nodes and wires
This commit is contained in:
@@ -223,338 +223,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $nodeGraph.shouldRenderSvelteNodes}
|
||||
<div
|
||||
class="graph-background"
|
||||
style:--grid-spacing={`${gridSpacing}px`}
|
||||
style:--grid-offset-x={`${$nodeGraph.transform.x}px`}
|
||||
style:--grid-offset-y={`${$nodeGraph.transform.y}px`}
|
||||
style:--grid-dot-radius={`${gridDotRadius}px`}
|
||||
style:--fade-artwork={`${$nodeGraph.opacity}%`}
|
||||
/>
|
||||
<div
|
||||
class="graph-background"
|
||||
style:--grid-spacing={`${gridSpacing}px`}
|
||||
style:--grid-offset-x={`${$nodeGraph.transform.x}px`}
|
||||
style:--grid-offset-y={`${$nodeGraph.transform.y}px`}
|
||||
style:--grid-dot-radius={`${gridDotRadius}px`}
|
||||
style:--fade-artwork={`${$nodeGraph.opacity}%`}
|
||||
/>
|
||||
|
||||
<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})`}>
|
||||
{#each Array.from($nodeGraph.nodesToRender) as [nodeId, nodeToRender]}
|
||||
{#if nodeToRender.nodeOrLayer.layer !== undefined}
|
||||
{@const nodeMetadata = nodeToRender.metadata}
|
||||
{@const layer = nodeToRender.nodeOrLayer.layer}
|
||||
{@const clipPathId = String(Math.random()).substring(2)}
|
||||
{@const layerAreaWidth = $nodeGraph.layerWidths.get(nodeToRender.metadata.nodeId) || 8}
|
||||
{@const layerChainWidth = layer.chainWidth !== 0 ? layer.chainWidth + 0.5 : 0}
|
||||
{@const description = (nodeMetadata.reference && $nodeGraph.nodeDescriptions.get(nodeMetadata.reference)) || undefined}
|
||||
<div
|
||||
class="layer"
|
||||
class:selected={nodeMetadata.selected}
|
||||
class:in-selected-network={$nodeGraph.inSelectedNetwork}
|
||||
class:previewed={$nodeGraph.previewedNode === nodeId}
|
||||
class:disabled={!nodeMetadata.visible}
|
||||
style:--offset-left={layer.position.x}
|
||||
style:--offset-top={layer.position.y}
|
||||
style:--clip-path-id={`url(#${clipPathId})`}
|
||||
style:--data-color={`var(--color-data-${layer.output.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${layer.output.dataType.toLowerCase()}-dim)`}
|
||||
style:--layer-area-width={layerAreaWidth}
|
||||
style:--node-chain-area-left-extension={layerChainWidth}
|
||||
title={`${nodeMetadata.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${nodeId}` : "")}
|
||||
>
|
||||
{#if nodeMetadata.errors}
|
||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{layer.errors}</span>
|
||||
<span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{layer.errors}</span>
|
||||
{/if}
|
||||
<div class="thumbnail">
|
||||
{#if $nodeGraph.thumbnails.has(nodeId)}
|
||||
{@html $nodeGraph.thumbnails.get(nodeId)}
|
||||
{/if}
|
||||
<!-- Layer stacking top output -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 12"
|
||||
class="connector top"
|
||||
style:--data-color={`var(--color-data-${layer.output.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${layer.output.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{outputTooltip(layer.output)}</title>
|
||||
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill={layer.output.connectedTo.length > 0 ? "var(--data-color)" : "var(--data-color-dim)"} />
|
||||
|
||||
{#if layer.output.connectedTo.length > 0 && layer.primaryOutputConnectedToLayer}
|
||||
<path d="M0,-3.5h8v8l-2.521,-1.681a2.666,2.666,0,0,0,-2.959,0l-2.52,1.681z" fill="var(--data-color-dim)" />
|
||||
{/if}
|
||||
</svg>
|
||||
<!-- Layer stacking bottom input -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 12"
|
||||
class="connector bottom"
|
||||
style:--data-color={`var(--color-data-${layer.bottomInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${layer.bottomInput.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
{#if layer.bottomInput}
|
||||
<title>{inputTooltip(layer.bottomInput)}</title>
|
||||
{/if}
|
||||
{#if layer.bottomInput?.connectedToNode !== undefined}
|
||||
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color)" />
|
||||
{#if layer.primaryInputConnectedToLayer}
|
||||
<path d="M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z" fill="var(--data-color-dim)" />
|
||||
{/if}
|
||||
{:else}
|
||||
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color-dim)" />
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
<!-- Layer input connector (from left) -->
|
||||
{#if layer.sideInput}
|
||||
<div class="input connectors">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
style:--data-color={`var(--color-data-${layer.sideInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${layer.sideInput.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{inputTooltip(layer.sideInput)}</title>
|
||||
<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={layer.sideInput.connectedToNode !== undefined ? "var(--data-color)" : "var(--data-color-dim)"}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="details">
|
||||
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
|
||||
<TextLabel>{nodeMetadata.displayName}</TextLabel>
|
||||
</div>
|
||||
<div class="solo-drag-grip" title="Drag only this layer without pushing others outside the stack"></div>
|
||||
<IconButton
|
||||
class={"visibility"}
|
||||
size={24}
|
||||
icon={nodeMetadata.visible ? "EyeVisible" : "EyeHidden"}
|
||||
action={() => {
|
||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||
}}
|
||||
tooltip={nodeMetadata.visible ? "Visible" : "Hidden"}
|
||||
/>
|
||||
|
||||
<svg class="border-mask" width="0" height="0">
|
||||
<defs>
|
||||
<clipPath id={clipPathId}>
|
||||
<!-- Keep this equation in sync with the equivalent one in the CSS rule for `.layer { width: ... }` below -->
|
||||
<path clip-rule="evenodd" d={layerBorderMask(24 * layerAreaWidth - 12, layerChainWidth * 24, layer.layerHasLeftBorderGap)} />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#each Array.from($nodeGraph.nodesToRender) as [_, nodeToRender]}
|
||||
{#each nodeToRender.wires as [wire, thick, dataType]}
|
||||
<svg class="wire">
|
||||
<path d={wire} style:--data-line-width={`${thick ? 8 : 2}px`} style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`} style:--data-dasharray={"3,0"} />
|
||||
</svg>
|
||||
{/each}
|
||||
{/each}
|
||||
{#each Array.from($nodeGraph.nodesToRender) as [nodeId, nodeToRender]}
|
||||
{#if nodeToRender.nodeOrLayer.node !== undefined && $nodeGraph.visibleNodes.has(nodeId)}
|
||||
{@const nodeMetadata = nodeToRender.metadata}
|
||||
{@const node = nodeToRender.nodeOrLayer.node}
|
||||
{@const exposedInputsOutputs = collectExposedInputsOutputs(node.inputs, node.outputs)}
|
||||
{@const clipPathId = String(Math.random()).substring(2)}
|
||||
{@const description = (nodeMetadata.reference && $nodeGraph.nodeDescriptions.get(nodeMetadata.reference)) || undefined}
|
||||
<div
|
||||
class="node"
|
||||
class:selected={nodeMetadata.selected}
|
||||
class:previewed={$nodeGraph.previewedNode == nodeId}
|
||||
class:disabled={!nodeMetadata.visible}
|
||||
style:--offset-left={node.position.x}
|
||||
style:--offset-top={node.position.y}
|
||||
style:--clip-path-id={`url(#${clipPathId})`}
|
||||
style:--data-color={`var(--color-data-${(node.outputs[0]?.dataType || "General").toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${(node.outputs[0]?.dataType || "General").toLowerCase()}-dim)`}
|
||||
title={`${nodeMetadata.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${nodeId}` : "")}
|
||||
>
|
||||
{#if nodeMetadata.errors}
|
||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||
<span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||
{/if}
|
||||
<!-- Primary row -->
|
||||
<div class="primary" class:in-selected-network={$nodeGraph.inSelectedNetwork} class:no-secondary-section={exposedInputsOutputs.length === 0}>
|
||||
<IconLabel icon={nodeIcon(nodeMetadata.reference)} />
|
||||
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
|
||||
<TextLabel>{nodeMetadata.displayName}</TextLabel>
|
||||
</div>
|
||||
<!-- Secondary rows -->
|
||||
{#if exposedInputsOutputs.length > 0}
|
||||
<div class="secondary" class:in-selected-network={$nodeGraph.inSelectedNetwork}>
|
||||
{#each exposedInputsOutputs as [input, output]}
|
||||
<div class={`secondary-row expanded ${input ? "input" : output ? "output" : ""}`}>
|
||||
<TextLabel tooltip={(input ? `${input.name}\n\n${input.description}` : output ? `${output.name}\n\n${output.description}` : "").trim()}>
|
||||
{input?.name ?? output?.name ?? ""}
|
||||
</TextLabel>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Input connectors -->
|
||||
<div class="input connectors">
|
||||
{#each node.inputs as input}
|
||||
{#if input !== undefined}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
style:--data-color={`var(--color-data-${input.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${input.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{inputTooltip(input)}</title>
|
||||
<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${input.connectedToString === "nothing" ? "-dim" : ""})`}
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<!-- Output connectors -->
|
||||
<div class="output connectors">
|
||||
{#each node.outputs as output}
|
||||
{#if output !== undefined}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
style:--data-color={`var(--color-data-${output.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${output.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{outputTooltip(output)}</title>
|
||||
<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={output.connectedTo !== undefined ? "var(--data-color)" : "var(--data-color-dim)"}
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<svg class="border-mask" width="0" height="0">
|
||||
<defs>
|
||||
<clipPath id={clipPathId}>
|
||||
<path clip-rule="evenodd" d={nodeBorderMask(node.inputs, node.outputs)} />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="native-node-graph-ui">{@html $nodeGraph.nativeNodeGraphSVGString}</div>
|
||||
{/if}
|
||||
{#each Array.from($nodeGraph.nodesToRender) as [_, nodeToRender]}
|
||||
{#each nodeToRender.wires as [wire, thick, dataType]}
|
||||
<svg class="wire">
|
||||
<path d={wire} style:--data-line-width={`${thick ? 8 : 2}px`} style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`} style:--data-dasharray={"3,0"} />
|
||||
</svg>
|
||||
{/each}
|
||||
{/each}
|
||||
{#each Array.from($nodeGraph.nodesToRender) as [nodeId, nodeToRender]}
|
||||
{#if nodeToRender.nodeOrLayer.node !== undefined && $nodeGraph.visibleNodes.has(nodeId)}
|
||||
{@const nodeMetadata = nodeToRender.metadata}
|
||||
{@const node = nodeToRender.nodeOrLayer.node}
|
||||
{@const exposedInputsOutputs = collectExposedInputsOutputs(node.inputs, node.outputs)}
|
||||
{@const clipPathId = String(Math.random()).substring(2)}
|
||||
{@const description = (nodeMetadata.reference && $nodeGraph.nodeDescriptions.get(nodeMetadata.reference)) || undefined}
|
||||
<div
|
||||
class="node"
|
||||
class:selected={nodeMetadata.selected}
|
||||
class:previewed={$nodeGraph.previewedNode == nodeId}
|
||||
class:disabled={!nodeMetadata.visible}
|
||||
style:--offset-left={node.position.x}
|
||||
style:--offset-top={node.position.y}
|
||||
style:--clip-path-id={`url(#${clipPathId})`}
|
||||
style:--data-color={`var(--color-data-${(node.outputs[0]?.dataType || "General").toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${(node.outputs[0]?.dataType || "General").toLowerCase()}-dim)`}
|
||||
title={`${nodeMetadata.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${nodeId}` : "")}
|
||||
>
|
||||
{#if nodeMetadata.errors}
|
||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||
<span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||
{/if}
|
||||
<!-- Primary row -->
|
||||
<div class="primary" class:in-selected-network={$nodeGraph.inSelectedNetwork} class:no-secondary-section={exposedInputsOutputs.length === 0}>
|
||||
<IconLabel icon={nodeIcon(nodeMetadata.reference)} />
|
||||
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
|
||||
<TextLabel>{nodeMetadata.displayName}</TextLabel>
|
||||
</div>
|
||||
<!-- Secondary rows -->
|
||||
{#if exposedInputsOutputs.length > 0}
|
||||
<div class="secondary" class:in-selected-network={$nodeGraph.inSelectedNetwork}>
|
||||
{#each exposedInputsOutputs as [input, output]}
|
||||
<div class={`secondary-row expanded ${input ? "input" : output ? "output" : ""}`}>
|
||||
<TextLabel tooltip={(input ? `${input.name}\n\n${input.description}` : output ? `${output.name}\n\n${output.description}` : "").trim()}>
|
||||
{input?.name ?? output?.name ?? ""}
|
||||
</TextLabel>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Input connectors -->
|
||||
<div class="input connectors">
|
||||
{#each node.inputs as input}
|
||||
{#if input !== undefined}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
style:--data-color={`var(--color-data-${input.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${input.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{inputTooltip(input)}</title>
|
||||
<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${input.connectedToString === "nothing" ? "-dim" : ""})`}
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<!-- Output connectors -->
|
||||
<div class="output connectors">
|
||||
{#each node.outputs as output}
|
||||
{#if output !== undefined}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
class="connector"
|
||||
style:--data-color={`var(--color-data-${output.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${output.dataType.toLowerCase()}-dim)`}
|
||||
>
|
||||
<title>{outputTooltip(output)}</title>
|
||||
<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={output.connectedTo !== undefined ? "var(--data-color)" : "var(--data-color-dim)"}
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<svg class="border-mask" width="0" height="0">
|
||||
<defs>
|
||||
<clipPath id={clipPathId}>
|
||||
<path clip-rule="evenodd" d={nodeBorderMask(node.inputs, node.outputs)} />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="native-node-graph-ui">{@html $nodeGraph.nativeNodeGraphSVGString}</div>
|
||||
|
||||
<div class="graph" bind:this={graph}>
|
||||
<!-- Right click menu for adding nodes -->
|
||||
@@ -889,6 +558,193 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wire {
|
||||
position: absolute;
|
||||
overflow: visible;
|
||||
top: 0;
|
||||
left: 0;
|
||||
path {
|
||||
fill: none;
|
||||
stroke: var(--data-color-dim);
|
||||
stroke-width: var(--data-line-width);
|
||||
stroke-dasharray: var(--data-dasharray);
|
||||
}
|
||||
}
|
||||
|
||||
.graph {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
> img {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-trail-buttons {
|
||||
margin-top: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
width: max-content;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
z-index: 3;
|
||||
background-color: var(--color-3-darkgray);
|
||||
border-radius: 4px;
|
||||
|
||||
.toggle-layer-or-node .text-label {
|
||||
line-height: 24px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.merge-selected-nodes {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.click-targets {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
|
||||
svg {
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
stroke-width: 1;
|
||||
fill: none;
|
||||
|
||||
.layer {
|
||||
stroke: yellow;
|
||||
}
|
||||
|
||||
.node {
|
||||
stroke: blue;
|
||||
}
|
||||
|
||||
.connector {
|
||||
stroke: green;
|
||||
}
|
||||
|
||||
.visibility {
|
||||
stroke: red;
|
||||
}
|
||||
|
||||
.all-nodes-bounding-box {
|
||||
stroke: purple;
|
||||
}
|
||||
|
||||
.modify-import-export {
|
||||
stroke: orange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.imports-and-exports {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
// Keeps the connectors above the wires
|
||||
z-index: 1;
|
||||
|
||||
.connector {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-top: 4px;
|
||||
margin-left: 5px;
|
||||
top: calc(var(--offset-top) * 24px);
|
||||
left: calc(var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
.reorder-bar {
|
||||
position: absolute;
|
||||
top: calc(var(--offset-top) * 24px);
|
||||
left: calc(var(--offset-left) * 24px);
|
||||
width: 50px;
|
||||
height: 2px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.plus {
|
||||
position: absolute;
|
||||
top: calc(var(--offset-top) * 24px);
|
||||
left: calc(var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
.edit-import-export {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
top: calc(var(--offset-top) * 24px);
|
||||
margin-top: -5px;
|
||||
height: 24px;
|
||||
|
||||
&.separator-bottom::after,
|
||||
&.separator-top::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background: var(--color-8-uppergray);
|
||||
height: 1px;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
&.separator-bottom::after {
|
||||
bottom: -1px;
|
||||
}
|
||||
|
||||
&.separator-top::before {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.import {
|
||||
right: calc(100% - var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
&.export {
|
||||
left: calc(var(--offset-left) * 24px + 17px);
|
||||
}
|
||||
|
||||
.import-text {
|
||||
text-align: right;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.export-text {
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.import-text-input {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.remove-button-import {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.remove-button-export {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.reorder-drag-grip {
|
||||
width: 8px;
|
||||
height: 24px;
|
||||
background-position: 2px 8px;
|
||||
border-radius: 2px;
|
||||
margin: -6px 0;
|
||||
background-image: var(--icon-drag-grip-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-trail-buttons {
|
||||
margin-top: 8px;
|
||||
margin-left: 8px;
|
||||
@@ -1642,4 +1498,25 @@
|
||||
background: rgba(0, 168, 255, 0.05);
|
||||
border: 1px solid #00a8ff;
|
||||
}
|
||||
|
||||
.connector-tooltip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* position the bottom-center at a point */
|
||||
// transform: translate(-50%, -100%);
|
||||
.connector-tooltip-div {
|
||||
padding: 5px;
|
||||
white-space: pre;
|
||||
position: absolute;
|
||||
top: calc(var(--offset-top));
|
||||
left: calc(var(--offset-left));
|
||||
color: black;
|
||||
font-size: 12px;
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
transform::ApplyTransform,
|
||||
vector::{
|
||||
Vector,
|
||||
style::{Fill, Stroke},
|
||||
style::{Fill, Stroke, StrokeAlign},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -102,16 +102,17 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
|
||||
let border_mask_table = Table::new_from_row(border_mask_row);
|
||||
node_table.push(TableRow::new_from_element(Graphic::Vector(border_mask_table)));
|
||||
|
||||
// Border table is implemented as a clip mask
|
||||
// Border is implemented as a clip mask
|
||||
let mut border_table = Table::new();
|
||||
let mut border_vector = Vector::from_bezpath(node_bez_path);
|
||||
let primary_output_color = frontend_node
|
||||
let border_color = frontend_node
|
||||
.primary_output
|
||||
.as_ref()
|
||||
.map(|primary_output| primary_output.data_type.data_color_dim())
|
||||
.unwrap_or(FrontendGraphDataType::General.data_color_dim());
|
||||
let border_color = Color::from_rgba8_no_srgb(primary_output_color).unwrap();
|
||||
border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.));
|
||||
let stroke = Stroke::new(Some(border_color), 1.);
|
||||
// stroke.align = StrokeAlign::Inside;
|
||||
border_vector.style.stroke = Some(stroke);
|
||||
let mut border_vector_row = TableRow::new_from_element(border_vector);
|
||||
border_vector_row.alpha_blending.clip = true;
|
||||
border_table.push(border_vector_row);
|
||||
@@ -182,22 +183,22 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
|
||||
// Input and output ports
|
||||
let mut ports_table = Table::new();
|
||||
if let Some(primary_input) = &frontend_node.primary_input {
|
||||
let mut row = port_row(&primary_input.data_type);
|
||||
let mut row = port_row(&primary_input.data_type, primary_input.connected_to_node.is_some());
|
||||
row.transform = DAffine2::from_translation(DVec2::new(0., 12.));
|
||||
ports_table.push(row);
|
||||
}
|
||||
for (index, secondary_input) in frontend_node.secondary_inputs.iter().enumerate() {
|
||||
let mut row = port_row(&secondary_input.data_type);
|
||||
let mut row = port_row(&secondary_input.data_type, secondary_input.connected_to_node.is_some());
|
||||
row.transform = DAffine2::from_translation(DVec2::new(0., 12. + GRID_SIZE * (index + 1) as f64));
|
||||
ports_table.push(row);
|
||||
}
|
||||
if let Some(primary_output) = &frontend_node.primary_output {
|
||||
let mut row = port_row(&primary_output.data_type);
|
||||
let mut row = port_row(&primary_output.data_type, true);
|
||||
row.transform = DAffine2::from_translation(DVec2::new(5. * GRID_SIZE, 12.));
|
||||
ports_table.push(row);
|
||||
}
|
||||
for (index, secondary_output) in frontend_node.secondary_outputs.iter().enumerate() {
|
||||
let mut row = port_row(&secondary_output.data_type);
|
||||
let mut row = port_row(&secondary_output.data_type, true);
|
||||
row.transform = DAffine2::from_translation(DVec2::new(5. * GRID_SIZE, 12. + GRID_SIZE * (index + 1) as f64));
|
||||
ports_table.push(row);
|
||||
}
|
||||
@@ -212,7 +213,7 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
|
||||
|
||||
pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<Graphic>) {
|
||||
let mut layer_table = Table::new();
|
||||
let mut side_ports = Table::new();
|
||||
let mut side_ports_table = Table::new();
|
||||
for node_to_render in nodes {
|
||||
if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() {
|
||||
// The layer position is the top left of the thumbnail
|
||||
@@ -246,16 +247,14 @@ pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<
|
||||
0.
|
||||
};
|
||||
|
||||
// Text starts at thumbnail + left padding
|
||||
let text_start = 12. + 8.;
|
||||
let text_left_padding = 8.;
|
||||
let right_text_edge = 8. + text_width;
|
||||
let rounded_text_edge = (right_text_edge as f64 / 24.).ceil() * 24.;
|
||||
// Text starts at thumbnail + left padding
|
||||
let rounded_text_edge = ((12. + right_text_edge as f64) / 24.).ceil() * 24.;
|
||||
|
||||
let rounded_layer_width_pixels = rounded_text_edge + 24.;
|
||||
// add the left thumbnail gap
|
||||
let layer_right_edge_width = rounded_layer_width_pixels + 12.;
|
||||
let rounded_layer_width_pixels = rounded_text_edge + 12.;
|
||||
|
||||
let right_layer_width = layer_right_edge_width.max(4.5 * GRID_SIZE);
|
||||
let right_layer_width = rounded_layer_width_pixels.max(4.5 * GRID_SIZE);
|
||||
let thumbnail_width = 3. * GRID_SIZE;
|
||||
let full_layer_width = chain_width + thumbnail_width + right_layer_width;
|
||||
|
||||
@@ -321,7 +320,9 @@ pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<
|
||||
let bez_path = border_rect.to_path(BEZ_PATH_TOLERANCE);
|
||||
let mut border_vector = Vector::from_bezpath(bez_path);
|
||||
let border_color = Color::from_rgba8_no_srgb(COLOR_5_DULLGRAY).unwrap();
|
||||
border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.));
|
||||
let stroke = Stroke::new(Some(border_color), 1.);
|
||||
// stroke.align = StrokeAlign::Inside;
|
||||
border_vector.style.stroke = Some(stroke);
|
||||
let mut layer_border_clip = TableRow::new_from_element(border_vector);
|
||||
layer_border_clip.alpha_blending.clip = true;
|
||||
border_table.push(layer_border_clip);
|
||||
@@ -330,7 +331,7 @@ pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<
|
||||
// The top layer contains the ports,thumbnail,text, etc
|
||||
for text_row in text_table.iter_mut() {
|
||||
text_row.element.style.fill = Fill::Solid(Color::WHITE);
|
||||
*text_row.transform = DAffine2::from_translation(layer_position + DVec2::new(thumbnail_width + text_start, 16.));
|
||||
*text_row.transform = DAffine2::from_translation(layer_position + DVec2::new(thumbnail_width + text_left_padding, 16.));
|
||||
}
|
||||
let top_layer = text_table;
|
||||
layer_table.push(TableRow::new_from_element(Graphic::Vector(top_layer)));
|
||||
@@ -338,34 +339,40 @@ pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<
|
||||
// Ports
|
||||
let mut ports_table = Table::new();
|
||||
if let Some(side_input) = &frontend_layer.side_input {
|
||||
let mut port = port_row(&side_input.data_type);
|
||||
let mut port: TableRow<Vector> = port_row(&side_input.data_type, side_input.connected_to_node.is_some());
|
||||
port.transform = DAffine2::from_translation(DVec2::new(layer_position.x - 15., layer_position.y + GRID_SIZE - 4.));
|
||||
ports_table.push(port);
|
||||
side_ports_table.push(port);
|
||||
}
|
||||
let top_port = BezPath::from_svg("M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z").unwrap();
|
||||
let mut vector = Vector::from_bezpath(top_port);
|
||||
vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(frontend_layer.output.data_type.data_color()).unwrap());
|
||||
let mut side_port = TableRow::new_from_element(vector);
|
||||
side_port.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y - 12.));
|
||||
side_ports.push(side_port);
|
||||
vector.style.fill = Fill::Solid(frontend_layer.output.data_type.data_color());
|
||||
let mut top_port = TableRow::new_from_element(vector);
|
||||
top_port.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y - 12.));
|
||||
ports_table.push(top_port);
|
||||
|
||||
if frontend_layer.primary_output_connected_to_layer {
|
||||
let top_wire_cap = BezPath::from_svg("M0,-3.5h8v8l-2.521,-1.681a2.666,2.666,0,0,0,-2.959,0l-2.52,1.681z").unwrap();
|
||||
let mut vector = Vector::from_bezpath(top_wire_cap);
|
||||
vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(frontend_layer.output.data_type.data_color_dim()).unwrap());
|
||||
vector.style.fill = Fill::Solid(frontend_layer.output.data_type.data_color_dim());
|
||||
let mut vector_row = TableRow::new_from_element(vector);
|
||||
vector_row.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y - 12.));
|
||||
ports_table.push(vector_row);
|
||||
}
|
||||
let bottom_port = BezPath::from_svg("M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z").unwrap();
|
||||
let mut vector = Vector::from_bezpath(bottom_port);
|
||||
vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(frontend_layer.bottom_input.data_type.data_color()).unwrap());
|
||||
let mut bottom_port_fill = if frontend_layer.bottom_input.connected_to_node.is_some() {
|
||||
frontend_layer.bottom_input.data_type.data_color()
|
||||
} else {
|
||||
frontend_layer.bottom_input.data_type.data_color_dim()
|
||||
};
|
||||
vector.style.fill = Fill::Solid(bottom_port_fill);
|
||||
let mut vector_row = TableRow::new_from_element(vector);
|
||||
vector_row.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y + 2. * GRID_SIZE));
|
||||
ports_table.push(vector_row);
|
||||
if frontend_layer.primary_input_connected_to_layer {
|
||||
let bottom_port_cap = BezPath::from_svg("M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z").unwrap();
|
||||
let mut vector = Vector::from_bezpath(bottom_port_cap);
|
||||
vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(frontend_layer.bottom_input.data_type.data_color_dim()).unwrap());
|
||||
vector.style.fill = Fill::Solid(frontend_layer.bottom_input.data_type.data_color_dim());
|
||||
let mut vector_row = TableRow::new_from_element(vector);
|
||||
vector_row.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y + 2. * GRID_SIZE));
|
||||
ports_table.push(vector_row);
|
||||
@@ -406,11 +413,59 @@ pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> (Table<Graphic>, Table<
|
||||
icons_table.push(grip_row);
|
||||
}
|
||||
layer_table.push(TableRow::new_from_element(Graphic::Vector(icons_table)));
|
||||
|
||||
// Thumbnail border/bg
|
||||
let border = RoundedRect::new(layer_position.x, layer_position.y, layer_position.x + thumbnail_width, layer_position.y + 2. * GRID_SIZE, 2.);
|
||||
let mut border_vec = Vector::from_bezpath(border.to_path(BEZ_PATH_TOLERANCE));
|
||||
let stroke = Stroke::new(Some(frontend_layer.output.data_type.data_color_dim()), 1.);
|
||||
// stroke.align = StrokeAlign::Inside;
|
||||
border_vec.style.stroke = Some(stroke);
|
||||
border_vec.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(COLOR_2_MILDBLACK).unwrap());
|
||||
layer_table.push(TableRow::new_from_element(Graphic::Vector(Table::new_from_element(border_vec))));
|
||||
|
||||
// Region to display thumbnail
|
||||
let clip_vector = Vector::from_bezpath(
|
||||
Rect::new(
|
||||
layer_position.x + 2.,
|
||||
layer_position.y + 2.,
|
||||
layer_position.x + thumbnail_width - 2.,
|
||||
layer_position.y + GRID_SIZE * 2. - 2.,
|
||||
)
|
||||
.to_path(BEZ_PATH_TOLERANCE),
|
||||
);
|
||||
layer_table.push(TableRow::new_from_element(Graphic::Vector(Table::new_from_row(TableRow::new_from_element(clip_vector)))));
|
||||
|
||||
// Inner thumbnail
|
||||
let mut inner_thumbnail_table = Table::new();
|
||||
for col in 0..9 {
|
||||
for row in 0..6 {
|
||||
let fill = if (col + row) % 2 == 0 {
|
||||
Color::from_rgba8_no_srgb(COLOR_C_BRIGHTGRAY).unwrap()
|
||||
} else {
|
||||
Color::from_rgba8_no_srgb(COLOR_F_WHITE).unwrap()
|
||||
};
|
||||
let mut vector = Vector::from_bezpath(
|
||||
Rect::new(
|
||||
2. + 8. * col as f64 + layer_position.x,
|
||||
2. + 8. * row as f64 + layer_position.y,
|
||||
2. + 8. * col as f64 + layer_position.x + 9.,
|
||||
2. + 8. * row as f64 + layer_position.y + 9.,
|
||||
)
|
||||
.to_path(BEZ_PATH_TOLERANCE),
|
||||
);
|
||||
vector.style.fill = Fill::Solid(fill);
|
||||
inner_thumbnail_table.push(TableRow::new_from_element(vector));
|
||||
}
|
||||
}
|
||||
let mut thumbnail_row = TableRow::new_from_element(Graphic::Vector(inner_thumbnail_table));
|
||||
thumbnail_row.alpha_blending.clip = true;
|
||||
let graphic_table = Table::new_from_row(thumbnail_row);
|
||||
layer_table.push(TableRow::new_from_element(Graphic::Graphic(graphic_table)));
|
||||
}
|
||||
}
|
||||
|
||||
let mut ports_table = Table::new();
|
||||
ports_table.push(TableRow::new_from_element(Graphic::Vector(side_ports)));
|
||||
ports_table.push(TableRow::new_from_element(Graphic::Vector(side_ports_table)));
|
||||
(layer_table, ports_table)
|
||||
}
|
||||
|
||||
@@ -455,12 +510,17 @@ fn node_first_row(x0: f64, y0: f64, rounded_bottom: bool) -> Vector {
|
||||
vector
|
||||
}
|
||||
|
||||
fn port_row(data_type: &FrontendGraphDataType) -> TableRow<Vector> {
|
||||
fn port_row(data_type: &FrontendGraphDataType, full_brightness: bool) -> TableRow<Vector> {
|
||||
let path = BezPath::from_svg("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").unwrap_or_else(|e| {
|
||||
panic!("Could not parse port svg from string: {}", e);
|
||||
});
|
||||
let mut vector = Vector::from_bezpath(path);
|
||||
vector.style.fill = Fill::Solid(Color::from_rgba8_no_srgb(data_type.data_color()).unwrap());
|
||||
let fill = if full_brightness {
|
||||
Fill::Solid(data_type.data_color())
|
||||
} else {
|
||||
Fill::Solid(data_type.data_color_dim())
|
||||
};
|
||||
vector.style.fill = fill;
|
||||
TableRow::new_from_element(vector)
|
||||
}
|
||||
|
||||
@@ -470,7 +530,7 @@ pub fn draw_wires(nodes: &mut Vec<FrontendNodeToRender>) -> Table<Graphic> {
|
||||
for (wire_string, thick, data_type) in &mut node.wires {
|
||||
let mut wire_vector = Vector::from_bezpath(std::mem::take(wire_string));
|
||||
let weight = if *thick { 8. } else { 2. };
|
||||
wire_vector.style.set_stroke(Stroke::new(Some(Color::from_rgba8_no_srgb(data_type.data_color_dim()).unwrap()), weight));
|
||||
wire_vector.style.set_stroke(Stroke::new(Some(data_type.data_color_dim()), weight));
|
||||
wire_table.push(TableRow::new_from_element(wire_vector));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core_shaders::color::Color;
|
||||
use kurbo::BezPath;
|
||||
|
||||
use crate::{node_graph_overlay::consts::*, uuid::NodeId};
|
||||
@@ -214,8 +215,8 @@ pub enum FrontendGraphDataType {
|
||||
}
|
||||
|
||||
impl FrontendGraphDataType {
|
||||
pub fn data_color(&self) -> &'static str {
|
||||
match self {
|
||||
pub fn data_color(&self) -> Color {
|
||||
let color_str = match self {
|
||||
FrontendGraphDataType::General => COLOR_DATA_GENERAL,
|
||||
FrontendGraphDataType::Number => COLOR_DATA_NUMBER,
|
||||
FrontendGraphDataType::Artboard => COLOR_DATA_ARTBOARD,
|
||||
@@ -225,10 +226,11 @@ impl FrontendGraphDataType {
|
||||
FrontendGraphDataType::Color => COLOR_DATA_COLOR,
|
||||
FrontendGraphDataType::Gradient => COLOR_DATA_GRADIENT,
|
||||
FrontendGraphDataType::Typography => COLOR_DATA_TYPOGRAPHY,
|
||||
}
|
||||
};
|
||||
Color::from_rgba8_no_srgb(color_str).unwrap()
|
||||
}
|
||||
pub fn data_color_dim(&self) -> &'static str {
|
||||
match self {
|
||||
pub fn data_color_dim(&self) -> Color {
|
||||
let color_str = match self {
|
||||
FrontendGraphDataType::General => COLOR_DATA_GENERAL_DIM,
|
||||
FrontendGraphDataType::Number => COLOR_DATA_NUMBER_DIM,
|
||||
FrontendGraphDataType::Artboard => COLOR_DATA_ARTBOARD_DIM,
|
||||
@@ -238,6 +240,7 @@ impl FrontendGraphDataType {
|
||||
FrontendGraphDataType::Color => COLOR_DATA_COLOR_DIM,
|
||||
FrontendGraphDataType::Gradient => COLOR_DATA_GRADIENT_DIM,
|
||||
FrontendGraphDataType::Typography => COLOR_DATA_TYPOGRAPHY_DIM,
|
||||
}
|
||||
};
|
||||
Color::from_rgba8_no_srgb(color_str).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user