this repo has no description

rendering changes + qol updates + more todos

phaz.uk 3de4485c adb5c83a

verified
Changed files
+93 -46
public
src
+1
public/assets/icons/circle-info-solid-full.svg
···
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="#fff" d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM288 224C288 206.3 302.3 192 320 192C337.7 192 352 206.3 352 224C352 241.7 337.7 256 320 256C302.3 256 288 241.7 288 224zM280 288L328 288C341.3 288 352 298.7 352 312L352 400L360 400C373.3 400 384 410.7 384 424C384 437.3 373.3 448 360 448L280 448C266.7 448 256 437.3 256 424C256 410.7 266.7 400 280 400L304 400L304 336L280 336C266.7 336 256 325.3 256 312C256 298.7 266.7 288 280 288z"/></svg>
+18 -8
src/App.tsx
··· 2 import "./App.css"; 3 import { renderBackgroundGrid, renderContextMenu, renderNodes, renderNullTab, renderTempDrawing } from "./renderer"; 4 import { lerp } from "./utils/lerp"; 5 - import { Node, NodeIO, NodeIOResolveAnyTypes } from "./structs/node"; 6 import { isPointInRect, isPointInRectApplyOffset, screenToWorldSpace } from "./utils/interections"; 7 import { ControlBar } from "./components/ControlBar"; 8 import { CanvasContextMenu } from "./ContextMenu/Canvas"; ··· 13 import { ConfirmationPopup } from "./components/ConfirmationPopup"; 14 15 let App = () => { 16 - // TODO: Delete selected node when delete key is pressed 17 // TODO: Keybind system 18 // TODO: Add undo / redo -ing 19 20 let [ selectedNode, setSelectedNode ] = createSignal<Node | null>(null); 21 ··· 123 return; 124 } 125 126 if(contextMenu.visible){ 127 let submenus: ContextMenu[] = []; 128 contextMenu.items.map(x => x.menu ? submenus.push(x.menu): null); ··· 161 162 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale }, 163 e.clientX, e.clientY, 164 - node.x, node.y, node.w, node.h 165 )){ 166 node.outputs.map(( output, i ) => { 167 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale }, 168 e.clientX, e.clientY, 169 - node.x + (node.w - 30), 170 node.y + 50 + (30 * i), 171 20, 20 172 )){ 173 output.index = i; 174 175 - drawingTo = [ node.x + (node.w - 30), node.y + 50 + (30 * i) ]; 176 drawingFrom = output; 177 178 isDrawing = true; ··· 183 node.inputs.map(( input, i ) => { 184 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale }, 185 e.clientX, e.clientY, 186 - node.x + 10, 187 node.y + 50 + (30 * i), 188 20, 20 189 )){ ··· 285 node.inputs.map(( input, i ) => { 286 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale }, 287 e.clientX, e.clientY, 288 - node.x + 10, 289 node.y + 50 + (30 * i), 290 20, 20 291 )){ ··· 297 drawingFrom!.connections.indexOf(input) === -1 && 298 ( 299 toType === null || 300 - fromType === toType 301 ) 302 ){ 303 drawingFrom!.connections.push(input);
··· 2 import "./App.css"; 3 import { renderBackgroundGrid, renderContextMenu, renderNodes, renderNullTab, renderTempDrawing } from "./renderer"; 4 import { lerp } from "./utils/lerp"; 5 + import { Node, NodeIO, NodeIOCanCast, NodeIOResolveAnyTypes } from "./structs/node"; 6 import { isPointInRect, isPointInRectApplyOffset, screenToWorldSpace } from "./utils/interections"; 7 import { ControlBar } from "./components/ControlBar"; 8 import { CanvasContextMenu } from "./ContextMenu/Canvas"; ··· 13 import { ConfirmationPopup } from "./components/ConfirmationPopup"; 14 15 let App = () => { 16 // TODO: Keybind system 17 + // TODO: Delete selected node when delete key is pressed 18 + // TODO: Copy / paste 19 // TODO: Add undo / redo -ing 20 + // TODO: Multi-select 21 22 let [ selectedNode, setSelectedNode ] = createSignal<Node | null>(null); 23 ··· 125 return; 126 } 127 128 + if(e.shiftKey){ 129 + // TODO: Multi-select 130 + return; 131 + } 132 + 133 if(contextMenu.visible){ 134 let submenus: ContextMenu[] = []; 135 contextMenu.items.map(x => x.menu ? submenus.push(x.menu): null); ··· 168 169 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale }, 170 e.clientX, e.clientY, 171 + node.x - 20, node.y, node.w + 40, node.h 172 )){ 173 node.outputs.map(( output, i ) => { 174 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale }, 175 e.clientX, e.clientY, 176 + node.x + (node.w - 10), 177 node.y + 50 + (30 * i), 178 20, 20 179 )){ 180 output.index = i; 181 182 + drawingTo = [ 183 + node.x + (node.w - 10), 184 + node.y + 50 + (30 * i) 185 + ]; 186 drawingFrom = output; 187 188 isDrawing = true; ··· 193 node.inputs.map(( input, i ) => { 194 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale }, 195 e.clientX, e.clientY, 196 + node.x - 10, 197 node.y + 50 + (30 * i), 198 20, 20 199 )){ ··· 295 node.inputs.map(( input, i ) => { 296 if(isPointInRectApplyOffset(canvas, { x: offset[0], y: offset[1], scale }, 297 e.clientX, e.clientY, 298 + node.x - 10, 299 node.y + 50 + (30 * i), 300 20, 20 301 )){ ··· 307 drawingFrom!.connections.indexOf(input) === -1 && 308 ( 309 toType === null || 310 + NodeIOCanCast(fromType, toType) 311 ) 312 ){ 313 drawingFrom!.connections.push(input);
+2 -2
src/Nodes/OSCTrigger.tsx
··· 10 typeId: 'osctrigger', 11 12 w: 200, 13 - h: 50, 14 15 statics: [ 16 { ··· 83 } 84 }); 85 86 - node.h = 60 + (parameters.length + 1) * 30; 87 NodeManager.Instance.UpdateConfig(); 88 } 89 };
··· 10 typeId: 'osctrigger', 11 12 w: 200, 13 + h: 55, 14 15 statics: [ 16 { ··· 83 } 84 }); 85 86 + node.h = 65 + (parameters.length + 1) * 30; 87 NodeManager.Instance.UpdateConfig(); 88 } 89 };
+3
src/components/TabMenu.css
··· 117 border-radius: 5px; 118 transition: 0.1s; 119 background: transparent; 120 } 121 122 .tab-icon-bar img:hover{
··· 117 border-radius: 5px; 118 transition: 0.1s; 119 background: transparent; 120 + cursor: pointer; 121 + user-select: none; 122 + -webkit-user-select: none; 123 } 124 125 .tab-icon-bar img:hover{
+4 -1
src/components/TabMenu.tsx
··· 4 import { NodeManager } from '../Mangers/NodeManager'; 5 import { Tab } from '../structs/Tab'; 6 import { SettingsMenu } from './SettingsMenu'; 7 8 export let TabMenu = () => { 9 let [ tabImportOpen, setTabImportOpen ] = createSignal(false); ··· 25 <Show when={settingsOpen()}> 26 <SettingsMenu close={() => setSettingsOpen(false)} /> 27 </Show> 28 - 29 <div class="tab-menu"> 30 <div class="tab-container"> 31 <For each={Object.values(tabs())}> ··· 86 87 <div class="tab-icon-bar"> 88 <img src="/assets/icons/gear-solid-full.svg" width="25" onClick={() => setSettingsOpen(true)} /> 89 </div> 90 </div> 91 </>
··· 4 import { NodeManager } from '../Mangers/NodeManager'; 5 import { Tab } from '../structs/Tab'; 6 import { SettingsMenu } from './SettingsMenu'; 7 + import { openUrl } from '@tauri-apps/plugin-opener'; 8 9 export let TabMenu = () => { 10 let [ tabImportOpen, setTabImportOpen ] = createSignal(false); ··· 26 <Show when={settingsOpen()}> 27 <SettingsMenu close={() => setSettingsOpen(false)} /> 28 </Show> 29 + 30 <div class="tab-menu"> 31 <div class="tab-container"> 32 <For each={Object.values(tabs())}> ··· 87 88 <div class="tab-icon-bar"> 89 <img src="/assets/icons/gear-solid-full.svg" width="25" onClick={() => setSettingsOpen(true)} /> 90 + <div style={{ width: 'calc(100% - 50px)' }}></div> 91 + <img src="/assets/icons/circle-info-solid-full.svg" width="25" onClick={() => openUrl('https://github.com/phaze-the-dumb/VRCMacros/wiki')} /> 92 </div> 93 </div> 94 </>
+58 -35
src/renderer.ts
··· 63 let nodeX = Math.round(node.x / 10) * 10; 64 let nodeY = Math.round(node.y / 10) * 10; 65 66 - ctx.fillStyle = '#1f2129'; 67 - ctx.strokeStyle = node.selected ? '#004696ff' : '#fff5'; 68 ctx.lineWidth = 5 * position.scale; 69 70 // Draw Node Box ··· 75 node.h * position.scale, 76 10 * position.scale); 77 78 ctx.stroke(); 79 ctx.fill(); 80 81 // Draw Node Name 82 ctx.fillStyle = '#fff'; 83 - ctx.font = (25 * position.scale) + 'px Comic Mono'; 84 ctx.textAlign = 'center'; 85 86 ctx.fillText(node.name, ··· 89 ); 90 91 // Draw Inputs 92 - ctx.font = (15 * position.scale) + 'px Comic Mono'; 93 ctx.textAlign = 'left'; 94 95 node.inputs.map(( input, i ) => { 96 ctx.fillStyle = NodeIOLinkColours(input); 97 - ctx.fillRect( 98 - (nodeX + 10 + startX + position.x) * position.scale, 99 - (nodeY + 50 + (30 * i) + startY + position.y) * position.scale, 100 - 20 * position.scale, 20 * position.scale 101 - ) 102 103 ctx.fillText(input.name, 104 - (nodeX + 35 + startX + position.x) * position.scale, 105 (nodeY + 53 + (30 * i) + startY + position.y) * position.scale, 106 ) 107 }) ··· 111 112 node.outputs.map(( output, i ) => { 113 ctx.fillStyle = NodeIOLinkColours(output); 114 - ctx.fillRect( 115 - (nodeX + (node.w - 30) + startX + position.x) * position.scale, 116 - (nodeY + 50 + (30 * i) + startY + position.y) * position.scale, 117 - 20 * position.scale, 20 * position.scale 118 - ) 119 120 ctx.fillText(output.name, 121 - (nodeX + (node.w - 35) + startX + position.x) * position.scale, 122 (nodeY + 53 + (30 * i) + startY + position.y) * position.scale, 123 ) 124 }) ··· 131 node.outputs.map(( output, i ) => { 132 output.connections.map(partner => { 133 ctx.strokeStyle = NodeIOLinkColours(output); 134 drawCurve(ctx, 135 - (nodeX + (node.w - 30) + 10 + startX + position.x) * position.scale, 136 (nodeY + 50 + (30 * i) + 10 + startY + position.y) * position.scale, 137 - ((Math.round(partner.parent.x / 10) * 10) + 20 + startX + position.x) * position.scale, 138 ((Math.round(partner.parent.y / 10) * 10) + 60 + (30 * partner.index) + startY + position.y) * position.scale, 139 ); 140 ctx.stroke(); ··· 148 contextMenu: ContextMenu 149 ) => { 150 if(contextMenu.visible){ 151 - ctx.font = '20px Arial'; 152 ctx.textBaseline = 'top'; 153 ctx.textAlign = 'left'; 154 ··· 192 let startX = canvas.width / -2; 193 let startY = canvas.height / -2; 194 195 - ctx.fillStyle = '#f00'; 196 197 - ctx.fillRect( 198 - (drawingTo[0] + 10 + startX + position.x) * position.scale, 199 - (drawingTo[1] + 10 + startY + position.y) * position.scale, 200 - 10, 10 201 - ); 202 203 - ctx.fillRect( 204 - (drawingFrom.parent.x + (drawingFrom.parent.w - 30) + 10 + startX + position.x) * position.scale, 205 - (drawingFrom.parent.y + 50 + (30 * drawingFrom.index) + 10 + startY + position.y) * position.scale, 206 - 10, 10 207 - ); 208 209 ctx.strokeStyle = NodeIOLinkColours(drawingFrom); 210 drawCurve(ctx, 211 - (drawingFrom.parent.x + (drawingFrom.parent.w - 30) + 10 + startX + position.x) * position.scale, 212 - (drawingFrom.parent.y + 50 + (30 * drawingFrom.index) + 10 + startY + position.y) * position.scale, 213 (drawingTo[0] + 10 + startX + position.x) * position.scale, 214 (drawingTo[1] + 10 + startY + position.y) * position.scale, 215 ); ··· 239 ) => { 240 ctx.fillStyle = '#fff'; 241 242 - ctx.font = '20px Arial'; 243 ctx.textBaseline = 'middle'; 244 ctx.textAlign = 'center'; 245 246 let textX = lerp((canvas.width / -2) + 200, canvas.width / 2, 0.5); 247 let textY = lerp((canvas.height / -2) + 40, canvas.height / 2, 0.5); 248 249 - ctx.font = '40px Arial'; 250 ctx.fillText('Welcome to VRCMacros', textX, textY); 251 252 - ctx.font = '20px Arial'; 253 ctx.fillText('Create a new tab to get started!', textX, textY + 40); 254 } 255
··· 63 let nodeX = Math.round(node.x / 10) * 10; 64 let nodeY = Math.round(node.y / 10) * 10; 65 66 + ctx.fillStyle = '#343742ff'; 67 + ctx.strokeStyle = node.selected ? '#004696ff' : '#fff0'; 68 ctx.lineWidth = 5 * position.scale; 69 70 // Draw Node Box ··· 75 node.h * position.scale, 76 10 * position.scale); 77 78 + ctx.shadowColor = '#0005'; 79 + ctx.shadowBlur = 10; 80 + 81 ctx.stroke(); 82 ctx.fill(); 83 + 84 + ctx.shadowBlur = 0; 85 86 // Draw Node Name 87 ctx.fillStyle = '#fff'; 88 + ctx.font = (25 * position.scale) + 'px Rubik'; 89 ctx.textAlign = 'center'; 90 91 ctx.fillText(node.name, ··· 94 ); 95 96 // Draw Inputs 97 + ctx.font = (15 * position.scale) + 'px Rubik'; 98 ctx.textAlign = 'left'; 99 100 node.inputs.map(( input, i ) => { 101 ctx.fillStyle = NodeIOLinkColours(input); 102 + 103 + ctx.beginPath(); 104 + ctx.arc( 105 + (nodeX - 10 + startX + 10 + position.x) * position.scale, 106 + (nodeY + 50 + (30 * i) + startY + 10 + position.y) * position.scale, 107 + 7 * position.scale, 108 + 0, 109 + Math.PI * 2, 110 + ); 111 + ctx.fill(); 112 113 ctx.fillText(input.name, 114 + (nodeX + 15 + startX + position.x) * position.scale, 115 (nodeY + 53 + (30 * i) + startY + position.y) * position.scale, 116 ) 117 }) ··· 121 122 node.outputs.map(( output, i ) => { 123 ctx.fillStyle = NodeIOLinkColours(output); 124 + 125 + ctx.beginPath(); 126 + ctx.arc( 127 + (nodeX + (node.w - 10) + startX + 10 + position.x) * position.scale, 128 + (nodeY + 50 + (30 * i) + startY + 10 + position.y) * position.scale, 129 + 7 * position.scale, 130 + 0, 131 + Math.PI * 2, 132 + ); 133 + ctx.fill(); 134 135 ctx.fillText(output.name, 136 + (nodeX + (node.w - 15) + startX + position.x) * position.scale, 137 (nodeY + 53 + (30 * i) + startY + position.y) * position.scale, 138 ) 139 }) ··· 146 node.outputs.map(( output, i ) => { 147 output.connections.map(partner => { 148 ctx.strokeStyle = NodeIOLinkColours(output); 149 + ctx.lineWidth = 3 * position.scale; 150 + 151 drawCurve(ctx, 152 + (nodeX + (node.w - 10) + 10 + startX + position.x) * position.scale, 153 (nodeY + 50 + (30 * i) + 10 + startY + position.y) * position.scale, 154 + ((Math.round(partner.parent.x / 10) * 10) + startX + position.x) * position.scale, 155 ((Math.round(partner.parent.y / 10) * 10) + 60 + (30 * partner.index) + startY + position.y) * position.scale, 156 ); 157 ctx.stroke(); ··· 165 contextMenu: ContextMenu 166 ) => { 167 if(contextMenu.visible){ 168 + ctx.font = '20px Rubik'; 169 ctx.textBaseline = 'top'; 170 ctx.textAlign = 'left'; 171 ··· 209 let startX = canvas.width / -2; 210 let startY = canvas.height / -2; 211 212 + // DEBUG STUFF 213 + // ctx.fillStyle = '#f00'; 214 215 + // ctx.fillRect( 216 + // (drawingTo[0] + 10 + startX + position.x) * position.scale, 217 + // (drawingTo[1] + 10 + startY + position.y) * position.scale, 218 + // 10, 10 219 + // ); 220 221 + // ctx.fillRect( 222 + // (drawingFrom.parent.x + (drawingFrom.parent.w - 10) + 10 + startX + position.x) * position.scale, 223 + // (drawingFrom.parent.y + 50 + (30 * drawingFrom.index) + 10 + startY + position.y) * position.scale, 224 + // 10, 10 225 + // ); 226 227 ctx.strokeStyle = NodeIOLinkColours(drawingFrom); 228 + ctx.lineWidth = 3 * position.scale; 229 + 230 + let nodeX = Math.round(drawingFrom.parent.x / 10) * 10; 231 + let nodeY = Math.round(drawingFrom.parent.y / 10) * 10; 232 + 233 drawCurve(ctx, 234 + (nodeX + (drawingFrom.parent.w - 10) + 10 + startX + position.x) * position.scale, 235 + (nodeY + 50 + (30 * drawingFrom.index) + 10 + startY + position.y) * position.scale, 236 (drawingTo[0] + 10 + startX + position.x) * position.scale, 237 (drawingTo[1] + 10 + startY + position.y) * position.scale, 238 ); ··· 262 ) => { 263 ctx.fillStyle = '#fff'; 264 265 + ctx.font = '20px Rubik'; 266 ctx.textBaseline = 'middle'; 267 ctx.textAlign = 'center'; 268 269 let textX = lerp((canvas.width / -2) + 200, canvas.width / 2, 0.5); 270 let textY = lerp((canvas.height / -2) + 40, canvas.height / 2, 0.5); 271 272 + ctx.font = '40px Rubik'; 273 ctx.fillText('Welcome to VRCMacros', textX, textY); 274 275 + ctx.font = '20px Rubik'; 276 ctx.fillText('Create a new tab to get started!', textX, textY + 40); 277 } 278
+7
src/structs/node.ts
··· 74 ParameterList = 10 75 } 76 77 export let NodeIOResolveAnyTypes = ( nodeio: NodeIO ): NodeType | null => { 78 if(nodeio.type > 0 && nodeio.type < 6){ 79 // It's a base type
··· 74 ParameterList = 10 75 } 76 77 + export let NodeIOCanCast = ( input: NodeType | null, output: NodeType | null ): boolean => { 78 + if(input === output)return true; 79 + if(!input || !output)return false; 80 + 81 + return false; 82 + } 83 + 84 export let NodeIOResolveAnyTypes = ( nodeio: NodeIO ): NodeType | null => { 85 if(nodeio.type > 0 && nodeio.type < 6){ 86 // It's a base type