@elicdavis/node-flow 0.1.0

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.
Files changed (68) hide show
  1. package/.github/workflows/deploy.yml +57 -0
  2. package/README.md +159 -0
  3. package/dev.tsconfig.json +18 -0
  4. package/docs/promotional-image.png +0 -0
  5. package/esbuild.ts +32 -0
  6. package/index.html +311 -0
  7. package/package.json +50 -0
  8. package/src/camera.ts +30 -0
  9. package/src/connection.ts +249 -0
  10. package/src/contextMenu.ts +367 -0
  11. package/src/draggable.ts +13 -0
  12. package/src/elements/base.ts +192 -0
  13. package/src/elements/container.ts +119 -0
  14. package/src/elements/interfaces.ts +50 -0
  15. package/src/elements/text.ts +86 -0
  16. package/src/graph.ts +535 -0
  17. package/src/graphSubsystem.ts +31 -0
  18. package/src/index.ts +28 -0
  19. package/src/input.ts +153 -0
  20. package/src/markdown/entry.ts +323 -0
  21. package/src/markdown/lexicalParser.ts +148 -0
  22. package/src/markdown/markdown.ts +14 -0
  23. package/src/markdown/syntaxParser.ts +344 -0
  24. package/src/markdown/token.ts +43 -0
  25. package/src/metadata.ts +3 -0
  26. package/src/node.ts +1130 -0
  27. package/src/nodes/factory.ts +87 -0
  28. package/src/nodes/publisher.ts +98 -0
  29. package/src/nodes/subsystem.ts +760 -0
  30. package/src/notes/note.ts +295 -0
  31. package/src/notes/subsystem.ts +262 -0
  32. package/src/organize.ts +181 -0
  33. package/src/pass/subsystem.ts +64 -0
  34. package/src/performance.ts +9 -0
  35. package/src/popup.ts +81 -0
  36. package/src/popups/form.ts +63 -0
  37. package/src/popups/string.ts +33 -0
  38. package/src/port.ts +223 -0
  39. package/src/quickMenu.ts +330 -0
  40. package/src/styles/box.ts +104 -0
  41. package/src/styles/canvasTextAlign.ts +8 -0
  42. package/src/styles/canvasTextBaseline.ts +9 -0
  43. package/src/styles/cursor.ts +18 -0
  44. package/src/styles/stroke.ts +39 -0
  45. package/src/styles/text.ts +90 -0
  46. package/src/styles/textBox.ts +82 -0
  47. package/src/theme.ts +78 -0
  48. package/src/types/box.ts +126 -0
  49. package/src/types/list.ts +41 -0
  50. package/src/types/pool.ts +66 -0
  51. package/src/types/text.ts +136 -0
  52. package/src/types/vector2.ts +34 -0
  53. package/src/utils/color.ts +136 -0
  54. package/src/utils/html.ts +21 -0
  55. package/src/utils/math.ts +8 -0
  56. package/src/utils/string.ts +98 -0
  57. package/src/widgets/button.ts +107 -0
  58. package/src/widgets/color.ts +151 -0
  59. package/src/widgets/factory.ts +46 -0
  60. package/src/widgets/image.ts +163 -0
  61. package/src/widgets/number.ts +153 -0
  62. package/src/widgets/slider.ts +216 -0
  63. package/src/widgets/string.ts +128 -0
  64. package/src/widgets/text.ts +92 -0
  65. package/src/widgets/toggle.ts +205 -0
  66. package/src/widgets/widget.ts +12 -0
  67. package/tsconfig.json +15 -0
  68. package/tsup.config.ts +10 -0
