Skip to content

Commit d502b19

Browse files
committed
Optimized node rendering and port positioning with OOP principles
1 parent b385b37 commit d502b19

File tree

5 files changed

+650
-699
lines changed

5 files changed

+650
-699
lines changed

src/VisualScripting.js

Lines changed: 39 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import { nodeTypes, nodeGroups } from './nodeDefinitions';
1111
import examples from './examples';
1212
import { saveAs } from 'file-saver';
1313
import GraphInspector from './components/GraphInspector';
14+
import Node from './engine/Node';
1415

1516
const VisualScripting = () => {
1617
// #region State Declarations
1718
const canvasRef = useRef(null);
1819
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 });
19-
const [nodes, setNodes] = useState([]);
20+
const [nodes, setNodes] = useState(() => []);
2021
const [edges, setEdges] = useState([]);
2122
const [draggingNode, setDraggingNode] = useState(null);
2223
const [connecting, setConnecting] = useState(null);
@@ -119,24 +120,9 @@ const VisualScripting = () => {
119120
const clickedPort = findClickedPort(x, y);
120121
if (clickedPort) {
121122
const node = nodes.find(n => n.id === clickedPort.nodeId);
122-
const { width } = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));
123-
124-
const triangleWidth = 6;
125-
const triangleHeight = 10;
126-
const portOffset = 5;
127-
128-
const portY = node.y + 35 + (clickedPort.index * 14);
129-
const portYMiddle = portY + (triangleHeight / 2);
130-
131-
const portX = clickedPort.isInput
132-
? node.x - portOffset - (triangleWidth / 2)
133-
: node.x + width + portOffset + (triangleWidth / 2);
134-
135-
setConnecting({
136-
...clickedPort,
137-
portY: portYMiddle,
138-
portX
139-
});
123+
const dimensions = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));
124+
const portPosition = node.getPortPosition(clickedPort.index, clickedPort.isInput, dimensions);
125+
setConnecting(portPosition);
140126
} else {
141127
const clickedNode = findClickedNode(x, y);
142128
if (clickedNode) {
@@ -160,15 +146,13 @@ const VisualScripting = () => {
160146
camera.move(dx, dy);
161147
setLastMousePosition({ x: e.clientX, y: e.clientY });
162148
} else if (draggingNode) {
163-
setNodes(nodes.map(node =>
164-
node.id === draggingNode.id
165-
? {
166-
...node,
167-
x: x - draggingNode.offsetX,
168-
y: y - draggingNode.offsetY
169-
}
170-
: node
171-
));
149+
setNodes(nodes.map(node => {
150+
if (node.id === draggingNode.id) {
151+
return Node.createInstance(node, nodeTypes)
152+
.moveTo(x - draggingNode.offsetX, y - draggingNode.offsetY);
153+
}
154+
return node;
155+
}));
172156
}
173157

174158
setMousePosition({ x, y });
@@ -236,68 +220,23 @@ const VisualScripting = () => {
236220

237221
// #region Node Operations
238222
const findClickedNode = (x, y) => {
239-
return nodes.find(node => {
240-
const { width, height } = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));
241-
242-
return x >= node.x && x <= node.x + width && y >= node.y && y <= node.y + height;
243-
});
223+
return nodes.find(node =>
224+
node.isPointInside(x, y, renderer.getNodeDimensions(node, canvasRef.current.getContext('2d')))
225+
);
244226
};
245227

246228
const findClickedPort = (x, y) => {
247-
const PORT_WIDTH = 6;
248-
const PORT_HEIGHT = 10;
249-
const PORT_OFFSET = 5;
250-
const SCALE_MULTIPLIER = 1.5;
251-
const VERTICAL_OFFSET = 2;
252-
253229
for (const node of nodes) {
254230
const nodeType = nodeTypes[node.type];
255-
const { width } = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));
256-
257-
let currentHeight = 35; // Start after title (25 + 10 padding)
258-
259-
// Check input ports
260-
for (let i = 0; i < nodeType.inputs.length; i++) {
261-
const portX = node.x - PORT_OFFSET - PORT_WIDTH;
262-
const portY = node.y + currentHeight + (i * 14);
263-
264-
if (x >= portX && x <= portX + PORT_WIDTH * SCALE_MULTIPLIER &&
265-
y >= portY - VERTICAL_OFFSET && y <= portY + PORT_HEIGHT + VERTICAL_OFFSET) {
266-
return { nodeId: node.id, isInput: true, index: i };
267-
}
268-
}
269-
270-
// Check output ports
271-
for (let i = 0; i < nodeType.outputs.length; i++) {
272-
const portX = node.x + width + PORT_OFFSET;
273-
const portY = node.y + currentHeight + (i * 14);
274-
275-
if (x >= portX && x <= portX + PORT_WIDTH * SCALE_MULTIPLIER &&
276-
y >= portY - VERTICAL_OFFSET && y <= portY + PORT_HEIGHT + VERTICAL_OFFSET) {
277-
return { nodeId: node.id, isInput: false, index: i };
278-
}
279-
}
231+
const dimensions = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));
232+
const clickedPort = node.findClickedPort(x, y, dimensions, nodeType);
233+
if (clickedPort) return clickedPort;
280234
}
281235
return null;
282236
};
283237

