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