chore: refresh fxnode gesture fixes
Amp-Thread-ID: https://ampcode.com/threads/T-019f9d91-77c1-7206-a60f-ed6554ce92ab Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Vendored
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
# fxnode upstream provenance
|
# fxnode upstream provenance
|
||||||
|
|
||||||
- Source: https://github.com/Heaust-ops/fxnode
|
- Source: https://github.com/Heaust-ops/fxnode
|
||||||
- Commit: `4e96585e99742959660d4107b0078c27ff13e708`
|
- Commit: `618c4e02265568d542350902217641d1bbf1ef40`
|
||||||
- Tree: `4fcafa60557bcbd760d7aa8d8898c0adbe0bb2c8`
|
- Tree: `03fd4246af80f2e4c855369c5dcf362157c6ef58`
|
||||||
- Imported: 2026-07-29
|
- Imported: 2026-07-29
|
||||||
- License: MIT (see `LICENSE` and `NOTICE.md`)
|
- License: MIT (see `LICENSE` and `NOTICE.md`)
|
||||||
- Local patches: none
|
- Local patches: none
|
||||||
@@ -14,8 +14,8 @@ This directory is a committed source snapshot. Application adaptations belong ou
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --filter=blob:none https://github.com/Heaust-ops/fxnode /tmp/fxnode
|
git clone --filter=blob:none https://github.com/Heaust-ops/fxnode /tmp/fxnode
|
||||||
git -C /tmp/fxnode fetch --depth=1 origin 4e96585e99742959660d4107b0078c27ff13e708
|
git -C /tmp/fxnode fetch --depth=1 origin 618c4e02265568d542350902217641d1bbf1ef40
|
||||||
git -C /tmp/fxnode checkout --detach 4e96585e99742959660d4107b0078c27ff13e708
|
git -C /tmp/fxnode checkout --detach 618c4e02265568d542350902217641d1bbf1ef40
|
||||||
rm -rf vendor/fxnode
|
rm -rf vendor/fxnode
|
||||||
mkdir -p vendor/fxnode
|
mkdir -p vendor/fxnode
|
||||||
git -C /tmp/fxnode archive HEAD | tar -x -C vendor/fxnode
|
git -C /tmp/fxnode archive HEAD | tar -x -C vendor/fxnode
|
||||||
|
|||||||
Vendored
+8
@@ -204,3 +204,11 @@ export function layoutSocketsCompatible(from: LayoutSocket, to: LayoutSocket): b
|
|||||||
from.direction === "output" && to.direction === "input" && (to.wildcardInput || to.accepts.includes(from.dataType))
|
from.direction === "output" && to.direction === "input" && (to.wildcardInput || to.accepts.includes(from.dataType))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function layoutSocketAcceptsLink(from: LayoutSocket, to: LayoutSocket): boolean {
|
||||||
|
return (
|
||||||
|
from.nodeId !== to.nodeId &&
|
||||||
|
layoutSocketsCompatible(from, to) &&
|
||||||
|
(to.capacity === 1 || to.linkIds.length < to.capacity)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
+18
-5
@@ -2,7 +2,7 @@ import { GEOMETRY as G } from "../layout/constants.js";
|
|||||||
import { worldToView } from "../layout/geometry.js";
|
import { worldToView } from "../layout/geometry.js";
|
||||||
import type { LinkId, NodeId, ParameterValue, SocketId } from "../core/types.js";
|
import type { LinkId, NodeId, ParameterValue, SocketId } from "../core/types.js";
|
||||||
import {
|
import {
|
||||||
layoutSocketsCompatible,
|
layoutSocketAcceptsLink,
|
||||||
type LayoutControl,
|
type LayoutControl,
|
||||||
type LayoutSnapshot,
|
type LayoutSnapshot,
|
||||||
type LayoutSocket,
|
type LayoutSocket,
|
||||||
@@ -755,11 +755,24 @@ export function renderCanvas(
|
|||||||
context.bezierCurveTo(a.x + dx, a.y, b.x - dx, b.y, b.x, b.y);
|
context.bezierCurveTo(a.x + dx, a.y, b.x - dx, b.y, b.x, b.y);
|
||||||
context.stroke();
|
context.stroke();
|
||||||
for (const socket of snapshot.sockets.values()) {
|
for (const socket of snapshot.sockets.values()) {
|
||||||
if (layoutSocketsCompatible(from, socket)) {
|
if (layoutSocketAcceptsLink(from, socket)) {
|
||||||
const p = worldToView(socket.anchor, t);
|
const candidate = socket.id === interaction.linkDrag.candidate;
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.arc(p.x, p.y, socket.id === interaction.linkDrag.candidate ? 10 : 8, 0, Math.PI * 2);
|
if (socket.shape === "multi-input" && socket.bounds) {
|
||||||
context.strokeStyle = socket.id === interaction.linkDrag.candidate ? theme.emphasis : socket.color;
|
const topLeft = worldToView({ x: socket.bounds.x, y: socket.bounds.y }, t),
|
||||||
|
padding = candidate ? 4 : 2;
|
||||||
|
context.roundRect(
|
||||||
|
topLeft.x - padding,
|
||||||
|
topLeft.y - padding,
|
||||||
|
socket.bounds.width * t.zoom + padding * 2,
|
||||||
|
socket.bounds.height * t.zoom + padding * 2,
|
||||||
|
G.socket * t.zoom + padding,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const p = worldToView(socket.anchor, t);
|
||||||
|
context.arc(p.x, p.y, candidate ? 10 : 8, 0, Math.PI * 2);
|
||||||
|
}
|
||||||
|
context.strokeStyle = candidate ? theme.emphasis : socket.color;
|
||||||
context.stroke();
|
context.stroke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-6
@@ -3,7 +3,7 @@ import type { Command } from "../commands/types.js";
|
|||||||
import { viewToWorld } from "../layout/geometry.js";
|
import { viewToWorld } from "../layout/geometry.js";
|
||||||
import { GEOMETRY as G } from "../layout/constants.js";
|
import { GEOMETRY as G } from "../layout/constants.js";
|
||||||
import {
|
import {
|
||||||
layoutSocketsCompatible,
|
layoutSocketAcceptsLink,
|
||||||
type LayoutControl,
|
type LayoutControl,
|
||||||
type LayoutSnapshot,
|
type LayoutSnapshot,
|
||||||
type Rect,
|
type Rect,
|
||||||
@@ -219,7 +219,7 @@ export const boxNodes = (layout: LayoutSnapshot, a: Vec2, b: Vec2): NodeId[] =>
|
|||||||
export const compatibleTargets = (layout: LayoutSnapshot, fromId: SocketId) => {
|
export const compatibleTargets = (layout: LayoutSnapshot, fromId: SocketId) => {
|
||||||
const from = layout.sockets.get(fromId);
|
const from = layout.sockets.get(fromId);
|
||||||
if (!from || from.direction !== "output") return [];
|
if (!from || from.direction !== "output") return [];
|
||||||
return [...layout.sockets.values()].filter((to) => to.nodeId !== from.nodeId && layoutSocketsCompatible(from, to));
|
return [...layout.sockets.values()].filter((to) => layoutSocketAcceptsLink(from, to));
|
||||||
};
|
};
|
||||||
export function planLink(
|
export function planLink(
|
||||||
layout: LayoutSnapshot,
|
layout: LayoutSnapshot,
|
||||||
@@ -230,8 +230,9 @@ export function planLink(
|
|||||||
const from = layout.sockets.get(fromId),
|
const from = layout.sockets.get(fromId),
|
||||||
to = layout.sockets.get(toId);
|
to = layout.sockets.get(toId);
|
||||||
if (!from || !to || !compatibleTargets(layout, fromId).some((s) => s.id === toId)) return;
|
if (!from || !to || !compatibleTargets(layout, fromId).some((s) => s.id === toId)) return;
|
||||||
|
const replacing = to.capacity === 1 && to.linkIds.length > 0;
|
||||||
const link: GraphLink = {
|
const link: GraphLink = {
|
||||||
id: to.linkIds[0] ?? newId,
|
id: replacing ? to.linkIds[0]! : newId,
|
||||||
fromNodeId: from.nodeId,
|
fromNodeId: from.nodeId,
|
||||||
fromSocketId: from.id,
|
fromSocketId: from.id,
|
||||||
toNodeId: to.nodeId,
|
toNodeId: to.nodeId,
|
||||||
@@ -239,9 +240,7 @@ export function planLink(
|
|||||||
muted: false,
|
muted: false,
|
||||||
extensions: {},
|
extensions: {},
|
||||||
};
|
};
|
||||||
return to.capacity === 1 && to.linkIds.length
|
return replacing ? { type: "link.replace", removeId: to.linkIds[0]!, link } : { type: "link.add", link };
|
||||||
? { type: "link.replace", removeId: to.linkIds[0]!, link }
|
|
||||||
: { type: "link.add", link };
|
|
||||||
}
|
}
|
||||||
export const clampResize = (layout: LayoutSnapshot, id: NodeId, world: Vec2): Vec2 | undefined => {
|
export const clampResize = (layout: LayoutSnapshot, id: NodeId, world: Vec2): Vec2 | undefined => {
|
||||||
const n = layout.nodes.get(id);
|
const n = layout.nodes.get(id);
|
||||||
|
|||||||
+45
@@ -161,6 +161,51 @@ test("logic nodes accept five links through one input and application evaluation
|
|||||||
expect(initial.incoming).toBe(5);
|
expect(initial.incoming).toBe(5);
|
||||||
expect(initial.labels).toContain("AND: false");
|
expect(initial.labels).toContain("AND: false");
|
||||||
|
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const view = window.fxnodeStandalone.view!;
|
||||||
|
const modifiers = { alt: false, control: false, meta: false, shift: false };
|
||||||
|
view.feedInput({
|
||||||
|
kind: "pointer",
|
||||||
|
phase: "down",
|
||||||
|
pointerId: 41,
|
||||||
|
pointerType: "mouse",
|
||||||
|
position: { x: 353, y: 190 },
|
||||||
|
button: 0,
|
||||||
|
buttons: 1,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
view.feedInput({
|
||||||
|
kind: "pointer",
|
||||||
|
phase: "move",
|
||||||
|
pointerId: 41,
|
||||||
|
pointerType: "mouse",
|
||||||
|
position: { x: 464, y: 468 },
|
||||||
|
button: 0,
|
||||||
|
buttons: 1,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
await view.whenRendered();
|
||||||
|
view.feedInput({
|
||||||
|
kind: "pointer",
|
||||||
|
phase: "up",
|
||||||
|
pointerId: 41,
|
||||||
|
pointerType: "mouse",
|
||||||
|
position: { x: 464, y: 468 },
|
||||||
|
button: 0,
|
||||||
|
buttons: 0,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await expect
|
||||||
|
.poll(() =>
|
||||||
|
page.evaluate(() =>
|
||||||
|
window.fxnodeStandalone
|
||||||
|
.root!.getState()
|
||||||
|
.then((state) => state.links.filter((link) => link.toSocketId === "or:inputs").length),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toBe(4);
|
||||||
|
|
||||||
await page.evaluate(async () => {
|
await page.evaluate(async () => {
|
||||||
const root = window.fxnodeStandalone.root!;
|
const root = window.fxnodeStandalone.root!;
|
||||||
const source = (await root.getState()).nodes.find((node) => node.id === "c")!;
|
const source = (await root.getState()).nodes.find((node) => node.id === "c")!;
|
||||||
|
|||||||
Vendored
+8
@@ -497,6 +497,14 @@ test("multi-input sockets use pill hit bounds and stable per-link anchors", () =
|
|||||||
for (const [id, anchor] of socket.linkAnchors) {
|
for (const [id, anchor] of socket.linkAnchors) {
|
||||||
assert.deepEqual(snapshot.links.get(id)!.points.at(-1), anchor);
|
assert.deepEqual(snapshot.links.get(id)!.points.at(-1), anchor);
|
||||||
}
|
}
|
||||||
|
const next = planLink(
|
||||||
|
snapshot,
|
||||||
|
sources[0]!.sockets.find((candidate) => candidate.key === "mesh")!.id,
|
||||||
|
socket.id,
|
||||||
|
linkId("fresh-link"),
|
||||||
|
);
|
||||||
|
assert.equal(next?.type, "link.add");
|
||||||
|
if (next?.type === "link.add") assert.equal(next.link.id, linkId("fresh-link"));
|
||||||
assert.deepEqual(hitTest(snapshot, worldToView({ x: socket.anchor.x, y: socket.bounds!.y - 2 }, transform)), {
|
assert.deepEqual(hitTest(snapshot, worldToView({ x: socket.anchor.x, y: socket.bounds!.y - 2 }, transform)), {
|
||||||
kind: "socket",
|
kind: "socket",
|
||||||
id: socket.id,
|
id: socket.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user