diff --git a/Cargo.toml b/Cargo.toml index 75d402e6..02e1607b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,10 @@ resolver = "2" exclude = ["node-graph/gpu-compiler"] [workspace.dependencies] +# We are using this fork because: +# - They specify glam=0.22 whereas we use glam=0.24 so the encoding doesn't work. +# - Their current release doesn't allow doc comments and produces a compile error. +# See: https://github.com/GraphiteEditor/Graphite/pull/1346/files/a2206401b5b4cf669e71df57f6c95c67336802c8#r1280201659 specta = { git = "https://github.com/0HyperCube/specta.git", rev = "c47a22b4c0863d27bc47529f300de3969480c66d", features = [ "glam", ] } diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs index 05b8a805..1ef21edd 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs @@ -307,9 +307,9 @@ impl<'a> ModifyInputsContext<'a> { let [mut old_bounds_min, mut old_bounds_max] = [DVec2::ZERO, DVec2::ONE]; let [mut new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE]; - self.modify_inputs("Path Generator", false, |inputs| { + self.modify_inputs("Shape", false, |inputs| { let [subpaths, mirror_angle_groups] = inputs.as_mut_slice() else { - panic!("Path generator does not have subpath and mirror angle inputs"); + panic!("Shape does not have subpath and mirror angle inputs"); }; let NodeInput::Value { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 303d78a6..2d09fef9 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -80,7 +80,10 @@ pub struct FrontendNode { pub primary_input: Option, #[serde(rename = "exposedInputs")] pub exposed_inputs: Vec, - pub outputs: Vec, // TODO: Break this apart into `primary_output` and `exposed_outputs` + #[serde(rename = "primaryOutput")] + pub primary_output: Option, + #[serde(rename = "exposedOutputs")] + pub exposed_outputs: Vec, pub position: (i32, i32), pub disabled: bool, pub previewed: bool, @@ -336,14 +339,11 @@ impl NodeGraphMessageHandler { }) .collect(); - let outputs = node_type - .outputs - .iter() - .map(|output_type| NodeGraphOutput { - data_type: output_type.data_type, - name: output_type.name.to_string(), - }) - .collect(); + let mut outputs = node_type.outputs.iter().map(|output_type| NodeGraphOutput { + data_type: output_type.data_type, + name: output_type.name.to_string(), + }); + let primary_output = outputs.next(); let graph_identifier = GraphIdentifier::new(layer_id); let thumbnail_svg = executor.thumbnails.get(&graph_identifier).and_then(|thumbnails| thumbnails.get(id)).map(|svg| svg.to_string()); @@ -353,7 +353,8 @@ impl NodeGraphMessageHandler { display_name: node.name.clone(), primary_input, exposed_inputs, - outputs, + primary_output, + exposed_outputs: outputs.collect::>(), position: node.metadata.position.into(), previewed: network.outputs_contain(*id), disabled: network.disabled.contains(id), diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 3fe71104..2f6a99f9 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -1662,7 +1662,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNodeType { - name: "Path Generator", + name: "Shape", category: "Vector", identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator<_>"), inputs: vec![ @@ -1998,7 +1998,7 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw } pub fn new_vector_network(subpaths: Vec>) -> NodeNetwork { - let path_generator = resolve_document_node_type("Path Generator").expect("Path Generator node does not exist"); + let path_generator = resolve_document_node_type("Shape").expect("Shape node does not exist"); let transform = resolve_document_node_type("Transform").expect("Transform node does not exist"); let fill = resolve_document_node_type("Fill").expect("Fill node does not exist"); let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist"); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 37a3faa6..09b68669 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -790,7 +790,7 @@ fn get_subpaths<'a>(layer_path: &[LayerId], document: &'a DocumentMessageHandler let layer = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_layer().ok())?; let network = &layer.network; for (node, _node_id) in network.primary_flow() { - if node.name == "Path Generator" { + if node.name == "Shape" { let subpaths_input = node.inputs.get(0)?; let NodeInput::Value { tagged_value: TaggedValue::Subpaths(subpaths), diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index 514d424b..7564b932 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -140,6 +140,7 @@ --icon-expand-collapse-arrow: url('data:image/svg+xml;utf8,'); --icon-expand-collapse-arrow-hover: url('data:image/svg+xml;utf8,'); + --icon-expand-collapse-arrow-disabled: url('data:image/svg+xml;utf8,'); } html, diff --git a/frontend/src/components/floating-menus/ColorPicker.svelte b/frontend/src/components/floating-menus/ColorPicker.svelte index cde3708a..b6fc3962 100644 --- a/frontend/src/components/floating-menus/ColorPicker.svelte +++ b/frontend/src/components/floating-menus/ColorPicker.svelte @@ -285,12 +285,12 @@ {#if !isNone} -
+
{/if} {#if !isNone} -
+
{/if} @@ -434,11 +434,12 @@ background-blend-mode: screen; background: linear-gradient(to top, #ff0000ff 16.666%, #ff000000 33.333%, #ff000000 66.666%, #ff0000ff 83.333%), linear-gradient(to top, #00ff0000 0%, #00ff00ff 16.666%, #00ff00ff 50%, #00ff0000 66.666%), linear-gradient(to top, #0000ff00 33.333%, #0000ffff 50%, #0000ffff 83.333%, #0000ff00 100%); - --selection-pincers-color: var(--hue-color-contrasting); + --selection-needle-color: var(--hue-color-contrasting); } .alpha-picker { background: linear-gradient(to bottom, var(--opaque-color), transparent); + --selection-needle-color: var(--new-color-contrasting); &::before { content: ""; @@ -450,7 +451,6 @@ background-size: var(--color-transparent-checkered-background-size); background-position: var(--color-transparent-checkered-background-position); } - --selection-pincers-color: var(--new-color-contrasting); } .selection-circle { @@ -475,7 +475,7 @@ } } - .selection-pincers { + .selection-needle { position: absolute; top: 0%; width: 100%; @@ -489,7 +489,7 @@ left: 0; border-style: solid; border-width: 4px 0 4px 4px; - border-color: transparent transparent transparent var(--selection-pincers-color); + border-color: transparent transparent transparent var(--selection-needle-color); } &::after { @@ -499,7 +499,7 @@ right: 0; border-style: solid; border-width: 4px 4px 4px 0; - border-color: transparent var(--selection-pincers-color) transparent transparent; + border-color: transparent var(--selection-needle-color) transparent transparent; } } diff --git a/frontend/src/components/panels/NodeGraph.svelte b/frontend/src/components/panels/NodeGraph.svelte index 9d5f3242..199c586d 100644 --- a/frontend/src/components/panels/NodeGraph.svelte +++ b/frontend/src/components/panels/NodeGraph.svelte @@ -2,9 +2,8 @@ import { getContext, onMount, tick } from "svelte"; import type { IconName } from "@graphite/utility-functions/icons"; - - import { UpdateNodeGraphSelection, type FrontendNodeLink, type FrontendNodeType, type FrontendNode } from "@graphite/wasm-communication/messages"; - + import { UpdateNodeGraphSelection } from "@graphite/wasm-communication/messages"; + import type { FrontendNodeLink, FrontendNodeType, FrontendNode } from "@graphite/wasm-communication/messages"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte"; @@ -24,6 +23,8 @@ const editor = getContext("editor"); const nodeGraph = getContext("nodeGraph"); + type LinkPath = { pathString: string; dataType: string; thick: boolean }; + let graph: LayoutRow | undefined; let nodesContainer: HTMLDivElement | undefined; let nodeSearchInput: TextInput | undefined; @@ -33,10 +34,10 @@ let selected: bigint[] = []; let draggingNodes: { startX: number; startY: number; roundX: number; roundY: number } | undefined = undefined; let selectIfNotDragged: undefined | bigint = undefined; - let linkInProgressFromConnector: HTMLDivElement | undefined = undefined; - let linkInProgressToConnector: HTMLDivElement | DOMRect | undefined = undefined; + let linkInProgressFromConnector: SVGSVGElement | undefined = undefined; + let linkInProgressToConnector: SVGSVGElement | DOMRect | undefined = undefined; let disconnecting: { nodeId: bigint; inputIndex: number; linkIndex: number } | undefined = undefined; - let nodeLinkPaths: [string, string][] = []; + let nodeLinkPaths: LinkPath[] = []; let searchTerm = ""; let nodeListLocation: { x: number; y: number } | undefined = undefined; @@ -109,14 +110,19 @@ return Array.from(categories); } - function createLinkPathInProgress(linkInProgressFromConnector?: HTMLDivElement, linkInProgressToConnector?: HTMLDivElement | DOMRect): [string, string] | undefined { + function createLinkPathInProgress(linkInProgressFromConnector?: SVGSVGElement, linkInProgressToConnector?: SVGSVGElement | DOMRect): LinkPath | undefined { if (linkInProgressFromConnector && linkInProgressToConnector && nodesContainer) { - return createWirePath(linkInProgressFromConnector, linkInProgressToConnector, false, false); + 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; + return createWirePath(linkInProgressFromConnector, linkInProgressToConnector, linkStart, linkEnd); } return undefined; } - function createLinkPaths(linkPathInProgress: [string, string] | undefined, nodeLinkPaths: [string, string][]): [string, string][] { + function createLinkPaths(linkPathInProgress: LinkPath | undefined, nodeLinkPaths: LinkPath[]): LinkPath[] { const optionalTuple = linkPathInProgress ? [linkPathInProgress] : []; return [...optionalTuple, ...nodeLinkPaths]; } @@ -126,7 +132,7 @@ await refreshLinks(); } - function resolveLink(link: FrontendNodeLink, containerBounds: HTMLDivElement): { nodePrimaryOutput: HTMLDivElement | undefined; nodePrimaryInput: HTMLDivElement | undefined } { + function resolveLink(link: FrontendNodeLink, containerBounds: HTMLDivElement): { nodeOutput: SVGSVGElement | undefined; nodeInput: SVGSVGElement | undefined } { const outputIndex = Number(link.linkStartOutputIndex); const inputIndex = Number(link.linkEndInputIndex); @@ -134,9 +140,9 @@ const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkEnd)}"] [data-port="input"]`) || undefined; - const nodePrimaryOutput = nodeOutputConnectors?.[outputIndex] as HTMLDivElement | undefined; - const nodePrimaryInput = nodeInputConnectors?.[inputIndex] as HTMLDivElement | undefined; - return { nodePrimaryOutput, nodePrimaryInput }; + const nodeOutput = nodeOutputConnectors?.[outputIndex] as SVGSVGElement | undefined; + const nodeInput = nodeInputConnectors?.[inputIndex] as SVGSVGElement | undefined; + return { nodeOutput, nodeInput }; } async function refreshLinks(): Promise { @@ -147,11 +153,13 @@ const links = $nodeGraph.links; nodeLinkPaths = links.flatMap((link, index) => { - const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, theNodesContainer); - if (!nodePrimaryInput || !nodePrimaryOutput) return []; + const { nodeInput, nodeOutput } = resolveLink(link, theNodesContainer); + 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; - return [createWirePath(nodePrimaryOutput, nodePrimaryInput.getBoundingClientRect(), false, false)]; + return [createWirePath(nodeOutput, nodeInput.getBoundingClientRect(), linkStart, linkEnd)]; }); } @@ -205,13 +213,13 @@ return `M${locations[0].x},${locations[0].y} C${locations[1].x},${locations[1].y} ${locations[2].x},${locations[2].y} ${locations[3].x},${locations[3].y}`; } - function createWirePath(outputPort: HTMLDivElement, inputPort: HTMLDivElement | DOMRect, verticalOut: boolean, verticalIn: boolean): [string, string] { - const inputPortRect = inputPort instanceof HTMLDivElement ? inputPort.getBoundingClientRect() : inputPort; + function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement | DOMRect, verticalOut: boolean, verticalIn: boolean): LinkPath { + const inputPortRect = inputPort instanceof DOMRect ? inputPort : inputPort.getBoundingClientRect(); const pathString = buildWirePathString(outputPort.getBoundingClientRect(), inputPortRect, verticalOut, verticalIn); const dataType = outputPort.getAttribute("data-datatype") || "general"; - return [pathString, dataType]; + return { pathString, dataType, thick: verticalIn && verticalOut }; } function scroll(e: WheelEvent) { @@ -273,7 +281,7 @@ function pointerDown(e: PointerEvent) { const [lmb, rmb] = [e.button === 0, e.button === 2]; - const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement; + const port = (e.target as SVGSVGElement).closest("[data-port]") as SVGSVGElement; const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; const nodeId = node?.getAttribute("data-node") || undefined; const nodeList = (e.target as HTMLElement).closest("[data-node-list]") as HTMLElement | undefined; @@ -315,17 +323,19 @@ const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(port); const inputIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined; // Set the link to draw from the input that a previous link was on - if (inputIndex !== undefined && nodeId) { + if (inputIndex !== undefined && nodeId !== undefined) { const nodeIdInt = BigInt(nodeId); const inputIndexInt = BigInt(inputIndex); const links = $nodeGraph.links; const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt); - const nodeOutputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined; - linkInProgressFromConnector = nodeOutputConnectors?.[Number(links[linkIndex].linkStartOutputIndex)] as HTMLDivElement | undefined; - const nodeInputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined; - linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined; - disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex }; - refreshLinks(); + if (linkIndex !== -1) { + const nodeOutputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined; + linkInProgressFromConnector = nodeOutputConnectors?.[Number(links[linkIndex].linkStartOutputIndex)] as SVGSVGElement | undefined; + const nodeInputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined; + linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as SVGSVGElement | undefined; + disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex }; + refreshLinks(); + } } } @@ -354,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) : null); return; } @@ -384,7 +394,7 @@ transform.y += e.movementY / transform.scale; } else if (linkInProgressFromConnector) { const target = e.target as Element | undefined; - const dot = (target?.closest(`[data-port="input"]`) || undefined) as HTMLDivElement | undefined; + const dot = (target?.closest(`[data-port="input"]`) || undefined) as SVGSVGElement | undefined; if (dot) { linkInProgressToConnector = dot; } else { @@ -401,6 +411,23 @@ } } + function connectorToNodeIndex(svg: SVGSVGElement): { nodeId: bigint; index: number } | undefined { + const node = svg.closest("[data-node]"); + + if (!node) return undefined; + const nodeIdAttribute = node.getAttribute("data-node"); + if (!nodeIdAttribute) return undefined; + const nodeId = BigInt(nodeIdAttribute); + + const inputPortElements = Array.from(node.querySelectorAll(`[data-port="input"]`)); + const outputPortElements = Array.from(node.querySelectorAll(`[data-port="output"]`)); + const inputNodeConnectionIndexSearch = inputPortElements.includes(svg) ? inputPortElements.indexOf(svg) : outputPortElements.indexOf(svg); + const index = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined; + + if (nodeId !== undefined && index !== undefined) return { nodeId, index }; + else return undefined; + } + function pointerUp(e: PointerEvent) { panning = false; @@ -409,26 +436,14 @@ } disconnecting = undefined; - if (linkInProgressToConnector instanceof HTMLDivElement && linkInProgressFromConnector) { - const outputNode = linkInProgressFromConnector.closest("[data-node]"); - const inputNode = linkInProgressToConnector.closest("[data-node]"); + if (linkInProgressToConnector instanceof SVGSVGElement && linkInProgressFromConnector) { + const from = connectorToNodeIndex(linkInProgressFromConnector); + const to = connectorToNodeIndex(linkInProgressToConnector); - const outputConnectedNodeID = outputNode?.getAttribute("data-node") ?? undefined; - const inputConnectedNodeID = inputNode?.getAttribute("data-node") ?? undefined; - - if (outputNode && inputNode && outputConnectedNodeID && inputConnectedNodeID) { - const inputNodeInPorts = Array.from(inputNode.querySelectorAll(`[data-port="input"]`)); - const outputNodeInPorts = Array.from(outputNode.querySelectorAll(`[data-port="output"]`)); - - const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(linkInProgressToConnector); - const outputNodeConnectionIndexSearch = outputNodeInPorts.indexOf(linkInProgressFromConnector); - - const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined; - const outputNodeConnectionIndex = outputNodeConnectionIndexSearch > -1 ? outputNodeConnectionIndexSearch : undefined; - - if (inputNodeConnectionIndex !== undefined && outputNodeConnectionIndex !== undefined) { - editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), outputNodeConnectionIndex, BigInt(inputConnectedNodeID), inputNodeConnectionIndex); - } + if (from !== undefined && to !== undefined) { + const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from; + const { nodeId: inputConnectedNodeID, index: inputNodeConnectionIndex } = to; + editor.instance.connectNodesByLink(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex); } } else if (draggingNodes) { if (draggingNodes.startX === e.x || draggingNodes.startY === e.y) { @@ -456,10 +471,10 @@ // Find the link that the node has been dragged on top of const link = $nodeGraph.links.find((link): boolean => { - const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, theNodesContainer); - if (!nodePrimaryInput || !nodePrimaryOutput) return false; + const { nodeInput, nodeOutput } = resolveLink(link, theNodesContainer); + if (!nodeInput || !nodeOutput) return false; - const wireCurveLocations = buildWirePathLocations(nodePrimaryOutput.getBoundingClientRect(), nodePrimaryInput.getBoundingClientRect(), false, false); + const wireCurveLocations = buildWirePathLocations(nodeOutput.getBoundingClientRect(), nodeInput.getBoundingClientRect(), false, false); const selectedNodeBounds = selectedNode.getBoundingClientRect(); const containerBoundsBounds = theNodesContainer.getBoundingClientRect(); @@ -498,15 +513,20 @@ nodeListLocation = undefined; } + function buildBorderMask(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 }[] = []; + if (primaryInputExists) boxes.push({ x: -8, y: 4, width: 16, height: 16 }); + for (let i = 0; i < parameters; i++) boxes.push({ x: -8, y: 4 + (i + 1) * 24, width: 16, height: 16 }); + if (primaryOutputExists) boxes.push({ x: nodeWidth - 8, y: 4, width: 16, height: 16 }); + for (let i = 0; i < exposedOutputs; i++) boxes.push({ x: nodeWidth - 8, y: 4 + (i + 1) * 24, width: 16, height: 16 }); + + 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(" ")}`; + } + onMount(() => { - const outputPort1 = document.querySelectorAll(`[data-port="output"]`)[4] as HTMLDivElement | undefined; - const inputPort1 = document.querySelectorAll(`[data-port="input"]`)[1] as HTMLDivElement | undefined; - if (outputPort1 && inputPort1) createWirePath(outputPort1, inputPort1.getBoundingClientRect(), true, true); - - const outputPort2 = document.querySelectorAll(`[data-port="output"]`)[6] as HTMLDivElement | undefined; - const inputPort2 = document.querySelectorAll(`[data-port="input"]`)[3] as HTMLDivElement | undefined; - if (outputPort2 && inputPort2) createWirePath(outputPort2, inputPort2.getBoundingClientRect(), true, false); - editor.subscriptions.subscribeJsMessage(UpdateNodeGraphSelection, (updateNodeGraphSelection) => { selected = updateNodeGraphSelection.selected; }); @@ -530,6 +550,9 @@ "--dot-radius": `${dotRadius}px`, }} > + +
+ {#if nodeListLocation} {/if} -
- {#each $nodeGraph.nodes as node (String(node.id))} - {@const exposedInputsOutputs = [...node.exposedInputs, ...node.outputs.slice(1)]} + +
+ + {#each linkPaths as { pathString, dataType, thick }} + + {/each} + +
+ +
+ + {#each $nodeGraph.nodes.filter((node) => node.displayName === "Layer") as node (String(node.id))} + {@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]} + {@const clipPathId = `${Math.random()}`.substring(2)} + {@const stackDatainput = node.exposedInputs[0]}
-
-
- {#if node.primaryInput} -
-
-
- {/if} - {#if node.outputs.length > 0} -
-
-
- {/if} -
- {#if node.thumbnailSvg} - {@html node.thumbnailSvg} - {:else} - +
+ +
+ + + +
+
+ {@html node.thumbnailSvg} + {#if node.primaryOutput} + + + {/if} + + + +
+
{node.displayName}
+ + + + + + + + +
+ {/each} + + {#each $nodeGraph.nodes.filter((node) => node.displayName !== "Layer") as node (String(node.id))} + {@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]} + {@const clipPathId = `${Math.random()}`.substring(2)} +
+ +
+ + {node.displayName} +
+ {#if exposedInputsOutputs.length > 0}
- {#each exposedInputsOutputs as parameter, index (index)} -
-
- {#if index < node.exposedInputs.length} -
-
-
- {:else} -
-
-
- {/if} -
+ {#each exposedInputsOutputs as parameter, index} +
+
{parameter.name}
{/each}
{/if} + +
+ {#if node.primaryInput} + + + + {/if} + {#each node.exposedInputs as parameter, index} + {#if index < node.exposedInputs.length} + + + + {/if} + {/each} +
+ +
+ {#if node.primaryOutput} + + + + {/if} + {#each node.exposedOutputs as parameter} + + + + {/each} +
+ + + + + + +
{/each}
-
- - {#each linkPaths as [pathString, dataType], index (index)} - - {/each} - -
@@ -712,6 +838,13 @@ } } + .fade-artwork { + background: var(--color-2-mildblack); + opacity: 0.8; + width: 100%; + height: 100%; + } + .graph { position: relative; background: var(--color-2-mildblack); @@ -721,6 +854,11 @@ border-radius: 2px; overflow: hidden; + > img { + position: absolute; + bottom: 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 &::before { content: ""; @@ -735,7 +873,7 @@ } } - .nodes, + .layers-and-nodes, .wires { position: absolute; width: 100%; @@ -753,27 +891,46 @@ path { fill: none; - // stroke: var(--color-data-raster-dim); stroke: var(--data-color-dim); - stroke-width: 2px; + stroke-width: var(--data-line-width); } } } - &.nodes { + &.layers-and-nodes { + .layer, .node { position: absolute; display: flex; - flex-direction: column; - min-width: 120px; - border-radius: 4px; - background: var(--color-4-dimgray); - left: calc((var(--offset-left) + 0.5) * 24px); - top: calc((var(--offset-top) - 0.5) * 24px); + backdrop-filter: blur(8px) brightness(100% - 33%); + left: calc(var(--offset-left) * 24px); + top: calc(var(--offset-top) * 24px); + + &::after { + content: ""; + position: absolute; + box-sizing: border-box; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + clip-path: var(--clip-path-id); + } + + .border-mask { + position: absolute; + top: 0; + } &.selected { - border: 1px solid var(--color-e-nearwhite); - margin: -1px; + .primary { + background: rgba(255, 255, 255, 0.15); + } + + .parameters { + background: rgba(255, 255, 255, 0.1); + } } &.disabled { @@ -783,24 +940,170 @@ .icon-label { fill: var(--color-a-softgray); } + + .expand-arrow::after { + background: var(--icon-expand-collapse-arrow-disabled); + } } - &.previewed { - outline: 3px solid var(--color-data-vector); + &.previewed::after { + border: 1px dashed var(--color-data-vector); + } + + .ports { + position: absolute; + + &.input { + left: -3px; + } + + &.output { + right: -5px; + } + } + + .port { + fill: var(--data-color); + // Double the intended value because of margin collapsing, but for the first and last we divide it by two as intended + margin: calc(24px - 8px) 0; + width: 8px; + height: 8px; + + &:first-of-type { + margin-top: calc((24px - 8px) / 2); + } + + &:last-of-type { + margin-bottom: calc((24px - 8px) / 2); + } + } + + .expand-arrow { + width: 16px; + height: 16px; + margin: 0; + padding: 0; + position: relative; + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + + &::after { + content: ""; + position: absolute; + width: 8px; + height: 8px; + background: var(--icon-expand-collapse-arrow); + } + + &:hover::after { + background: var(--icon-expand-collapse-arrow-hover); + } + } + + .expanded .expand-arrow::after { + transform: rotate(90deg); + } + } + + .layer { + border-radius: 8px; + min-width: 216px; + + &::after { + border: 1px solid var(--color-5-dullgray); + border-radius: 8px; + } + + .node-chain { + width: 36px; + } + + .thumbnail { + background: var(--color-2-mildblack); + border: 1px solid var(--color-data-vector-dim); + border-radius: 2px; + position: relative; + box-sizing: border-box; + width: 72px; + height: 48px; + + &::before { + content: ""; + background: var(--color-transparent-checkered-background); + background-size: var(--color-transparent-checkered-background-size); + background-position: var(--color-transparent-checkered-background-position); + } + + &::before, + svg:not(.port) { + position: absolute; + margin: auto; + inset: 1px; + pointer-events: none; + } + + .port { + position: absolute; + margin: 0 auto; + left: 0; + right: 0; + + &.top { + top: -12px; + } + + &.bottom { + bottom: -12px; + } + } + } + + .details { + margin-left: 12px; + + .text-label { + line-height: 48px; + } + } + + .input.ports, + .input.ports .port { + position: absolute; + margin: auto 0; + top: 0; + bottom: 0; + } + } + + .node { + flex-direction: column; + border-radius: 2px; + min-width: 120px; + top: calc((var(--offset-top) + 0.5) * 24px); + + &::after { + border: 1px solid var(--color-data-vector-dim); + border-radius: 2px; } .primary { display: flex; align-items: center; position: relative; - gap: 4px; width: 100%; height: 24px; - background: var(--color-5-dullgray); - border-radius: 4px; + border-radius: 2px 2px 0 0; + font-style: italic; + background: rgba(255, 255, 255, 0.05); + + &.no-parameter-section { + border-radius: 2px; + } .icon-label { - margin-left: 4px; + margin: 0 8px; } .text-label { @@ -818,10 +1121,16 @@ position: relative; display: flex; align-items: center; + width: 100%; height: 24px; - width: calc(100% - 24px * 2); - margin-left: 24px; - margin-right: 24px; + + &:last-of-type { + border-radius: 0 0 2px 2px; + } + + .expand-arrow { + margin-left: 4px; + } .text-label { width: 100%; @@ -832,17 +1141,6 @@ } } - // Squares to cover up the rounded corners of the primary area and make them have a straight edge - &::before, - &::after { - content: ""; - position: absolute; - background: var(--color-5-dullgray); - width: 4px; - height: 4px; - top: -4px; - } - &::before { left: 0; } @@ -851,46 +1149,6 @@ right: 0; } } - - .ports { - position: absolute; - width: 100%; - height: 100%; - - .port { - position: absolute; - margin: auto 0; - top: 0; - bottom: 0; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--data-color-dim); - // background: var(--color-data-raster-dim); - - div { - background: var(--data-color); - // background: var(--color-data-raster); - width: 8px; - height: 8px; - border-radius: 50%; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - margin: auto; - } - - &.input { - left: calc(-12px - 6px); - } - - &.output { - right: calc(-12px - 6px); - } - } - } } } } diff --git a/frontend/src/components/widgets/groups/WidgetSection.svelte b/frontend/src/components/widgets/groups/WidgetSection.svelte index d051f7dc..32bb4872 100644 --- a/frontend/src/components/widgets/groups/WidgetSection.svelte +++ b/frontend/src/components/widgets/groups/WidgetSection.svelte @@ -49,7 +49,7 @@ margin-bottom: 4px; border: 0; border-radius: 4px; - background: var(--color-5-dullgray); + background: var(--color-2-mildblack); .expand-arrow { width: 8px; @@ -87,7 +87,7 @@ } &:hover { - background: var(--color-6-lowergray); + background: var(--color-4-dimgray); .expand-arrow::after { background: var(--icon-expand-collapse-arrow-hover); @@ -98,7 +98,7 @@ } + .body { - border: 1px solid var(--color-6-lowergray); + border: 1px solid var(--color-4-dimgray); } } } @@ -108,7 +108,7 @@ padding-top: 1px; margin-top: -1px; margin-bottom: 4px; - border: 1px solid var(--color-5-dullgray); + border: 1px solid var(--color-2-mildblack); border-radius: 0 0 4px 4px; overflow: hidden; diff --git a/frontend/src/components/window/workspace/Workspace.svelte b/frontend/src/components/window/workspace/Workspace.svelte index 744bc471..3113f7a1 100644 --- a/frontend/src/components/window/workspace/Workspace.svelte +++ b/frontend/src/components/window/workspace/Workspace.svelte @@ -14,8 +14,8 @@ const PANEL_SIZES = { /**/ root: 100, /* ├── */ content: 80, - /* │ ├── */ document: 80, - /* │ └── */ graph: 20, + /* │ ├── */ document: 50, + /* │ └── */ graph: 50, /* └── */ details: 20, /* ├── */ properties: 45, /* └── */ layers: 55, diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 96523a1b..3ea2382b 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -96,7 +96,9 @@ export class FrontendNode { readonly exposedInputs!: NodeGraphInput[]; - readonly outputs!: NodeGraphOutput[]; + readonly primaryOutput!: NodeGraphOutput | undefined; + + readonly exposedOutputs!: NodeGraphOutput[]; @TupleToVec2 readonly position!: XY | undefined;