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