mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Add merging nodes into a subgraph with Ctrl+M and basic subgraph signature customization (#2097)
* Merge nodes * Fix bugs/crashes * WIP: Debugging * Fix bugs, add button * Add imports/exports * Improve button * Fix breadcrumbs * Fix lints and change shortcut key --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
4c4d559d97
commit
4250f291ab
13 changed files with 735 additions and 238 deletions
|
@ -13,8 +13,10 @@
|
|||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
|
||||
import RadioInput from "@graphite/components/widgets/inputs/RadioInput.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
const GRID_COLLAPSE_SPACING = 10;
|
||||
const GRID_SIZE = 24;
|
||||
|
@ -309,6 +311,15 @@
|
|||
});
|
||||
return connectedNode?.isLayer || false;
|
||||
}
|
||||
|
||||
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
|
||||
const maxLength = Math.max(arr1.length, arr2.length);
|
||||
const result = [];
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
result.push([arr1[i], arr2[i]]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -357,6 +368,10 @@
|
|||
disabled={!canBeToggledBetweenNodeAndLayer(contextMenuData.nodeId)}
|
||||
/>
|
||||
</LayoutRow>
|
||||
<Separator type="Section" direction="Vertical" />
|
||||
<LayoutRow class="merge-selected-nodes">
|
||||
<TextButton label="Merge Selected Nodes" action={() => editor.handle.mergeSelectedNodes()} />
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
{/if}
|
||||
|
@ -424,6 +439,19 @@
|
|||
</svg>
|
||||
<p class="import-text" style:--offset-left={position.x / 24} style:--offset-top={position.y / 24}>{outputMetadata.name}</p>
|
||||
{/each}
|
||||
{#if $nodeGraph.addImport !== undefined}
|
||||
<div class="plus" style:--offset-left={$nodeGraph.addImport.x / 24} style:--offset-top={$nodeGraph.addImport.y / 24}>
|
||||
<IconButton
|
||||
class={"visibility"}
|
||||
data-visibility-button
|
||||
size={24}
|
||||
icon={"Add"}
|
||||
action={() => {
|
||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#each $nodeGraph.exports as { inputMetadata, position }, index}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -446,6 +474,19 @@
|
|||
</svg>
|
||||
<p class="export-text" style:--offset-left={position.x / 24} style:--offset-top={position.y / 24}>{inputMetadata.name}</p>
|
||||
{/each}
|
||||
{#if $nodeGraph.addExport !== undefined}
|
||||
<div class="plus" style:--offset-left={$nodeGraph.addExport.x / 24} style:--offset-top={$nodeGraph.addExport.y / 24}>
|
||||
<IconButton
|
||||
class={"visibility"}
|
||||
data-visibility-button
|
||||
size={24}
|
||||
icon={"Add"}
|
||||
action={() => {
|
||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Layers and nodes -->
|
||||
|
@ -603,7 +644,7 @@
|
|||
|
||||
<!-- Nodes -->
|
||||
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)}
|
||||
{@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]}
|
||||
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
|
||||
{@const clipPathId = String(Math.random()).substring(2)}
|
||||
{@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined}
|
||||
<div
|
||||
|
@ -633,9 +674,11 @@
|
|||
<!-- Secondary rows -->
|
||||
{#if exposedInputsOutputs.length > 0}
|
||||
<div class="secondary" class:in-selected-network={$nodeGraph.inSelectedNetwork}>
|
||||
{#each exposedInputsOutputs as secondary, index}
|
||||
<div class={`secondary-row expanded ${index < node.exposedInputs.length ? "input" : "output"}`}>
|
||||
<TextLabel tooltip={secondary.name}>{secondary.name}</TextLabel>
|
||||
{#each exposedInputsOutputs as [input, output]}
|
||||
<div class={`secondary-row expanded ${input !== undefined ? "input" : "output"}`}>
|
||||
<TextLabel tooltip={input !== undefined ? input.name : output.name}>
|
||||
{input !== undefined ? input.name : output.name}
|
||||
</TextLabel>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -796,6 +839,10 @@
|
|||
line-height: 24px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.merge-selected-nodes {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.click-targets {
|
||||
|
@ -869,6 +916,14 @@
|
|||
left: calc(var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
.plus {
|
||||
margin-top: -4px;
|
||||
margin-left: -4px;
|
||||
position: absolute;
|
||||
top: calc(var(--offset-top) * 24px);
|
||||
left: calc(var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
.export-text {
|
||||
position: absolute;
|
||||
margin-top: 0;
|
||||
|
|
|
@ -174,7 +174,7 @@
|
|||
{/if}
|
||||
{@const breadcrumbTrailButtons = narrowWidgetProps(component.props, "BreadcrumbTrailButtons")}
|
||||
{#if breadcrumbTrailButtons}
|
||||
<BreadcrumbTrailButtons {...exclude(breadcrumbTrailButtons)} action={(index) => widgetValueCommitAndUpdate(index, index)} />
|
||||
<BreadcrumbTrailButtons {...exclude(breadcrumbTrailButtons)} action={(breadcrumbIndex) => widgetValueCommitAndUpdate(index, breadcrumbIndex)} />
|
||||
{/if}
|
||||
{@const textInput = narrowWidgetProps(component.props, "TextInput")}
|
||||
{#if textInput}
|
||||
|
|
|
@ -36,6 +36,8 @@ export function createNodeGraphState(editor: Editor) {
|
|||
hasLeftInputWire: new Map<bigint, boolean>(),
|
||||
imports: [] as { outputMetadata: FrontendGraphOutput; position: { x: number; y: number } }[],
|
||||
exports: [] as { inputMetadata: FrontendGraphInput; position: { x: number; y: number } }[],
|
||||
addImport: undefined as { x: number; y: number } | undefined,
|
||||
addExport: undefined as { x: number; y: number } | undefined,
|
||||
nodes: new Map<bigint, FrontendNode>(),
|
||||
wires: [] as FrontendNodeWire[],
|
||||
wirePathInProgress: undefined as WirePath | undefined,
|
||||
|
@ -80,6 +82,8 @@ export function createNodeGraphState(editor: Editor) {
|
|||
update((state) => {
|
||||
state.imports = updateImportsExports.imports;
|
||||
state.exports = updateImportsExports.exports;
|
||||
state.addImport = updateImportsExports.addImport;
|
||||
state.addExport = updateImportsExports.addExport;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -61,6 +61,12 @@ export class UpdateImportsExports extends JsMessage {
|
|||
|
||||
@ExportsToVec2Array
|
||||
readonly exports!: { inputMetadata: FrontendGraphInput; position: XY }[];
|
||||
|
||||
@TupleToVec2
|
||||
readonly addImport!: XY | undefined;
|
||||
|
||||
@TupleToVec2
|
||||
readonly addExport!: XY | undefined;
|
||||
}
|
||||
|
||||
export class UpdateInSelectedNetwork extends JsMessage {
|
||||
|
|
|
@ -569,6 +569,13 @@ impl EditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Merge a group of nodes into a subnetwork
|
||||
#[wasm_bindgen(js_name = mergeSelectedNodes)]
|
||||
pub fn merge_nodes(&self) {
|
||||
let message = NodeGraphMessage::MergeSelectedNodes;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Creates a new document node in the node graph
|
||||
#[wasm_bindgen(js_name = createNode)]
|
||||
pub fn create_node(&self, node_type: String, x: i32, y: i32) {
|
||||
|
@ -763,9 +770,7 @@ impl EditorHandle {
|
|||
document
|
||||
.network_interface
|
||||
.replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ToArtboardNode"));
|
||||
document
|
||||
.network_interface
|
||||
.add_input(&node_id, &[], TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string());
|
||||
document.network_interface.add_import(TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string(), &[node_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue