this repo has no description
1import { createEffect, createSignal, onCleanup, onMount } from "solid-js";
2import "./App.css";
3import { renderBackgroundGrid, renderContextMenu, renderNodes, renderNullTab, renderTempDrawing } from "./renderer";
4import { lerp } from "./utils/lerp";
5import { Node, NodeIO, NodeIOCanCast, NodeIOResolveAnyTypes } from "./structs/node";
6import { isPointInRect, isPointInRectApplyOffset, screenToWorldSpace } from "./utils/interections";
7import { ControlBar } from "./components/ControlBar";
8import { CanvasContextMenu } from "./ContextMenu/Canvas";
9import { NodeContextMenu } from "./ContextMenu/Node";
10import { ContextMenu } from "./structs/ContextMenu";
11import { NodeManager } from "./Mangers/NodeManager";
12import { TabMenu } from "./components/TabMenu";
13import { ConfirmationPopup } from "./components/ConfirmationPopup";
14
15import * as keybinds from './keybinds';
16import { listen } from "@tauri-apps/api/event";
17
18// TODO: Only allow one node to input on non-flow inputs
19
20let App = () => {
21 let [ selectedNodes, setSelectedNodes ] = createSignal<Node[]>([], { equals: false });
22 let [ mousePos, setMousePos ] = createSignal<[ number, number ]>([ 0, 0 ]);
23
24 let canvas!: HTMLCanvasElement;
25 let ctx: CanvasRenderingContext2D;
26
27 let stopRender = false;
28
29 let scale = 0.25;
30 let targetScale = 1;
31
32 let offset = [ 0, 0 ];
33 let offsetTarget = [ 0, 0 ];
34
35 let movingNode: Node | null = null;
36
37 let isDrawing = false;
38 let drawingFrom: NodeIO | null = null;
39 let drawingTo: [ number, number ] = [ 0, 0 ];
40
41 let lockMovement = false;
42
43 {
44 let loadedScale = localStorage.getItem('scale');
45 if(loadedScale)targetScale = parseFloat(loadedScale);
46
47 let loadedOffsetX = localStorage.getItem('offsetX');
48 if(loadedOffsetX)offsetTarget[0] = parseFloat(loadedOffsetX);
49
50 let loadedOffsetY = localStorage.getItem('offsetY');
51 if(loadedOffsetY)offsetTarget[1] = parseFloat(loadedOffsetY);
52 };
53
54 let screenMoved = false;
55
56 let contextMenu: ContextMenu = {
57 items: [],
58 position: [ 0, 0 ],
59 size: [ 0, 0 ],
60 visible: false
61 }
62
63 createEffect(() => {
64 let snodes = selectedNodes();
65
66 let anodes = NodeManager.Instance.GetNodes();
67 if(!anodes)return;
68
69 for(let node of anodes)node.selected = false;
70 for(let node of snodes)node.selected = true;
71 })
72
73 onMount(async () => {
74 NodeManager.Instance.HookTabChange(() => setSelectedNodes([]));
75
76 ctx = canvas.getContext('2d')!;
77
78 canvas.width = window.innerWidth;
79 canvas.height = window.innerHeight;
80 ctx.translate(canvas.width / 2, canvas.height / 2);
81
82 window.onresize = () => {
83 canvas.width = window.innerWidth;
84 canvas.height = window.innerHeight;
85
86 ctx.translate(canvas.width / 2, canvas.height / 2);
87 }
88
89 canvas.onwheel = ( e ) => {
90 targetScale += e.deltaY * -(Math.sqrt(targetScale) * 0.001);
91
92 if(targetScale < 0.25)targetScale = 0.25
93 else if(targetScale > 5)targetScale = 5;
94
95 screenMoved = true;
96 }
97
98 canvas.oncontextmenu = ( e ) => {
99 e.preventDefault();
100
101 let clickedNode: Node | null = null
102 let nodes = NodeManager.Instance.GetNodes();
103
104 if(nodes){
105 nodes.map(node => {
106 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale },
107 e.clientX, e.clientY,
108 node.x, node.y, node.w, node.h
109 )){
110 clickedNode = node;
111 return;
112 }
113 })
114 }
115
116 if(clickedNode){
117 contextMenu.items = NodeContextMenu(clickedNode, selectedNodes, setSelectedNodes);
118 } else{
119 contextMenu.items = CanvasContextMenu;
120 }
121
122 contextMenu.position = [ e.clientX - 10 - canvas.width / 2, e.clientY - 10 - canvas.height / 2 ];
123 contextMenu.visible = true;
124 }
125
126 let isShiftClick = false;
127 canvas.onmousedown = ( e ) => {
128 isShiftClick = e.shiftKey;
129
130 if(
131 e.clientY < 60 ||
132 e.clientX < 220 ||
133 lockMovement
134 )return;
135
136 if(e.button !== 0){
137 contextMenu.visible = false;
138 return;
139 }
140
141 if(contextMenu.visible){
142 let submenus: ContextMenu[] = [];
143 contextMenu.items.map(x => x.menu ? submenus.push(x.menu): null);
144
145 submenus.map(x => {
146 if(!x.visible)return;
147 if(isPointInRect(canvas, e.clientX, e.clientY,
148 x.position[0], x.position[1],
149 x.size[0], x.size[1]
150 )){
151 let item = x.items.filter(x => x.hovered)[0];
152 if(item && item.clicked)item.clicked(e, canvas, { x: offset[0], y: offset[1], scale });
153 }
154 });
155
156 if(isPointInRect(canvas, e.clientX, e.clientY,
157 contextMenu.position[0], contextMenu.position[1],
158 contextMenu.size[0], contextMenu.size[1]
159 )){
160 let item = contextMenu.items.filter(x => x.hovered)[0];
161 if(item && item.clicked)item.clicked(e, canvas, { x: offset[0], y: offset[1], scale });
162 }
163 }
164
165 contextMenu.visible = false;
166
167 let clickedNode: any = null;
168 isDrawing = false;
169
170 let clickedInput: any = null;
171 let nodes = NodeManager.Instance.GetNodes();
172
173 if(nodes){
174 nodes.map(node => {
175 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale },
176 e.clientX, e.clientY,
177 node.x - 20, node.y, node.w + 40, node.h
178 )){
179 node.outputs.map(( output, i ) => {
180 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale },
181 e.clientX, e.clientY,
182 node.x + (node.w - 10),
183 node.y + 50 + (30 * i),
184 20, 20
185 )){
186 output.index = i;
187
188 drawingTo = [
189 node.x + (node.w - 10),
190 node.y + 50 + (30 * i)
191 ];
192 drawingFrom = output;
193
194 isDrawing = true;
195 return;
196 }
197 })
198
199 node.inputs.map(( input, i ) => {
200 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale },
201 e.clientX, e.clientY,
202 node.x - 10,
203 node.y + 50 + (30 * i),
204 20, 20
205 )){
206 clickedInput = input;
207 }
208 })
209
210 clickedNode = node;
211 return;
212 }
213 })
214 }
215
216 if(clickedInput){
217 let partner = clickedInput.connections.pop();
218 if(!partner)return;
219
220 partner.connections = partner.connections.filter(( x: any ) => x !== clickedInput);
221
222 isDrawing = true;
223 isMouseDown = true;
224
225 drawingFrom = partner;
226 drawingTo = screenToWorldSpace(canvas, { x: offset[0], y: offset[1], scale }, e.clientX - 10 * scale, e.clientY - 10 * scale) as [ number, number ];;
227
228 return;
229 }
230
231 movingNode = clickedNode;
232
233 isMouseDown = true;
234 mouseStartPos = [ e.clientX, e.clientY ];
235 mouseMovePos = [ e.clientX, e.clientY ];
236 }
237
238 canvas.onmousemove = ( e ) => {
239 setMousePos([ e.clientX, e.clientY ]);
240
241 if(e.shiftKey && isMouseDown){
242 let nodes = NodeManager.Instance.GetNodes();
243 let hoveredNode: Node | null = null;
244
245 if(nodes){
246 nodes.map(node => {
247 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale },
248 e.clientX, e.clientY,
249 node.x - 20, node.y, node.w + 40, node.h
250 )){
251 hoveredNode = node;
252 return;
253 }
254 })
255 }
256
257 if(hoveredNode !== null){
258 let snodes = selectedNodes();
259 if(!snodes.find(x => x.id === hoveredNode!.id)){
260 snodes.push(hoveredNode);
261
262 // @ts-ignore
263 hoveredNode.x = Math.round(hoveredNode.x / 10) * 10;
264 // @ts-ignore
265 hoveredNode.y = Math.round(hoveredNode.y / 10) * 10;
266
267 setSelectedNodes(snodes);
268 }
269 }
270
271 return;
272 } else if(isShiftClick)return;
273
274 if(isMouseDown){
275 if(isDrawing){
276 drawingTo = screenToWorldSpace(canvas, { x: offset[0], y: offset[1], scale }, e.clientX - 10 * scale, e.clientY - 10 * scale) as [ number, number ];
277 } else if(movingNode){
278 let nodes = selectedNodes();
279
280 for(let node of nodes){
281 node.x = node.x - (mouseMovePos[0] - e.clientX) / scale;
282 node.y = node.y - (mouseMovePos[1] - e.clientY) / scale;
283 }
284
285 mouseMovePos = [ e.clientX, e.clientY ];
286 NodeManager.Instance.UpdateConfig();
287 } else{
288 offsetTarget = [ offsetTarget[0] - (mouseMovePos[0] - e.clientX) / scale, offsetTarget[1] - (mouseMovePos[1] - e.clientY) / scale ];
289 mouseMovePos = [ e.clientX, e.clientY ];
290
291 screenMoved = true;
292 }
293 }
294
295 // TODO: Fix this shit lmao please
296 if(contextMenu.visible){
297 let submenus: ContextMenu[] = [];
298 contextMenu.items.map(x => x.menu ? submenus.push(x.menu): null);
299
300 submenus.map(x => {
301 if(!x.visible)return;
302 if(isPointInRect(canvas, e.clientX, e.clientY,
303 x.position[0], x.position[1],
304 x.size[0], x.size[1]
305 )){
306 x.items.map((y, i) => {
307 y.hovered = isPointInRect(canvas, e.clientX, e.clientY,
308 x.position[0], x.position[1] + 10 + 25 * i,
309 x.size[0], 25
310 )
311 });
312 }
313 });
314
315 if(isPointInRect(canvas, e.clientX, e.clientY,
316 contextMenu.position[0], contextMenu.position[1],
317 contextMenu.size[0], contextMenu.size[1]
318 )){
319 contextMenu.items.map((x, i) => {
320 x.hovered = isPointInRect(canvas, e.clientX, e.clientY,
321 contextMenu.position[0], contextMenu.position[1] + 10 + 25 * i,
322 contextMenu.size[0], 25
323 )
324
325 if(x.menu)x.menu.visible = x.hovered;
326 });
327 }
328 }
329 }
330
331 canvas.onmouseup = ( e ) => {
332 let nodes = NodeManager.Instance.GetNodes();
333 let clickedNode;
334
335 if(nodes){
336 nodes.map(node => {
337 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale },
338 e.clientX, e.clientY,
339 node.x - 20, node.y, node.w + 40, node.h
340 )){
341 clickedNode = node;
342
343 node.inputs.map(( input, i ) => {
344 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale },
345 e.clientX, e.clientY,
346 node.x - 10,
347 node.y + 50 + (30 * i),
348 20, 20
349 )){
350 if(isDrawing){
351 let fromType = NodeIOResolveAnyTypes(drawingFrom!);
352 let toType = NodeIOResolveAnyTypes(input);
353
354 if(
355 drawingFrom!.connections.indexOf(input) === -1 &&
356 (
357 toType === null ||
358 NodeIOCanCast(fromType, toType)
359 )
360 ){
361 drawingFrom!.connections.push(input);
362 input.connections.push(drawingFrom!);
363
364 NodeManager.Instance.UpdateConfig();
365 }
366 }
367 }
368 })
369 }
370 })
371 }
372
373 let diffX = mouseStartPos[0] - e.clientX;
374 let diffY = mouseStartPos[1] - e.clientY;
375
376 let dist = Math.sqrt(diffX * diffX + diffY * diffY);
377
378 if(dist < 10){
379 if(clickedNode){
380 if(e.shiftKey){
381 let snodes = selectedNodes();
382 if(snodes.indexOf(clickedNode) === -1)snodes.push(clickedNode);
383
384 // @ts-ignore
385 clickedNode.x = Math.round(clickedNode.x / 10) * 10;
386 // @ts-ignore
387 clickedNode.y = Math.round(clickedNode.y / 10) * 10;
388
389 setSelectedNodes(snodes);
390 } else{
391 // @ts-ignore
392 clickedNode.x = Math.round(clickedNode.x / 10) * 10;
393 // @ts-ignore
394 clickedNode.y = Math.round(clickedNode.y / 10) * 10;
395
396 setSelectedNodes([ clickedNode ]);
397 }
398 } else {
399 setSelectedNodes([]);
400 }
401 }
402
403 isDrawing = false;
404 isMouseDown = false;
405 }
406
407 keybinds.load(canvas, mousePos, selectedNodes, setSelectedNodes);
408 requestAnimationFrame(update);
409
410 let unlisten_0 = await listen('hide-window', () => {
411 stopRender = true;
412 })
413
414 let unlisten_1 = await listen('show-window', () => {
415 if(stopRender)window.location.reload();
416 })
417
418 onCleanup(() => {
419 stopRender = true;
420 window.clearInterval(interval);
421
422 unlisten_0();
423 unlisten_1();
424 });
425 });
426
427 let update = () => {
428 if(stopRender)return;
429 scale = lerp(scale, targetScale, 0.25);
430
431 offset[0] = lerp(offset[0], offsetTarget[0], 0.5);
432 offset[1] = lerp(offset[1], offsetTarget[1], 0.5);
433
434 ctx.clearRect(canvas.width / -2, canvas.height / -2, canvas.width, canvas.height);
435
436 let nodes = NodeManager.Instance.GetNodes();
437
438 renderBackgroundGrid(canvas, ctx, { x: offset[0], y: offset[1], scale });
439
440 if(nodes)
441 renderNodes(canvas, ctx, nodes, { x: offset[0], y: offset[1], scale });
442 else
443 renderNullTab(canvas, ctx);
444
445 if(isDrawing)renderTempDrawing(canvas, ctx, drawingTo, drawingFrom!, { x: offset[0], y: offset[1], scale });
446 renderContextMenu(ctx, contextMenu);
447
448 requestAnimationFrame(update);
449 }
450
451 let isMouseDown = false;
452 let mouseStartPos = [ 0, 0 ];
453 let mouseMovePos = [ 0, 0 ];
454
455 let interval = setInterval(() => {
456 if(screenMoved){
457 localStorage.setItem('scale', targetScale.toFixed(4));
458 localStorage.setItem('offsetX', offset[0].toFixed(4));
459 localStorage.setItem('offsetY', offset[1].toFixed(4));
460 }
461 }, 1000);
462
463 return (
464 <>
465 <ConfirmationPopup />
466 <TabMenu />
467 <ControlBar node={selectedNodes} lockMovement={( lock ) => lockMovement = lock} />
468 <canvas ref={canvas}/>
469 </>
470 );
471}
472
473export default App;