@elicdavis/node-flow 0.2.2 → 0.2.4
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 +142 -103
- package/dist/index.d.ts +142 -103
- package/dist/index.js +201 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +198 -22
- package/dist/index.mjs.map +1 -1
- package/dist/web/NodeFlow.js +8 -0
- package/dist/web/NodeFlow.js.map +7 -0
- package/index.html +48 -50
- package/package.json +1 -1
- package/src/contextMenu.ts +3 -3
- package/src/graph.ts +39 -5
- package/src/index.ts +6 -6
- package/src/input.ts +16 -0
- package/src/markdown/syntaxParser.ts +2 -2
- package/src/node.ts +74 -2
- package/src/nodes/publisher.ts +6 -8
- package/src/nodes/subsystem.ts +43 -5
- package/src/port.ts +62 -1
- package/src/quickMenu.ts +11 -0
- package/src/theme.ts +3 -1
- package/test/markdown.test.ts +11 -0
- package/test/markdown_code_block.test.ts +21 -1
- package/test/mouse_leave.test.ts +70 -0
- package/test/node_hover.test.ts +69 -0
- package/test/node_menu_filter.test.ts +24 -0
- package/test/port.test.ts +49 -1
package/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="shortcut icon" href="#" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap"
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=VT323&display=swap"
|
|
12
12
|
rel="stylesheet">
|
|
13
13
|
<style>
|
|
14
14
|
* {
|
|
@@ -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";
|
|
@@ -253,6 +253,7 @@
|
|
|
253
253
|
},
|
|
254
254
|
Note: {
|
|
255
255
|
FontColor: "#ffffff",
|
|
256
|
+
CodeFontFamily: "VT323",
|
|
256
257
|
CodeBlock: { BackgroundColor: "#000000", FontColor: "#c0c0c0", BorderRadius: 0 },
|
|
257
258
|
},
|
|
258
259
|
},
|
|
@@ -352,46 +353,6 @@
|
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
|
|
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
356
|
const mathPublisher = new Publisher({
|
|
396
357
|
name: "Math",
|
|
397
358
|
description: "Nodes the live example evaluates",
|
|
@@ -745,7 +706,7 @@ inputs: [
|
|
|
745
706
|
|
|
746
707
|
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
708
|
|
|
748
|
-
|
|
709
|
+
A node with hidden ports gets a **more** line under them. Click it to show or hide them all.
|
|
749
710
|
|
|
750
711
|
\`\`\`
|
|
751
712
|
inputs: [
|
|
@@ -770,15 +731,52 @@ port.setHidden(false);
|
|
|
770
731
|
],
|
|
771
732
|
outputs: [{ name: "material", type: "material" }],
|
|
772
733
|
});
|
|
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
734
|
graph.connectNodes(sheen, 0, material, 5);
|
|
781
735
|
|
|
736
|
+
note(at(4, 0), `
|
|
737
|
+
# Widgets on ports
|
|
738
|
+
|
|
739
|
+
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.
|
|
740
|
+
|
|
741
|
+
Try wiring **Size** into **Radius**.
|
|
742
|
+
|
|
743
|
+
\`\`\`
|
|
744
|
+
inputs: [
|
|
745
|
+
{ name: "Radius", type: "float32",
|
|
746
|
+
widget: { type: "number", config:
|
|
747
|
+
{ property: "radius", value: 1 } } },
|
|
748
|
+
]
|
|
749
|
+
\`\`\`
|
|
750
|
+
|
|
751
|
+
The value lives in the node's **property**, so edit listeners and \`setProperty\` work as they do for any widget.
|
|
752
|
+
`);
|
|
753
|
+
const size = add({
|
|
754
|
+
title: "Size", position: at(4, 0, 0, 600),
|
|
755
|
+
outputs: [{ name: "value", type: "float32" }],
|
|
756
|
+
widgets: [{ type: "number", config: { value: 2.5, label: "Value" } }],
|
|
757
|
+
});
|
|
758
|
+
const circle = add({
|
|
759
|
+
title: "Circle", subTitle: "Widgets on ports", position: at(4, 0, 330, 540),
|
|
760
|
+
inputs: [
|
|
761
|
+
{ name: "Radius", type: "float32", widget: { type: "number", config: { property: "radius", value: 1, width: 90 } } },
|
|
762
|
+
{ name: "Segments", type: "int", widget: { type: "number", config: { property: "segments", value: 32, integer: true, min: 3, width: 90 } } },
|
|
763
|
+
{
|
|
764
|
+
name: "Center", type: "vector2", widget: {
|
|
765
|
+
type: "row", config: {
|
|
766
|
+
widgets: ["X", "Y"].map((axis) => ({ type: "number", config: { property: axis.toLowerCase(), label: axis, value: 0, width: 64 } })),
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
{ name: "Fill", type: "color", widget: { type: "color", config: { property: "fill", value: "#3fa7d6", width: 90 } } },
|
|
771
|
+
{ name: "Filled", type: "bool", widget: { type: "toggle", config: { property: "filled", value: true, label: "", width: 60 } } },
|
|
772
|
+
{ name: "Name", type: "string", widget: { type: "string", config: { property: "name", value: "circle", width: 120 } } },
|
|
773
|
+
],
|
|
774
|
+
outputs: [{ name: "shape", type: "shape" }],
|
|
775
|
+
});
|
|
776
|
+
logEdits(circle, "radius", "segments", "x", "y", "fill", "filled", "name");
|
|
777
|
+
circle.inputPort(0).addConnectionAddedListener(() => log("Circle", "Radius wired, its widget steps aside"));
|
|
778
|
+
circle.inputPort(0).addConnectionRemovedListener(() => log("Circle", "Radius unwired, its widget is back"));
|
|
779
|
+
|
|
782
780
|
note(at(0, 1), `
|
|
783
781
|
# Widgets
|
|
784
782
|
|
package/package.json
CHANGED
package/src/contextMenu.ts
CHANGED
|
@@ -261,11 +261,11 @@ export class ContextMenu {
|
|
|
261
261
|
|
|
262
262
|
#bounds: Box | null = null;
|
|
263
263
|
|
|
264
|
-
|
|
264
|
+
public contains(point: Vector2): boolean {
|
|
265
265
|
if (this.#bounds !== null && InBox(this.#bounds, point)) {
|
|
266
266
|
return true;
|
|
267
267
|
}
|
|
268
|
-
return this.#openSubMenu !== undefined && this.#openSubMenu
|
|
268
|
+
return this.#openSubMenu !== undefined && this.#openSubMenu.contains(point);
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
/**
|
|
@@ -315,7 +315,7 @@ export class ContextMenu {
|
|
|
315
315
|
Size: { x: scaledEntryWidth, y: totalScaledHeight },
|
|
316
316
|
};
|
|
317
317
|
|
|
318
|
-
const pointer = mousePosition !== undefined && this.#openSubMenu !== undefined && this.#openSubMenu
|
|
318
|
+
const pointer = mousePosition !== undefined && this.#openSubMenu !== undefined && this.#openSubMenu.contains(mousePosition)
|
|
319
319
|
? undefined
|
|
320
320
|
: mousePosition;
|
|
321
321
|
|
package/src/graph.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { Clamp01 } from "./utils/math";
|
|
|
10
10
|
import { GraphSubsystem, RenderResults } from './graphSubsystem';
|
|
11
11
|
import { FlowNote } from "./notes/note";
|
|
12
12
|
import { NoteAddedCallback, NoteDragStartCallback, NoteDragStopCallback, NoteRemovedCallback, NoteSubsystem, NoteSubsystemConfig } from "./notes/subsystem";
|
|
13
|
-
import { ConnectionRendererConfiguration, DanglingConnection, InternalConnection, NodeAddedCallback, NodeRemovedCallback, NodeSubsystem } from "./nodes/subsystem";
|
|
13
|
+
import { ConnectionRendererConfiguration, DanglingConnection, InternalConnection, NodeAddedCallback, NodeHoverCallback, NodeRemovedCallback, NodeSubsystem } from "./nodes/subsystem";
|
|
14
14
|
import { Connection } from './connection';
|
|
15
15
|
import { Publisher } from './nodes/publisher';
|
|
16
16
|
import { VectorPool } from './types/pool';
|
|
@@ -21,6 +21,7 @@ import { QuickMenu } from './quickMenu';
|
|
|
21
21
|
import { onRenderRequested, renderContinuously, requestRender } from './render_scheduler';
|
|
22
22
|
import { placeEditsDuring } from './widgets/inplace';
|
|
23
23
|
import { Minimap, MinimapConfig } from './minimap';
|
|
24
|
+
import { Cast } from './port';
|
|
24
25
|
|
|
25
26
|
export type GraphRenderer = (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, position: Vector2, scale: number) => void;
|
|
26
27
|
|
|
@@ -65,6 +66,7 @@ export interface FlowNodeGraphConfiguration {
|
|
|
65
66
|
idleConnection?: ConnectionRendererConfiguration
|
|
66
67
|
contextMenu?: ContextMenuConfig
|
|
67
68
|
nodes?: NodeFactoryConfig
|
|
69
|
+
casts?: Array<Cast>
|
|
68
70
|
board?: NoteSubsystemConfig
|
|
69
71
|
minimap?: MinimapConfig
|
|
70
72
|
}
|
|
@@ -164,6 +166,8 @@ export class NodeFlowGraph {
|
|
|
164
166
|
|
|
165
167
|
#mousePosition: Vector2 | undefined;
|
|
166
168
|
|
|
169
|
+
#mouseOverCanvas = false;
|
|
170
|
+
|
|
167
171
|
#camera: Camera;
|
|
168
172
|
|
|
169
173
|
#openedContextMenu: OpenContextMenu | null;
|
|
@@ -185,7 +189,8 @@ export class NodeFlowGraph {
|
|
|
185
189
|
|
|
186
190
|
this.#mainNodeSubsystem = new NodeSubsystem(postProcessPass, {
|
|
187
191
|
nodes: config?.nodes,
|
|
188
|
-
idleConnection: config?.idleConnection
|
|
192
|
+
idleConnection: config?.idleConnection,
|
|
193
|
+
casts: config?.casts,
|
|
189
194
|
});
|
|
190
195
|
|
|
191
196
|
this.#lastFrameCursor = CursorStyle.Default;
|
|
@@ -245,6 +250,7 @@ export class NodeFlowGraph {
|
|
|
245
250
|
this.#mouseDragEvent.bind(this),
|
|
246
251
|
(mousePosition) => {
|
|
247
252
|
this.#mousePosition = mousePosition;
|
|
253
|
+
this.#mouseOverCanvas = true;
|
|
248
254
|
},
|
|
249
255
|
this.#clickStart.bind(this),
|
|
250
256
|
this.#clickEnd.bind(this),
|
|
@@ -252,6 +258,9 @@ export class NodeFlowGraph {
|
|
|
252
258
|
this.#fileDrop.bind(this),
|
|
253
259
|
(factor, anchor) => {
|
|
254
260
|
this.#zoomBy(factor, anchor);
|
|
261
|
+
},
|
|
262
|
+
() => {
|
|
263
|
+
this.#mouseOverCanvas = false;
|
|
255
264
|
}
|
|
256
265
|
);
|
|
257
266
|
|
|
@@ -455,6 +464,7 @@ export class NodeFlowGraph {
|
|
|
455
464
|
this.#contextMenuEntryHovering = null;
|
|
456
465
|
|
|
457
466
|
this.#mousePosition = mousePosition;
|
|
467
|
+
this.#mouseOverCanvas = true;
|
|
458
468
|
|
|
459
469
|
if (this.#minimap.contains(mousePosition)) {
|
|
460
470
|
this.#lookAtFromMinimap(mousePosition);
|
|
@@ -494,6 +504,10 @@ export class NodeFlowGraph {
|
|
|
494
504
|
this.#mainNodeSubsystem.addPublisher(identifier, publisher);
|
|
495
505
|
}
|
|
496
506
|
|
|
507
|
+
setCasts(casts: Array<Cast>): void {
|
|
508
|
+
this.#mainNodeSubsystem.setCasts(casts);
|
|
509
|
+
}
|
|
510
|
+
|
|
497
511
|
getNodes(): Array<FlowNode> {
|
|
498
512
|
return this.#mainNodeSubsystem.getNodes();
|
|
499
513
|
}
|
|
@@ -643,6 +657,10 @@ export class NodeFlowGraph {
|
|
|
643
657
|
this.#mainNodeSubsystem.addOnNodeRemovedListener(callback);
|
|
644
658
|
}
|
|
645
659
|
|
|
660
|
+
public addNodeHoverListener(callback: NodeHoverCallback): void {
|
|
661
|
+
this.#mainNodeSubsystem.addNodeHoverListener(callback);
|
|
662
|
+
}
|
|
663
|
+
|
|
646
664
|
#sceenPositionToGraphPosition(screenPosition: Vector2): Vector2 {
|
|
647
665
|
const out = Zero();
|
|
648
666
|
this.#camera.screenSpaceToGraphSpace(screenPosition, out);
|
|
@@ -674,6 +692,7 @@ export class NodeFlowGraph {
|
|
|
674
692
|
{
|
|
675
693
|
needs: dangling.needs,
|
|
676
694
|
types: dangling.types,
|
|
695
|
+
casts: this.#mainNodeSubsystem.casts(),
|
|
677
696
|
onCreated: dangling.connectTo,
|
|
678
697
|
},
|
|
679
698
|
);
|
|
@@ -767,7 +786,7 @@ export class NodeFlowGraph {
|
|
|
767
786
|
|
|
768
787
|
TimeExecution("Render_View_" + this.#currentView, () => {
|
|
769
788
|
placeEditsDuring(this.#canvas, () => {
|
|
770
|
-
let results = this.currentView().render(this.#ctx, this.#camera, this.#
|
|
789
|
+
let results = this.currentView().render(this.#ctx, this.#camera, this.#pointerOnGraph());
|
|
771
790
|
if (results?.cursorStyle) {
|
|
772
791
|
this.#cursor = results?.cursorStyle;
|
|
773
792
|
}
|
|
@@ -790,6 +809,21 @@ export class NodeFlowGraph {
|
|
|
790
809
|
this.#lastFrameCursor = this.#cursor;
|
|
791
810
|
}
|
|
792
811
|
|
|
812
|
+
#pointer(): Vector2 | undefined {
|
|
813
|
+
return this.#mouseOverCanvas ? this.#mousePosition : undefined;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
#pointerOnGraph(): Vector2 | undefined {
|
|
817
|
+
const pointer = this.#pointer();
|
|
818
|
+
if (pointer === undefined) {
|
|
819
|
+
return undefined;
|
|
820
|
+
}
|
|
821
|
+
if (this.#openedContextMenu?.Menu.contains(pointer) || this.#openQuickMenu?.Menu.contains(pointer)) {
|
|
822
|
+
return undefined;
|
|
823
|
+
}
|
|
824
|
+
return pointer;
|
|
825
|
+
}
|
|
826
|
+
|
|
793
827
|
#renderBackground(): void {
|
|
794
828
|
this.#backgroundRenderer(this.#canvas, this.#ctx, this.#camera.position, this.#camera.zoom);
|
|
795
829
|
}
|
|
@@ -799,7 +833,7 @@ export class NodeFlowGraph {
|
|
|
799
833
|
if (this.#openedContextMenu !== null) {
|
|
800
834
|
const pos = VectorPool.get();
|
|
801
835
|
this.#camera.graphSpaceToScreenSpace(this.#openedContextMenu.Position, pos)
|
|
802
|
-
this.#contextMenuEntryHovering = this.#openedContextMenu.Menu.render(this.#ctx, pos, this.#camera.zoom, this.#
|
|
836
|
+
this.#contextMenuEntryHovering = this.#openedContextMenu.Menu.render(this.#ctx, pos, this.#camera.zoom, this.#pointer(), true);
|
|
803
837
|
|
|
804
838
|
if (this.#contextMenuEntryHovering !== null) {
|
|
805
839
|
this.#cursor = CursorStyle.Pointer;
|
|
@@ -813,7 +847,7 @@ export class NodeFlowGraph {
|
|
|
813
847
|
if (this.#openQuickMenu !== null) {
|
|
814
848
|
const pos = VectorPool.get();
|
|
815
849
|
this.#camera.graphSpaceToScreenSpace(this.#openQuickMenu.Position, pos)
|
|
816
|
-
this.#contextMenuEntryHovering = this.#openQuickMenu.Menu.render(this.#ctx, pos, this.#camera.zoom, this.#
|
|
850
|
+
this.#contextMenuEntryHovering = this.#openQuickMenu.Menu.render(this.#ctx, pos, this.#camera.zoom, this.#pointer());
|
|
817
851
|
|
|
818
852
|
if (this.#contextMenuEntryHovering !== null) {
|
|
819
853
|
this.#cursor = CursorStyle.Pointer;
|
package/src/index.ts
CHANGED
|
@@ -24,8 +24,8 @@ export {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
// Ports
|
|
27
|
-
import { Port, PortConfig, PortStyle, PortType, portsCompatible } from './port';
|
|
28
|
-
export { Port, PortConfig, PortStyle, PortType, portsCompatible };
|
|
27
|
+
import { Cast, Port, PortConfig, PortStyle, PortType, portsCompatible, portsConnect } from './port';
|
|
28
|
+
export { Cast, Port, PortConfig, PortStyle, PortType, portsCompatible, portsConnect };
|
|
29
29
|
|
|
30
30
|
// Rendering
|
|
31
31
|
import { renderContinuously, requestRender } from './render_scheduler';
|
|
@@ -34,14 +34,14 @@ export { renderContinuously, requestRender };
|
|
|
34
34
|
import { Minimap, MinimapConfig } from './minimap';
|
|
35
35
|
export { Minimap, MinimapConfig };
|
|
36
36
|
|
|
37
|
-
import { DanglingConnection, InternalConnection } from './nodes/subsystem';
|
|
38
|
-
export { DanglingConnection, InternalConnection };
|
|
37
|
+
import { DanglingConnection, InternalConnection, NodeHoverCallback } from './nodes/subsystem';
|
|
38
|
+
export { DanglingConnection, InternalConnection, NodeHoverCallback };
|
|
39
39
|
|
|
40
40
|
import { NodeMenuFilter } from './nodes/publisher';
|
|
41
41
|
export { NodeMenuFilter };
|
|
42
42
|
|
|
43
|
-
import { portConfigTypes, typeSetsCompatible } from './port';
|
|
44
|
-
export { portConfigTypes, typeSetsCompatible };
|
|
43
|
+
import { portConfigTypes, portTypeColor, typeSetsCompatible, typeSetsConnect } from './port';
|
|
44
|
+
export { portConfigTypes, portTypeColor, typeSetsCompatible, typeSetsConnect };
|
|
45
45
|
|
|
46
46
|
// Connections
|
|
47
47
|
import {
|
package/src/input.ts
CHANGED
|
@@ -19,6 +19,8 @@ export class MouseObserver {
|
|
|
19
19
|
#clickStop: () => void;
|
|
20
20
|
#contextMenu: (position: Vector2) => void;
|
|
21
21
|
#zoomCallback: (factor: number, anchor: Vector2) => void;
|
|
22
|
+
#leaveCallback: (() => void) | undefined;
|
|
23
|
+
#inside = false;
|
|
22
24
|
|
|
23
25
|
#longPressTimer: ReturnType<typeof setTimeout> | null = null;
|
|
24
26
|
#longPressOrigin: Vector2 = Zero();
|
|
@@ -35,6 +37,7 @@ export class MouseObserver {
|
|
|
35
37
|
contextMenu: (position: Vector2) => void,
|
|
36
38
|
fileDrop: (file: File) => void,
|
|
37
39
|
zoomCallback: (factor: number, anchor: Vector2) => void,
|
|
40
|
+
leaveCallback?: () => void,
|
|
38
41
|
) {
|
|
39
42
|
this.#ele = ele;
|
|
40
43
|
this.#dragCallback = dragCallback;
|
|
@@ -43,6 +46,7 @@ export class MouseObserver {
|
|
|
43
46
|
this.#clickStop = clickStop;
|
|
44
47
|
this.#contextMenu = contextMenu;
|
|
45
48
|
this.#zoomCallback = zoomCallback;
|
|
49
|
+
this.#leaveCallback = leaveCallback;
|
|
46
50
|
|
|
47
51
|
this.#clicked = false;
|
|
48
52
|
this.#lastTouch = Zero();
|
|
@@ -64,6 +68,12 @@ export class MouseObserver {
|
|
|
64
68
|
document.addEventListener("touchcancel", redraws(this.#touchEnd.bind(this)), false);
|
|
65
69
|
|
|
66
70
|
ele.addEventListener("mousemove", redraws(this.#move.bind(this)), false);
|
|
71
|
+
ele.addEventListener("mouseleave", redraws(() => {
|
|
72
|
+
this.#inside = false;
|
|
73
|
+
if (!this.#clicked) {
|
|
74
|
+
this.#leaveCallback?.();
|
|
75
|
+
}
|
|
76
|
+
}), false);
|
|
67
77
|
ele.addEventListener("touchmove", redraws(this.#touchMove.bind(this)), { passive: false });
|
|
68
78
|
|
|
69
79
|
ele.addEventListener("wheel", redraws(this.#wheel.bind(this)), { passive: false });
|
|
@@ -144,6 +154,7 @@ export class MouseObserver {
|
|
|
144
154
|
if (this.#fromTouch()) {
|
|
145
155
|
return;
|
|
146
156
|
}
|
|
157
|
+
this.#inside = true;
|
|
147
158
|
const pos = this.#mousePos(event);
|
|
148
159
|
if (this.#clicked) {
|
|
149
160
|
const delta = Zero();
|
|
@@ -158,6 +169,7 @@ export class MouseObserver {
|
|
|
158
169
|
if (event.button !== 0 || this.#fromTouch()) {
|
|
159
170
|
return;
|
|
160
171
|
}
|
|
172
|
+
this.#inside = true;
|
|
161
173
|
this.#clicked = true;
|
|
162
174
|
this.#clickStart(this.#mousePos(event), event.ctrlKey || event.shiftKey);
|
|
163
175
|
}
|
|
@@ -166,8 +178,12 @@ export class MouseObserver {
|
|
|
166
178
|
if (event.button !== 0 || this.#fromTouch()) {
|
|
167
179
|
return;
|
|
168
180
|
}
|
|
181
|
+
const wasClicked = this.#clicked;
|
|
169
182
|
this.#clicked = false;
|
|
170
183
|
this.#clickStop();
|
|
184
|
+
if (wasClicked && !this.#inside) {
|
|
185
|
+
this.#leaveCallback?.();
|
|
186
|
+
}
|
|
171
187
|
}
|
|
172
188
|
|
|
173
189
|
#touchStart(event: TouchEvent): void {
|
|
@@ -150,7 +150,7 @@ export class MarkdownSyntaxParser {
|
|
|
150
150
|
this.#inc();
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
return [new Text(content, { font: "monospace" })];
|
|
153
|
+
return [new Text(content, { font: Theme.Note.CodeFontFamily ?? "monospace" })];
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
#text(): Array<Text> {
|
|
@@ -315,7 +315,7 @@ export class MarkdownSyntaxParser {
|
|
|
315
315
|
this.#inc(); // puts us on the next token
|
|
316
316
|
|
|
317
317
|
const codeBlockText = this.#originalText.substring(start as number, end).replace(/\r?\n$/, "");
|
|
318
|
-
const entryText = new Text(codeBlockText);
|
|
318
|
+
const entryText = new Text(codeBlockText, { font: Theme.Note.CodeFontFamily ?? undefined });
|
|
319
319
|
this.#assignStandardStyling([entryText])
|
|
320
320
|
entryText.setColor(Theme.Note.CodeBlock.FontColor);
|
|
321
321
|
return new CodeBlockEntry(entryText)
|
package/src/node.ts
CHANGED
|
@@ -237,12 +237,16 @@ export class FlowNode {
|
|
|
237
237
|
|
|
238
238
|
#selected: boolean;
|
|
239
239
|
|
|
240
|
+
#highlighted = false;
|
|
241
|
+
|
|
240
242
|
#inputPortPositions: List<Box>;
|
|
241
243
|
|
|
242
244
|
#outputPortPositions: List<Box>;
|
|
243
245
|
|
|
244
246
|
#widgetPositions: List<Box>;
|
|
245
247
|
|
|
248
|
+
#portWidgetPositions: List<Box> = new List<Box>();
|
|
249
|
+
|
|
246
250
|
#data: Metadata;
|
|
247
251
|
|
|
248
252
|
#registeredAnyPropertyChangeCallbacks: Array<AnyPropertyChangeCallback>;
|
|
@@ -366,6 +370,18 @@ export class FlowNode {
|
|
|
366
370
|
}
|
|
367
371
|
}
|
|
368
372
|
|
|
373
|
+
public highlighted(): boolean {
|
|
374
|
+
return this.#highlighted;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
public setHighlighted(highlighted: boolean): void {
|
|
378
|
+
if (this.#highlighted === highlighted) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
this.#highlighted = highlighted;
|
|
382
|
+
requestRender();
|
|
383
|
+
}
|
|
384
|
+
|
|
369
385
|
public raiseDragStoppedEvent() {
|
|
370
386
|
for (let i = 0; i < this.#onDragStop.length; i++) {
|
|
371
387
|
this.#onDragStop[i](this);
|
|
@@ -802,7 +818,7 @@ export class FlowNode {
|
|
|
802
818
|
}
|
|
803
819
|
this.#portTextStyle.measure(ctx, 1, port.getDisplayName(), tempMeasurement);
|
|
804
820
|
size.y += this.#portRowHeight(port, tempMeasurement.y) + this.#elementSpacing;
|
|
805
|
-
size.x = Math.max(size.x, tempMeasurement.x + doublePadding)
|
|
821
|
+
size.x = Math.max(size.x, this.#portRowWidth(port, tempMeasurement.x) + doublePadding)
|
|
806
822
|
}
|
|
807
823
|
|
|
808
824
|
const widgets = this.#hasHiddenPortsToggle() ? [this.#hiddenPortsToggle, ...this.#widgets] : this.#widgets;
|
|
@@ -829,6 +845,9 @@ export class FlowNode {
|
|
|
829
845
|
|
|
830
846
|
addInput(config: PortConfig): Port {
|
|
831
847
|
const port = new Port(this, config.array ? PortType.InputArray : PortType.Input, config);
|
|
848
|
+
if (config.widget !== undefined) {
|
|
849
|
+
port.setWidget(GlobalWidgetFactory.createFromConfig(this, config.widget));
|
|
850
|
+
}
|
|
832
851
|
this.#input.push(port);
|
|
833
852
|
return port;
|
|
834
853
|
}
|
|
@@ -980,6 +999,18 @@ export class FlowNode {
|
|
|
980
999
|
}
|
|
981
1000
|
}
|
|
982
1001
|
|
|
1002
|
+
for (let i = 0; i < this.#portWidgetPositions.Count(); i++) {
|
|
1003
|
+
const port = this.#input[i];
|
|
1004
|
+
if (port !== undefined && port.widgetShown() && InBox(this.#portWidgetPositions.At(i), position)) {
|
|
1005
|
+
intersection.Node = this;
|
|
1006
|
+
intersection.Widget = port.widget() as Widget;
|
|
1007
|
+
intersection.WidgetBox = this.#portWidgetPositions.At(i);
|
|
1008
|
+
intersection.Port = undefined;
|
|
1009
|
+
intersection.PortIndex = undefined;
|
|
1010
|
+
intersection.PortIsInput = undefined;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
983
1014
|
return intersection;
|
|
984
1015
|
}
|
|
985
1016
|
|
|
@@ -1125,7 +1156,15 @@ export class FlowNode {
|
|
|
1125
1156
|
|
|
1126
1157
|
#portRowHeight(port: Port, textHeight: number): number {
|
|
1127
1158
|
const slots = port.slots();
|
|
1128
|
-
|
|
1159
|
+
const widget = port.widgetShown() ? (port.widget() as Widget).Size().y : 0;
|
|
1160
|
+
return Math.max(slots > 1 ? slots * ARRAY_SLOT_PITCH : 0, textHeight, widget);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
#portRowWidth(port: Port, textWidth: number): number {
|
|
1164
|
+
if (!port.widgetShown()) {
|
|
1165
|
+
return textWidth;
|
|
1166
|
+
}
|
|
1167
|
+
return textWidth + this.#elementSpacing + (port.widget() as Widget).Size().x;
|
|
1129
1168
|
}
|
|
1130
1169
|
|
|
1131
1170
|
public hasBeenLaidOut(): boolean {
|
|
@@ -1153,6 +1192,7 @@ export class FlowNode {
|
|
|
1153
1192
|
this.#inputPortPositions.Clear();
|
|
1154
1193
|
this.#outputPortPositions.Clear();
|
|
1155
1194
|
this.#widgetPositions.Clear();
|
|
1195
|
+
this.#portWidgetPositions.Clear();
|
|
1156
1196
|
const scaledPadding = this.#padding * camera.zoom;
|
|
1157
1197
|
const scaledElementSpacing = this.#elementSpacing * camera.zoom;
|
|
1158
1198
|
|
|
@@ -1162,6 +1202,21 @@ export class FlowNode {
|
|
|
1162
1202
|
const nodeStyle = this.#calculateStyle(state);
|
|
1163
1203
|
nodeStyle.Draw(ctx, nodeBounds, camera.zoom);
|
|
1164
1204
|
|
|
1205
|
+
if (this.#highlighted) {
|
|
1206
|
+
const gap = 3;
|
|
1207
|
+
ctx.save();
|
|
1208
|
+
ctx.strokeStyle = Theme.Node.Border.Highlighted;
|
|
1209
|
+
ctx.lineWidth = 2;
|
|
1210
|
+
ctx.beginPath();
|
|
1211
|
+
ctx.roundRect(
|
|
1212
|
+
nodeBounds.Position.x - gap, nodeBounds.Position.y - gap,
|
|
1213
|
+
nodeBounds.Size.x + gap * 2, nodeBounds.Size.y + gap * 2,
|
|
1214
|
+
nodeStyle.radius() * camera.zoom + gap
|
|
1215
|
+
);
|
|
1216
|
+
ctx.stroke();
|
|
1217
|
+
ctx.restore();
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1165
1220
|
// Title
|
|
1166
1221
|
const borderSize = nodeStyle.borderSize();
|
|
1167
1222
|
ctx.textAlign = TextAlign.Center;
|
|
@@ -1293,6 +1348,7 @@ export class FlowNode {
|
|
|
1293
1348
|
const port = this.#input[i];
|
|
1294
1349
|
if (port.hidden()) {
|
|
1295
1350
|
this.#inputPortPositions.Push({ Position: { x: nodeBounds.Position.x, y: startY }, Size: { x: 0, y: 0 } });
|
|
1351
|
+
this.#portWidgetPositions.Push({ Position: { x: nodeBounds.Position.x, y: startY }, Size: { x: 0, y: 0 } });
|
|
1296
1352
|
continue;
|
|
1297
1353
|
}
|
|
1298
1354
|
this.#portTextStyle.measure(ctx, 1, port.getDisplayName(), tempMeasurement);
|
|
@@ -1306,6 +1362,21 @@ export class FlowNode {
|
|
|
1306
1362
|
this.#portTextStyle.setupStyle(ctx, camera.zoom);
|
|
1307
1363
|
ctx.fillText(port.getDisplayName(), leftSide, position.y);
|
|
1308
1364
|
|
|
1365
|
+
// Widget
|
|
1366
|
+
if (port.widgetShown()) {
|
|
1367
|
+
const widget = port.widget() as Widget;
|
|
1368
|
+
const widgetHeight = widget.Size().y * camera.zoom;
|
|
1369
|
+
const widgetLeft = leftSide + (tempMeasurement.x * camera.zoom) + scaledElementSpacing;
|
|
1370
|
+
const box: Box = {
|
|
1371
|
+
Position: { x: widgetLeft, y: startY + ((portHeight - widgetHeight) / 2) },
|
|
1372
|
+
Size: { x: nodeBounds.Position.x + nodeBounds.Size.x - scaledPadding - widgetLeft, y: widgetHeight },
|
|
1373
|
+
};
|
|
1374
|
+
widget.Draw(ctx, box, camera.zoom, mousePosition);
|
|
1375
|
+
this.#portWidgetPositions.Push(box);
|
|
1376
|
+
} else {
|
|
1377
|
+
this.#portWidgetPositions.Push({ Position: { x: nodeBounds.Position.x, y: startY }, Size: { x: 0, y: 0 } });
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1309
1380
|
// Port
|
|
1310
1381
|
this.#inputPortPositions.Push(port.render(ctx, position, camera, mousePosition, postProcess));
|
|
1311
1382
|
|
|
@@ -1411,6 +1482,7 @@ export class FlowNode {
|
|
|
1411
1482
|
return false;
|
|
1412
1483
|
}
|
|
1413
1484
|
this.#widgetPositions.Clear();
|
|
1485
|
+
this.#portWidgetPositions.Clear();
|
|
1414
1486
|
this.#hiddenPortsTogglePosition = null;
|
|
1415
1487
|
return true;
|
|
1416
1488
|
}
|
package/src/nodes/publisher.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContextMenuConfig, ContextMenuItemConfig } from "../contextMenu";
|
|
2
2
|
import { FlowNode, FlowNodeConfig } from '../node';
|
|
3
|
-
import { PortConfig, PortType, portConfigTypes,
|
|
3
|
+
import { Cast, PortConfig, PortType, portConfigTypes, typeSetsConnect } from "../port";
|
|
4
4
|
import { Vector2 } from "../types/vector2";
|
|
5
5
|
import { NodeSubsystem } from "./subsystem";
|
|
6
6
|
|
|
@@ -15,18 +15,16 @@ export interface NodeMenuFilter {
|
|
|
15
15
|
/** Types that side has to accept. An empty list takes anything. */
|
|
16
16
|
types: Array<string>;
|
|
17
17
|
|
|
18
|
+
casts?: Array<Cast>;
|
|
19
|
+
|
|
18
20
|
onCreated?: (node: FlowNode) => void;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
function offersMatchingPort(config: FlowNodeConfig, filter: NodeMenuFilter): boolean {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (ports === undefined) {
|
|
26
|
-
return false;
|
|
24
|
+
if (filter.needs === PortType.Input) {
|
|
25
|
+
return (config.inputs ?? []).some((port) => typeSetsConnect(filter.types, portConfigTypes(port), filter.casts));
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
return ports.some((port) => typeSetsCompatible(portConfigTypes(port), filter.types));
|
|
27
|
+
return (config.outputs ?? []).some((port) => typeSetsConnect(portConfigTypes(port), filter.types, filter.casts));
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
interface PublisherNodes {
|