Add viewing/editing layer names, add Blend Mode node, and clean up Layer node (#1489)

This commit is contained in:
Keavon Chambers 2023-12-07 15:10:47 -08:00 committed by GitHub
parent b7e304a708
commit 60a9c27bf1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 437 additions and 463 deletions

View file

@ -83,7 +83,8 @@
await tick();
const textInput = (list?.div()?.querySelector("[data-text-input]:not([disabled])") || undefined) as HTMLInputElement | undefined;
const query = list?.div()?.querySelector("[data-text-input]:not([disabled])");
const textInput = (query instanceof HTMLInputElement && query) || undefined;
textInput?.select();
}
@ -92,20 +93,23 @@
if (!listing.editingName) return;
draggable = true;
const name = (e.target as HTMLInputElement | undefined)?.value;
listing.editingName = false;
layers = layers;
if (name) editor.instance.setLayerName(listing.entry.path, name);
const name = (e.target instanceof HTMLInputElement && e.target.value) || "";
editor.instance.setLayerName(listing.entry.path, name);
listing.entry.name = name;
}
async function onEditLayerNameDeselect(listing: LayerListingInfo) {
draggable = true;
listing.editingName = false;
layers = layers;
await tick();
// Set it back to the original name if the user didn't enter a new name
if (document.activeElement instanceof HTMLInputElement) document.activeElement.value = listing.entry.name;
// Deselect the text so it doesn't appear selected while the input field becomes disabled and styled to look like regular text
window.getSelection()?.removeAllRanges();
}
@ -153,7 +157,7 @@
let highlightFolder = false;
let markerHeight = 0;
let previousHeight = undefined as undefined | number;
let previousHeight: number | undefined = undefined;
if (treeChildren !== undefined && treeOffset !== undefined) {
Array.from(treeChildren).forEach((treeChild, index) => {
@ -212,8 +216,9 @@
if (!layer.layerMetadata.selected) selectLayer(false, false, listing);
};
const target = (event.target || undefined) as HTMLElement | undefined;
const draggingELement = (target?.closest("[data-layer]") || undefined) as HTMLElement | undefined;
const target = (event.target instanceof HTMLElement && event.target) || undefined;
const closest = target?.closest("[data-layer]") || undefined;
const draggingELement = (closest instanceof HTMLElement && closest) || undefined;
if (draggingELement) beginDraggingElement(draggingELement);
// Set style of cursor for drag
@ -332,7 +337,7 @@
data-text-input
type="text"
value={listing.entry.name}
placeholder={`Untitled ${listing.entry.layerType || "[Unknown Layer Type]"}`}
placeholder={listing.entry.layerType}
disabled={!listing.editingName}
on:blur={() => onEditLayerNameDeselect(listing)}
on:keydown={(e) => e.key === "Escape" && onEditLayerNameDeselect(listing)}
@ -484,12 +489,6 @@
pointer-events: none;
}
&::placeholder {
opacity: 1;
color: inherit;
font-style: italic;
}
&:focus {
background: var(--color-1-nearblack);
padding: 0 4px;
@ -498,6 +497,12 @@
opacity: 0.5;
}
}
&::placeholder {
opacity: 1;
color: inherit;
font-style: italic;
}
}
}

View file

