Skip to content

Commit 7056b76

Browse files
committed
Improved port visualization with distinct control/data icons and better hit detection
1 parent 7ed5508 commit 7056b76

File tree

5 files changed

+133
-32
lines changed

5 files changed

+133
-32
lines changed

TODO.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
- [ ] Copy node
88
- [ ] Delete node
99

10-
- [ ] Improve port appearance
11-
- [ ] Add arrows to ports instead of circles
12-
1310
- [ ] Node labeling
1411

1512
- [ ] Add new node types:

src/VisualScripting.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -226,31 +226,33 @@ const VisualScripting = () => {
226226
};
227227

228228
const findClickedPort = (x, y) => {
229-
const PORT_RADIUS = 5;
230-
const PORT_RADIUS_SQUARED = PORT_RADIUS * PORT_RADIUS;
229+
const PORT_WIDTH = 6; // Width of the gray arrow
230+
const PORT_HEIGHT = 10; // Height of the gray arrow
231+
const PORT_OFFSET = 5; // Distance from node border
232+
const SCALE_MULTIPLIER = 1.5; // Scale multiplier for hit detection
231233

232234
for (const node of nodes) {
233235
const nodeType = nodeTypes[node.type];
234236
const { width, portStartY } = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));
235237

236238
// Check input ports
237239
for (let i = 0; i < nodeType.inputs.length; i++) {
238-
const portX = node.x;
239-
const portY = node.y + portStartY + i * 20;
240-
const dx = x - portX;
241-
const dy = y - portY;
242-
if (dx * dx + dy * dy <= PORT_RADIUS_SQUARED) {
240+
const portX = node.x - PORT_OFFSET - PORT_WIDTH; // Position of gray arrow
241+
const portY = node.y + portStartY + i * 20 - PORT_HEIGHT/2;
242+
243+
if (x >= portX && x <= portX + PORT_WIDTH * SCALE_MULTIPLIER &&
244+
y >= portY && y <= portY + PORT_HEIGHT * SCALE_MULTIPLIER) {
243245
return { nodeId: node.id, isInput: true, index: i };
244246
}
245247
}
246248

247249
// Check output ports
248250
for (let i = 0; i < nodeType.outputs.length; i++) {
249-
const portX = node.x + width;
250-
const portY = node.y + portStartY + i * 20;
251-
const dx = x - portX;
252-
const dy = y - portY;
253-
if (dx * dx + dy * dy <= PORT_RADIUS_SQUARED) {
251+
const portX = node.x + width + PORT_OFFSET; // Position of gray arrow
252+
const portY = node.y + portStartY + i * 20 - PORT_HEIGHT/2;
253+
254+
if (x >= portX && x <= portX + PORT_WIDTH * SCALE_MULTIPLIER &&
255+
y >= portY && y <= portY + PORT_HEIGHT * SCALE_MULTIPLIER) {
254256
return { nodeId: node.id, isInput: false, index: i };
255257
}
256258
}

src/components/GraphInspector.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const GraphInspector = ({
8585
</div>
8686
{nodeType.inputs.map((input, index) => (
8787
<div key={index} className={`${styles.portContainer} ${config.isDarkTheme ? styles.portContainerDark : styles.portContainerLight}`}>
88-
<div className={styles.portIcon} style={{ transform: 'rotate(180deg)' }} />
88+
<div className={input.type === 'control' ? styles.portIconControl : styles.portIconData} />
8989
<span>"{input.name}"</span>
9090
<span className={styles.portType}>({input.type})</span>
9191
</div>
@@ -98,7 +98,7 @@ const GraphInspector = ({
9898
</div>
9999
{nodeType.outputs.map((output, index) => (
100100
<div key={index} className={`${styles.portContainer} ${config.isDarkTheme ? styles.portContainerDark : styles.portContainerLight}`}>
101-
<div className={styles.portIcon} />
101+
<div className={output.type === 'control' ? styles.portIconControl : styles.portIconData} />
102102
<span>"{output.name}"</span>
103103
<span className={styles.portType}>({output.type})</span>
104104
</div>

src/components/GraphInspector.module.css

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,15 @@
156156
align-items: center;
157157
}
158158

159-
.portIcon::before {
159+
.portIconControl {
160+
position: relative;
161+
width: 16px;
162+
height: 12px;
163+
display: flex;
164+
align-items: center;
165+
}
166+
167+
.portIconControl::before {
160168
content: '';
161169
position: absolute;
162170
width: 10px;
@@ -165,7 +173,7 @@
165173
left: 0;
166174
}
167175

168-
.portIcon::after {
176+
.portIconControl::after {
169177
content: '';
170178
position: absolute;
171179
right: 0;
@@ -176,6 +184,14 @@
176184
border-left: 6px solid #4CAF50;
177185
}
178186

187+
.portIconData {
188+
width: 8px;
189+
height: 8px;
190+
border-radius: 50%;
191+
background-color: #FFA500;
192+
margin: 0 4px;
193+
}
194+
179195
.portType {
180196
opacity: 0.7;
181197
font-weight: 500;
@@ -194,4 +210,20 @@
194210

195211
.emptyMessageLight {
196212
color: #666;
213+
}
214+
215+
.portIconControlInput {
216+
composes: portIconControl;
217+
}
218+
219+
.portIconControlInput::before {
220+
left: auto;
221+
right: 0;
222+
}
223+
224+
.portIconControlInput::after {
225+
right: auto;
226+
left: 0;
227+
border-left: none;
228+
border-right: 6px solid #4CAF50;
197229
}

src/engine/Renderer.js

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class Renderer {
9797
this.drawEdges(ctx, edges, nodes);
9898

9999
// Draw nodes
100-
this.drawNodes(ctx, nodes, selectedNodes);
100+
this.drawNodes(ctx, nodes, edges, selectedNodes);
101101

102102
// Draw connection line
103103
if (connecting) {
@@ -164,11 +164,59 @@ class Renderer {
164164
});
165165
}
166166

167-
drawNodes(ctx, nodes, selectedNodes) {
167+
drawPortIcon(ctx, x, y, isInput) {
168+
const offset = 5; // Distance from node border
169+
const arrowX = isInput ? x - offset : x + offset;
170+
171+
ctx.beginPath();
172+
if (isInput) {
173+
ctx.moveTo(arrowX - 6, y - 5);
174+
ctx.lineTo(arrowX - 6, y + 5);
175+
ctx.lineTo(arrowX, y);
176+
} else {
177+
ctx.moveTo(arrowX, y - 5);
178+
ctx.lineTo(arrowX, y + 5);
179+
ctx.lineTo(arrowX + 6, y);
180+
}
181+
ctx.closePath();
182+
ctx.strokeStyle = '#999999';
183+
ctx.lineWidth = 1;
184+
ctx.stroke();
185+
}
186+
187+
drawLabelArrow(ctx, x, y, isControl) {
188+
if (isControl) {
189+
const lineLength = 10;
190+
191+
// Draw line
192+
ctx.beginPath();
193+
ctx.moveTo(x, y);
194+
ctx.lineTo(x + lineLength, y);
195+
ctx.strokeStyle = '#4CAF50';
196+
ctx.lineWidth = 2;
197+
ctx.stroke();
198+
199+
// Draw arrow
200+
ctx.beginPath();
201+
ctx.moveTo(x + lineLength, y - 5);
202+
ctx.lineTo(x + lineLength, y + 5);
203+
ctx.lineTo(x + lineLength + 6, y);
204+
ctx.closePath();
205+
ctx.fillStyle = '#4CAF50';
206+
ctx.fill();
207+
} else {
208+
// Draw orange circle for data ports
209+
ctx.beginPath();
210+
ctx.arc(x + 5, y, 4, 0, Math.PI * 2);
211+
ctx.fillStyle = '#FFA500';
212+
ctx.fill();
213+
}
214+
}
215+
216+
drawNodes(ctx, nodes, edges, selectedNodes) {
168217
nodes.forEach(node => {
169218
const { width, height } = this.getNodeDimensions(node, ctx);
170219

171-
// Skip nodes that are completely outside the view
172220
if (!this.isRectInView(node.x, node.y, width, height, ctx.canvas.width, ctx.canvas.height)) {
173221
return;
174222
}
@@ -220,22 +268,44 @@ class Renderer {
220268

221269
// Input ports
222270
nodeType.inputs.forEach((input, i) => {
223-
ctx.fillStyle = '#FFA500';
224-
ctx.beginPath();
225-
ctx.arc(node.x, node.y + currentHeight + i * 20, 5, 0, 2 * Math.PI);
226-
ctx.fill();
271+
const portY = node.y + currentHeight + i * 20;
272+
const isControl = input.type === 'control';
273+
274+
// Check if port is connected
275+
const isPortConnected = edges.some(edge =>
276+
edge.end.nodeId === node.id &&
277+
edge.end.index === i &&
278+
edge.end.isInput
279+
);
280+
281+
if (!isPortConnected) {
282+
this.drawPortIcon(ctx, node.x, portY, true);
283+
}
284+
285+
this.drawLabelArrow(ctx, node.x + 15, portY, isControl);
227286
ctx.fillStyle = 'white';
228-
ctx.fillText(`${input.type === 'control' ? '▶' : '●'} ${input.name}`, node.x + 10, node.y + currentHeight + 5 + i * 20);
287+
ctx.fillText(input.name, node.x + 35, portY + 5);
229288
});
230289

231290
// Output ports
232291
nodeType.outputs.forEach((output, i) => {
233-
ctx.fillStyle = '#FFA500';
234-
ctx.beginPath();
235-
ctx.arc(node.x + width, node.y + currentHeight + i * 20, 5, 0, 2 * Math.PI);
236-
ctx.fill();
292+
const portY = node.y + currentHeight + i * 20;
293+
const isControl = output.type === 'control';
294+
295+
const isPortConnected = edges.some(edge =>
296+
edge.start.nodeId === node.id &&
297+
edge.start.index === i &&
298+
!edge.start.isInput
299+
);
300+
301+
if (!isPortConnected) {
302+
this.drawPortIcon(ctx, node.x + width, portY, false);
303+
}
304+
237305
ctx.fillStyle = 'white';
238-
ctx.fillText(`${output.name} ${output.type === 'control' ? '▶' : '●'}`, node.x + width - 70, node.y + currentHeight + 5 + i * 20);
306+
const textWidth = ctx.measureText(output.name).width;
307+
ctx.fillText(output.name, node.x + width - textWidth - 35, portY + 5);
308+
this.drawLabelArrow(ctx, node.x + width - 25, portY, isControl);
239309
});
240310

241311
currentHeight += Math.max(nodeType.inputs.length, nodeType.outputs.length) * 15;

0 commit comments

Comments
 (0)