feat: add theming basics

This commit is contained in:
max_richter 2024-03-14 16:28:38 +01:00
parent f9d211eb72
commit 9b76299272
20 changed files with 307 additions and 167 deletions

View File

@ -1,12 +1,15 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/svelte.svg" /> <link rel="icon" href="%sveltekit.assets%/svelte.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@ -3,12 +3,13 @@
import { OrbitControls } from "@threlte/extras"; import { OrbitControls } from "@threlte/extras";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { MOUSE, type OrthographicCamera } from "three"; import { MOUSE, type OrthographicCamera } from "three";
import type { OrbitControls as OrbitControlsType } from "three/examples/jsm/Addons.js";
export let camera: OrthographicCamera | undefined = undefined; export let camera: OrthographicCamera | undefined = undefined;
export let maxZoom = 150; export let maxZoom = 150;
export let minZoom = 4; export let minZoom = 4;
let controls: OrbitControls | undefined = undefined; export let controls: OrbitControlsType | undefined = undefined;
export const position: [number, number, number] = [0, 1, 0]; export const position: [number, number, number] = [0, 1, 0];
@ -56,8 +57,8 @@
<T.OrthographicCamera bind:ref={camera} position.y={10} makeDefault> <T.OrthographicCamera bind:ref={camera} position.y={10} makeDefault>
<OrbitControls <OrbitControls
args={[camera, window]} args={[camera, document.body]}
mouseButtons={{ LEFT: MOUSE.PAN, MIDDLE: 0, RIGHT: 0 }} mouseButtons={{ LEFT: 0, MIDDLE: 0, RIGHT: MOUSE.PAN }}
bind:ref={controls} bind:ref={controls}
enableZoom={true} enableZoom={true}
zoomSpeed={2} zoomSpeed={2}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Node } from "$lib/types"; import type { Node } from "$lib/types";
import { getContext, onMount } from "svelte"; import { getContext } from "svelte";
import NodeHeader from "./NodeHeader.svelte"; import NodeHeader from "./NodeHeader.svelte";
import NodeParameter from "./NodeParameter.svelte"; import NodeParameter from "./NodeParameter.svelte";
import { activeNodeId, selectedNodes } from "./graph/stores"; import { activeNodeId, selectedNodes } from "./graph/stores";
@ -38,7 +38,6 @@
<NodeParameter <NodeParameter
{node} {node}
id={key} id={key}
index={i}
input={value} input={value}
isLast={i == parameters.length - 1} isLast={i == parameters.length - 1}
/> />
@ -51,25 +50,24 @@
box-sizing: border-box; box-sizing: border-box;
user-select: none !important; user-select: none !important;
cursor: pointer; cursor: pointer;
width: 100px; width: 200px;
color: var(--text-color); color: var(--text-color);
transform: translate3d(var(--nx), var(--ny), 0); transform: translate3d(var(--nx), var(--ny), 0);
z-index: 1; z-index: 1;
font-weight: 300; font-weight: 300;
font-size: 0.5em;
display: none; display: none;
--stroke: #777; --stroke: var(--background-color-lighter);
--stroke-width: 0.1px; --stroke-width: 2px;
} }
.node.active { .node.active {
--stroke: white; --stroke: white;
--stroke-width: 0.3px; --stroke-width: 1px;
} }
.node.selected { .node.selected {
--stroke: #f2be90; --stroke: #f2be90;
--stroke-width: 0.2px; --stroke-width: 1px;
} }
.node.in-view { .node.in-view {

View File

@ -81,7 +81,7 @@
.wrapper { .wrapper {
position: relative; position: relative;
width: 100%; width: 100%;
height: 25px; height: 50px;
} }
.click-target { .click-target {
@ -89,8 +89,8 @@
right: 0px; right: 0px;
top: 50%; top: 50%;
transform: translateX(50%) translateY(-50%); transform: translateX(50%) translateY(-50%);
height: 15px; height: 30px;
width: 15px; width: 30px;
z-index: 100; z-index: 100;
border-radius: 50%; border-radius: 50%;
/* background: red; */ /* background: red; */
@ -114,7 +114,9 @@
svg path { svg path {
stroke-width: 0.2px; stroke-width: 0.2px;
transition: 0.2s; transition:
d 0.3s ease,
fill 0.3s ease;
fill: var(--background-color-lighter); fill: var(--background-color-lighter);
stroke: var(--stroke); stroke: var(--stroke);
stroke-width: var(--stroke-width); stroke-width: var(--stroke-width);
@ -125,7 +127,7 @@
font-size: 1em; font-size: 1em;
display: flex; display: flex;
align-items: center; align-items: center;
padding-left: 5px; padding-left: 20px;
height: 100%; height: 100%;
} }

View File

@ -47,9 +47,9 @@
aspectRatio, aspectRatio,
}); });
const pathHover = createNodePath({ const pathHover = createNodePath({
depth: 8, depth: 6,
height: 24, height: 18,
y: 50, y: 50.5,
cornerBottom, cornerBottom,
leftBump, leftBump,
aspectRatio, aspectRatio,
@ -102,7 +102,7 @@
.wrapper { .wrapper {
position: relative; position: relative;
width: 100%; width: 100%;
height: 50px; height: 100px;
transform: translateY(-0.5px); transform: translateY(-0.5px);
} }
@ -116,13 +116,13 @@
} }
.small.target { .small.target {
width: 15px; width: 30px;
height: 15px; height: 30px;
} }
.large.target { .large.target {
width: 30px; width: 60px;
height: 30px; height: 60px;
cursor: unset; cursor: unset;
pointer-events: none; pointer-events: none;
} }
@ -133,14 +133,13 @@
.content { .content {
position: relative; position: relative;
padding: 2px 5px; padding: 10px 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
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: var(--input-opacity);
opacity: calc((var(--cz) - 10) / 20);
} }
:global(.zoom-small) .content { :global(.zoom-small) .content {
@ -150,14 +149,11 @@
.input { .input {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
border-radius: 2px; border-radius: 3px;
font-size: 1em; font-size: 1em;
padding: 2px 2px; padding: 10px;
background: #111; background: #111;
} background: var(--background-color-lighter);
label {
font-size: 1em;
} }
svg { svg {
@ -172,7 +168,9 @@
} }
svg path { svg path {
transition: 0.2s; transition:
d 0.3s ease,
fill 0.3s ease;
fill: var(--background-color); fill: var(--background-color);
stroke: var(--stroke); stroke: var(--stroke);
stroke-width: var(--stroke-width); stroke-width: var(--stroke-width);

View File

@ -4,13 +4,10 @@ varying vec2 vUv;
const float PI = 3.14159265359; const float PI = 3.14159265359;
uniform float width; uniform vec2 dimensions;
uniform float height; uniform vec3 camPos;
uniform float cx; uniform vec2 zoomLimits;
uniform float cy; uniform vec3 backgroundColor;
uniform float cz;
uniform float minZ;
uniform float maxZ;
float grid(float x, float y, float divisions, float thickness) { float grid(float x, float y, float divisions, float thickness) {
x = fract(x * divisions); x = fract(x * divisions);
@ -47,6 +44,17 @@ float lerp(float a, float b,float t) {
} }
void main(void) { void main(void) {
float cx = camPos.x;
float cy = camPos.y;
float cz = camPos.z;
float width = dimensions.x;
float height = dimensions.y;
float minZ = zoomLimits.x;
float maxZ = zoomLimits.y;
float divisions = 0.1/cz; float divisions = 0.1/cz;
float thickness = 0.05/cz; float thickness = 0.05/cz;
float delta = 0.1 / 2.0; float delta = 0.1 / 2.0;
@ -84,7 +92,7 @@ void main(void) {
float c = mix(large, small, min(nz*2.0+0.05, 1.0)); float c = mix(large, small, min(nz*2.0+0.05, 1.0));
c = mix(c, xsmall, max(min((nz-0.3)/0.7, 1.0), 0.0)); c = mix(c, xsmall, max(min((nz-0.3)/0.7, 1.0), 0.0));
//c = xsmall; vec3 color = mix(backgroundColor, vec3(1.0), c*0.5);
gl_FragColor = vec4(c, c, c, 1.0); gl_FragColor = vec4(color, 1.0);
} }

View File

@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import { T } from "@threlte/core"; import { T } from "@threlte/core";
import { onMount } from "svelte";
import BackgroundVert from "./Background.vert"; import BackgroundVert from "./Background.vert";
import BackgroundFrag from "./Background.frag"; import BackgroundFrag from "./Background.frag";
import { Color } from "three";
import { colors } from "../graph/stores";
export let minZoom = 4; export let minZoom = 4;
export let maxZoom = 150; export let maxZoom = 150;
@ -34,33 +35,23 @@
vertexShader={BackgroundVert} vertexShader={BackgroundVert}
fragmentShader={BackgroundFrag} fragmentShader={BackgroundFrag}
uniforms={{ uniforms={{
cx: { camPos: {
value: 0, value: [0, 1, 0],
}, },
cy: { backgroundColor: {
value: 0, value: [0, 0, 0],
}, },
cz: { zoomLimits: {
value: 30, value: [2, 50],
}, },
minZ: { dimensions: {
value: minZoom, value: [100, 100],
},
maxZ: {
value: maxZoom,
},
height: {
value: 100,
},
width: {
value: 100,
}, },
}} }}
uniforms.cx.value={cameraPosition[0]} uniforms.camPos.value={cameraPosition}
uniforms.cy.value={cameraPosition[1]} uniforms.backgroundColor.value={$colors.backgroundColorDarker}
uniforms.cz.value={cameraPosition[2]} uniforms.zoomLimits.value={[minZoom, maxZoom]}
uniforms.width.value={width} uniforms.dimensions.value={[width, height]}
uniforms.height.value={height}
/> />
</T.Mesh> </T.Mesh>
</T.Group> </T.Group>

View File

@ -22,6 +22,10 @@
let mesh: Mesh; let mesh: Mesh;
import { colors } from "../graph/stores";
$: color = $colors.backgroundColorLighter;
export const update = function (force = false) { export const update = function (force = false) {
if (!force) { if (!force) {
const new_x = from.x + to.x; const new_x = from.x + to.x;
@ -64,8 +68,8 @@
position.y={0.8} position.y={0.8}
rotation.x={-Math.PI / 2} rotation.x={-Math.PI / 2}
> >
<T.CircleGeometry args={[0.2, 16]} /> <T.CircleGeometry args={[0.3, 16]} />
<T.MeshBasicMaterial color={0x555555} /> <T.MeshBasicMaterial {color} />
</T.Mesh> </T.Mesh>
<T.Mesh <T.Mesh
@ -74,11 +78,11 @@
position.y={0.8} position.y={0.8}
rotation.x={-Math.PI / 2} rotation.x={-Math.PI / 2}
> >
<T.CircleGeometry args={[0.2, 16]} /> <T.CircleGeometry args={[0.3, 16]} />
<T.MeshBasicMaterial color={0x555555} /> <T.MeshBasicMaterial {color} />
</T.Mesh> </T.Mesh>
<T.Mesh position.y={0.5} bind:ref={mesh}> <T.Mesh position.y={0.5} bind:ref={mesh}>
<MeshLineGeometry {points} /> <MeshLineGeometry {points} />
<MeshLineMaterial width={2} attenuate={false} color={0x555555} /> <MeshLineMaterial width={2} attenuate={false} {color} />
</T.Mesh> </T.Mesh>

View File

@ -19,6 +19,7 @@
selectedNodes, selectedNodes,
} from "./stores"; } from "./stores";
import BoxSelection from "../BoxSelection.svelte"; import BoxSelection from "../BoxSelection.svelte";
import type { OrbitControls } from "three/examples/jsm/Addons.js";
export let graph: GraphManager; export let graph: GraphManager;
setContext("graphManager", graph); setContext("graphManager", graph);
@ -27,8 +28,9 @@
const edges = graph.edges; const edges = graph.edges;
let camera: OrthographicCamera; let camera: OrthographicCamera;
const minZoom = 4; let controls: OrbitControls;
const maxZoom = 100; const minZoom = 2;
const maxZoom = 40;
let mousePosition = [0, 0]; let mousePosition = [0, 0];
let mouseDown: null | [number, number] = null; let mouseDown: null | [number, number] = null;
let boxSelection = false; let boxSelection = false;
@ -80,16 +82,16 @@
} }
const node = graph.getNodeType(nodeTypeId); const node = graph.getNodeType(nodeTypeId);
if (!node?.inputs) { if (!node?.inputs) {
return 2.5; return 5;
} }
const height = 2.5 + 5 * Object.keys(node.inputs).length; const height = 5 + 10 * Object.keys(node.inputs).length;
nodeHeightCache[nodeTypeId] = height; nodeHeightCache[nodeTypeId] = height;
return height; return height;
} }
setContext("isNodeInView", (node: NodeType) => { setContext("isNodeInView", (node: NodeType) => {
const height = getNodeHeight(node.type); const height = getNodeHeight(node.type);
const width = 10; const width = 20;
return ( return (
// check x-axis // check x-axis
node.position.x > cameraBounds[0] - width && node.position.x > cameraBounds[0] - width &&
@ -159,14 +161,14 @@
): [number, number] { ): [number, number] {
if (typeof index === "number") { if (typeof index === "number") {
return [ return [
(node?.tmp?.x ?? node.position.x) + 10, (node?.tmp?.x ?? node.position.x) + 20,
(node?.tmp?.y ?? node.position.y) + 1.25 + 5 * index, (node?.tmp?.y ?? node.position.y) + 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.x, node?.tmp?.x ?? node.position.x,
(node?.tmp?.y ?? node.position.y) + 5 + 5 * _index, (node?.tmp?.y ?? node.position.y) + 10 + 10 * _index,
]; ];
} }
} }
@ -209,28 +211,30 @@
// handle box selection // handle box selection
if (boxSelection) { if (boxSelection) {
event.preventDefault();
event.stopPropagation();
const mouseD = projectScreenToWorld(mouseDown[0], mouseDown[1]); const mouseD = projectScreenToWorld(mouseDown[0], mouseDown[1]);
const x1 = Math.min(mouseD[0], mousePosition[0]); const x1 = Math.min(mouseD[0], mousePosition[0]);
const x2 = Math.max(mouseD[0], mousePosition[0]); const x2 = Math.max(mouseD[0], mousePosition[0]);
const y1 = Math.min(mouseD[1], mousePosition[1]); const y1 = Math.min(mouseD[1], mousePosition[1]);
const y2 = Math.max(mouseD[1], mousePosition[1]); const y2 = Math.max(mouseD[1], mousePosition[1]);
const _selectedNodes = $selectedNodes || new Set();
for (const node of $nodes.values()) { for (const node of $nodes.values()) {
if (!node?.tmp) continue; if (!node?.tmp) continue;
const x = node.position.x; const x = node.position.x;
const y = node.position.y; const y = node.position.y;
if (x > x1 && x < x2 && y > y1 && y < y2) { const height = getNodeHeight(node.type);
_selectedNodes.add(node.id); if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
$selectedNodes?.add(node.id);
} else { } else {
_selectedNodes?.delete(node.id); $selectedNodes?.delete(node.id);
} }
} }
$selectedNodes = _selectedNodes; $selectedNodes = $selectedNodes;
return;
} }
// here we are handling dragging of nodes // here we are handling dragging of nodes
if ($activeNodeId === -1) return; if ($activeNodeId !== -1) {
const node = graph.getNode($activeNodeId); const node = graph.getNode($activeNodeId);
if (!node || event.buttons !== 1) return; if (!node || event.buttons !== 1) return;
@ -275,6 +279,7 @@
$edges = $edges; $edges = $edges;
} }
}
function handleMouseDown(event: MouseEvent) { function handleMouseDown(event: MouseEvent) {
if (mouseDown) return; if (mouseDown) return;
@ -316,6 +321,7 @@
} }
} else if (event.ctrlKey) { } else if (event.ctrlKey) {
boxSelection = true; boxSelection = true;
controls.enabled = false;
} else { } else {
$activeNodeId = -1; $activeNodeId = -1;
$selectedNodes?.clear(); $selectedNodes?.clear();
@ -489,6 +495,7 @@
} }
mouseDown = null; mouseDown = null;
controls.enabled = true;
boxSelection = false; boxSelection = false;
$activeSocket = null; $activeSocket = null;
$possibleSockets = []; $possibleSockets = [];
@ -497,18 +504,24 @@
} }
</script> </script>
<svelte:window <svelte:document
on:mousemove={handleMouseMove} on:mousemove={handleMouseMove}
on:mouseup={handleMouseUp} on:mouseup={handleMouseUp}
on:mousedown={handleMouseDown} on:mousedown={handleMouseDown}
on:keydown={handleKeyDown} on:keydown={handleKeyDown}
bind:innerWidth={width}
bind:innerHeight={height}
/> />
<svelte:window bind:innerWidth={width} bind:innerHeight={height} />
<Debug /> <Debug />
<Camera bind:camera {maxZoom} {minZoom} bind:position={cameraPosition} /> <Camera
bind:controls
bind:camera
{maxZoom}
{minZoom}
bind:position={cameraPosition}
/>
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} /> <Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { Edge as EdgeType, Node as NodeType, Socket } from "$lib/types"; import type { Edge as EdgeType, Node as NodeType } from "$lib/types";
import { HTML } from "@threlte/extras"; import { HTML } from "@threlte/extras";
import Edge from "../edges/Edge.svelte"; import Edge from "../edges/Edge.svelte";
import Node from "../Node.svelte"; import Node from "../Node.svelte";
import { getContext, onMount } from "svelte"; import { getContext, onMount } from "svelte";
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import { activeSocket } from "./stores"; import { activeSocket, colors } from "./stores";
export let nodes: Writable<Map<number, NodeType>>; export let nodes: Writable<Map<number, NodeType>>;
export let edges: Writable<EdgeType[]>; export let edges: Writable<EdgeType[]>;
@ -40,6 +40,7 @@
{@const pos = getEdgePosition(edge)} {@const pos = getEdgePosition(edge)}
{@const [x1, y1, x2, y2] = pos} {@const [x1, y1, x2, y2] = pos}
<Edge <Edge
color={$colors.backgroundColorLighter}
from={{ from={{
x: x1, x: x1,
y: y1, y: y1,
@ -56,9 +57,9 @@
role="tree" role="tree"
tabindex="0" tabindex="0"
class="wrapper" class="wrapper"
class:zoom-small={cameraPosition[2] < 10} class:zoom-small={cameraPosition[2] < 2}
class:hovering-sockets={activeSocket} class:hovering-sockets={activeSocket}
style={`--cz: ${cameraPosition[2]}`} style={`--cz: ${cameraPosition[2]};`}
> >
{#each $nodes.values() as node (node.id)} {#each $nodes.values() as node (node.id)}
<Node {node} inView={cameraPosition && isNodeInView(node)} /> <Node {node} inView={cameraPosition && isNodeInView(node)} />
@ -73,5 +74,6 @@
width: 0px; width: 0px;
height: 0px; height: 0px;
transform: scale(calc(var(--cz) * 0.1)); transform: scale(calc(var(--cz) * 0.1));
--input-opacity: calc((var(--cz) - 2) / 5);
} }
</style> </style>

View File

@ -1,5 +1,7 @@
import type { Node, Socket } from "$lib/types"; import { browser } from "$app/environment";
import type { Socket } from "$lib/types";
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
import { Color } from "three";
export const activeNodeId: Writable<number> = writable(-1); export const activeNodeId: Writable<number> = writable(-1);
export const selectedNodes: Writable<Set<number> | null> = writable(null); export const selectedNodes: Writable<Set<number> | null> = writable(null);
@ -8,3 +10,41 @@ export const activeSocket: Writable<Socket | null> = writable(null);
export const hoveredSocket: Writable<Socket | null> = writable(null); export const hoveredSocket: Writable<Socket | null> = writable(null);
export const possibleSockets: Writable<Socket[]> = writable([]); export const possibleSockets: Writable<Socket[]> = writable([]);
export const possibleSocketIds: Writable<Set<string> | null> = writable(null); export const possibleSocketIds: Writable<Set<string> | null> = writable(null);
export const colors = writable({
backgroundColorDarker: new Color().setStyle("#101010"),
backgroundColor: new Color().setStyle("#151515"),
backgroundColorLighter: new Color().setStyle("#202020")
});
if (browser) {
const body = document.body;
function updateColors() {
const style = getComputedStyle(body);
const backgroundColorDarker = style.getPropertyValue("--background-color-darker");
const backgroundColor = style.getPropertyValue("--background-color");
const backgroundColorLighter = style.getPropertyValue("--background-color-lighter");
colors.update(col => {
col.backgroundColorDarker.setStyle(backgroundColorDarker);
col.backgroundColor.setStyle(backgroundColor);
col.backgroundColorLighter.setStyle(backgroundColorLighter);
return col;
});
}
globalThis["updateColors"] = updateColors;
body.addEventListener("transitionstart", () => {
updateColors();
})
window.onload = () => {
updateColors();
}
}

View File

@ -0,0 +1,17 @@
<script lang="ts">
import type { Hst } from "@histoire/plugin-svelte";
export let Hst: Hst;
import Checkbox from "./Checkbox.svelte";
</script>
<Hst.Story>
<div>
<Checkbox checked={false} />
</div>
</Hst.Story>
<style>
div {
padding: 1em;
}
</style>

View File

@ -0,0 +1,58 @@
<script lang="ts">
export let checked: boolean;
</script>
<input type="checkbox" bind:checked />
<style>
input[type="checkbox"] {
/* Add if not using autoprefixer */
-webkit-appearance: none;
/* Remove most all native input styles */
appearance: none;
/* For iOS < 15 */
background-color: var(--form-background);
/* Not removed via appearance */
margin: 0;
font: inherit;
color: currentColor;
width: 1.15em;
height: 1.15em;
border: 0.15em solid currentColor;
border-radius: 0.15em;
transform: translateY(-0.075em);
display: grid;
place-content: center;
}
input[type="checkbox"]::before {
content: "";
width: 0.65em;
height: 0.65em;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
transform: scale(0);
transform-origin: bottom left;
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em var(--form-control-color);
/* Windows High Contrast Mode */
background-color: CanvasText;
}
input[type="checkbox"]:checked::before {
transform: scale(1);
}
input[type="checkbox"]:focus {
outline: max(2px, 0.15em) solid currentColor;
outline-offset: max(2px, 0.15em);
}
input[type="checkbox"]:disabled {
--form-control-color: var(--form-control-disabled);
color: var(--form-control-disabled);
cursor: not-allowed;
}
</style>

View File

View File

View File

View File

@ -319,8 +319,8 @@ export class GraphManager {
visible: false, visible: false,
}, },
position: { position: {
x: x * 15, x: x * 30,
y: y * 20, y: y * 40,
}, },
props: i == 0 ? { value: 0 } : {}, props: i == 0 ? { value: 0 } : {},
type: i == 0 ? "input/float" : "math", type: i == 0 ? "input/float" : "math",
@ -335,8 +335,8 @@ export class GraphManager {
visible: false, visible: false,
}, },
position: { position: {
x: width * 15, x: width * 30,
y: (height - 1) * 20, y: (height - 1) * 40,
}, },
type: "output", type: "output",
props: {}, props: {},

View File

@ -62,3 +62,11 @@ export function createNodePath({
} }
Z`.replace(/\s+/g, " "); Z`.replace(/\s+/g, " ");
} }
export const debounce = (fn: Function, ms = 300) => {
let timeoutId: ReturnType<typeof setTimeout>;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
};

View File

@ -37,7 +37,7 @@
</div> </div>
<div class="canvas-wrapper"> <div class="canvas-wrapper">
<Canvas shadows={false} renderMode="on-demand" autoRender={true}> <Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
<!-- <PerfMonitor /> --> <!-- <PerfMonitor /> -->
<Graph {graph} bind:debug /> <Graph {graph} bind:debug />
</Canvas> </Canvas>

View File

@ -23,28 +23,6 @@
:root { :root {
font-family: 'Fira Code', monospace; font-family: 'Fira Code', monospace;
/* Colors */
--primary-color: #007bff;
/* Primary brand color */
--secondary-color: #6c757d;
/* Secondary color */
--background-color: #151515;
--background-color-lightest: #ffffff;
--background-color-lighter: #202020;
--background-color-dark: #dae0e5;
--background-color-darkest: #c8d1d7;
/* Background color */
--text-color: #aeaeae;
/* Text color */
--accent-color: #ffc107;
/* Accent color */
/* Typography */
--font-family: Arial, sans-serif;
--font-size-base: 16px;
/* Base font size */
--line-height-base: 1.5;
/* Base line height */
/* Spacing */ /* Spacing */
--spacing-xs: 4px; --spacing-xs: 4px;
@ -62,4 +40,23 @@
body { body {
overflow: hidden; overflow: hidden;
/* Secondary color */
--secondary-color: #6c757d;
/* Background color */
--background-color-lighter: #202020;
--background-color: #151515;
--background-color-darker: #101010;
--text-color: #aeaeae;
} }
body.theme-catppuccin {
--text-color: #CDD6F4;
--background-color-lighter: #313244;
--background-color: #1E1E2E;
--background-color-darker: #11111b;
}
/* canvas { */
/* display: none !important; */
/* } */