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
@@ -18,6 +18,10 @@ const FOLD_STUB = 16;
|
||||
const FOLD_MARK_RADIUS = 5;
|
||||
const FOLD_LABEL_WIDTH = 96;
|
||||
const BEAD_RADIUS = 4.5;
|
||||
/** Dash pattern and sampling of an edge hanging on a port that is gone. */
|
||||
const GHOST_DASH = 7;
|
||||
const GHOST_GAP = 5;
|
||||
const DASH_STEPS = 80;
|
||||
|
||||
/** One port's hidden links as drawn: what they stand in for, and where they can be clicked. */
|
||||
export interface FoldMark {
|
||||
@@ -99,9 +103,13 @@ export class EdgeLayer extends Container {
|
||||
const a = from?.portPosition(edge.from.port, true);
|
||||
const b = to?.portPosition(edge.to.port, false);
|
||||
if (!from || !to || !a || !b) return;
|
||||
const type = from.def.outputs.find((p) => p.key === edge.from.port)?.type ?? 'any';
|
||||
const inType = to.def.inputs.find((p) => p.key === edge.to.port)?.type ?? 'any';
|
||||
const valid = portsCompatible(type, inType);
|
||||
const out = from.def.outputs.find((p) => p.key === edge.from.port);
|
||||
const inp = to.def.inputs.find((p) => p.key === edge.to.port);
|
||||
const type = out?.type ?? 'any';
|
||||
const inType = inp?.type ?? 'any';
|
||||
// One end on a port that is gone: the edge is drawn, but never as a working link.
|
||||
const ghost = out?.ghost === true || inp?.ghost === true;
|
||||
const valid = !ghost && portsCompatible(type, inType);
|
||||
const color = hot ? Theme.colors.hot : valid ? portColor(type) : Theme.colors.danger;
|
||||
const start = { x: from.x + a.x, y: from.y + a.y };
|
||||
const end = { x: to.x + b.x, y: to.y + b.y };
|
||||
@@ -110,7 +118,8 @@ export class EdgeLayer extends Container {
|
||||
this.addFoldEnd(edge, views, end, false, from.def.label, color, hot);
|
||||
if (!foldedEdgeShown(edge, selected, hot)) return;
|
||||
}
|
||||
bezier(this.curves, start.x, start.y, end.x, end.y);
|
||||
if (ghost) dashedBezier(this.curves, start.x, start.y, end.x, end.y, GHOST_DASH, GHOST_GAP);
|
||||
else bezier(this.curves, start.x, start.y, end.x, end.y);
|
||||
this.curves.stroke({
|
||||
width: hot ? 4 : type === 'exec' ? 2.5 : 2,
|
||||
color,
|
||||
@@ -191,3 +200,34 @@ export function bezier(g: Graphics, x1: number, y1: number, x2: number, y2: numb
|
||||
const dx = curveReach(x1, x2);
|
||||
g.moveTo(x1, y1).bezierCurveTo(x1 + dx, y1, x2 - dx, y2, x2, y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* The same curve as dashes. Pixi has no dash style, and unlike a straight line a bezier cannot be
|
||||
* stepped by length, so it is sampled and the walked distance decides which steps are drawn.
|
||||
*/
|
||||
export function dashedBezier(
|
||||
g: Graphics,
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
dash: number,
|
||||
gap: number,
|
||||
): void {
|
||||
const dx = curveReach(x1, x2);
|
||||
const at = (t: number) => {
|
||||
const u = 1 - t;
|
||||
return {
|
||||
x: u * u * u * x1 + 3 * u * u * t * (x1 + dx) + 3 * u * t * t * (x2 - dx) + t * t * t * x2,
|
||||
y: u * u * u * y1 + 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t * y2,
|
||||
};
|
||||
};
|
||||
let prev = at(0);
|
||||
let walked = 0;
|
||||
for (let i = 1; i <= DASH_STEPS; i++) {
|
||||
const point = at(i / DASH_STEPS);
|
||||
if (walked % (dash + gap) < dash) g.moveTo(prev.x, prev.y).lineTo(point.x, point.y);
|
||||
walked += Math.hypot(point.x - prev.x, point.y - prev.y);
|
||||
prev = point;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user