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