Create node by dragging link into empty space (#1438)

* Create node by dragging into empty space

* Prevent add menu when disconnecting a link

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-10-24 21:22:41 +01:00 committed by GitHub
parent b8906f344e
commit 6ff958d6ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 100 additions and 57 deletions

View File

@ -271,9 +271,22 @@
if (e.key.toLowerCase() === "escape") {
nodeListLocation = undefined;
document.removeEventListener("keydown", keydown);
linkInProgressFromConnector = undefined;
}
}
function loadNodeList(e: PointerEvent, graphBounds: DOMRect) {
nodeListLocation = {
x: Math.round(((e.clientX - graphBounds.x) / transform.scale - transform.x) / GRID_SIZE),
y: Math.round(((e.clientY - graphBounds.y) / transform.scale - transform.y) / GRID_SIZE),
};
// Find actual relevant child and focus it (setTimeout is required to actually focus the input element)
setTimeout(() => nodeSearchInput?.focus(), 0);
document.addEventListener("keydown", keydown);
}
// TODO: Move the event listener from the graph to the window so dragging outside the graph area (or even the whole browser window) works
function pointerDown(e: PointerEvent) {
const [lmb, rmb] = [e.button === 0, e.button === 2];
@ -287,15 +300,7 @@
if (rmb) {
const graphBounds = graph?.getBoundingClientRect();
if (!graphBounds) return;
nodeListLocation = {
x: Math.round(((e.clientX - graphBounds.x) / transform.scale - transform.x) / GRID_SIZE),
y: Math.round(((e.clientY - graphBounds.y) / transform.scale - transform.y) / GRID_SIZE),
};
// Find actual relevant child and focus it (setTimeout is required to actually focus the input element)
setTimeout(() => nodeSearchInput?.focus(), 0);
document.addEventListener("keydown", keydown);
loadNodeList(e, graphBounds);
return;
}
@ -303,7 +308,10 @@
if (lmb && nodeList) return;
// Since the user is clicking elsewhere in the graph, ensure the add nodes list is closed
if (lmb) nodeListLocation = undefined;
if (lmb) {
nodeListLocation = undefined;
linkInProgressFromConnector = undefined;
}
// Alt-click sets the clicked node as previewed
if (lmb && e.altKey && nodeId) {
@ -389,7 +397,7 @@
if (panning) {
transform.x += e.movementX / transform.scale;
transform.y += e.movementY / transform.scale;
} else if (linkInProgressFromConnector) {
} else if (linkInProgressFromConnector && !nodeListLocation) {
const target = e.target as Element | undefined;
const dot = (target?.closest(`[data-port="input"]`) || undefined) as SVGSVGElement | undefined;
if (dot) {
@ -436,35 +444,9 @@
else return undefined;
}
function pointerUp(e: PointerEvent) {
panning = false;
if (disconnecting) {
editor.instance.disconnectNodes(BigInt(disconnecting.nodeId), disconnecting.inputIndex);
}
disconnecting = undefined;
if (linkInProgressToConnector instanceof SVGSVGElement && linkInProgressFromConnector) {
const from = connectorToNodeIndex(linkInProgressFromConnector);
const to = connectorToNodeIndex(linkInProgressToConnector);
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) {
if (selectIfNotDragged !== undefined && (selected.length !== 1 || selected[0] !== selectIfNotDragged)) {
selected = [selectIfNotDragged];
editor.instance.selectNodes(new BigUint64Array(selected));
}
}
if (selected.length > 0 && (draggingNodes.roundX !== 0 || draggingNodes.roundY !== 0)) editor.instance.moveSelectedNodes(draggingNodes.roundX, draggingNodes.roundY);
// Check if this node should be inserted between two other nodes
if (selected.length === 1) {
function checkInsertBetween() {
if (selected.length !== 1) return;
const selectedNodeId = selected[0];
const selectedNode = nodesContainer?.querySelector(`[data-node="${String(selectedNodeId)}"]`) || undefined;
@ -474,7 +456,9 @@
const output = selectedNode?.querySelector(`[data-port="output"]`) || undefined;
// TODO: Make sure inputs are correctly typed
if (selectedNode && notConnected && input && output && nodesContainer) {
if (!selectedNode || !notConnected || !input || !output || !nodesContainer) return;
// Fixes typing for some reason?
const theNodesContainer = nodesContainer;
// Find the link that the node has been dragged on top of
@ -504,7 +488,53 @@
editor.instance.shiftNode(selectedNodeId);
}
}
function pointerUp(e: PointerEvent) {
panning = false;
const initialDisconnecting = disconnecting;
if (disconnecting) {
editor.instance.disconnectNodes(BigInt(disconnecting.nodeId), disconnecting.inputIndex);
}
disconnecting = undefined;
if (linkInProgressToConnector instanceof SVGSVGElement && linkInProgressFromConnector) {
const from = connectorToNodeIndex(linkInProgressFromConnector);
const to = connectorToNodeIndex(linkInProgressToConnector);
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 (linkInProgressFromConnector && !initialDisconnecting) {
// If the add node menu is already open, we don't want to open it again
if (nodeListLocation) return;
const graphBounds = graph?.getBoundingClientRect();
if (!graphBounds) return;
// Create the node list, which should set nodeListLocation to a valid value
loadNodeList(e, graphBounds);
if (!nodeListLocation) return;
let nodeListLocation2: { x: number; y: number } = nodeListLocation;
linkInProgressToConnector = new DOMRect(
(nodeListLocation2.x * GRID_SIZE + transform.x) * transform.scale + graphBounds.x,
(nodeListLocation2.y * GRID_SIZE + transform.y) * transform.scale + graphBounds.y
);
return;
} else if (draggingNodes) {
if (draggingNodes.startX === e.x || draggingNodes.startY === e.y) {
if (selectIfNotDragged !== undefined && (selected.length !== 1 || selected[0] !== selectIfNotDragged)) {
selected = [selectIfNotDragged];
editor.instance.selectNodes(new BigUint64Array(selected));
}
}
if (selected.length > 0 && (draggingNodes.roundX !== 0 || draggingNodes.roundY !== 0)) editor.instance.moveSelectedNodes(draggingNodes.roundX, draggingNodes.roundY);
checkInsertBetween();
draggingNodes = undefined;
selectIfNotDragged = undefined;
@ -517,8 +547,19 @@
function createNode(nodeType: string): void {
if (!nodeListLocation) return;
editor.instance.createNode(nodeType, nodeListLocation.x, nodeListLocation.y);
const inputNodeConnectionIndex = 0;
const inputConnectedNodeID = editor.instance.createNode(nodeType, nodeListLocation.x, nodeListLocation.y - 1);
nodeListLocation = undefined;
if (!linkInProgressFromConnector) return;
const from = connectorToNodeIndex(linkInProgressFromConnector);
if (from !== undefined) {
const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from;
editor.instance.connectNodesByLink(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
}
linkInProgressFromConnector = undefined;
}
function nodeBorderMask(nodeWidth: number, primaryInputExists: boolean, parameters: number, primaryOutputExists: boolean, exposedOutputs: number): string {
@ -654,7 +695,7 @@
</div>
<div class="thumbnail">
{#if $nodeGraph.thumbnails.has(node.id)}
{@html $nodeGraph.thumbnails.get(node.id) }
{@html $nodeGraph.thumbnails.get(node.id)}
{/if}
{#if node.primaryOutput}
<svg

View File

@ -643,9 +643,11 @@ impl JsEditorHandle {
/// 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) {
let message = NodeGraphMessage::CreateNode { node_id: None, node_type, x, y };
pub fn create_node(&self, node_type: String, x: i32, y: i32) -> u64 {
let id = generate_uuid();
let message = NodeGraphMessage::CreateNode { node_id: Some(id), node_type, x, y };
self.dispatch(message);
id
}
/// Notifies the backend that the user selected a node in the node graph