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