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