@@ -0,0 +1,181 @@
1
+ import { Camera } from "./camera";
2
+ import { NodeFlowGraph } from "./graph";
3
+ import { FlowNode } from "./node";
4
+ import { NodeSubsystem } from "./nodes/subsystem";
5
+ import { Box } from "./types/box";
6
+
7
+ // TODO: Cyclical dependencies will end our life
8
+
9
+ function MarkInputs(graph: NodeSubsystem, positions: Array<number>, nodeLUT: Map<FlowNode, number>, node: number, depth: number, shouldSort: Map<FlowNode, boolean>) {
10
+ const inputs = graph.connectedInputsNodeReferencesByIndex(node);
11
+ for (let i = 0; i < inputs.length; i++) {
12
+ const nodeIndex = nodeLUT.get(inputs[i]) as number;
13
+
14
+ if (!shouldSort.has(inputs[i])) {
15
+ continue;
16
+ }
17
+
18
+ positions[nodeIndex] = depth;
19
+ MarkInputs(graph, positions, nodeLUT, nodeIndex, depth - 1, shouldSort);
20
+ }
21
+ }
22
+
23
+ function MarkOutputs(graph: NodeSubsystem, positions: Array<number>, nodeLUT: Map<FlowNode, number>, node: number, depth: number, shouldSort: Map<FlowNode, boolean>) {
24
+ const outputs = graph.connectedInputsNodeReferencesByIndex(node);
25
+ for (let i = 0; i < outputs.length; i++) {
26
+ const nodeIndex = nodeLUT.get(outputs[i]) as number;
27
+
28
+ if (!shouldSort.has(outputs[i])) {
29
+ continue;
30
+ }
31
+
32
+ positions[nodeIndex] = depth;
33
+ MarkOutputs(graph, positions, nodeLUT, nodeIndex, depth + 1, shouldSort);
34
+ }
35
+ }
36
+
37
+ export function Organize(ctx: CanvasRenderingContext2D, graph: NodeSubsystem, nodesToSort?: Array<number>): void {
38
+ const nodes = graph.getNodes();
39
+ const nodeLUT = new Map<FlowNode, number>();
40
+ const bounds = new Array<Box>(nodes.length);
41
+ const relativePosition = new Array<Array<number>>(nodes.length);
42
+ const claimed = new Array<boolean>(nodes.length);
43
+ const shouldSort = new Map<FlowNode, boolean>(); // TODO: we don't really need a Map, is their a Set data structure?
44
+
45
+ if (nodesToSort) {
46
+ if (nodesToSort.length < 2) {
47
+ return;
48
+ }
49
+ for (let i = 0; i < nodesToSort.length; i++) {
50
+ shouldSort.set(nodes[nodesToSort[i]], true);
51
+ }
52
+ } else {
53
+ for (let i = 0; i < nodes.length; i++) {
54
+ shouldSort.set(nodes[i], true);
55
+ }
56
+ }
57
+
58
+ const camera = new Camera();
59
+
60
+ // Initialize everything
61
+ for (let i = 0; i < nodes.length; i++) {
62
+ bounds[i] = nodes[i].calculateBounds(ctx, camera);
63
+ relativePosition[i] = new Array<number>(nodes.length);
64
+ nodeLUT.set(nodes[i], i);
65
+ claimed[i] = false;
66
+ }
67
+
68
+ for (let i = 0; i < nodes.length; i++) {
69
+ relativePosition[i][i] = 0;
70
+ MarkInputs(graph, relativePosition[i], nodeLUT, i, -1, shouldSort);
71
+ MarkOutputs(graph, relativePosition[i], nodeLUT, i, 1, shouldSort);
72
+ }
73
+
74
+ interface entry {
75
+ node: number;
76
+ length: number;
77
+ min: number;
78
+ max: number
79
+ }
80
+
81
+ let entries = new Array<entry>(shouldSort.size);
82
+
83
+ let nodeIndex = 0;
84
+ for (let i = 0; i < nodes.length; i++) {
85
+ if (!shouldSort.has(nodes[i])) {
86
+ continue;
87
+ }
88
+
89
+ let min = 0;
90
+ let max = 0;
91
+ for (let x = 0; x < nodes.length; x++) {
92
+ const val = relativePosition[i][x];
93
+ if (val === undefined) {
94
+ continue;
95
+ }
96
+
97
+ min = Math.min(min, val);
98
+ max = Math.max(max, val);
99
+ }
100
+ entries[nodeIndex] = {
101
+ length: max - min,
102
+ node: i,
103
+ min: min,
104
+ max: max
105
+ }
106
+ nodeIndex++
107
+ }
108
+
109
+
110
+ entries.sort((a, b) => b.length - a.length);
111
+
112
+ interface Column {
113
+ Nodes: Array<FlowNode>;
114
+ Width: number;
115
+ }
116
+
117
+ const columns = Array<Column>(entries[0].length + 1);
118
+ for (let i = 0; i < columns.length; i++) {
119
+ columns[i] = {
120
+ Nodes: new Array<FlowNode>(),
121
+ Width: 0
122
+ };
123
+ }
124
+
125
+ for (let i = 0; i < entries.length; i++) {
126
+ const entry = entries[i];
127
+ if (claimed[entry.node] === true) {
128
+ continue;
129
+ }
130
+
131
+ const positions = relativePosition[entry.node];
132
+
133
+ for (let p = 0; p < positions.length; p++) {
134
+ const position = positions[p];
135
+ if (position === undefined) {
136
+ continue;
137
+ }
138
+
139
+ if (claimed[p] === true) {
140
+ continue;
141
+ }
142
+
143
+ const nodeBounds = bounds[p];
144
+ const column = columns[position - entry.min];
145
+ column.Nodes.push(nodes[p])
146
+ column.Width = Math.max(column.Width, nodeBounds.Size.x)
147
+
148
+ claimed[p] = true;
149
+ }
150
+ }
151
+
152
+ let allColumnsWidths = 0;
153
+ for (let c = 0; c < columns.length; c++) {
154
+ allColumnsWidths += columns[c].Width;
155
+ }
156
+
157
+ const widthSpacing = 100;
158
+ const heightSpacing = 50;
159
+
160
+ let widthOffset = 0;
161
+ for (let c = 0; c < columns.length; c++) {
162
+ var column = columns[c];
163
+ let heightOffset = 0;
164
+
165
+ widthOffset -= widthSpacing + column.Width;
166
+
167
+ for (let n = 0; n < column.Nodes.length; n++) {
168
+ const node = column.Nodes[n];
169
+ const nodeBounds = bounds[nodeLUT.get(node) as number];
170
+
171
+ let pos = {
172
+ x: widthOffset + allColumnsWidths + (columns.length * widthSpacing),
173
+ y: heightOffset
174
+ }
175
+
176
+ heightOffset += nodeBounds.Size.y + heightSpacing
177
+ node.setPosition(pos);
178
+ }
179
+ }
180
+
181
+ }
@@ -0,0 +1,64 @@
1
+ import { Camera } from "../camera";
2
+ import { ContextMenuConfig } from "../contextMenu";
3
+ import { RenderResults } from "../graphSubsystem";
4
+ import { List } from "../types/list";
5
+ import { Vector2 } from "../types/vector2";
6
+
7
+ type RenderLambda = () => void
8
+
9
+
10
+ /**
11
+ * Generic subsystem meant for other sybsystems to pass whatever to to render
12
+ * at a certain time.
13
+ *
14
+ * Initial inspriation is for a subsytem to submit something to render on top
15
+ * of all other subsystems.
16
+ */
17
+ export class PassSubsystem {
18
+
19
+ #queue: List<RenderLambda>
20
+
21
+ constructor() {
22
+ this.#queue = new List<RenderLambda>();
23
+ }
24
+
25
+ queue(lambda: RenderLambda): void {
26
+ if(!lambda) {
27
+ return;
28
+ }
29
+ this.#queue.Push(lambda);
30
+ }
31
+
32
+ render(ctx: CanvasRenderingContext2D, camera: Camera, mousePosition: Vector2 | undefined): RenderResults | undefined {
33
+ for (let i = 0; i < this.#queue.Count(); i++) {
34
+ this.#queue.At(i)();
35
+ }
36
+ this.#queue.Clear();
37
+ return;
38
+ }
39
+
40
+ openContextMenu(ctx: CanvasRenderingContext2D, position: Vector2): ContextMenuConfig | null {
41
+ // Left intentionally blank
42
+ return null;
43
+ }
44
+
45
+ clickStart(mousePosition: Vector2, camera: Camera, ctrlKey: boolean): boolean {
46
+ // Left intentionally blank
47
+ return false;
48
+ }
49
+
50
+ mouseDragEvent(delta: Vector2, scale: number): boolean {
51
+ // Left intentionally blank
52
+ return false;
53
+ }
54
+
55
+ fileDrop(file: File): boolean {
56
+ // Left intentionally blank
57
+ return false;
58
+ }
59
+
60
+ clickEnd(): void {
61
+ // Left intentionally blank
62
+ }
63
+
64
+ }
@@ -0,0 +1,9 @@
1
+ export function TimeExecution(name: string, callback: Function) {
2
+ const start = name + "_Start";
3
+ const end = name + "_End";
4
+
5
+ performance.mark(start);
6
+ callback();
7
+ performance.mark(end);
8
+ performance.measure(name, start, end);
9
+ }
package/src/popup.ts ADDED
@@ -0,0 +1,81 @@
1
+ export interface PopupConfig {
2
+ title: string;
3
+ options: Array<string>;
4
+
5
+ onClose?: (button: string | null) => void;
6
+ content?: () => Element;
7
+ buttonCSS?: string;
8
+ }
9
+
10
+ export class Popup {
11
+
12
+ #title: string;
13
+
14
+ #options: Array<string>;
15
+
16
+ #content?: () => Element;
17
+
18
+ #onClose?: (button: string | null) => void;
19
+
20
+ // Styling ================================================================
21
+
22
+ #buttonCSS: string;
23
+
24
+ // Runtime ================================================================
25
+
26
+ #popup: HTMLDivElement | null;
27
+
28
+ constructor(config: PopupConfig) {
29
+ this.#title = config.title;
30
+ this.#options = config.options;
31
+ this.#content = config.content;
32
+ this.#onClose = config.onClose;
33
+ this.#buttonCSS = config.buttonCSS === undefined ? "flex-grow: 1; margin-right: 8px;" : config.buttonCSS;
34
+ this.#popup = null;
35
+ }
36
+
37
+ Hide(): void {
38
+ if (this.#popup === null) {
39
+ return;
40
+ }
41
+ document.body.removeChild(this.#popup);
42
+ this.#popup = null;
43
+ }
44
+
45
+ Show(): void {
46
+ this.#popup = document.createElement('div');
47
+ this.#popup.style.cssText = 'position:absolute;top:0;width:100%;display:flex;height:100%;justify-content:center;align-items:center;background-color:#00000070;';
48
+ document.body.appendChild(this.#popup);
49
+
50
+ const container = document.createElement('div');
51
+ container.style.cssText = 'background-color:#000;color:white;font-family:Verdana;border-radius:8px;padding:16px;';
52
+ this.#popup.appendChild(container);
53
+
54
+ const title = document.createElement("h2");
55
+ title.textContent = this.#title;
56
+ title.style.cssText = "margin-top:0;margin-bottom:16px;";
57
+ container.appendChild(title)
58
+
59
+ if (this.#content !== undefined) {
60
+ container.appendChild(this.#content());
61
+ }
62
+
63
+ const buttonContainer = document.createElement("div");
64
+ buttonContainer.style.cssText = 'margin-top:16px;justify-content:space-around;align-items:center;flex-direction:row;display:flex;';
65
+ container.appendChild(buttonContainer);
66
+
67
+ for (let i = 0; i < this.#options.length; i++) {
68
+ const button = document.createElement("button");
69
+ button.style.cssText = this.#buttonCSS;
70
+ button.textContent = this.#options[i];
71
+ button.onclick = () => {
72
+ this.Hide();
73
+ if (this.#onClose !== undefined) {
74
+ this.#onClose(this.#options[i]);
75
+ }
76
+ };
77
+ buttonContainer.appendChild(button);
78
+ }
79
+ }
80
+
81
+ }
@@ -0,0 +1,63 @@
1
+ import { Popup } from "../popup";
2
+
3
+ interface FormValue {
4
+ name: string;
5
+
6
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-type
7
+ type: string;
8
+
9
+ startingValue: any;
10
+ }
11
+
12
+ interface Config {
13
+ title: string;
14
+ form: Array<FormValue>;
15
+ onUpdate: (newValue: Array<any>) => void;
16
+ onCancel?: () => void;
17
+ }
18
+
19
+ export function FormPopup(config: Config): Popup {
20
+ let input = new Array<HTMLInputElement | null>();
21
+
22
+ const setOption = "Set";
23
+ const cancelOption = "Cancel";
24
+
25
+ return new Popup({
26
+ title: config.title,
27
+ options: [setOption, cancelOption],
28
+ content: () => {
29
+ const container = document.createElement('div');
30
+ container.style.flexDirection = "column";
31
+ container.style.display = "flex";
32
+
33
+ for (let i = 0; i < config.form.length; i++) {
34
+ const title = document.createElement("h4");
35
+ title.innerText = config.form[i].name;
36
+ container.append(title);
37
+
38
+ const ele = document.createElement('input');
39
+ ele.value = config.form[i].startingValue;
40
+ ele.type = config.form[i].type;
41
+ container.append(ele);
42
+ input.push(ele);
43
+ container.append(document.createElement("br"));
44
+ }
45
+
46
+ return container;
47
+ },
48
+ onClose: (button: string | null): void => {
49
+ if (button !== setOption || input === null) {
50
+ if (config.onCancel) {
51
+ config.onCancel();
52
+ }
53
+ return;
54
+ }
55
+
56
+ const values = new Array<any>();
57
+ for (let i = 0; i < config.form.length; i++) {
58
+ values[i] = input[i]?.value;
59
+ }
60
+ config.onUpdate(values);
61
+ },
62
+ });
63
+ }
@@ -0,0 +1,33 @@
1
+ import { Popup } from "../popup";
2
+
3
+ interface Config {
4
+ title: string;
5
+ startingValue: string;
6
+ onUpdate: (newValue: string) => void;
7
+ onCancel?: () => void;
8
+ }
9
+
10
+ export function SetStringPopup(config: Config): Popup {
11
+ let input: HTMLInputElement | null = null;
12
+
13
+ return new Popup({
14
+ title: config.title,
15
+ options: ["Set", "Cancel"],
16
+ content: () => {
17
+ const container = document.createElement('div');
18
+ input = document.createElement('input')
19
+ input.value = config.startingValue;
20
+ container.append(input);
21
+ return container;
22
+ },
23
+ onClose: (button: string | null): void => {
24
+ if (button !== "Set" || input === null) {
25
+ if(config.onCancel) {
26
+ config.onCancel();
27
+ }
28
+ return;
29
+ }
30
+ config.onUpdate(input.value);
31
+ },
32
+ });
33
+ }
package/src/port.ts ADDED
@@ -0,0 +1,223 @@
1
+ import { Camera } from "./camera";
2
+ import { Connection } from './connection';
3
+ import { FlowNode } from "./node";
4
+ import { PassSubsystem } from "./pass/subsystem";
5
+ import { TextAlign } from "./styles/canvasTextAlign";
6
+ import { Box, BoxIntersection, InBox } from "./types/box";
7
+ import { Vector2 } from "./types/vector2";
8
+ import { Color, HSV, HSV2RGB, RgbToHex } from "./utils/color";
9
+
10
+ export enum PortType {
11
+ Input = "INPUT",
12
+ Output = "OUTPUT",
13
+ InputArray = "INPUTARRAY",
14
+ }
15
+
16
+ export interface PortStyle {
17
+ size?: number;
18
+ fillColor?: string;
19
+ borderColor?: string;
20
+ borderSize?: number;
21
+ }
22
+
23
+ type ConnectionChangeCallback = (connection: Connection, connectionIndex: number, port: Port, portType: PortType, node: FlowNode) => void
24
+
25
+ export interface PortConfig {
26
+ name?: string;
27
+ type?: string;
28
+ array?: boolean;
29
+ emptyStyle?: PortStyle;
30
+ filledStyle?: PortStyle;
31
+ onConnectionAdded?: ConnectionChangeCallback;
32
+ onConnectionRemoved?: ConnectionChangeCallback;
33
+ };
34
+
35
+ // Calculate a color hash for an arbirary type
36
+ function fallbackColor(type: string, s: number): string {
37
+ let value = 0;
38
+ for (var i = 0; i < type.length; i++) {
39
+ value += type.charCodeAt(i) * (i + 1);
40
+ }
41
+
42
+ const mod = 24;
43
+ value = Math.round(value) % mod;
44
+
45
+ const hsv: HSV = { h: (value / (mod - 1)) * 360, s: s, v: 1 };
46
+ const color: Color = { r: 0, g: 0, b: 0 };
47
+ HSV2RGB(hsv, color);
48
+ return RgbToHex(color);
49
+ }
50
+
51
+ export class Port {
52
+
53
+ #node: FlowNode;
54
+
55
+ #displayName: string;
56
+
57
+ #emptyStyle: PortStyle;
58
+
59
+ #filledStyle: PortStyle;
60
+
61
+ #connections: Array<Connection>;
62
+
63
+ #portType: PortType
64
+
65
+ #dataType: string;
66
+
67
+ #onConnectionAdded: Array<ConnectionChangeCallback>;
68
+
69
+ #onConnectionRemoved: Array<ConnectionChangeCallback>;;
70
+
71
+ constructor(node: FlowNode, portType: PortType, config?: PortConfig) {
72
+ this.#node = node;
73
+ this.#connections = new Array<Connection>();
74
+ this.#portType = portType;
75
+ this.#displayName = config?.name === undefined ? "Port" : config?.name;
76
+ this.#dataType = config?.type === undefined ? "" : config?.type;
77
+
78
+ this.#emptyStyle = {
79
+ borderColor: config?.emptyStyle?.borderColor === undefined ? "#1c1c1c" : config.emptyStyle?.borderColor,
80
+ fillColor: config?.emptyStyle?.fillColor === undefined ? fallbackColor(this.#dataType, 0.3) : config.emptyStyle?.fillColor,
81
+ borderSize: config?.emptyStyle?.borderSize === undefined ? 1 : config.emptyStyle?.borderSize,
82
+ size: config?.emptyStyle?.size === undefined ? 4 : config.emptyStyle?.size
83
+ }
84
+
85
+ this.#filledStyle = {
86
+ borderColor: config?.filledStyle?.borderColor === undefined ? "#1c1c1c" : config.filledStyle?.borderColor,
87
+ fillColor: config?.filledStyle?.fillColor === undefined ? fallbackColor(this.#dataType, 1) : config.filledStyle?.fillColor,
88
+ borderSize: config?.filledStyle?.borderSize === undefined ? 1 : config.filledStyle?.borderSize,
89
+ size: config?.filledStyle?.size === undefined ? 5 : config.filledStyle?.size
90
+ }
91
+
92
+ this.#onConnectionAdded = new Array<ConnectionChangeCallback>();
93
+ if (config?.onConnectionAdded) {
94
+ this.#onConnectionAdded.push(config?.onConnectionAdded);
95
+ }
96
+
97
+ this.#onConnectionRemoved = new Array<ConnectionChangeCallback>();
98
+ if (config?.onConnectionRemoved) {
99
+ this.#onConnectionRemoved.push(config?.onConnectionRemoved);
100
+ }
101
+ }
102
+
103
+ addConnection(connection: Connection): void {
104
+ const c = this.#connections.length;
105
+ this.#connections.push(connection);
106
+ for (let i = 0; i < this.#onConnectionAdded.length; i++) {
107
+ this.#onConnectionAdded[i](connection, c, this, this.#portType, this.#node);
108
+ }
109
+ }
110
+
111
+ replaceConnection(connection: Connection, index: number): void {
112
+ const c = this.#connections.length;
113
+ this.#connections[index] = connection;
114
+ for (let i = 0; i < this.#onConnectionAdded.length; i++) {
115
+ this.#onConnectionAdded[i](connection, c, this, this.#portType, this.#node);
116
+ }
117
+ }
118
+
119
+ addConnectionAddedListener(callback: ConnectionChangeCallback) {
120
+ if (callback === undefined) {
121
+ return;
122
+ }
123
+ this.#onConnectionAdded.push(callback);
124
+ }
125
+
126
+ connections(): Array<Connection> {
127
+ return this.#connections;
128
+ }
129
+
130
+ addConnectionRemovedListener(callback: ConnectionChangeCallback) {
131
+ if (callback === undefined) {
132
+ return;
133
+ }
134
+ this.#onConnectionRemoved.push(callback);
135
+ }
136
+
137
+ clearConnection(connection: Connection): void {
138
+ const index = this.#connections.indexOf(connection);
139
+ if (index > -1) {
140
+ this.#connections.splice(index, 1);
141
+ for (let i = 0; i < this.#onConnectionRemoved.length; i++) {
142
+ this.#onConnectionRemoved[i](connection, index, this, this.#portType, this.#node);
143
+ }
144
+ } else {
145
+ console.error("no connection found to remove");
146
+ }
147
+ }
148
+
149
+ getDataType(): string {
150
+ return this.#dataType;
151
+ }
152
+
153
+ getPortType(): PortType {
154
+ return this.#portType;
155
+ }
156
+
157
+ getDisplayName(): string {
158
+ return this.#displayName;
159
+ }
160
+
161
+ filledStyleColor(): string {
162
+ if (this.#filledStyle.fillColor === undefined) {
163
+ console.error("There's no fill color!!!!!!!!!")
164
+ return "black";
165
+ }
166
+ return this.#filledStyle.fillColor;
167
+ }
168
+
169
+ #box: Box = { Position: { x: 0, y: 0 }, Size: { x: 0, y: 0 } };
170
+
171
+ render(ctx: CanvasRenderingContext2D, position: Vector2, camera: Camera, mousePosition: Vector2 | undefined, postProcess: PassSubsystem): Box {
172
+ let style = this.#emptyStyle;
173
+ if (this.#connections.length > 0) {
174
+ style = this.#filledStyle;
175
+ }
176
+
177
+ let scaledRadius = style.size as number * camera.zoom
178
+
179
+ if (mousePosition && InBox(this.#box, mousePosition)) {
180
+ scaledRadius *= 1.25;
181
+
182
+ // Redeclare so lambda is ensured to use these values
183
+ const xPos = position.x;
184
+ const yPos = position.y;
185
+
186
+ postProcess.queue(() => {
187
+ ctx.textAlign = TextAlign.Center;
188
+
189
+ const padding = 13;
190
+ const measurement = ctx.measureText(this.#dataType)
191
+ const w = measurement.width + (padding * camera.zoom);
192
+
193
+ ctx.beginPath();
194
+ ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
195
+ ctx.roundRect(xPos - w / 2, yPos + (scaledRadius * 3) - (padding * camera.zoom), w, (padding * 2) * camera.zoom, 4 * camera.zoom);
196
+ ctx.fill();
197
+
198
+ ctx.fillStyle = "#FFFFFF";
199
+ ctx.fillText(this.#dataType, xPos, yPos + (scaledRadius * 3));
200
+ })
201
+ }
202
+
203
+ this.#box.Position.x = position.x - scaledRadius;
204
+ this.#box.Position.y = position.y - scaledRadius;
205
+ this.#box.Size.x = scaledRadius * 2;
206
+ this.#box.Size.y = scaledRadius * 2;
207
+
208
+ ctx.strokeStyle = style.borderColor as string;
209
+ ctx.fillStyle = style.fillColor as string;
210
+
211
+ ctx.beginPath();
212
+ if (this.#portType === PortType.InputArray) {
213
+ ctx.rect(position.x - scaledRadius, position.y - scaledRadius, scaledRadius * 2, scaledRadius * 2)
214
+ } else {
215
+ ctx.arc(position.x, position.y, scaledRadius, 0, 2 * Math.PI);
216
+ }
217
+ ctx.fill();
218
+ ctx.stroke();
219
+
220
+
221
+ return this.#box;
222
+ }
223
+ }