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