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