Add layer node chains, import/export edge connectors, and refactor graph editing to go thru a NodeNetworkInterface (#1794)

* WIP: NodeNetworkInterface

* Organize ModifyInputsContext to use network interface

* Improve ClickTarget and Position state

* Rework ClickTarget state

* Continue fixing NodeGraphMessageHandler

* Restructure network_metadata

* Final(?) NodeNetworkInterface struct

* Final(??) NodeNetworkInterface

* Final(???) NodeNetworkInterface. Separated persistent and transient data

* Final NodeNetworkInterface data structure. Implemented all basic getters

* Continue migrating functionality to network interface

* Migrate all NodeGraphMessage's to use network interface

* Fix all helper functions in NodeGraphMessageHandler

* Move document metadata to network interface, remove various cached fields

* Move all editor only NodeNetwork implementations to NodeNetworkInterface

* Fix all DocumentNodeDefinitions

* Rework and migrate GraphOperationMessages to network interface

* Continue migration to NodeNetworkInterface

* Save point before merging master

* Fix all errors in network_interface

* 850 -> 160 errors

* Fix all errors :D

* Render default document

* Visualize click targets

* merge conflicts

* Cache transient metadata separately, store entire interface in document history

* Start migration to storing selected nodes for each network

* Remove selected nodes from document message handler

* Move outward wires and all nodes bounding box to transient metadata

* Fix connecting/disconnecting nodes

* Layer stack organization for disconnecting/connecting nodes

* Basic chain locking

* Improve chain positioning

* Add copy/pasting

* Move upstream nodes on shift+drag

* merge conflict fixes

* Improve Graph.svelte code quality

* Final improvements to Graph.svelte

* Fix layer panel

* Performance optimizations

* Bug fixes and derived PTZ

* Chain organization improvement and bug fixes

* Bug fixes, remove all warnings

* Automatic file upgrade

* Final code review

* Fix editor tests

* Fix compile errors

* Remove select tool intersection check when panning

* WIP: Import/Exports

* Fix JS issues

* Finish simplified import/export UI

* Import/Export viewport edge UI

* Remove minimum y bound on import/export ports

* Improve performance while panning graph

* cargo fmt

* Fix CI code build

* Format the demo artwork graph with chains

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: dennis@kobert.dev <dennis@kobert.dev>
This commit is contained in:
adamgerhant
2024-08-04 06:47:13 -07:00
committed by GitHub
co-authored by Keavon Chambers dennis@kobert.dev
parent ea44d1440a
commit 0dbbabe73e
77 changed files with 11361 additions and 8011 deletions
+156 -14
View File
@@ -12,6 +12,29 @@ export class JsMessage {
}
const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] }));
const ImportsToVec2Array = Transform(({ obj }) => {
const imports: { outputMetadata: FrontendGraphOutput; position: XY }[] = [];
obj.imports.forEach(([outputMetadata, x, y]: [FrontendGraphOutput, number, number]) => {
outputMetadata.connectedTo = outputMetadata.connectedTo.map((connector: any) => {
if (connector.export !== undefined) return { index: connector.export.index };
return { nodeId: connector.node.nodeId, index: connector.node.inputIndex };
});
imports.push({ outputMetadata, position: { x, y } });
});
return imports;
});
const ExportsToVec2Array = Transform(({ obj }) => {
const exports: { inputMetadata: FrontendGraphInput; position: XY }[] = [];
obj.exports.forEach(([inputMetadata, x, y]: [FrontendGraphInput, number, number]) => {
inputMetadata.connectedTo = ((connectedTo: any) => {
if (connectedTo?.import !== undefined) return { index: connectedTo?.import.index };
return { nodeId: connectedTo?.node.nodeId, index: connectedTo?.node.outputIndex };
})(inputMetadata.connectedTo);
exports.push({ inputMetadata, position: { x, y } });
});
return exports;
});
// const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) }));
export type XY = { x: number; y: number };
@@ -29,6 +52,10 @@ export class UpdateBox extends JsMessage {
readonly box!: Box | undefined;
}
export class UpdateClickTargets extends JsMessage {
readonly clickTargets!: FrontendClickTargets | undefined;
}
const ContextTupleToVec2 = Transform((data) => {
if (data.obj.contextMenuInformation === undefined) return undefined;
const contextMenuCoordinates = { x: data.obj.contextMenuInformation.contextMenuCoordinates[0], y: data.obj.contextMenuInformation.contextMenuCoordinates[1] };
@@ -43,11 +70,27 @@ export class UpdateContextMenuInformation extends JsMessage {
@ContextTupleToVec2
readonly contextMenuInformation!: ContextMenuInformation | undefined;
}
export class UpdateImportsExports extends JsMessage {
@ImportsToVec2Array
readonly imports!: { outputMetadata: FrontendGraphOutput; position: XY }[];
@ExportsToVec2Array
readonly exports!: { inputMetadata: FrontendGraphInput; position: XY }[];
}
export class UpdateInSelectedNetwork extends JsMessage {
readonly inSelectedNetwork!: boolean;
}
const LayerWidths = Transform(({ obj }) => obj.layerWidths);
const ChainWidths = Transform(({ obj }) => obj.chainWidths);
export class UpdateLayerWidths extends JsMessage {
@LayerWidths
readonly layerWidths!: Map<bigint, number>;
@ChainWidths
readonly chainWidths!: Map<bigint, number>;
}
export class UpdateNodeGraph extends JsMessage {
@@ -123,6 +166,14 @@ export class Box {
readonly endY!: number;
}
export type FrontendClickTargets = {
readonly nodeClickTargets: string[];
readonly layerClickTargets: string[];
readonly portClickTargets: string[];
readonly visibilityClickTargets: string[];
readonly allNodesBoundingBox: string;
};
export type ContextMenuInformation = {
contextMenuCoordinates: XY;
@@ -131,6 +182,37 @@ export type ContextMenuInformation = {
export type FrontendGraphDataType = "General" | "Raster" | "VectorData" | "Number" | "Graphic" | "Artboard";
export class Node {
readonly nodeId!: bigint;
readonly index!: bigint;
}
export class Export {
readonly index!: bigint;
}
export class Import {
readonly index!: bigint;
}
export type OutputConnector = Node | Import;
export type InputConnector = Node | Export;
const CreateOutputConnectorOptional = Transform(({ obj }) => {
if (obj.connectedTo?.export !== undefined) {
return { index: obj.connectedTo?.export };
} else if (obj.connectedTo?.import !== undefined) {
return { index: obj.connectedTo?.import };
} else {
if (obj.connectedTo?.node.inputIndex !== undefined) {
return { nodeId: obj.connectedTo?.node.nodeId, index: obj.connectedTo?.node.inputIndex };
} else {
return { nodeId: obj.connectedTo?.node.nodeId, index: obj.connectedTo?.node.outputIndex };
}
}
});
export class FrontendGraphInput {
readonly dataType!: FrontendGraphDataType;
@@ -138,9 +220,28 @@ export class FrontendGraphInput {
readonly resolvedType!: string | undefined;
readonly connected!: bigint | undefined;
@CreateOutputConnectorOptional
readonly connectedTo!: OutputConnector | undefined;
}
const CreateInputConnectorArray = Transform(({ obj }) => {
const newInputConnectors: InputConnector[] = [];
obj.connectedTo.forEach((connector: any) => {
if (connector.export !== undefined) {
newInputConnectors.push({ index: connector.export });
} else if (connector.import !== undefined) {
newInputConnectors.push({ index: connector.import });
} else {
if (connector.node.inputIndex !== undefined) {
newInputConnectors.push({ nodeId: connector.node.nodeId, index: connector.node.inputIndex });
} else {
newInputConnectors.push({ nodeId: connector.node.nodeId, index: connector.node.outputIndex });
}
}
});
return newInputConnectors;
});
export class FrontendGraphOutput {
readonly dataType!: FrontendGraphDataType;
@@ -148,9 +249,8 @@ export class FrontendGraphOutput {
readonly resolvedType!: string | undefined;
readonly connected!: bigint[];
readonly connectedIndex!: bigint[];
@CreateInputConnectorArray
readonly connectedTo!: InputConnector[];
}
export class FrontendNode {
@@ -160,22 +260,26 @@ export class FrontendNode {
readonly id!: bigint;
readonly alias!: string;
readonly reference!: string | undefined;
readonly name!: string;
readonly displayName!: string;
@Type(() => FrontendGraphInput)
readonly primaryInput!: FrontendGraphInput | undefined;
@Type(() => FrontendGraphInput)
readonly exposedInputs!: FrontendGraphInput[];
@Type(() => FrontendGraphOutput)
readonly primaryOutput!: FrontendGraphOutput | undefined;
@Type(() => FrontendGraphOutput)
readonly exposedOutputs!: FrontendGraphOutput[];
@TupleToVec2
readonly position!: XY | undefined;
//TODO: Store field for the width of the left node chain
// TODO: Store field for the width of the left node chain
readonly previewed!: boolean;
@@ -188,14 +292,40 @@ export class FrontendNode {
readonly uiOnly!: boolean;
}
const CreateOutputConnector = Transform(({ obj }) => {
if (obj.wireStart.export !== undefined) {
return { index: obj.wireStart.export };
} else if (obj.wireStart.import !== undefined) {
return { index: obj.wireStart.import };
} else {
if (obj.wireStart.node.inputIndex !== undefined) {
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.inputIndex };
} else {
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.outputIndex };
}
}
});
const CreateInputConnector = Transform(({ obj }) => {
if (obj.wireEnd.export !== undefined) {
return { index: obj.wireEnd.export };
} else if (obj.wireEnd.import !== undefined) {
return { index: obj.wireEnd.import };
} else {
if (obj.wireEnd.node.inputIndex !== undefined) {
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.inputIndex };
} else {
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.outputIndex };
}
}
});
export class FrontendNodeWire {
readonly wireStart!: bigint;
@CreateOutputConnector
readonly wireStart!: OutputConnector;
readonly wireStartOutputIndex!: bigint;
readonly wireEnd!: bigint;
readonly wireEndInputIndex!: bigint;
@CreateInputConnector
readonly wireEnd!: InputConnector;
readonly dashed!: boolean;
}
@@ -747,6 +877,10 @@ export class LayerPanelEntry {
parentsUnlocked!: boolean;
parentId!: bigint | undefined;
selected!: boolean;
inSelectedNetwork!: boolean;
}
export class DisplayDialogDismiss extends JsMessage {}
@@ -1351,7 +1485,12 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
}
if (layoutGroup.section) {
const result: WidgetSection = { name: layoutGroup.section.name, visible: layoutGroup.section.visible, id: layoutGroup.section.id, layout: layoutGroup.section.layout.map(createLayoutGroup) };
const result: WidgetSection = {
name: layoutGroup.section.name,
visible: layoutGroup.section.visible,
id: layoutGroup.section.id,
layout: layoutGroup.section.layout.map(createLayoutGroup),
};
return result;
}
@@ -1448,7 +1587,10 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerVisitLink,
UpdateActiveDocument,
UpdateBox,
UpdateClickTargets,
UpdateContextMenuInformation,
UpdateInSelectedNetwork,
UpdateImportsExports,
UpdateLayerWidths,
UpdateDialogButtons,
UpdateDialogColumn1,