Skip to content

Commit 2015b52

Browse files
walmikclaude
andcommitted
Improve cable visuals and patching UX
- Gravity droop bezier: control points pushed downward by distance-scaled sag so cables look like physical patch cables in all directions - Cables render on top of modules (SVG z-index raised, pointer-events: none on overlay with stroke-level hit-testing preserved) - Colored endpoint dots at each cable connection point pin cables visually to their patch points above module cards - Thinner cable strokes (8→6, selected 10→8) for cleaner diagrams - Anchor hover only shows in patching mode (cable tool active); suppressed in Select and Text modes to avoid middle-click/Cmd+click conflicts - Fix null dereference crash in clearTransientInteraction when reroute ref is cleared before the setConnections callback fires - Middle-click and Cmd/Ctrl+click on cable paths no longer select the cable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 01b675c commit 2015b52

2 files changed

Lines changed: 39 additions & 36 deletions

File tree

app/globals.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,8 @@ html.theme-dark .custom-module-delete:hover {
399399
.canvas-overlay {
400400
position: absolute;
401401
inset: 0;
402-
pointer-events: auto;
403-
z-index: 1;
402+
pointer-events: none;
403+
z-index: 5;
404404
}
405405

406406
.canvas-empty {
@@ -446,7 +446,7 @@ html.theme-dark .custom-module-delete:hover {
446446
outline: 3px solid rgba(11, 136, 216, 0.22);
447447
}
448448

449-
.node-card:hover .anchor,
449+
.patching-mode .node-card:hover .anchor,
450450
.node-card.selected .anchor {
451451
opacity: 1;
452452
transform: scale(1);

components/PatchEditor.jsx

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,11 @@ const cableColorAliases = {
185185
function getConnectionPath(source, target) {
186186
const dx = target.x - source.x;
187187
const dy = target.y - source.y;
188-
189-
if (Math.abs(dy) > Math.abs(dx)) {
190-
const handle = Math.max(80, Math.abs(dy) * 0.45);
191-
return `M ${source.x} ${source.y} C ${source.x} ${source.y + handle}, ${target.x} ${target.y - handle}, ${target.x} ${target.y}`;
192-
}
193-
194-
const handle = Math.max(100, Math.abs(dx) * 0.45);
195-
return `M ${source.x} ${source.y} C ${source.x + handle} ${source.y}, ${target.x - handle} ${target.y}, ${target.x} ${target.y}`;
188+
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
189+
// Gravity droop: control points sit 35% along the straight line,
190+
// then pushed downward to simulate a physical cable hanging under its weight.
191+
const droop = Math.max(30, dist * 0.2);
192+
return `M ${source.x} ${source.y} C ${source.x + dx * 0.35} ${source.y + dy * 0.35 + droop}, ${target.x - dx * 0.35} ${target.y - dy * 0.35 + droop}, ${target.x} ${target.y}`;
196193
}
197194

198195

@@ -462,7 +459,8 @@ export default function PatchEditor() {
462459
return "";
463460
}
464461

465-
return `<path d="${getConnectionPath(source, target)}" stroke="${cableColors[connection.color] || cableColors.modulation}" stroke-width="8" stroke-linecap="round" fill="none" opacity="0.95" />`;
462+
const color = cableColors[connection.color] || cableColors.modulation;
463+
return `<path d="${getConnectionPath(source, target)}" stroke="${color}" stroke-width="6" stroke-linecap="round" fill="none" opacity="0.95" /><circle cx="${source.x}" cy="${source.y}" r="5" fill="${color}" opacity="0.9" /><circle cx="${target.x}" cy="${target.y}" r="5" fill="${color}" opacity="0.9" />`;
466464
})
467465
.join("");
468466

@@ -831,8 +829,9 @@ export default function PatchEditor() {
831829
panRef.current = null;
832830
cableDragRef.current = null;
833831
if (rerouteRef.current) {
834-
setConnections((c) => [...c, rerouteRef.current.originalConnection]);
832+
const original = rerouteRef.current.originalConnection;
835833
rerouteRef.current = null;
834+
setConnections((c) => [...c, original]);
836835
}
837836
setIsPanning(false);
838837
setCablePreview(null);
@@ -2220,7 +2219,7 @@ export default function PatchEditor() {
22202219
) : null}
22212220

22222221
<div
2223-
className="canvas-stage"
2222+
className={`canvas-stage${activeTool === "cable" ? " patching-mode" : ""}`}
22242223
style={{
22252224
width: STAGE_WIDTH,
22262225
height: STAGE_HEIGHT,
@@ -2241,28 +2240,32 @@ export default function PatchEditor() {
22412240
return null;
22422241
}
22432242

2243+
const color =
2244+
cableColors[connection.color] || cableColors.modulation;
2245+
const isSelected =
2246+
selectedConnectionId === connection.id;
22442247
return (
2245-
<path
2246-
key={connection.id}
2247-
className="connection-path"
2248-
d={getConnectionPath(source, target)}
2249-
stroke={
2250-
cableColors[connection.color] || cableColors.modulation
2251-
}
2252-
strokeWidth={
2253-
selectedConnectionId === connection.id ? "10" : "8"
2254-
}
2255-
strokeLinecap="round"
2256-
fill="none"
2257-
opacity={
2258-
selectedConnectionId === connection.id ? "1" : "0.95"
2259-
}
2260-
onPointerDown={(event) => {
2261-
event.stopPropagation();
2262-
setSelectedConnectionId(connection.id);
2263-
setSelectedNodeIds([]);
2264-
}}
2265-
/>
2248+
<g key={connection.id}>
2249+
<path
2250+
className="connection-path"
2251+
d={getConnectionPath(source, target)}
2252+
stroke={color}
2253+
strokeWidth={isSelected ? "8" : "6"}
2254+
strokeLinecap="round"
2255+
fill="none"
2256+
opacity={isSelected ? "1" : "0.95"}
2257+
onPointerDown={(event) => {
2258+
if (event.button !== 0 || event.metaKey || event.ctrlKey) {
2259+
return;
2260+
}
2261+
event.stopPropagation();
2262+
setSelectedConnectionId(connection.id);
2263+
setSelectedNodeIds([]);
2264+
}}
2265+
/>
2266+
<circle cx={source.x} cy={source.y} r="5" fill={color} opacity="0.9" style={{ pointerEvents: "none" }} />
2267+
<circle cx={target.x} cy={target.y} r="5" fill={color} opacity="0.9" style={{ pointerEvents: "none" }} />
2268+
</g>
22662269
);
22672270
})}
22682271
{cablePreview ? (
@@ -2271,7 +2274,7 @@ export default function PatchEditor() {
22712274
stroke={
22722275
cableColors[cablePreview.color] || cableColors.modulation
22732276
}
2274-
strokeWidth="8"
2277+
strokeWidth="6"
22752278
strokeLinecap="round"
22762279
fill="none"
22772280
opacity="0.55"

0 commit comments

Comments
 (0)