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