feat: add color to sockets
Some checks failed
🚀 Lint & Test & Deploy / release (push) Failing after 3m5s

Closes #34
This commit is contained in:
2026-02-13 00:52:07 +01:00
parent 4c7b03dfb8
commit c1ae70282c
8 changed files with 135 additions and 21 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -6,15 +6,12 @@
toneMapped: false toneMapped: false
}); });
let lineColor = $state(colors.outline.clone().convertSRGBToLinear());
$effect.root(() => { $effect.root(() => {
$effect(() => { $effect(() => {
if (appSettings.value.theme === undefined) { if (appSettings.value.theme === undefined) {
return; return;
} }
circleMaterial.color = colors.outline.clone().convertSRGBToLinear(); circleMaterial.color = colors.outline.clone().convertSRGBToLinear();
lineColor = colors.outline.clone().convertSRGBToLinear();
}); });
}); });
@@ -35,6 +32,7 @@
import { CubicBezierCurve } from 'three/src/extras/curves/CubicBezierCurve.js'; import { CubicBezierCurve } from 'three/src/extras/curves/CubicBezierCurve.js';
import { Vector2 } from 'three/src/math/Vector2.js'; import { Vector2 } from 'three/src/math/Vector2.js';
import { getGraphState } from '../graph-state.svelte'; import { getGraphState } from '../graph-state.svelte';
import MeshGradientLineMaterial from './MeshGradientLine/MeshGradientLineMaterial.svelte';
const graphState = getGraphState(); const graphState = getGraphState();
@@ -45,12 +43,17 @@
y2: number; y2: number;
z: number; z: number;
id?: string; id?: string;
inputType?: string;
outputType?: string;
}; };
const { x1, y1, x2, y2, z, id }: Props = $props(); const { x1, y1, x2, y2, z, inputType = 'unknown', outputType = 'unknown', id }: Props = $props();
const thickness = $derived(Math.max(0.001, 0.00082 * Math.exp(0.055 * z))); const thickness = $derived(Math.max(0.001, 0.00082 * Math.exp(0.055 * z)));
const inputColor = $derived(graphState.colors.getColor(inputType));
const outputColor = $derived(graphState.colors.getColor(outputType));
let points = $state<Vector3[]>([]); let points = $state<Vector3[]>([]);
let lastId: string | null = null; let lastId: string | null = null;
@@ -106,9 +109,9 @@
position.z={y1} position.z={y1}
position.y={0.8} position.y={0.8}
rotation.x={-Math.PI / 2} rotation.x={-Math.PI / 2}
material={circleMaterial}
> >
<T.CircleGeometry args={[0.5, 16]} /> <T.CircleGeometry args={[0.5, 16]} />
<T.MeshBasicMaterial color={inputColor} toneMapped={false} />
</T.Mesh> </T.Mesh>
<T.Mesh <T.Mesh
@@ -119,6 +122,7 @@
material={circleMaterial} material={circleMaterial}
> >
<T.CircleGeometry args={[0.5, 16]} /> <T.CircleGeometry args={[0.5, 16]} />
<T.MeshBasicMaterial color={outputColor} toneMapped={false} />
</T.Mesh> </T.Mesh>
{#if graphState.hoveredEdgeId === id} {#if graphState.hoveredEdgeId === id}
@@ -126,7 +130,8 @@
<MeshLineGeometry {points} /> <MeshLineGeometry {points} />
<MeshLineMaterial <MeshLineMaterial
width={thickness * 5} width={thickness * 5}
color={lineColor} color={inputColor}
tonemapped={false}
opacity={0.5} opacity={0.5}
transparent transparent
/> />
@@ -135,5 +140,10 @@
<T.Mesh position.x={x1} position.z={y1} position.y={0.1}> <T.Mesh position.x={x1} position.z={y1} position.y={0.1}>
<MeshLineGeometry {points} /> <MeshLineGeometry {points} />
<MeshLineMaterial width={thickness} color={lineColor} /> <MeshGradientLineMaterial
width={thickness}
colorStart={inputColor}
colorEnd={outputColor}
tonemapped={false}
/>
</T.Mesh> </T.Mesh>

View File

@@ -23,6 +23,7 @@
let { invalidate, size } = useThrelte(); let { invalidate, size } = useThrelte();
// svelte-ignore state_referenced_locally
const uniforms = { const uniforms = {
lineWidth: { value: width }, lineWidth: { value: width },
colorStart: { value: new Color(colorStart) }, colorStart: { value: new Color(colorStart) },

View File

@@ -3,6 +3,7 @@ import { getContext, setContext } from 'svelte';
import { SvelteMap, SvelteSet } from 'svelte/reactivity'; import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import type { OrthographicCamera, Vector3 } from 'three'; import type { OrthographicCamera, Vector3 } from 'three';
import type { GraphManager } from './graph-manager.svelte'; import type { GraphManager } from './graph-manager.svelte';
import { ColorGenerator } from './graph/colors';
import { getNodeHeight, getSocketPosition } from './helpers/nodeHelpers'; import { getNodeHeight, getSocketPosition } from './helpers/nodeHelpers';
const graphStateKey = Symbol('graph-state'); const graphStateKey = Symbol('graph-state');
@@ -28,7 +29,32 @@ type EdgeData = {
points: Vector3[]; points: Vector3[];
}; };
const predefinedColors = {
path: {
hue: 80,
lightness: 20,
saturation: 80
},
float: {
hue: 70,
lightness: 10,
saturation: 0
},
geometry: {
hue: 0,
lightness: 50,
saturation: 70
},
'*': {
hue: 200,
lightness: 20,
saturation: 100
}
} as const;
export class GraphState { export class GraphState {
colors = new ColorGenerator(predefinedColors);
constructor(private graph: GraphManager) { constructor(private graph: GraphManager) {
$effect.root(() => { $effect.root(() => {
$effect(() => { $effect(() => {

View File

@@ -95,6 +95,13 @@
graphState.activeSocket = null; graphState.activeSocket = null;
graphState.addMenuPosition = null; graphState.addMenuPosition = null;
} }
function getSocketType(node: NodeInstance, index: number | string): string {
if (typeof index === 'string') {
return node.state.type?.inputs?.[index].type || 'unknown';
}
return node.state.type?.outputs?.[index] || 'unknown';
}
</script> </script>
<svelte:window <svelte:window
@@ -175,6 +182,8 @@
{#if graphState.activeSocket} {#if graphState.activeSocket}
<EdgeEl <EdgeEl
z={graphState.cameraPosition[2]} z={graphState.cameraPosition[2]}
inputType={getSocketType(graphState.activeSocket.node, graphState.activeSocket.index)}
outputType={getSocketType(graphState.activeSocket.node, graphState.activeSocket.index)}
x1={graphState.activeSocket.position[0]} x1={graphState.activeSocket.position[0]}
y1={graphState.activeSocket.position[1]} y1={graphState.activeSocket.position[1]}
x2={graphState.edgeEndPosition?.[0] ?? graphState.mousePosition[0]} x2={graphState.edgeEndPosition?.[0] ?? graphState.mousePosition[0]}
@@ -187,6 +196,8 @@
<EdgeEl <EdgeEl
id={graph.getEdgeId(edge)} id={graph.getEdgeId(edge)}
z={graphState.cameraPosition[2]} z={graphState.cameraPosition[2]}
inputType={getSocketType(edge[0], edge[1])}
outputType={getSocketType(edge[2], edge[3])}
{x1} {x1}
{y1} {y1}
{x2} {x2}

View File

@@ -0,0 +1,45 @@
type Color = { hue: number; saturation: number; lightness: number };
export class ColorGenerator {
private colors: Map<string, Color> = new Map();
private lightnessLevels = [10, 60];
constructor(predefined: Record<string, Color>) {
for (const [id, colorStr] of Object.entries(predefined)) {
this.colors.set(id, colorStr);
}
}
public getColor(id: string): string {
if (this.colors.has(id)) {
return this.colorToHsl(this.colors.get(id)!);
}
const newColor = this.generateNewColor();
this.colors.set(id, newColor);
console.log(id, newColor);
return this.colorToHsl(newColor);
}
private generateNewColor(): Color {
const existingHues = Array.from(this.colors.values()).map(c => c.hue).sort();
let hue = existingHues[0];
let attempts = 0;
while (
existingHues.some(h => Math.abs(h - hue) < 30 || Math.abs(h - hue) > 330)
&& attempts < 360
) {
hue = (hue + 30) % 360;
attempts++;
}
const lightness = 60;
return { hue, lightness, saturation: 100 };
}
private colorToHsl(c: Color): string {
return `hsl(${c.hue}, ${c.saturation}%, ${c.lightness}%)`;
}
}

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { appSettings } from '$lib/settings/app-settings.svelte'; import { appSettings } from '$lib/settings/app-settings.svelte';
import type { NodeInstance } from '@nodarium/types'; import type { NodeInstance, Socket } from '@nodarium/types';
import { getGraphState } from '../graph-state.svelte'; import { getGraphState } from '../graph-state.svelte';
import { createNodePath } from '../helpers/index.js'; import { createNodePath } from '../helpers/index.js';
import { getSocketPosition } from '../helpers/nodeHelpers'; import { getSocketPosition } from '../helpers/nodeHelpers';
@@ -47,12 +47,23 @@
); );
const socketId = $derived(`${node.id}-${0}`); const socketId = $derived(`${node.id}-${0}`);
function getSocketType(s: Socket | null) {
if (!s) return 'unknown';
if (typeof s.index === 'string') {
return s.node.state.type?.inputs?.[s.index].type || 'unknown';
}
return s.node.state.type?.outputs?.[s.index] || 'unknown';
}
const socketType = $derived(getSocketType(graphState.activeSocket));
const hoverColor = $derived(graphState.colors.getColor(socketType));
</script> </script>
<div <div
class="wrapper" class="wrapper"
data-node-id={node.id} data-node-id={node.id}
data-node-type={node.type} data-node-type={node.type}
style:--socket-color={hoverColor}
class:possible-socket={graphState?.possibleSocketIds.has(socketId)} class:possible-socket={graphState?.possibleSocketIds.has(socketId)}
> >
<div class="content"> <div class="content">
@@ -96,10 +107,10 @@
width: 30px; width: 30px;
height: 30px; height: 30px;
border-radius: 100%; border-radius: 100%;
box-shadow: 0px 0px 10px var(--color-layer-3); box-shadow: 0px 0px 10px var(--socket-color);
background-color: var(--color-layer-3); background-color: var(--socket-color);
outline: solid thin white; outline: solid thin var(--socket-color);
opacity: 0.2; opacity: 0.7;
z-index: -10; z-index: -10;
} }

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { NodeInput, NodeInstance } from '@nodarium/types'; import type { NodeInput, NodeInstance, Socket } from '@nodarium/types';
import { getGraphManager, getGraphState } from '../graph-state.svelte'; import { getGraphManager, getGraphState } from '../graph-state.svelte';
import { createNodePath } from '../helpers'; import { createNodePath } from '../helpers';
import { getParameterHeight, getSocketPosition } from '../helpers/nodeHelpers'; import { getParameterHeight, getSocketPosition } from '../helpers/nodeHelpers';
@@ -60,6 +60,17 @@
aspectRatio aspectRatio
}) })
); );
function getSocketType(s: Socket | null) {
if (!s) return 'unknown';
if (typeof s.index === 'string') {
return s.node.state.type?.inputs?.[s.index].type || 'unknown';
}
return s.node.state.type?.outputs?.[s.index] || 'unknown';
}
const socketType = $derived(getSocketType(graphState.activeSocket));
const hoverColor = $derived(graphState.colors.getColor(socketType));
</script> </script>
<div <div
@@ -67,6 +78,7 @@
data-node-type={node.type} data-node-type={node.type}
data-node-input={id} data-node-input={id}
style:height="{height}px" style:height="{height}px"
style:--socket-color={hoverColor}
class:possible-socket={graphState?.possibleSocketIds.has(socketId)} class:possible-socket={graphState?.possibleSocketIds.has(socketId)}
> >
{#key id && graphId} {#key id && graphId}
@@ -95,10 +107,8 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100" viewBox="0 0 100 100"
preserveAspectRatio="none" preserveAspectRatio="none"
style={` style:--path={`path("${path}")`}
--path: path("${path}"); style:--hover-path={`path("${pathHover}")`}
--hover-path: path("${pathHover}");
`}
> >
<path vector-effect="non-scaling-stroke"></path> <path vector-effect="non-scaling-stroke"></path>
</svg> </svg>
@@ -126,10 +136,10 @@
width: 30px; width: 30px;
height: 30px; height: 30px;
border-radius: 100%; border-radius: 100%;
box-shadow: 0px 0px 10px var(--color-layer-3); box-shadow: 0px 0px 10px var(--socket-color);
background-color: var(--color-layer-3); background-color: var(--socket-color);
outline: solid thin white; outline: solid thin var(--socket-color);
opacity: 0.2; opacity: 0.5;
z-index: -10; z-index: -10;
} }