@elicdavis/node-flow 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +113 -102
- package/dist/index.d.ts +113 -102
- package/dist/index.js +55 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +55 -3
- package/dist/index.mjs.map +1 -1
- package/index.html +46 -49
- package/package.json +1 -1
- package/src/node.ts +45 -2
- package/src/port.ts +31 -1
package/index.html
CHANGED
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
import {
|
|
193
193
|
Theme, FlowNode, FlowNote, NodeFlowGraph, Publisher,
|
|
194
194
|
MessageType, ContextMenuItemState, DefaultConnectionRenderer,
|
|
195
|
-
|
|
195
|
+
ValueWidget, drawFieldBox, widgetFont,
|
|
196
196
|
NumberWidget, StringWidget, ToggleWidget, ButtonWidget, TextWidget, ImageWidget, RowWidget,
|
|
197
197
|
setTheme, onThemeChange,
|
|
198
198
|
} from "./dist/web/NodeFlow.js";
|
|
@@ -352,46 +352,6 @@
|
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
class MorePortsWidget extends WidgetBase {
|
|
356
|
-
#ports;
|
|
357
|
-
#open = false;
|
|
358
|
-
|
|
359
|
-
constructor(ports) {
|
|
360
|
-
super();
|
|
361
|
-
this.#ports = ports;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
Size() {
|
|
365
|
-
return { x: 150, y: 16 };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
render(ctx, box, scale) {
|
|
369
|
-
const waiting = this.#ports.filter((p) => p.connections().length === 0).length;
|
|
370
|
-
const label = this.#open ? "▴ less" : `▾ ${waiting} more`;
|
|
371
|
-
const y = box.Position.y + box.Size.y / 2;
|
|
372
|
-
const cx = box.Position.x + box.Size.x / 2;
|
|
373
|
-
ctx.font = widgetFont(scale * 0.8);
|
|
374
|
-
const half = ctx.measureText(label).width / 2 + 6 * scale;
|
|
375
|
-
ctx.globalAlpha = this.hovered() ? 1 : 0.55;
|
|
376
|
-
ctx.strokeStyle = ctx.fillStyle = Theme.Widget.FontColor;
|
|
377
|
-
ctx.lineWidth = Math.max(1, scale);
|
|
378
|
-
ctx.beginPath();
|
|
379
|
-
ctx.moveTo(box.Position.x, y);
|
|
380
|
-
ctx.lineTo(cx - half, y);
|
|
381
|
-
ctx.moveTo(cx + half, y);
|
|
382
|
-
ctx.lineTo(box.Position.x + box.Size.x, y);
|
|
383
|
-
ctx.stroke();
|
|
384
|
-
ctx.textAlign = "center";
|
|
385
|
-
ctx.textBaseline = "middle";
|
|
386
|
-
ctx.fillText(label, cx, y);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
onClick() {
|
|
390
|
-
this.#open = !this.#open;
|
|
391
|
-
this.#ports.forEach((p) => p.setHidden(!this.#open));
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
355
|
const mathPublisher = new Publisher({
|
|
396
356
|
name: "Math",
|
|
397
357
|
description: "Nodes the live example evaluates",
|
|
@@ -745,7 +705,7 @@ inputs: [
|
|
|
745
705
|
|
|
746
706
|
A port can start **hidden**, to keep busy nodes small. A hidden port still shows while something is wired to it, so no wire ever points at nothing.
|
|
747
707
|
|
|
748
|
-
|
|
708
|
+
A node with hidden ports gets a **more** line under them. Click it to show or hide them all.
|
|
749
709
|
|
|
750
710
|
\`\`\`
|
|
751
711
|
inputs: [
|
|
@@ -770,15 +730,52 @@ port.setHidden(false);
|
|
|
770
730
|
],
|
|
771
731
|
outputs: [{ name: "material", type: "material" }],
|
|
772
732
|
});
|
|
773
|
-
const hiddenPorts = [];
|
|
774
|
-
for (let i = 0; i < material.inputs(); i++) {
|
|
775
|
-
if (["Alpha", "Clearcoat", "Sheen", "Specular", "Unlit"].includes(material.inputPort(i).getDisplayName())) {
|
|
776
|
-
hiddenPorts.push(material.inputPort(i));
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
material.insertWidget(0, new MorePortsWidget(hiddenPorts));
|
|
780
733
|
graph.connectNodes(sheen, 0, material, 5);
|
|
781
734
|
|
|
735
|
+
note(at(4, 0), `
|
|
736
|
+
# Widgets on ports
|
|
737
|
+
|
|
738
|
+
An input can hold its own value in a **widget** on its row. The widget stands in for a wire, so it steps aside while something is connected and comes back when the wire is removed.
|
|
739
|
+
|
|
740
|
+
Try wiring **Size** into **Radius**.
|
|
741
|
+
|
|
742
|
+
\`\`\`
|
|
743
|
+
inputs: [
|
|
744
|
+
{ name: "Radius", type: "float32",
|
|
745
|
+
widget: { type: "number", config:
|
|
746
|
+
{ property: "radius", value: 1 } } },
|
|
747
|
+
]
|
|
748
|
+
\`\`\`
|
|
749
|
+
|
|
750
|
+
The value lives in the node's **property**, so edit listeners and \`setProperty\` work as they do for any widget.
|
|
751
|
+
`);
|
|
752
|
+
const size = add({
|
|
753
|
+
title: "Size", position: at(4, 0, 0, 600),
|
|
754
|
+
outputs: [{ name: "value", type: "float32" }],
|
|
755
|
+
widgets: [{ type: "number", config: { value: 2.5, label: "Value" } }],
|
|
756
|
+
});
|
|
757
|
+
const circle = add({
|
|
758
|
+
title: "Circle", subTitle: "Widgets on ports", position: at(4, 0, 330, 540),
|
|
759
|
+
inputs: [
|
|
760
|
+
{ name: "Radius", type: "float32", widget: { type: "number", config: { property: "radius", value: 1, width: 90 } } },
|
|
761
|
+
{ name: "Segments", type: "int", widget: { type: "number", config: { property: "segments", value: 32, integer: true, min: 3, width: 90 } } },
|
|
762
|
+
{
|
|
763
|
+
name: "Center", type: "vector2", widget: {
|
|
764
|
+
type: "row", config: {
|
|
765
|
+
widgets: ["X", "Y"].map((axis) => ({ type: "number", config: { property: axis.toLowerCase(), label: axis, value: 0, width: 64 } })),
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
{ name: "Fill", type: "color", widget: { type: "color", config: { property: "fill", value: "#3fa7d6", width: 90 } } },
|
|
770
|
+
{ name: "Filled", type: "bool", widget: { type: "toggle", config: { property: "filled", value: true, label: "", width: 60 } } },
|
|
771
|
+
{ name: "Name", type: "string", widget: { type: "string", config: { property: "name", value: "circle", width: 120 } } },
|
|
772
|
+
],
|
|
773
|
+
outputs: [{ name: "shape", type: "shape" }],
|
|
774
|
+
});
|
|
775
|
+
logEdits(circle, "radius", "segments", "x", "y", "fill", "filled", "name");
|
|
776
|
+
circle.inputPort(0).addConnectionAddedListener(() => log("Circle", "Radius wired, its widget steps aside"));
|
|
777
|
+
circle.inputPort(0).addConnectionRemovedListener(() => log("Circle", "Radius unwired, its widget is back"));
|
|
778
|
+
|
|
782
779
|
note(at(0, 1), `
|
|
783
780
|
# Widgets
|
|
784
781
|
|
package/package.json
CHANGED
package/src/node.ts
CHANGED
|
@@ -243,6 +243,8 @@ export class FlowNode {
|
|
|
243
243
|
|
|
244
244
|
#widgetPositions: List<Box>;
|
|
245
245
|
|
|
246
|
+
#portWidgetPositions: List<Box> = new List<Box>();
|
|
247
|
+
|
|
246
248
|
#data: Metadata;
|
|
247
249
|
|
|
248
250
|
#registeredAnyPropertyChangeCallbacks: Array<AnyPropertyChangeCallback>;
|
|
@@ -802,7 +804,7 @@ export class FlowNode {
|
|
|
802
804
|
}
|
|
803
805
|
this.#portTextStyle.measure(ctx, 1, port.getDisplayName(), tempMeasurement);
|
|
804
806
|
size.y += this.#portRowHeight(port, tempMeasurement.y) + this.#elementSpacing;
|
|
805
|
-
size.x = Math.max(size.x, tempMeasurement.x + doublePadding)
|
|
807
|
+
size.x = Math.max(size.x, this.#portRowWidth(port, tempMeasurement.x) + doublePadding)
|
|
806
808
|
}
|
|
807
809
|
|
|
808
810
|
const widgets = this.#hasHiddenPortsToggle() ? [this.#hiddenPortsToggle, ...this.#widgets] : this.#widgets;
|
|
@@ -829,6 +831,9 @@ export class FlowNode {
|
|
|
829
831
|
|
|
830
832
|
addInput(config: PortConfig): Port {
|
|
831
833
|
const port = new Port(this, config.array ? PortType.InputArray : PortType.Input, config);
|
|
834
|
+
if (config.widget !== undefined) {
|
|
835
|
+
port.setWidget(GlobalWidgetFactory.createFromConfig(this, config.widget));
|
|
836
|
+
}
|
|
832
837
|
this.#input.push(port);
|
|
833
838
|
return port;
|
|
834
839
|
}
|
|
@@ -980,6 +985,18 @@ export class FlowNode {
|
|
|
980
985
|
}
|
|
981
986
|
}
|
|
982
987
|
|
|
988
|
+
for (let i = 0; i < this.#portWidgetPositions.Count(); i++) {
|
|
989
|
+
const port = this.#input[i];
|
|
990
|
+
if (port !== undefined && port.widgetShown() && InBox(this.#portWidgetPositions.At(i), position)) {
|
|
991
|
+
intersection.Node = this;
|
|
992
|
+
intersection.Widget = port.widget() as Widget;
|
|
993
|
+
intersection.WidgetBox = this.#portWidgetPositions.At(i);
|
|
994
|
+
intersection.Port = undefined;
|
|
995
|
+
intersection.PortIndex = undefined;
|
|
996
|
+
intersection.PortIsInput = undefined;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
983
1000
|
return intersection;
|
|
984
1001
|
}
|
|
985
1002
|
|
|
@@ -1125,7 +1142,15 @@ export class FlowNode {
|
|
|
1125
1142
|
|
|
1126
1143
|
#portRowHeight(port: Port, textHeight: number): number {
|
|
1127
1144
|
const slots = port.slots();
|
|
1128
|
-
|
|
1145
|
+
const widget = port.widgetShown() ? (port.widget() as Widget).Size().y : 0;
|
|
1146
|
+
return Math.max(slots > 1 ? slots * ARRAY_SLOT_PITCH : 0, textHeight, widget);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
#portRowWidth(port: Port, textWidth: number): number {
|
|
1150
|
+
if (!port.widgetShown()) {
|
|
1151
|
+
return textWidth;
|
|
1152
|
+
}
|
|
1153
|
+
return textWidth + this.#elementSpacing + (port.widget() as Widget).Size().x;
|
|
1129
1154
|
}
|
|
1130
1155
|
|
|
1131
1156
|
public hasBeenLaidOut(): boolean {
|
|
@@ -1153,6 +1178,7 @@ export class FlowNode {
|
|
|
1153
1178
|
this.#inputPortPositions.Clear();
|
|
1154
1179
|
this.#outputPortPositions.Clear();
|
|
1155
1180
|
this.#widgetPositions.Clear();
|
|
1181
|
+
this.#portWidgetPositions.Clear();
|
|
1156
1182
|
const scaledPadding = this.#padding * camera.zoom;
|
|
1157
1183
|
const scaledElementSpacing = this.#elementSpacing * camera.zoom;
|
|
1158
1184
|
|
|
@@ -1293,6 +1319,7 @@ export class FlowNode {
|
|
|
1293
1319
|
const port = this.#input[i];
|
|
1294
1320
|
if (port.hidden()) {
|
|
1295
1321
|
this.#inputPortPositions.Push({ Position: { x: nodeBounds.Position.x, y: startY }, Size: { x: 0, y: 0 } });
|
|
1322
|
+
this.#portWidgetPositions.Push({ Position: { x: nodeBounds.Position.x, y: startY }, Size: { x: 0, y: 0 } });
|
|
1296
1323
|
continue;
|
|
1297
1324
|
}
|
|
1298
1325
|
this.#portTextStyle.measure(ctx, 1, port.getDisplayName(), tempMeasurement);
|
|
@@ -1306,6 +1333,21 @@ export class FlowNode {
|
|
|
1306
1333
|
this.#portTextStyle.setupStyle(ctx, camera.zoom);
|
|
1307
1334
|
ctx.fillText(port.getDisplayName(), leftSide, position.y);
|
|
1308
1335
|
|
|
1336
|
+
// Widget
|
|
1337
|
+
if (port.widgetShown()) {
|
|
1338
|
+
const widget = port.widget() as Widget;
|
|
1339
|
+
const widgetHeight = widget.Size().y * camera.zoom;
|
|
1340
|
+
const widgetLeft = leftSide + (tempMeasurement.x * camera.zoom) + scaledElementSpacing;
|
|
1341
|
+
const box: Box = {
|
|
1342
|
+
Position: { x: widgetLeft, y: startY + ((portHeight - widgetHeight) / 2) },
|
|
1343
|
+
Size: { x: nodeBounds.Position.x + nodeBounds.Size.x - scaledPadding - widgetLeft, y: widgetHeight },
|
|
1344
|
+
};
|
|
1345
|
+
widget.Draw(ctx, box, camera.zoom, mousePosition);
|
|
1346
|
+
this.#portWidgetPositions.Push(box);
|
|
1347
|
+
} else {
|
|
1348
|
+
this.#portWidgetPositions.Push({ Position: { x: nodeBounds.Position.x, y: startY }, Size: { x: 0, y: 0 } });
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1309
1351
|
// Port
|
|
1310
1352
|
this.#inputPortPositions.Push(port.render(ctx, position, camera, mousePosition, postProcess));
|
|
1311
1353
|
|
|
@@ -1411,6 +1453,7 @@ export class FlowNode {
|
|
|
1411
1453
|
return false;
|
|
1412
1454
|
}
|
|
1413
1455
|
this.#widgetPositions.Clear();
|
|
1456
|
+
this.#portWidgetPositions.Clear();
|
|
1414
1457
|
this.#hiddenPortsTogglePosition = null;
|
|
1415
1458
|
return true;
|
|
1416
1459
|
}
|
package/src/port.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { Box, InBox } from "./types/box";
|
|
|
11
11
|
import { Text } from "./types/text";
|
|
12
12
|
import { Vector2 } from "./types/vector2";
|
|
13
13
|
import { Color, HSV, HSV2RGB, RgbToHex } from './utils/color';
|
|
14
|
+
import type { Widget } from "./widgets/widget";
|
|
15
|
+
import type { WidgetConfig } from "./widgets/factory";
|
|
14
16
|
|
|
15
17
|
export enum PortType {
|
|
16
18
|
Input = "INPUT",
|
|
@@ -56,6 +58,12 @@ export interface PortConfig {
|
|
|
56
58
|
*/
|
|
57
59
|
hidden?: boolean;
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A widget drawn on the port's own row while nothing is connected to
|
|
63
|
+
* it, for a value the port holds on its own. Input ports only.
|
|
64
|
+
*/
|
|
65
|
+
widget?: WidgetConfig;
|
|
66
|
+
|
|
59
67
|
emptyStyle?: PortStyle;
|
|
60
68
|
filledStyle?: PortStyle;
|
|
61
69
|
onConnectionAdded?: ConnectionChangeCallback;
|
|
@@ -167,6 +175,8 @@ export class Port {
|
|
|
167
175
|
|
|
168
176
|
#onConnectionRemoved: Array<ConnectionChangeCallback>;
|
|
169
177
|
|
|
178
|
+
#widget: Widget | null = null;
|
|
179
|
+
|
|
170
180
|
constructor(node: FlowNode, portType: PortType, config?: PortConfig) {
|
|
171
181
|
this.#node = node;
|
|
172
182
|
this.#connections = new Array<Connection>();
|
|
@@ -339,11 +349,31 @@ export class Port {
|
|
|
339
349
|
}
|
|
340
350
|
|
|
341
351
|
#visibilityMayHaveChanged(was: boolean): void {
|
|
342
|
-
if (was !== this.hidden() || this.#portType === PortType.InputArray) {
|
|
352
|
+
if (was !== this.hidden() || this.#portType === PortType.InputArray || this.#widget !== null) {
|
|
343
353
|
this.#node?.invalidateLayout();
|
|
344
354
|
}
|
|
345
355
|
}
|
|
346
356
|
|
|
357
|
+
setWidget(widget: Widget | null): void {
|
|
358
|
+
this.#widget = widget;
|
|
359
|
+
this.#node?.invalidateLayout();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
widget(): Widget | null {
|
|
363
|
+
return this.#widget;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The widget stands in for a wire, so it leaves once one is connected.
|
|
368
|
+
*/
|
|
369
|
+
widgetShown(): boolean {
|
|
370
|
+
return this.#widget !== null
|
|
371
|
+
&& this.#portType !== PortType.Output
|
|
372
|
+
&& this.#connections.length === 0
|
|
373
|
+
&& !this.hidden()
|
|
374
|
+
&& !(this.#widget.Hidden?.() ?? false);
|
|
375
|
+
}
|
|
376
|
+
|
|
347
377
|
/**
|
|
348
378
|
* A port asked to hide still shows while anything is connected to it,
|
|
349
379
|
* and while its node is showing its hidden ports.
|