feat: implement first dragging edge prototype
This commit is contained in:
parent
a74bd0cf16
commit
1d6ae65630
@ -59,6 +59,7 @@
|
|||||||
<OrbitControls
|
<OrbitControls
|
||||||
bind:ref={controls}
|
bind:ref={controls}
|
||||||
enableZoom={true}
|
enableZoom={true}
|
||||||
|
zoomSpeed={2}
|
||||||
target.y={0}
|
target.y={0}
|
||||||
rotateSpeed={0}
|
rotateSpeed={0}
|
||||||
minPolarAngle={0}
|
minPolarAngle={0}
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
|
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
|
||||||
import { CubicBezierCurve, Vector2, Vector3 } from "three";
|
import { CubicBezierCurve, Vector2, Vector3 } from "three";
|
||||||
|
|
||||||
export let from: Node;
|
export let from: { position: { x: number; y: number } };
|
||||||
export let to: Node;
|
export let to: { position: { x: number; y: number } };
|
||||||
|
|
||||||
let samples = 25;
|
let samples = 12;
|
||||||
|
|
||||||
const curve = new CubicBezierCurve(
|
const curve = new CubicBezierCurve(
|
||||||
new Vector2(from.position.x + 20, from.position.y),
|
new Vector2(from.position.x + 20, from.position.y),
|
||||||
|
@ -4,29 +4,36 @@
|
|||||||
import { HTML } from "@threlte/extras";
|
import { HTML } from "@threlte/extras";
|
||||||
import Node from "./Node.svelte";
|
import Node from "./Node.svelte";
|
||||||
import type { GraphManager } from "$lib/graph-manager";
|
import type { GraphManager } from "$lib/graph-manager";
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { snapToGrid } from "$lib/helpers";
|
import { snapToGrid } from "$lib/helpers";
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
export let graph: GraphManager;
|
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 width = globalThis?.innerWidth || 100;
|
||||||
export let height = globalThis?.innerHeight || 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 mouseX = 0;
|
||||||
let mouseY = 0;
|
let mouseY = 0;
|
||||||
|
|
||||||
let mouseDown = false;
|
let mouseDown: Writable<false | { x: number; y: number; socket: any }> =
|
||||||
let mouseDownX = 0;
|
writable(false);
|
||||||
let mouseDownY = 0;
|
|
||||||
|
|
||||||
let activeNodeId: string;
|
let activeNodeId: string;
|
||||||
|
|
||||||
function handleMouseMove(event: MouseEvent) {
|
function handleMouseMove(event: MouseEvent) {
|
||||||
if (!mouseDown) return;
|
if (!$mouseDown) return;
|
||||||
|
|
||||||
mouseX =
|
mouseX =
|
||||||
cameraPosition[0] + (event.clientX - width / 2) / cameraPosition[2];
|
cameraPosition[0] + (event.clientX - width / 2) / cameraPosition[2];
|
||||||
@ -39,12 +46,15 @@
|
|||||||
|
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
|
||||||
|
if (!node.tmp) node.tmp = {};
|
||||||
|
node.tmp.isMoving = true;
|
||||||
|
|
||||||
let newX =
|
let newX =
|
||||||
(node?.tmp?.downX || 0) +
|
(node?.tmp?.downX || 0) +
|
||||||
(event.clientX - mouseDownX) / cameraPosition[2];
|
(event.clientX - $mouseDown.x) / cameraPosition[2];
|
||||||
let newY =
|
let newY =
|
||||||
(node?.tmp?.downY || 0) +
|
(node?.tmp?.downY || 0) +
|
||||||
(event.clientY - mouseDownY) / cameraPosition[2];
|
(event.clientY - $mouseDown.y) / cameraPosition[2];
|
||||||
|
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
const snapLevel = getSnapLevel();
|
const snapLevel = getSnapLevel();
|
||||||
@ -59,11 +69,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseDown(ev: MouseEvent) {
|
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;
|
$mouseDown = { x: ev.clientX, y: ev.clientY, socket: null };
|
||||||
mouseDownX = ev.clientX;
|
|
||||||
mouseDownY = ev.clientY;
|
|
||||||
const node = graph.nodes.find((node) => node.id === activeNodeId);
|
const node = graph.nodes.find((node) => node.id === activeNodeId);
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
@ -84,12 +98,23 @@
|
|||||||
return 1;
|
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() {
|
function handleMouseUp() {
|
||||||
mouseDown = false;
|
$mouseDown = false;
|
||||||
|
|
||||||
const node = graph.getNode(activeNodeId);
|
const node = graph.getNode(activeNodeId);
|
||||||
|
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
node.tmp = node.tmp || {};
|
||||||
|
node.tmp.isMoving = false;
|
||||||
const snapLevel = getSnapLevel();
|
const snapLevel = getSnapLevel();
|
||||||
node.position.x = snapToGrid(node.position.x, 5 / snapLevel);
|
node.position.x = snapToGrid(node.position.x, 5 / snapLevel);
|
||||||
node.position.y = snapToGrid(node.position.y, 5 / snapLevel);
|
node.position.y = snapToGrid(node.position.y, 5 / snapLevel);
|
||||||
@ -99,22 +124,38 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} />
|
<svelte:document
|
||||||
|
on:mousemove={handleMouseMove}
|
||||||
|
on:mouseup={handleMouseUp}
|
||||||
|
on:mousedown={handleMouseDown}
|
||||||
|
/>
|
||||||
|
|
||||||
{#each edges as edge}
|
{#each edges as edge}
|
||||||
<Edge from={edge[0]} to={edge[1]} />
|
<Edge from={edge[0]} to={edge[1]} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#if $mouseDown && $mouseDown?.socket}
|
||||||
|
<Edge
|
||||||
|
from={{ position: $mouseDown.socket }}
|
||||||
|
to={{ position: { x: mouseX, y: mouseY } }}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<HTML transform={false}>
|
<HTML transform={false}>
|
||||||
<div
|
<div
|
||||||
role="tree"
|
role="tree"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
|
class:zoom-small={cameraPosition[2] < 10}
|
||||||
style={`--cz: ${cameraPosition[2]}`}
|
style={`--cz: ${cameraPosition[2]}`}
|
||||||
on:mousedown={handleMouseDown}
|
|
||||||
>
|
>
|
||||||
{#each graph.nodes as node}
|
{#each graph.nodes as node}
|
||||||
<Node {node} {graph} />
|
<Node
|
||||||
|
{node}
|
||||||
|
{graph}
|
||||||
|
inView={cameraPosition && isNodeInView(node)}
|
||||||
|
{mouseDown}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { GraphManager } from "$lib/graph-manager";
|
import type { GraphManager } from "$lib/graph-manager";
|
||||||
import type { Node } from "$lib/types";
|
import type { Node } from "$lib/types";
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
import NodeHeader from "./NodeHeader.svelte";
|
import NodeHeader from "./NodeHeader.svelte";
|
||||||
import NodeParameter from "./NodeParameter.svelte";
|
import NodeParameter from "./NodeParameter.svelte";
|
||||||
|
|
||||||
export let node: Node;
|
export let node: Node;
|
||||||
export let graph: GraphManager;
|
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 type = graph.getNodeType(node.type);
|
||||||
|
|
||||||
const parameters = Object.entries(type?.inputs || {});
|
const parameters = Object.entries(type?.inputs || {});
|
||||||
@ -14,11 +19,13 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="node"
|
class="node"
|
||||||
|
class:in-view={inView}
|
||||||
|
class:is-moving={node?.tmp?.isMoving}
|
||||||
data-node-id={node.id}
|
data-node-id={node.id}
|
||||||
style={`--nx:${node.position.x * 10}px;
|
style={`--nx:${node.position.x * 10}px;
|
||||||
--ny: ${node.position.y * 10}px`}
|
--ny: ${node.position.y * 10}px`}
|
||||||
>
|
>
|
||||||
<NodeHeader {node} />
|
<NodeHeader {node} {mouseDown} />
|
||||||
|
|
||||||
{#each parameters as [key, value], i}
|
{#each parameters as [key, value], i}
|
||||||
<NodeParameter
|
<NodeParameter
|
||||||
@ -38,9 +45,20 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
color: white;
|
color: white;
|
||||||
transform: translate(var(--nx), var(--ny));
|
transform: translate3d(var(--nx), var(--ny), 0);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 0.5em;
|
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>
|
</style>
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node } from "$lib/types";
|
import type { Node } from "$lib/types";
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
export let node: Node;
|
export let node: Node;
|
||||||
|
|
||||||
|
export let mouseDown: Writable<false | { x: number; y: number; socket: any }>;
|
||||||
|
|
||||||
function createPath({ depth = 8, height = 20, y = 50 } = {}) {
|
function createPath({ depth = 8, height = 20, y = 50 } = {}) {
|
||||||
let corner = 10;
|
let corner = 10;
|
||||||
|
|
||||||
@ -29,6 +32,17 @@
|
|||||||
V100
|
V100
|
||||||
Z`.replace(/\s+/g, " ");
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper" data-node-id={node.id}>
|
<div class="wrapper" data-node-id={node.id}>
|
||||||
@ -43,9 +57,17 @@
|
|||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
style={`
|
style={`
|
||||||
--path: path("${createPath({ depth: 5, height: 27, y: 48.2 })}");
|
--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
|
<path
|
||||||
vector-effect="non-scaling-stroke"
|
vector-effect="non-scaling-stroke"
|
||||||
fill="none"
|
fill="none"
|
||||||
@ -62,7 +84,7 @@
|
|||||||
height: 12.5px;
|
height: 12.5px;
|
||||||
}
|
}
|
||||||
.wrapper > * {
|
.wrapper > * {
|
||||||
pointer-events: none;
|
/* pointer-events: none; */
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
style={`
|
style={`
|
||||||
--path: path("${createPath({ depth: 5, height: 15, y: 48.2 })}");
|
--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>
|
<path vector-effect="non-scaling-stroke"></path>
|
||||||
@ -78,6 +78,12 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
box-sizing: border-box;
|
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 {
|
input {
|
||||||
@ -102,7 +108,7 @@
|
|||||||
svg {
|
svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
pointer-events: none;
|
/* pointer-events: none; */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
@ -75,16 +75,18 @@ export class GraphManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static createEmptyGraph(): GraphManager {
|
static createEmptyGraph({ width = 20, height = 20 } = {}): GraphManager {
|
||||||
|
|
||||||
const graph: Graph = {
|
const graph: Graph = {
|
||||||
edges: [],
|
edges: [],
|
||||||
nodes: [],
|
nodes: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let i = 0; i < 40; i++) {
|
const amount = width * height;
|
||||||
const x = i % 20;
|
|
||||||
const y = Math.floor(i / 20);
|
for (let i = 0; i < amount; i++) {
|
||||||
|
const x = i % width;
|
||||||
|
const y = Math.floor(i / height);
|
||||||
|
|
||||||
graph.nodes.push({
|
graph.nodes.push({
|
||||||
id: `${i.toString()}`,
|
id: `${i.toString()}`,
|
||||||
@ -93,7 +95,7 @@ export class GraphManager {
|
|||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
x: x * 7.5,
|
x: x * 7.5,
|
||||||
y: y * 5,
|
y: y * 10,
|
||||||
},
|
},
|
||||||
props: i == 0 ? { value: 0 } : {},
|
props: i == 0 ? { value: 0 } : {},
|
||||||
type: i == 0 ? "input/float" : "math",
|
type: i == 0 ? "input/float" : "math",
|
||||||
|
@ -7,6 +7,7 @@ export type Node = {
|
|||||||
downX?: number;
|
downX?: number;
|
||||||
downY?: number;
|
downY?: number;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
|
isMoving?: boolean;
|
||||||
},
|
},
|
||||||
meta?: {
|
meta?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { PerfMonitor } from "@threlte/extras";
|
||||||
|
|
||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import Scene from "$lib/components/Scene.svelte";
|
import Scene from "$lib/components/Scene.svelte";
|
||||||
@ -27,7 +28,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Canvas shadows={false}>
|
<Canvas shadows={false} renderMode="on-demand">
|
||||||
|
<PerfMonitor />
|
||||||
<Scene {graph} />
|
<Scene {graph} />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user