feat: implement first dragging edge prototype

This commit is contained in:
max_richter 2024-03-11 02:02:04 +01:00
parent a74bd0cf16
commit 1d6ae65630
9 changed files with 127 additions and 34 deletions

View File

@ -59,6 +59,7 @@
<OrbitControls
bind:ref={controls}
enableZoom={true}
zoomSpeed={2}
target.y={0}
rotateSpeed={0}
minPolarAngle={0}

View File

@ -4,10 +4,10 @@
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
import { CubicBezierCurve, Vector2, Vector3 } from "three";
export let from: Node;
export let to: Node;
export let from: { position: { x: number; y: number } };
export let to: { position: { x: number; y: number } };
let samples = 25;
let samples = 12;
const curve = new CubicBezierCurve(
new Vector2(from.position.x + 20, from.position.y),

View File

@ -4,29 +4,36 @@
import { HTML } from "@threlte/extras";
import Node from "./Node.svelte";
import type { GraphManager } from "$lib/graph-manager";
import { onMount } from "svelte";
import { snapToGrid } from "$lib/helpers";
import { writable, type Writable } from "svelte/store";
export let graph: GraphManager;
let edges = graph?.getEdges() || [];
export let cameraPosition: [number, number, number] = [0, 1, 0];
export let width = globalThis?.innerWidth || 100;
export let height = globalThis?.innerHeight || 100;
let edges = graph?.getEdges() || [];
export let cameraPosition: [number, number, number] = [0, 1, 0];
let cameraBounds = [-Infinity, Infinity, -Infinity, Infinity];
$: if (cameraPosition[0]) {
cameraBounds[0] = cameraPosition[0] - width / cameraPosition[2];
cameraBounds[1] = cameraPosition[0] + width / cameraPosition[2];
cameraBounds[2] = cameraPosition[1] - height / cameraPosition[2];
cameraBounds[3] = cameraPosition[1] + height / cameraPosition[2];
}
let mouseX = 0;
let mouseY = 0;
let mouseDown = false;
let mouseDownX = 0;
let mouseDownY = 0;
let mouseDown: Writable<false | { x: number; y: number; socket: any }> =
writable(false);
let activeNodeId: string;
function handleMouseMove(event: MouseEvent) {
if (!mouseDown) return;
if (!$mouseDown) return;
mouseX =
cameraPosition[0] + (event.clientX - width / 2) / cameraPosition[2];
@ -39,12 +46,15 @@
if (!node) return;
if (!node.tmp) node.tmp = {};
node.tmp.isMoving = true;
let newX =
(node?.tmp?.downX || 0) +
(event.clientX - mouseDownX) / cameraPosition[2];
(event.clientX - $mouseDown.x) / cameraPosition[2];
let newY =
(node?.tmp?.downY || 0) +
(event.clientY - mouseDownY) / cameraPosition[2];
(event.clientY - $mouseDown.y) / cameraPosition[2];
if (event.ctrlKey) {
const snapLevel = getSnapLevel();
@ -59,11 +69,15 @@
}
function handleMouseDown(ev: MouseEvent) {
activeNodeId = (ev?.target as HTMLElement)?.getAttribute("data-node-id")!;
for (const node of ev.composedPath()) {
activeNodeId = (node as unknown as HTMLElement)?.getAttribute?.(
"data-node-id",
)!;
if (activeNodeId) break;
}
if (!activeNodeId) return;
mouseDown = true;
mouseDownX = ev.clientX;
mouseDownY = ev.clientY;
$mouseDown = { x: ev.clientX, y: ev.clientY, socket: null };
const node = graph.nodes.find((node) => node.id === activeNodeId);
if (!node) return;
node.tmp = node.tmp || {};
@ -84,12 +98,23 @@
return 1;
}
function isNodeInView(node: any) {
return (
node.position.x > cameraBounds[0] &&
node.position.x < cameraBounds[1] &&
node.position.y > cameraBounds[2] &&
node.position.y < cameraBounds[3]
);
}
function handleMouseUp() {
mouseDown = false;
$mouseDown = false;
const node = graph.getNode(activeNodeId);
if (!node) return;
node.tmp = node.tmp || {};
node.tmp.isMoving = false;
const snapLevel = getSnapLevel();
node.position.x = snapToGrid(node.position.x, 5 / snapLevel);
node.position.y = snapToGrid(node.position.y, 5 / snapLevel);
@ -99,22 +124,38 @@
}
</script>
<svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} />
<svelte:document
on:mousemove={handleMouseMove}
on:mouseup={handleMouseUp}
on:mousedown={handleMouseDown}
/>
{#each edges as edge}
<Edge from={edge[0]} to={edge[1]} />
{/each}
{#if $mouseDown && $mouseDown?.socket}
<Edge
from={{ position: $mouseDown.socket }}
to={{ position: { x: mouseX, y: mouseY } }}
/>
{/if}
<HTML transform={false}>
<div
role="tree"
tabindex="0"
class="wrapper"
class:zoom-small={cameraPosition[2] < 10}
style={`--cz: ${cameraPosition[2]}`}
on:mousedown={handleMouseDown}
>
{#each graph.nodes as node}
<Node {node} {graph} />
<Node
{node}
{graph}
inView={cameraPosition && isNodeInView(node)}
{mouseDown}
/>
{/each}
</div>
</HTML>

View File

@ -1,12 +1,17 @@
<script lang="ts">
import type { GraphManager } from "$lib/graph-manager";
import type { Node } from "$lib/types";
import type { Writable } from "svelte/store";
import NodeHeader from "./NodeHeader.svelte";
import NodeParameter from "./NodeParameter.svelte";
export let node: Node;
export let graph: GraphManager;
export let inView = true;
export let mouseDown: Writable<false | { x: number; y: number; socket: any }>;
const type = graph.getNodeType(node.type);
const parameters = Object.entries(type?.inputs || {});
@ -14,11 +19,13 @@
<div
class="node"
class:in-view={inView}
class:is-moving={node?.tmp?.isMoving}
data-node-id={node.id}
style={`--nx:${node.position.x * 10}px;
--ny: ${node.position.y * 10}px`}
>
<NodeHeader {node} />
<NodeHeader {node} {mouseDown} />
{#each parameters as [key, value], i}
<NodeParameter
@ -38,9 +45,20 @@
cursor: pointer;
width: 50px;
color: white;
transform: translate(var(--nx), var(--ny));
transform: translate3d(var(--nx), var(--ny), 0);
z-index: 1;
font-weight: 300;
font-size: 0.5em;
display: none;
transition: transform 0.2s ease;
}
.node.is-moving {
z-index: 100;
transition: none !important;
}
.node.in-view {
display: unset;
}
</style>

View File

@ -1,8 +1,11 @@
<script lang="ts">
import type { Node } from "$lib/types";
import type { Writable } from "svelte/store";
export let node: Node;
export let mouseDown: Writable<false | { x: number; y: number; socket: any }>;
function createPath({ depth = 8, height = 20, y = 50 } = {}) {
let corner = 10;
@ -29,6 +32,17 @@
V100
Z`.replace(/\s+/g, " ");
}
function handleMouseDown(event: MouseEvent) {
$mouseDown = {
x: event.clientX,
y: event.clientY,
socket: { x: node.position.x + 5, y: node.position.y + 0.65 },
};
console.log("click");
event.stopPropagation();
event.preventDefault();
}
</script>
<div class="wrapper" data-node-id={node.id}>
@ -43,9 +57,17 @@
preserveAspectRatio="none"
style={`
--path: path("${createPath({ depth: 5, height: 27, y: 48.2 })}");
--hover-path: path("${createPath({ depth: 8, height: 24 })}");
--hover-path: path("${createPath({ depth: 6, height: 33, y: 48.2 })}");
`}
>
<ellipse
cx="100"
cy="48"
rx="2.7"
ry="10"
fill="red"
on:mousedown={handleMouseDown}
/>
<path
vector-effect="non-scaling-stroke"
fill="none"
@ -62,7 +84,7 @@
height: 12.5px;
}
.wrapper > * {
pointer-events: none;
/* pointer-events: none; */
}
svg {

View File

@ -56,7 +56,7 @@
preserveAspectRatio="none"
style={`
--path: path("${createPath({ depth: 5, height: 15, y: 48.2 })}");
--hover-path: path("${createPath({ depth: 8, height: 24 })}");
--hover-path: path("${createPath({ depth: 8, height: 24, y: 48.2 })}");
`}
>
<path vector-effect="non-scaling-stroke"></path>
@ -78,6 +78,12 @@
height: 100%;
justify-content: space-around;
box-sizing: border-box;
opacity: calc((2 * var(--cz)) / 150 - 0.5);
opacity: calc((var(--cz) - 10) / 20);
}
:global(.zoom-small) .content {
/* display: none; */
}
input {
@ -102,7 +108,7 @@
svg {
position: absolute;
box-sizing: border-box;
pointer-events: none;
/* pointer-events: none; */
width: 100%;
height: 100%;
overflow: visible;

View File

@ -75,16 +75,18 @@ export class GraphManager {
}
static createEmptyGraph(): GraphManager {
static createEmptyGraph({ width = 20, height = 20 } = {}): GraphManager {
const graph: Graph = {
edges: [],
nodes: [],
};
for (let i = 0; i < 40; i++) {
const x = i % 20;
const y = Math.floor(i / 20);
const amount = width * height;
for (let i = 0; i < amount; i++) {
const x = i % width;
const y = Math.floor(i / height);
graph.nodes.push({
id: `${i.toString()}`,
@ -93,7 +95,7 @@ export class GraphManager {
},
position: {
x: x * 7.5,
y: y * 5,
y: y * 10,
},
props: i == 0 ? { value: 0 } : {},
type: i == 0 ? "input/float" : "math",

View File

@ -7,6 +7,7 @@ export type Node = {
downX?: number;
downY?: number;
visible?: boolean;
isMoving?: boolean;
},
meta?: {
title?: string;

View File

@ -1,6 +1,7 @@
<script lang="ts">
import { invoke } from "@tauri-apps/api/core";
import { onMount } from "svelte";
import { PerfMonitor } from "@threlte/extras";
import { Canvas } from "@threlte/core";
import Scene from "$lib/components/Scene.svelte";
@ -27,7 +28,8 @@
</script>
<div>
<Canvas shadows={false}>
<Canvas shadows={false} renderMode="on-demand">
<PerfMonitor />
<Scene {graph} />
</Canvas>
</div>