Improve the layers UI in the node graph

This commit is contained in:
Keavon Chambers 2023-08-18 14:26:59 -07:00
parent a871ea6d69
commit 5a7d230156
2 changed files with 84 additions and 37 deletions

View File

@ -113,7 +113,7 @@ impl<'a> ModifyInputsContext<'a> {
}
let layer_node = resolve_document_node_type("Layer").expect("Node").to_document_node_default_inputs([], Default::default());
let layer_node = self.insert_node_before(new_id, current_node, input_index, layer_node, IVec2::new(0, 3))?;
let layer_node = self.insert_node_before(new_id, current_node, input_index, layer_node, IVec2::new(-4, 3))?;
Some(layer_node)
}

View File

@ -180,12 +180,12 @@
const containerBounds = nodesContainer.getBoundingClientRect();
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
const outY = verticalOut ? outputBounds.y + 1 : outputBounds.y + outputBounds.height / 2;
const outY = verticalOut ? outputBounds.y - 1 : outputBounds.y + outputBounds.height / 2;
const outConnectorX = (outX - containerBounds.x) / transform.scale;
const outConnectorY = (outY - containerBounds.y) / transform.scale;
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x + 1;
const inY = verticalIn ? inputBounds.y + inputBounds.height - 1 : inputBounds.y + inputBounds.height / 2;
const inY = verticalIn ? inputBounds.y + inputBounds.height + 2 : inputBounds.y + inputBounds.height / 2;
const inConnectorX = (inX - containerBounds.x) / transform.scale;
const inConnectorY = (inY - containerBounds.y) / transform.scale;
const horizontalGap = Math.abs(outConnectorX - inConnectorX);
@ -364,7 +364,7 @@
draggingNodes = { startX: e.x, startY: e.y, roundX: 0, roundY: 0 };
}
if (modifiedSelected) editor.instance.selectNodes(selected.length > 0 ? new BigUint64Array(selected) : null);
if (modifiedSelected) editor.instance.selectNodes(selected.length > 0 ? new BigUint64Array(selected) : undefined);
return;
}
@ -372,7 +372,7 @@
// Clicked on the graph background
if (lmb && selected.length !== 0) {
selected = [];
editor.instance.selectNodes(null);
editor.instance.selectNodes(undefined);
}
// LMB clicked on the graph background or MMB clicked anywhere
@ -524,15 +524,40 @@
nodeListLocation = undefined;
}
function buildBorderMask(nodeWidth: number, primaryInputExists: boolean, parameters: number, primaryOutputExists: boolean, exposedOutputs: number): string {
function nodeBorderMask(nodeWidth: number, primaryInputExists: boolean, parameters: number, primaryOutputExists: boolean, exposedOutputs: number): string {
const nodeHeight = Math.max(1 + parameters, 1 + exposedOutputs) * 24;
const boxes: { x: number; y: number; width: number; height: number }[] = [];
// Primary input
if (primaryInputExists) boxes.push({ x: -8, y: 4, width: 16, height: 16 });
// Parameter inputs
for (let i = 0; i < parameters; i++) boxes.push({ x: -8, y: 4 + (i + 1) * 24, width: 16, height: 16 });
// Primary output
if (primaryOutputExists) boxes.push({ x: nodeWidth - 8, y: 4, width: 16, height: 16 });
// Exposed outputs
for (let i = 0; i < exposedOutputs; i++) boxes.push({ x: nodeWidth - 8, y: 4 + (i + 1) * 24, width: 16, height: 16 });
return borderMask(boxes, nodeWidth, nodeHeight);
}
function layerBorderMask(nodeWidth: number): string {
const NODE_HEIGHT = 2 * 24;
const THUMBNAIL_WIDTH = 96;
const FUDGE = 2;
const boxes: { x: number; y: number; width: number; height: number }[] = [];
// Left input
boxes.push({ x: -8, y: 16, width: 16, height: 16 });
// Thumbnail
boxes.push({ x: 24, y: -FUDGE, width: THUMBNAIL_WIDTH, height: NODE_HEIGHT + FUDGE * 2 });
return borderMask(boxes, nodeWidth, NODE_HEIGHT);
}
function borderMask(boxes: { x: number; y: number; width: number; height: number }[], nodeWidth: number, nodeHeight: number): string {
const rectangles = boxes.map((box) => `M${box.x},${box.y} L${box.x + box.width},${box.y} L${box.x + box.width},${box.y + box.height} L${box.x},${box.y + box.height}z`);
return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`;
}
@ -599,7 +624,7 @@
{#each linkPaths as { pathString, dataType, thick }}
<path
d={pathString}
style:--data-line-width={`${thick ? 5 : 2}px`}
style:--data-line-width={`${thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${dataType})`}
style:--data-color-dim={`var(--color-data-${dataType}-dim)`}
/>
@ -650,7 +675,7 @@
style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
>
<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" />
<path d="M0,2.953,2.521,1.259a2.649,2.649,0,0,1,2.959,0L8,2.953V8H0Z" />
</svg>
{/if}
<svg
@ -662,17 +687,17 @@
style:--data-color={`var(--color-data-${stackDatainput.dataType})`}
style:--data-color-dim={`var(--color-data-${stackDatainput.dataType}-dim)`}
>
<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" />
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" />
</svg>
</div>
<div class="details">
<TextLabel>{node.displayName}</TextLabel>
<TextLabel tooltip={node.displayName}>{node.displayName}</TextLabel>
</div>
<svg class="border-mask" width="0" height="0">
<defs>
<clipPath id={clipPathId}>
<path clip-rule="evenodd" d={buildBorderMask(120, node.primaryInput !== undefined, node.exposedInputs.length, node.primaryOutput !== undefined, node.exposedOutputs)} />
<path clip-rule="evenodd" d={layerBorderMask(216)} />
</clipPath>
</defs>
</svg>
@ -696,15 +721,15 @@
<!-- Primary row -->
<div class="primary" class:no-parameter-section={exposedInputsOutputs.length === 0}>
<IconLabel icon={nodeIcon(node.displayName)} />
<TextLabel>{node.displayName}</TextLabel>
<TextLabel tooltip={node.displayName}>{node.displayName}</TextLabel>
</div>
<!-- Parameter rows -->
{#if exposedInputsOutputs.length > 0}
<div class="parameters">
{#each exposedInputsOutputs as parameter, index}
<div class="parameter expanded">
<div class={`parameter expanded ${index < node.exposedInputs.length ? "input" : "output"}`}>
<div class="expand-arrow" />
<TextLabel class={index < node.exposedInputs.length ? "name" : "output"}>{parameter.name}</TextLabel>
<TextLabel tooltip={parameter.name}>{parameter.name}</TextLabel>
</div>
{/each}
</div>
@ -715,7 +740,7 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
class="port primary-port"
data-port="input"
data-datatype={node.primaryInput}
style:--data-color={`var(--color-data-${node.primaryInput})`}
@ -746,7 +771,7 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
class="port primary-port"
data-port="output"
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`}
@ -774,7 +799,7 @@
<clipPath id={clipPathId}>
<path
clip-rule="evenodd"
d={buildBorderMask(120, node.primaryInput !== undefined, node.exposedInputs.length, node.primaryOutput !== undefined, node.exposedOutputs.length)}
d={nodeBorderMask(120, node.primaryInput !== undefined, node.exposedInputs.length, node.primaryOutput !== undefined, node.exposedOutputs.length)}
/>
</clipPath>
</defs>
@ -940,14 +965,17 @@
top: 0;
}
&.selected {
.primary {
background: rgba(255, 255, 255, 0.15);
}
&.node.selected .primary {
background: rgba(255, 255, 255, 0.15);
}
.parameters {
background: rgba(255, 255, 255, 0.1);
}
&.node.selected .parameters {
background: rgba(255, 255, 255, 0.1);
}
&.layer.selected {
// This is the result of blending `rgba(255, 255, 255, 0.1)` over `rgba(0, 0, 0, 0.33)`
background: rgba(66, 66, 66, 0.4);
}
&.disabled {
@ -988,6 +1016,10 @@
&:first-of-type {
margin-top: calc((24px - 8px) / 2);
&:not(.primary-port) {
margin-top: calc((24px - 8px) / 2 + 24px);
}
}
&:last-of-type {
@ -1022,11 +1054,16 @@
.expanded .expand-arrow::after {
transform: rotate(90deg);
}
.text-label {
overflow: hidden;
text-overflow: ellipsis;
}
}
.layer {
border-radius: 8px;
min-width: 216px;
width: 216px;
&::after {
border: 1px solid var(--color-5-dullgray);
@ -1058,9 +1095,10 @@
pointer-events: none;
position: absolute;
margin: auto;
inset: 1px;
width: 100%;
height: 100%;
top: 1px;
left: 1px;
width: calc(100% - 2px);
height: calc(100% - 2px);
}
.port {
@ -1070,11 +1108,11 @@
right: 0;
&.top {
top: -12px;
top: -9px;
}
&.bottom {
bottom: -12px;
bottom: -9px;
}
}
}
@ -1099,7 +1137,7 @@
.node {
flex-direction: column;
border-radius: 2px;
min-width: 120px;
width: 120px;
top: calc((var(--offset-top) + 0.5) * 24px);
&::after {
@ -1122,10 +1160,12 @@
}
.icon-label {
display: none; // Remove after we have unique icons for the nodes
margin: 0 8px;
}
.text-label {
margin-left: 8px; // Remove after reenabling icon-label
margin-right: 4px;
}
}
@ -1147,15 +1187,22 @@
border-radius: 0 0 2px 2px;
}
.expand-arrow {
margin-left: 4px;
}
.text-label {
width: 100%;
}
&.output {
text-align: right;
&.input {
.expand-arrow {
margin-left: 4px;
}
}
&.output {
flex-direction: row-reverse;
text-align: right;
.expand-arrow {
margin-right: 4px;
}
}
}