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