feat: implement first dragging edge prototype
This commit is contained in:
parent
a74bd0cf16
commit
1d6ae65630
@ -59,6 +59,7 @@
|
||||
<OrbitControls
|
||||
bind:ref={controls}
|
||||
enableZoom={true}
|
||||
zoomSpeed={2}
|
||||
target.y={0}
|
||||
rotateSpeed={0}
|
||||
minPolarAngle={0}
|
||||
|
@ -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),
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -7,6 +7,7 @@ export type Node = {
|
||||
downX?: number;
|
||||
downY?: number;
|
||||
visible?: boolean;
|
||||
isMoving?: boolean;
|
||||
},
|
||||
meta?: {
|
||||
title?: string;
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user