An edge to a vanished port needs a handle, so the port stays as a ghost (#60)
game-maker / suite (push) Successful in 10m7s
game-maker / suite (push) Successful in 10m7s
Four ways erase a derived port and take no edge with them: deleting a state, deleting the `State machine` literal, deleting `Behavior start`/`end`, deleting the asset. The common cause is `RemoveNodeCommand` clearing only its own graph, so the fix sits in `drawnDef` (graph/nested.ts) and touches no delete site. A right-click on an edge goes through `view.portAt` only — there is no curve hit-test. Without a port row such an edge is not merely invisible, it is unreachable, so the ghost row is the handle Disconnect hangs on. `resolveDef` stays untouched: it is the truth for interpreter, compiler, ConnectCommand and dropTarget, so nothing runs or connects through a ghost without a second guard.
This commit is contained in:
1 parent
d5e67cd80c
commit
a041322a0e
8 files changed
+372
-16
No files matched your search
@@ -13,10 +13,12 @@ import {
|
||||
callProblem,
|
||||
canConnect,
|
||||
defAcceptsFrom,
|
||||
drawnDef,
|
||||
dropTargetPort,
|
||||
edgesInto,
|
||||
edgesOutOf,
|
||||
firstPortFor,
|
||||
ghostProblem,
|
||||
type Graph,
|
||||
type GraphNode,
|
||||
type GraphLookup,
|
||||
@@ -229,8 +231,9 @@ export class GraphCanvas extends Widget {
|
||||
? particleLibrary.buildFor(asset).problems
|
||||
: null;
|
||||
for (const node of asset.graph.nodes) {
|
||||
const def = resolveDef(node, asset.graph, lookup);
|
||||
if (!def) continue;
|
||||
const resolved = resolveDef(node, asset.graph, lookup);
|
||||
if (!resolved) continue;
|
||||
const def = drawnDef(node, asset.graph, resolved);
|
||||
seen.add(node.id);
|
||||
let view = this.views.get(node.id);
|
||||
if (!view || view.node !== node) {
|
||||
@@ -240,9 +243,9 @@ export class GraphCanvas extends Widget {
|
||||
this.views.set(node.id, view);
|
||||
this.nodeLayer.addChild(view);
|
||||
}
|
||||
const problem = compilerProblems
|
||||
? (compilerProblems[node.id] ?? null)
|
||||
: callProblem(node, lookup, asset.id);
|
||||
const problem =
|
||||
(compilerProblems ? (compilerProblems[node.id] ?? null) : callProblem(node, lookup, asset.id)) ??
|
||||
ghostProblem(node, asset.graph, resolved);
|
||||
view.sync(node, asset.graph, this.state.project, def, problem);
|
||||
}
|
||||
for (const [id, view] of this.views) {
|
||||
@@ -358,6 +361,9 @@ export class GraphCanvas extends Widget {
|
||||
return;
|
||||
}
|
||||
if (e.button !== 0) return;
|
||||
// A ghost pin is a handle for the right-click menu only: nothing to drag from, and dragging the
|
||||
// node instead would make a press on the very edge of it move the whole node.
|
||||
if (hit?.port.ghost) return;
|
||||
if (hit) {
|
||||
const p = this.toLocal(e.global);
|
||||
this.connecting = {
|
||||
@@ -595,7 +601,8 @@ export class GraphCanvas extends Widget {
|
||||
for (const view of this.topDownViews()) {
|
||||
const local = view.toLocal({ x: globalX, y: globalY });
|
||||
const hit = view.portAt(local.x, local.y);
|
||||
if (hit) return { nodeId: view.node.id, port: hit.port.key, isOutput: hit.isOutput };
|
||||
// A ghost is no target, and it must not shadow the node body underneath it either.
|
||||
if (hit && !hit.port.ghost) return { nodeId: view.node.id, port: hit.port.key, isOutput: hit.isOutput };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user