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