@ -5,7 +5,7 @@
import type { IconName } from "@graphite/utility-functions/icons";
import type { Editor } from "@graphite/wasm-communication/editor";
import { UpdateNodeGraphSelection } from "@graphite/wasm-communication/messages";
import type { FrontendNodeLink, FrontendNodeType, FrontendNode } from "@graphite/wasm-communication/messages";
import type { FrontendNodeLink, FrontendNodeType, FrontendNode, FrontendGraphDataType } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
@ -116,8 +116,8 @@
const from = connectorToNodeIndex(linkInProgressFromConnector);
const to = linkInProgressToConnector instanceof SVGSVGElement ? connectorToNodeIndex(linkInProgressToConnector) : undefined;
const linkStart = $nodeGraph.nodes.find((node) => node.id === from?.nodeId)?.displayName === "Layer";
const linkEnd = $nodeGraph.nodes.find((node) => node.id === to?.nodeId)?.displayName === "Layer" && to?.index !== 0;
const linkStart = $nodeGraph.nodes.find((node) => node.id === from?.nodeId)?.isLayer;
const linkEnd = $nodeGraph.nodes.find((node) => node.id === to?.nodeId)?.isLayer && to?.index !== 0;
return createWirePath(linkInProgressFromConnector, linkInProgressToConnector, linkStart, linkEnd);
}
return undefined;
@ -158,8 +158,8 @@
const { nodeInput, nodeOutput } = resolveLink(link);
if (!nodeInput || !nodeOutput) return [];
if (disconnecting?.linkIndex === index) return [];
const linkStart = $nodeGraph.nodes.find((node) => node.id === link.linkStart)?.displayName === "Layer";
const linkEnd = $nodeGraph.nodes.find((node) => node.id === link.linkEnd)?.displayName === "Layer" && link.linkEndInputIndex !== 0n;
const linkStart = $nodeGraph.nodes.find((node) => node.id === link.linkStart)?.isLayer;
const linkEnd = $nodeGraph.nodes.find((node) => node.id === link.linkEnd)?.isLayer && link.linkEndInputIndex !== 0n;
return [createWirePath(nodeOutput, nodeInput.getBoundingClientRect(), linkStart, linkEnd)];
});
@ -604,6 +604,11 @@
return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`;
}
function dataTypeTooltip(dataType: FrontendGraphDataType): string {
const capitalized = dataType[0].toUpperCase() + dataType.slice(1);
return `${capitalized} Data`;
}
onMount(() => {
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphSelection, (updateNodeGraphSelection) => {
selected = updateNodeGraphSelection.selected;
@ -665,7 +670,7 @@
<!-- Layers and nodes -->
<div class="layers-and-nodes" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`} bind:this={nodesContainer}>
<!-- Layers -->
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.displayName === "Layer" ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
{@const clipPathId = `${Math.random()}`.substring(2)}
{@const stackDatainput = node.exposedInputs[0]}
<div
@ -693,7 +698,9 @@
style:--data-color-dim={`var(--color-data-${node.primaryInput?.dataType}-dim)`}
bind:this={inputs[nodeIndex][0]}
>
<title>{node.primaryInput} data</title>
{#if node.primaryInput}
<title>{dataTypeTooltip(node.primaryInput.dataType)}</title>
{/if}
<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" />
</svg>
</div>
@ -712,7 +719,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
bind:this={outputs[nodeIndex][0]}
>
<title>{node.primaryOutput.dataType} data</title>
<title>{dataTypeTooltip(node.primaryOutput.dataType)}</title>
<path d="M0,2.953,2.521,1.259a2.649,2.649,0,0,1,2.959,0L8,2.953V8H0Z" />
</svg>
{/if}
@ -726,12 +733,13 @@
style:--data-color-dim={`var(--color-data-${stackDatainput.dataType}-dim)`}
bind:this={inputs[nodeIndex][1]}
>
<title>{stackDatainput.dataType} data</title>
<title>{dataTypeTooltip(stackDatainput.dataType)}</title>
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" />
</svg>
</div>
<div class="details">
<TextLabel tooltip={`${node.displayName} node with id: ${node.id}`}>{node.displayName}</TextLabel>
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
<TextLabel tooltip={editor.instance.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined} italic={!node.name}>{node.name || "Layer"}</TextLabel>
</div>
<svg class="border-mask" width="0" height="0">
@ -744,7 +752,7 @@
</div>
{/each}
<!-- Nodes -->
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.displayName !== "Layer" ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)}
{@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]}
{@const clipPathId = `${Math.random()}`.substring(2)}
<div
@ -752,7 +760,6 @@
class:selected={selected.includes(node.id)}
class:previewed={node.previewed}
class:disabled={node.disabled}
class:is-layer={node.displayName === "Layer"}
style:--offset-left={(node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
style:--offset-top={(node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`}
@ -762,8 +769,9 @@
>
<!-- Primary row -->
<div class="primary" class:no-parameter-section={exposedInputsOutputs.length === 0}>
<IconLabel icon={nodeIcon(node.displayName)} />
<TextLabel tooltip={`${node.displayName} node (ID: ${node.id})`}>{node.displayName}</TextLabel>
<IconLabel icon={nodeIcon(node.identifier)} />
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
<TextLabel tooltip={editor.instance.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined} italic={!node.name}>{node.name || node.identifier}</TextLabel>
</div>
<!-- Parameter rows -->
{#if exposedInputsOutputs.length > 0}
@ -789,7 +797,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryInput?.dataType}-dim)`}
bind:this={inputs[nodeIndex][0]}
>
<title>{node.primaryInput} data</title>
<title>{dataTypeTooltip(node.primaryInput.dataType)}</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" />
</svg>
{/if}
@ -805,7 +813,7 @@
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
bind:this={inputs[nodeIndex][index + 1]}
>
<title>{parameter.dataType} data</title>
<title>{dataTypeTooltip(parameter.dataType)}</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" />
</svg>
{/if}
@ -824,7 +832,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
bind:this={outputs[nodeIndex][0]}
>
<title>{node.primaryOutput.dataType} data</title>
<title>{dataTypeTooltip(node.primaryOutput.dataType)}</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" />
</svg>
{/if}
@ -839,7 +847,7 @@
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
bind:this={outputs[nodeIndex][outputIndex + 1]}
>
<title>{parameter.dataType} data</title>
<title>{dataTypeTooltip(parameter.dataType)}</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" />
</svg>
{/each}
@ -1179,7 +1187,6 @@
width: 100%;
height: 24px;
border-radius: 2px 2px 0 0;
font-style: italic;
background: rgba(255, 255, 255, 0.05);
&.no-parameter-section {

View file

@ -97,9 +97,13 @@ export class FrontendGraphOutput {
}
export class FrontendNode {
readonly isLayer!: boolean;
readonly id!: bigint;
readonly displayName!: string;
readonly name!: string;
readonly identifier!: string;
readonly primaryInput!: FrontendGraphInput | undefined;

View file

@ -556,7 +556,8 @@ impl JsEditorHandle {
/// Set the name for the layer
#[wasm_bindgen(js_name = setLayerName)]
pub fn set_layer_name(&self, layer_path: Vec<LayerId>, name: String) {
let message = DocumentMessage::SetLayerName { layer_path, name };
let node_id = *layer_path.last().unwrap();
let message = NodeGraphMessage::SetName { node_id, name };
self.dispatch(message);
}