284238
const addNode = (type) => {
285-
const nodeType = nodeTypes[type];
286-
const newNode = {
287-
id: Date.now(),
288-
type,
289-
x: contextMenu.x,
290-
y: contextMenu.y,
291-
properties: {}
292-
};
293-
294-
// Initialize properties with default values
295-
if (nodeType.properties) {
296-
nodeType.properties.forEach(prop => {
297-
newNode.properties[prop.name] = prop.default;
298-
});
299-
}
300-
239+
const newNode = Node.create(type, contextMenu.x, contextMenu.y, nodeTypes);
301240
const newNodes = [...nodes, newNode];
302241
setUndoStack([...undoStack, { nodes, edges }]);
303242
setRedoStack([]);
@@ -307,21 +246,26 @@ const VisualScripting = () => {
307246
};
308247

309248
const updateNodeProperty = (property, value) => {
310-
const updatedNodes = nodes.map(node =>
311-
node.id === selectedNodes[0].id
312-
? { ...node, properties: { ...node.properties, [property]: value } }
313-
: node
314-
);
249+
const updatedNodes = nodes.map(node => {
250+
if (node.id === selectedNodes[0].id) {
251+
return Node.createInstance(node, nodeTypes)
252+
.updateProperty(property, value);
253+
}
254+
return node;
255+
});
256+
315257
setUndoStack([...undoStack, { nodes, edges }]);
316258
setRedoStack([]);
317259
setNodes(updatedNodes);
318260

319-
// Update the selectedNodes state as well
320-
setSelectedNodes(prevSelected => prevSelected.map(node =>
321-
node.id === selectedNodes[0].id
322-
? { ...node, properties: { ...node.properties, [property]: value } }
323-
: node
324-
));
261+
setSelectedNodes(prevSelected => prevSelected.map(node => {
262+
if (node.id === selectedNodes[0].id) {
263+
return Node.createInstance(node, nodeTypes)
264+
.updateProperty(property, value);
265+
}
266+
return node;
267+
}));
268+
325269
setNeedsRedraw(true);
326270
};
327271

@@ -377,38 +321,16 @@ const VisualScripting = () => {
377321
break;
378322
case 'loadExample':
379323
if (examples[param]) {
380-
// Find any Switch nodes and update nodeTypes with dynamic outputs
381-
examples[param].nodes.forEach(node => {
382-
if (node.type === 'Switch' && node.properties.cases) {
383-
const switchNode = nodeTypes['Switch'];
384-
// Create a copy of the original outputs
385-
const baseOutputs = [...switchNode.outputs];
386-
387-
// Add case outputs dynamically
388-
node.properties.cases.forEach((caseItem, index) => {
389-
baseOutputs.splice(index, 0, {
390-
type: 'control',
391-
name: caseItem.output,
392-
description: `Triggered when value matches ${caseItem.value}`
393-
});
394-
});
395-
396-
// Create a temporary node type with the dynamic outputs
397-
nodeTypes['Switch'] = {
398-
...switchNode,
399-
outputs: baseOutputs
400-
};
401-
}
402-
});
403-
404-
setNodes(examples[param].nodes);
324+
const exampleNodes = examples[param].nodes.map(node =>
325+
Node.createFromExample(node, nodeTypes)
326+
);
327+
setNodes(exampleNodes);
405328
setEdges(examples[param].edges);
406329
setUndoStack([]);
407330
setRedoStack([]);
408331
}
409332
break;
410333
case 'save':
411-
// Implement file saving logic here
412334
const projectData = JSON.stringify({ nodes, edges });
413335
const blob = new Blob([projectData], { type: 'application/json' });
414336
const url = URL.createObjectURL(blob);

0 commit comments

Comments
 (0)