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;