feat: fix some issue with dragging sockets

This commit is contained in:
max_richter 2024-04-16 13:43:03 +02:00
parent 3d3ea5b5f8
commit 097b99ef55
4 changed files with 792 additions and 755 deletions

View File

@ -1,35 +1,37 @@
<script lang="ts"> <script lang="ts">
import { animate, lerp, snapToGrid } from '../helpers/index.js'; import { animate, lerp, snapToGrid } from "../helpers/index.js";
import { LinearSRGBColorSpace } from 'three'; import { LinearSRGBColorSpace } from "three";
import { Canvas } from '@threlte/core'; import { Canvas } from "@threlte/core";
import type { OrthographicCamera } from 'three'; import type { OrthographicCamera } from "three";
import Background from '../background/Background.svelte'; import Background from "../background/Background.svelte";
import type { GraphManager } from '../graph-manager.js'; import type { GraphManager } from "../graph-manager.js";
import { onMount, setContext } from 'svelte'; import { onMount, setContext } from "svelte";
import Camera from '../Camera.svelte'; import Camera from "../Camera.svelte";
import GraphView from './GraphView.svelte'; import GraphView from "./GraphView.svelte";
import type { Node, Node as NodeType, Socket } from '@nodes/types'; import type { Node, Node as NodeType, Socket } from "@nodes/types";
import FloatingEdge from '../edges/FloatingEdge.svelte'; import FloatingEdge from "../edges/FloatingEdge.svelte";
import { import {
activeNodeId, activeNodeId,
activeSocket, activeSocket,
hoveredSocket, hoveredSocket,
possibleSockets, possibleSockets,
possibleSocketIds, possibleSocketIds,
selectedNodes selectedNodes,
} from './stores.js'; } from "./stores.js";
import BoxSelection from '../BoxSelection.svelte'; import BoxSelection from "../BoxSelection.svelte";
import AddMenu from '../AddMenu.svelte'; import AddMenu from "../AddMenu.svelte";
export let graph: GraphManager; export let graph: GraphManager;
setContext('graphManager', graph); setContext("graphManager", graph);
const status = graph.status; const status = graph.status;
const nodes = graph.nodes; const nodes = graph.nodes;
const edges = graph.edges; const edges = graph.edges;
let wrapper: HTMLDivElement; let wrapper: HTMLDivElement;
$: rect = $: rect =
wrapper && width ? wrapper.getBoundingClientRect() : { x: 0, y: 0, width: 0, height: 0 }; wrapper && width
? wrapper.getBoundingClientRect()
: { x: 0, y: 0, width: 0, height: 0 };
let camera: OrthographicCamera; let camera: OrthographicCamera;
const minZoom = 1; const minZoom = 1;
@ -55,47 +57,59 @@
cameraPosition[0] - width / cameraPosition[2] / 2, cameraPosition[0] - width / cameraPosition[2] / 2,
cameraPosition[0] + width / cameraPosition[2] / 2, cameraPosition[0] + width / cameraPosition[2] / 2,
cameraPosition[1] - height / cameraPosition[2] / 2, cameraPosition[1] - height / cameraPosition[2] / 2,
cameraPosition[1] + height / cameraPosition[2] / 2 cameraPosition[1] + height / cameraPosition[2] / 2,
]; ];
function setCameraTransform(x = cameraPosition[0], y = cameraPosition[1], z = cameraPosition[2]) { function setCameraTransform(
x = cameraPosition[0],
y = cameraPosition[1],
z = cameraPosition[2],
) {
if (camera) { if (camera) {
camera.position.x = x; camera.position.x = x;
camera.position.z = y; camera.position.z = y;
camera.zoom = z; camera.zoom = z;
} }
cameraPosition = [x, y, z]; cameraPosition = [x, y, z];
localStorage.setItem('cameraPosition', JSON.stringify(cameraPosition)); localStorage.setItem("cameraPosition", JSON.stringify(cameraPosition));
} }
export let debug = {}; export let debug = {};
$: debug = { $: debug = {
activeNodeId: $activeNodeId, activeNodeId: $activeNodeId,
activeSocket: $activeSocket ? `${$activeSocket?.node.id}-${$activeSocket?.index}` : null, activeSocket: $activeSocket
hoveredSocket: $hoveredSocket ? `${$hoveredSocket?.node.id}-${$hoveredSocket?.index}` : null, ? `${$activeSocket?.node.id}-${$activeSocket?.index}`
: null,
hoveredSocket: $hoveredSocket
? `${$hoveredSocket?.node.id}-${$hoveredSocket?.index}`
: null,
selectedNodes: [...($selectedNodes?.values() || [])], selectedNodes: [...($selectedNodes?.values() || [])],
cameraPosition cameraPosition,
}; };
function updateNodePosition(node: NodeType) { function updateNodePosition(node: NodeType) {
if (node?.tmp?.ref) { if (node?.tmp?.ref) {
if (node.tmp['x'] !== undefined && node.tmp['y'] !== undefined) { if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty('--nx', `${node.tmp.x * 10}px`); node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
node.tmp.ref.style.setProperty('--ny', `${node.tmp.y * 10}px`); node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10; node.tmp.mesh.position.x = node.tmp.x + 10;
node.tmp.mesh.position.z = node.tmp.y + getNodeHeight(node.type) / 2; node.tmp.mesh.position.z = node.tmp.y + getNodeHeight(node.type) / 2;
if (node.tmp.x === node.position[0] && node.tmp.y === node.position[1]) { if (
node.tmp.x === node.position[0] &&
node.tmp.y === node.position[1]
) {
delete node.tmp.x; delete node.tmp.x;
delete node.tmp.y; delete node.tmp.y;
} }
} else { } else {
node.tmp.ref.style.setProperty('--nx', `${node.position[0] * 10}px`); node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty('--ny', `${node.position[1] * 10}px`); node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10; node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z = node.position[1] + getNodeHeight(node.type) / 2; node.tmp.mesh.position.z =
node.position[1] + getNodeHeight(node.type) / 2;
} }
} }
} }
setContext('updateNodePosition', updateNodePosition); setContext("updateNodePosition", updateNodePosition);
const nodeHeightCache: Record<string, number> = {}; const nodeHeightCache: Record<string, number> = {};
function getNodeHeight(nodeTypeId: string) { function getNodeHeight(nodeTypeId: string) {
@ -110,14 +124,15 @@
5 + 5 +
10 * 10 *
Object.keys(node.inputs) Object.keys(node.inputs)
.filter((i) => i !== 'seed') .filter((i) => i !== "seed")
.filter((p) => node?.inputs && !('setting' in node?.inputs?.[p])).length; .filter((p) => node?.inputs && !("setting" in node?.inputs?.[p]))
.length;
nodeHeightCache[nodeTypeId] = height; nodeHeightCache[nodeTypeId] = height;
return height; return height;
} }
setContext('getNodeHeight', getNodeHeight); setContext("getNodeHeight", getNodeHeight);
setContext('isNodeInView', (node: NodeType) => { setContext("isNodeInView", (node: NodeType) => {
const height = getNodeHeight(node.type); const height = getNodeHeight(node.type);
const width = 20; const width = 20;
return ( return (
@ -137,8 +152,8 @@
if (event.button === 0) { if (event.button === 0) {
// check if the clicked element is a node // check if the clicked element is a node
if (event.target instanceof HTMLElement) { if (event.target instanceof HTMLElement) {
const nodeElement = event.target.closest('.node'); const nodeElement = event.target.closest(".node");
const nodeId = nodeElement?.getAttribute?.('data-node-id'); const nodeId = nodeElement?.getAttribute?.("data-node-id");
if (nodeId) { if (nodeId) {
clickedNodeId = parseInt(nodeId, 10); clickedNodeId = parseInt(nodeId, 10);
} }
@ -162,13 +177,13 @@
return clickedNodeId; return clickedNodeId;
} }
setContext('setDownSocket', (socket: Socket) => { setContext("setDownSocket", (socket: Socket) => {
$activeSocket = socket; $activeSocket = socket;
let { node, index, position } = socket; let { node, index, position } = socket;
// remove existing edge // remove existing edge
if (typeof index === 'string') { if (typeof index === "string") {
const edges = graph.getEdgesToNode(node); const edges = graph.getEdgesToNode(node);
for (const edge of edges) { for (const edge of edges) {
if (edge[3] === index) { if (edge[3] === index) {
@ -185,17 +200,21 @@
$activeSocket = { $activeSocket = {
node, node,
index, index,
position position,
}; };
$possibleSockets = graph.getPossibleSockets($activeSocket).map(([node, index]) => { $possibleSockets = graph
.getPossibleSockets($activeSocket)
.map(([node, index]) => {
return { return {
node, node,
index, index,
position: getSocketPosition(node, index) position: getSocketPosition(node, index),
}; };
}); });
$possibleSocketIds = new Set($possibleSockets.map((s) => `${s.node.id}-${s.index}`)); $possibleSocketIds = new Set(
$possibleSockets.map((s) => `${s.node.id}-${s.index}`),
);
}); });
function getSnapLevel() { function getSnapLevel() {
@ -211,26 +230,29 @@
return 1; return 1;
} }
function getSocketPosition(node: NodeType, index: string | number): [number, number] { function getSocketPosition(
if (typeof index === 'number') { node: NodeType,
index: string | number,
): [number, number] {
if (typeof index === "number") {
return [ return [
(node?.tmp?.x ?? node.position[0]) + 20, (node?.tmp?.x ?? node.position[0]) + 20,
(node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index (node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index,
]; ];
} else { } else {
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index); const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
return [ return [
node?.tmp?.x ?? node.position[0], node?.tmp?.x ?? node.position[0],
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index (node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index,
]; ];
} }
} }
setContext('getSocketPosition', getSocketPosition); setContext("getSocketPosition", getSocketPosition);
function projectScreenToWorld(x: number, y: number): [number, number] { function projectScreenToWorld(x: number, y: number): [number, number] {
return [ return [
cameraPosition[0] + (x - width / 2) / cameraPosition[2], cameraPosition[0] + (x - width / 2) / cameraPosition[2],
cameraPosition[1] + (y - height / 2) / cameraPosition[2] cameraPosition[1] + (y - height / 2) / cameraPosition[2],
]; ];
} }
@ -243,13 +265,13 @@
if (!mouseDown) return; if (!mouseDown) return;
// we are creating a new edge here // we are creating a new edge here
if ($possibleSockets?.length) { if ($activeSocket || $possibleSockets?.length) {
let smallestDist = 1000; let smallestDist = 1000;
let _socket; let _socket;
for (const socket of $possibleSockets) { for (const socket of $possibleSockets) {
const dist = Math.sqrt( const dist = Math.sqrt(
(socket.position[0] - mousePosition[0]) ** 2 + (socket.position[0] - mousePosition[0]) ** 2 +
(socket.position[1] - mousePosition[1]) ** 2 (socket.position[1] - mousePosition[1]) ** 2,
); );
if (dist < smallestDist) { if (dist < smallestDist) {
smallestDist = dist; smallestDist = dist;
@ -350,7 +372,7 @@
const bodyIsFocused = const bodyIsFocused =
document.activeElement === document.body || document.activeElement === document.body ||
document.activeElement === wrapper || document.activeElement === wrapper ||
document?.activeElement?.id === 'graph'; document?.activeElement?.id === "graph";
if (!bodyIsFocused) return; if (!bodyIsFocused) return;
// Define zoom speed and clamp it between -1 and 1 // Define zoom speed and clamp it between -1 and 1
@ -361,7 +383,10 @@
// Calculate new zoom level and clamp it between minZoom and maxZoom // Calculate new zoom level and clamp it between minZoom and maxZoom
const newZoom = Math.max( const newZoom = Math.max(
minZoom, minZoom,
Math.min(maxZoom, isNegative ? cameraPosition[2] / delta : cameraPosition[2] * delta) Math.min(
maxZoom,
isNegative ? cameraPosition[2] / delta : cameraPosition[2] * delta,
),
); );
// Calculate the ratio of the new zoom to the original zoom // Calculate the ratio of the new zoom to the original zoom
@ -371,18 +396,20 @@
setCameraTransform( setCameraTransform(
mousePosition[0] - (mousePosition[0] - cameraPosition[0]) / zoomRatio, mousePosition[0] - (mousePosition[0] - cameraPosition[0]) / zoomRatio,
mousePosition[1] - (mousePosition[1] - cameraPosition[1]) / zoomRatio, mousePosition[1] - (mousePosition[1] - cameraPosition[1]) / zoomRatio,
newZoom newZoom,
); );
} }
function handleMouseDown(event: MouseEvent) { function handleMouseDown(event: MouseEvent) {
if (mouseDown) return; if (mouseDown) return;
console.log(event.target);
if (event.target instanceof HTMLElement) { if (event.target instanceof HTMLElement) {
if ( if (
event.target.nodeName !== 'CANVAS' && event.target.nodeName !== "CANVAS" &&
!event.target.classList.contains('node') && !event.target.classList.contains("node") &&
!event.target.classList.contains('content') !event.target.classList.contains("content")
) { ) {
return; return;
} }
@ -460,15 +487,15 @@
..._node, ..._node,
tmp: { tmp: {
downX: mousePosition[0] - _node.position[0], downX: mousePosition[0] - _node.position[0],
downY: mousePosition[1] - _node.position[1] downY: mousePosition[1] - _node.position[1],
} },
}); });
return node; return node;
}); });
clipboard = { clipboard = {
nodes: _nodes, nodes: _nodes,
edges: _edges edges: _edges,
}; };
} }
@ -490,9 +517,10 @@
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {
const bodyIsFocused = const bodyIsFocused =
document.activeElement === document.body || document?.activeElement?.id === 'graph'; document.activeElement === document.body ||
document?.activeElement?.id === "graph";
if (event.key === 'l') { if (event.key === "l") {
const activeNode = graph.getNode($activeNodeId); const activeNode = graph.getNode($activeNodeId);
if (activeNode) { if (activeNode) {
const nodes = graph.getLinkedNodes(activeNode); const nodes = graph.getLinkedNodes(activeNode);
@ -501,18 +529,18 @@
console.log(activeNode); console.log(activeNode);
} }
if (event.key === 'Escape') { if (event.key === "Escape") {
$activeNodeId = -1; $activeNodeId = -1;
$selectedNodes?.clear(); $selectedNodes?.clear();
$selectedNodes = $selectedNodes; $selectedNodes = $selectedNodes;
(document.activeElement as HTMLElement)?.blur(); (document.activeElement as HTMLElement)?.blur();
} }
if (event.key === 'A' && event.shiftKey) { if (event.key === "A" && event.shiftKey) {
addMenuPosition = [mousePosition[0], mousePosition[1]]; addMenuPosition = [mousePosition[0], mousePosition[1]];
} }
if (event.key === '.') { if (event.key === ".") {
const average = [0, 0]; const average = [0, 0];
for (const node of $nodes.values()) { for (const node of $nodes.values()) {
average[0] += node.position[0]; average[0] += node.position[0];
@ -531,32 +559,32 @@
setCameraTransform( setCameraTransform(
lerp(camX, average[0], ease(a)), lerp(camX, average[0], ease(a)),
lerp(camY, average[1], ease(a)), lerp(camY, average[1], ease(a)),
lerp(camZ, 2, ease(a)) lerp(camZ, 2, ease(a)),
); );
if (mouseDown) return false; if (mouseDown) return false;
}); });
} }
if (event.key === 'a' && event.ctrlKey && bodyIsFocused) { if (event.key === "a" && event.ctrlKey && bodyIsFocused) {
$selectedNodes = new Set($nodes.keys()); $selectedNodes = new Set($nodes.keys());
} }
if (event.key === 'c' && event.ctrlKey) { if (event.key === "c" && event.ctrlKey) {
copyNodes(); copyNodes();
} }
if (event.key === 'v' && event.ctrlKey) { if (event.key === "v" && event.ctrlKey) {
pasteNodes(); pasteNodes();
} }
if (event.key === 'z' && event.ctrlKey) { if (event.key === "z" && event.ctrlKey) {
graph.undo(); graph.undo();
for (const node of $nodes.values()) { for (const node of $nodes.values()) {
updateNodePosition(node); updateNodePosition(node);
} }
} }
if (event.key === 'y' && event.ctrlKey) { if (event.key === "y" && event.ctrlKey) {
graph.redo(); graph.redo();
for (const node of $nodes.values()) { for (const node of $nodes.values()) {
updateNodePosition(node); updateNodePosition(node);
@ -564,7 +592,9 @@
} }
if ( if (
(event.key === 'Delete' || event.key === 'Backspace' || event.key === 'x') && (event.key === "Delete" ||
event.key === "Backspace" ||
event.key === "x") &&
bodyIsFocused bodyIsFocused
) { ) {
graph.startUndoGroup(); graph.startUndoGroup();
@ -610,19 +640,19 @@
const snapLevel = getSnapLevel(); const snapLevel = getSnapLevel();
activeNode.position[0] = snapToGrid( activeNode.position[0] = snapToGrid(
activeNode?.tmp?.x ?? activeNode.position[0], activeNode?.tmp?.x ?? activeNode.position[0],
5 / snapLevel 5 / snapLevel,
); );
activeNode.position[1] = snapToGrid( activeNode.position[1] = snapToGrid(
activeNode?.tmp?.y ?? activeNode.position[1], activeNode?.tmp?.y ?? activeNode.position[1],
5 / snapLevel 5 / snapLevel,
); );
const nodes = [ const nodes = [
...[...($selectedNodes?.values() || [])].map((id) => graph.getNode(id)) ...[...($selectedNodes?.values() || [])].map((id) => graph.getNode(id)),
] as NodeType[]; ] as NodeType[];
const vec = [ const vec = [
activeNode.position[0] - (activeNode?.tmp.x || 0), activeNode.position[0] - (activeNode?.tmp.x || 0),
activeNode.position[1] - (activeNode?.tmp.y || 0) activeNode.position[1] - (activeNode?.tmp.y || 0),
]; ];
for (const node of nodes) { for (const node of nodes) {
@ -637,7 +667,11 @@
nodes.push(activeNode); nodes.push(activeNode);
animate(500, (a: number) => { animate(500, (a: number) => {
for (const node of nodes) { for (const node of nodes) {
if (node?.tmp && node.tmp['x'] !== undefined && node.tmp['y'] !== undefined) { if (
node?.tmp &&
node.tmp["x"] !== undefined &&
node.tmp["y"] !== undefined
) {
node.tmp.x = lerp(node.tmp.x, node.position[0], a); node.tmp.x = lerp(node.tmp.x, node.position[0], a);
node.tmp.y = lerp(node.tmp.y, node.position[1], a); node.tmp.y = lerp(node.tmp.y, node.position[1], a);
updateNodePosition(node); updateNodePosition(node);
@ -651,22 +685,25 @@
}); });
graph.save(); graph.save();
} else if ($hoveredSocket && $activeSocket) { } else if ($hoveredSocket && $activeSocket) {
if (typeof $hoveredSocket.index === 'number' && typeof $activeSocket.index === 'string') { if (
typeof $hoveredSocket.index === "number" &&
typeof $activeSocket.index === "string"
) {
graph.createEdge( graph.createEdge(
$hoveredSocket.node, $hoveredSocket.node,
$hoveredSocket.index || 0, $hoveredSocket.index || 0,
$activeSocket.node, $activeSocket.node,
$activeSocket.index $activeSocket.index,
); );
} else if ( } else if (
typeof $activeSocket.index == 'number' && typeof $activeSocket.index == "number" &&
typeof $hoveredSocket.index === 'string' typeof $hoveredSocket.index === "string"
) { ) {
graph.createEdge( graph.createEdge(
$activeSocket.node, $activeSocket.node,
$activeSocket.index || 0, $activeSocket.index || 0,
$hoveredSocket.node, $hoveredSocket.node,
$hoveredSocket.index $hoveredSocket.index,
); );
} }
graph.save(); graph.save();
@ -694,8 +731,8 @@
} }
onMount(() => { onMount(() => {
if (localStorage.getItem('cameraPosition')) { if (localStorage.getItem("cameraPosition")) {
const cPosition = JSON.parse(localStorage.getItem('cameraPosition')!); const cPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
if (Array.isArray(cPosition)) { if (Array.isArray(cPosition)) {
setCameraTransform(cPosition[0], cPosition[1], cPosition[2]); setCameraTransform(cPosition[0], cPosition[1], cPosition[2]);
} }
@ -732,13 +769,14 @@
{cameraPosition} {cameraPosition}
p1={{ p1={{
x: cameraPosition[0] + (mouseDown[0] - width / 2) / cameraPosition[2], x: cameraPosition[0] + (mouseDown[0] - width / 2) / cameraPosition[2],
y: cameraPosition[1] + (mouseDown[1] - height / 2) / cameraPosition[2] y:
cameraPosition[1] + (mouseDown[1] - height / 2) / cameraPosition[2],
}} }}
p2={{ x: mousePosition[0], y: mousePosition[1] }} p2={{ x: mousePosition[0], y: mousePosition[1] }}
/> />
{/if} {/if}
{#if $status === 'idle'} {#if $status === "idle"}
{#if addMenuPosition} {#if addMenuPosition}
<AddMenu bind:position={addMenuPosition} {graph} /> <AddMenu bind:position={addMenuPosition} {graph} />
{/if} {/if}
@ -751,9 +789,9 @@
{/if} {/if}
<GraphView {nodes} {edges} {cameraPosition} /> <GraphView {nodes} {edges} {cameraPosition} />
{:else if $status === 'loading'} {:else if $status === "loading"}
<span>Loading</span> <span>Loading</span>
{:else if $status === 'error'} {:else if $status === "error"}
<span>Error</span> <span>Error</span>
{/if} {/if}
</Canvas> </Canvas>

View File

@ -1,7 +1,6 @@
{ {
"input": { "input": {
"type": "float", "type": "plant",
"value": 0.0,
"external": true "external": true
} }
} }

View File

@ -3,7 +3,7 @@ use utils::{evaluate_args, generate_outputs, get_args};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::console; use web_sys::console;
generate_outputs!(["stem"]); generate_outputs!(["plant"]);
generate_input_types_file!("src/input.json"); generate_input_types_file!("src/input.json");
#[wasm_bindgen] #[wasm_bindgen]

View File

@ -1,7 +1,7 @@
{ {
"scripts": { "scripts": {
"build:nodes": "pnpm -r --filter './nodes/**' build", "build:nodes": "pnpm -r --filter './nodes/**' build",
"dev:nodes": "pnpm -r --filter './nodes/**' dev", "dev:nodes": "pnpm -r --parallel --filter './nodes/**' dev",
"dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev" "dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev"
} }
} }