@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,153 @@
1
+ import { Theme } from "../theme";
2
+ import { Popup } from "../popup";
3
+ import { TextBoxStyle, TextBoxStyleConfig, TextBoxStyleWithFallback } from "../styles/textBox";
4
+ import { Box, InBox } from '../types/box';
5
+ import { CopyVector2, Vector2 } from "../types/vector2";
6
+ import { height, width } from "./widget";
7
+ import { FlowNode } from "../node";
8
+
9
+ export interface NumberWidgetConfig {
10
+ value?: number;
11
+
12
+ property?: string;
13
+
14
+ idleBoxStyle?: TextBoxStyleConfig;
15
+
16
+ highlightBoxStyle?: TextBoxStyleConfig;
17
+
18
+ callback?: (newNumber: number) => void;
19
+ }
20
+
21
+ export class NumberWidget {
22
+
23
+ #node: FlowNode;
24
+
25
+ #nodeProperty: string | undefined;
26
+
27
+ #value: number;
28
+
29
+ #idleBoxStyle: TextBoxStyle;
30
+
31
+ #highlightBoxStyle: TextBoxStyle;
32
+
33
+ #text: string;
34
+
35
+ #callback?: (newNumber: number) => void;
36
+
37
+ constructor(node: FlowNode, config?: NumberWidgetConfig) {
38
+ this.#value = 0;
39
+ this.#text = "0";
40
+
41
+ this.#node = node;
42
+ this.#nodeProperty = config?.property;
43
+ this.#idleBoxStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.idleBoxStyle, {
44
+ box: {
45
+ color: Theme.Widget.BackgroundColor,
46
+ border: {
47
+ size: Theme.Widget.Border.Size,
48
+ color: Theme.Widget.Border.Color,
49
+ },
50
+ radius: Theme.Widget.Border.Radius
51
+ },
52
+ text: { color: Theme.Widget.FontColor },
53
+ }));
54
+ this.#highlightBoxStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.highlightBoxStyle, {
55
+ box: {
56
+ color: Theme.Widget.Hover.BackgroundColor,
57
+ border: {
58
+ size: Theme.Widget.Border.Size,
59
+ color: Theme.Widget.Border.Color,
60
+ },
61
+ radius: Theme.Widget.Border.Radius
62
+ },
63
+ text: { color: Theme.Widget.FontColor },
64
+ }));
65
+ this.Set(config?.value === undefined ? 0 : config?.value);
66
+ this.#callback = config?.callback;
67
+
68
+ if (this.#nodeProperty !== undefined) {
69
+ this.#node.addPropertyChangeListener(this.#nodeProperty, (oldVal, newVal) => {
70
+ this.Set(newVal);
71
+ });
72
+ }
73
+
74
+ }
75
+
76
+ Size(): Vector2 {
77
+ return { "x": width, "y": height }
78
+ }
79
+
80
+ Set(newNumber: number): void {
81
+ if (this.#value === newNumber) {
82
+ return;
83
+ }
84
+
85
+ this.#value = newNumber;
86
+
87
+ if (this.#nodeProperty !== undefined) {
88
+ this.#node.setProperty(this.#nodeProperty, this.#value);
89
+ }
90
+
91
+ if (this.#callback !== undefined) {
92
+ this.#callback(this.#value);
93
+ }
94
+
95
+ // https://stackoverflow.com/questions/5765398/whats-the-best-way-to-convert-a-number-to-a-string-in-javascript
96
+ this.#text = '' + parseFloat(this.#value.toPrecision(6));
97
+ }
98
+
99
+ ClickStart(): void {
100
+ }
101
+
102
+ ClickEnd(): void {
103
+ let input: HTMLInputElement | null = null;
104
+
105
+ const setOption = "Set";
106
+ const cancelOption = "Cancel";
107
+ const popup = new Popup({
108
+ title: "Set Number",
109
+ options: [setOption, cancelOption],
110
+ content: () => {
111
+ const container = document.createElement('div');
112
+ input = document.createElement('input')
113
+ input.type = "number";
114
+ input.valueAsNumber = this.#value;
115
+ container.append(input);
116
+ return container;
117
+ },
118
+ onClose: (button: string | null): void => {
119
+ if (button !== setOption || input === null) {
120
+ return;
121
+ }
122
+
123
+ this.Set(input.valueAsNumber);
124
+ },
125
+ });
126
+
127
+ popup.Show();
128
+ }
129
+
130
+
131
+ Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
132
+ const box = {
133
+ Position: { x: 0, y: 0 },
134
+ Size: {
135
+ x: width * scale,
136
+ y: height * scale
137
+ }
138
+ };
139
+ CopyVector2(box.Position, position);
140
+
141
+ let style: TextBoxStyle = this.#idleBoxStyle;
142
+
143
+ if (mousePosition !== undefined) {
144
+ if (InBox(box, mousePosition)) {
145
+ style = this.#highlightBoxStyle;
146
+ }
147
+ }
148
+
149
+ style.DrawUnderline(ctx, box, scale, this.#text)
150
+
151
+ return box;
152
+ }
153
+ }
@@ -0,0 +1,216 @@
1
+ import { Theme } from "../theme";
2
+ import { TextAlign } from "../styles/canvasTextAlign";
3
+ import { TextStyle, TextStyleConfig, TextStyleFallback } from "../styles/text";
4
+ import { Box } from '../types/box';
5
+ import { CopyVector2, Vector2, Zero } from "../types/vector2";
6
+ import { Clamp, Clamp01 } from "../utils/math";
7
+ import { height, width } from "./widget";
8
+ import { TextBaseline } from "../styles/canvasTextBaseline";
9
+ import { FlowNode } from "../node";
10
+
11
+ export interface SliderWidgetConfig {
12
+
13
+ property?: string;
14
+
15
+ min?: number;
16
+
17
+ max?: number;
18
+
19
+ value?: number;
20
+
21
+ backgroundColor?: string;
22
+
23
+ borderColor?: string;
24
+
25
+ fillColor?: string;
26
+
27
+ textStyle?: TextStyleConfig;
28
+
29
+ change?: (newValue: number) => void;
30
+
31
+ release?: (newValue: number) => void;
32
+
33
+ snapIncrement?: number;
34
+
35
+ }
36
+
37
+ export class SliderWidget {
38
+
39
+ // Data Variables =========================================================
40
+
41
+ #min: number;
42
+
43
+ #max: number;
44
+
45
+ #value: number;
46
+
47
+ #text: string;
48
+
49
+ #snapIncrement?: number;
50
+
51
+ #node: FlowNode;
52
+
53
+ #nodeProperty: string | undefined;
54
+
55
+ // Callbacks ==============================================================
56
+
57
+ #change?: (newValue: number) => void;
58
+
59
+ #release?: (newValue: number) => void;
60
+
61
+ // Styling Variables ======================================================
62
+
63
+ #textStyle: TextStyle
64
+
65
+ #backgroundColor: string;
66
+
67
+ #borderColor: string;
68
+
69
+ #fillColor: string;
70
+
71
+ // Runtime Variables ======================================================
72
+
73
+ #lastMousePosition: Vector2;
74
+
75
+ #clickStartMousePosition: Vector2;
76
+
77
+ #clicking: boolean;
78
+
79
+ constructor(node: FlowNode, config?: SliderWidgetConfig) {
80
+ this.#node = node;
81
+ this.#value = 0;
82
+ this.#min = config?.min === undefined ? 0 : config?.min;
83
+ this.#max = config?.max === undefined ? 1 : config?.max;
84
+ this.#snapIncrement = config?.snapIncrement;
85
+ this.#nodeProperty = config?.property;
86
+ this.#text = "";
87
+
88
+ this.#release = config?.release;
89
+
90
+ this.#backgroundColor = config?.backgroundColor === undefined ? Theme.Widget.BackgroundColor : config?.backgroundColor;
91
+ this.#fillColor = config?.fillColor === undefined ? Theme.Widget.Slider.FillColor : config?.fillColor;
92
+ this.#borderColor = config?.borderColor === undefined ? Theme.Widget.Border.Color : config?.borderColor;
93
+ this.#textStyle = new TextStyle(TextStyleFallback(config?.textStyle, {
94
+ color: Theme.Widget.FontColor,
95
+ }));
96
+ this.#lastMousePosition = Zero();
97
+ this.#clickStartMousePosition = Zero();
98
+ this.#clicking = false;
99
+
100
+
101
+ if (config?.property !== undefined && config?.property !== null) {
102
+ node.addPropertyChangeListener(config.property, (oldVal, newVal) => {
103
+ this.SetValue(newVal);
104
+ });
105
+ }
106
+
107
+ this.SetValue(config?.value === undefined ? 0 : config?.value);
108
+
109
+ // Setup change callback after we set the initial value to prevent the callback from being
110
+ this.#change = config?.change;
111
+ }
112
+
113
+ SetValue(newValue: number): void {
114
+ const cleanedValue = Clamp(newValue, this.#min, this.#max);
115
+ if (this.#value === cleanedValue) {
116
+ return;
117
+ }
118
+
119
+ this.#value = cleanedValue;
120
+
121
+ if (this.#nodeProperty) {
122
+ this.#node.setProperty(this.#nodeProperty, this.#value);
123
+ }
124
+
125
+ this.#text = this.#value.toFixed(3);
126
+ if (this.#change !== undefined) {
127
+ this.#change(this.#value);
128
+ }
129
+ }
130
+
131
+ Size(): Vector2 {
132
+ return { "x": width, "y": height }
133
+ }
134
+
135
+ ClickStart(): void {
136
+ this.#clicking = true;
137
+ CopyVector2(this.#clickStartMousePosition, this.#lastMousePosition)
138
+ }
139
+
140
+ ClickEnd(): void {
141
+ this.#clicking = false;
142
+ if (this.#release !== undefined) {
143
+ this.#release(this.#value);
144
+ }
145
+ }
146
+
147
+ Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
148
+ const scaledWidth = width * scale;
149
+ const scaledHeight = height * scale;
150
+
151
+ const scaledBorderThickness = Theme.Widget.Border.Size * scale;
152
+
153
+ // Render background
154
+ ctx.fillStyle = this.#backgroundColor;
155
+ ctx.strokeStyle = this.#borderColor;
156
+ ctx.lineWidth = scaledBorderThickness;
157
+ ctx.beginPath();
158
+ ctx.roundRect(
159
+ position.x,
160
+ position.y,
161
+ scaledWidth,
162
+ scaledHeight,
163
+ Theme.Widget.Border.Radius * scale
164
+ );
165
+ ctx.fill();
166
+ ctx.stroke();
167
+
168
+ // Render Fill
169
+ const fill = Clamp01((this.#value - this.#min) / (this.#max - this.#min));
170
+ ctx.fillStyle = this.#fillColor;
171
+ ctx.beginPath();
172
+ ctx.roundRect(
173
+ position.x + (scaledBorderThickness / 2),
174
+ position.y + (scaledBorderThickness / 2),
175
+ (scaledWidth * fill) - scaledBorderThickness,
176
+ scaledHeight - scaledBorderThickness,
177
+ Theme.Widget.Border.Radius * scale
178
+ );
179
+ ctx.fill();
180
+
181
+ // Render Number
182
+ ctx.textAlign = TextAlign.Center;
183
+ ctx.textBaseline = TextBaseline.Middle;
184
+ this.#textStyle.setupStyle(ctx, scale);
185
+ ctx.fillText(
186
+ this.#text,
187
+ position.x + (scaledWidth / 2),
188
+ position.y + (scaledHeight / 2),
189
+ );
190
+
191
+ // Update value based on mouse position
192
+ if (mousePosition !== undefined) {
193
+ CopyVector2(this.#lastMousePosition, mousePosition);
194
+ if (this.#clicking) {
195
+ const min = position.x + (scaledBorderThickness / 2);
196
+ const max = min + scaledWidth - scaledBorderThickness;
197
+ const p = Clamp01((this.#lastMousePosition.x - min) / (max - min));
198
+
199
+ let value = (p * (this.#max - this.#min)) + this.#min;
200
+ if (this.#snapIncrement !== undefined) {
201
+ value = Math.round(value / this.#snapIncrement) * this.#snapIncrement;
202
+ }
203
+
204
+ this.SetValue(value);
205
+ }
206
+ }
207
+
208
+ return {
209
+ Position: { x: position.x, y: position.y },
210
+ Size: {
211
+ x: scaledWidth,
212
+ y: scaledHeight
213
+ }
214
+ };
215
+ }
216
+ }
@@ -0,0 +1,128 @@
1
+ import { Theme } from "../theme";
2
+ import { TextBoxStyle, TextBoxStyleConfig, TextBoxStyleWithFallback } from "../styles/textBox";
3
+ import { Box, InBox } from "../types/box";
4
+ import { CopyVector2, Vector2 } from "../types/vector2";
5
+ import { fitString } from "../utils/string";
6
+ import { height, width } from "./widget";
7
+ import { FlowNode } from '../node';
8
+ import { SetStringPopup } from "../popups/string";
9
+
10
+ export interface StringWidgetConfig {
11
+ property?: string;
12
+
13
+ value?: string;
14
+
15
+ textBoxStyle?: TextBoxStyleConfig;
16
+
17
+ callback?: (newString: string) => void;
18
+ }
19
+
20
+ export class StringWidget {
21
+
22
+ #value: string;
23
+
24
+ #idleStyle: TextBoxStyle;
25
+
26
+ #hoverStyle: TextBoxStyle;
27
+
28
+ #callback?: (newString: string) => void;
29
+
30
+ #node: FlowNode
31
+
32
+ #nodeProperty: string | undefined;
33
+
34
+ constructor(node: FlowNode, config?: StringWidgetConfig) {
35
+ this.#value = "";
36
+
37
+ this.#node = node;
38
+ this.#nodeProperty = config?.property;
39
+ this.#idleStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.textBoxStyle, {
40
+ box: {
41
+ color: Theme.Widget.BackgroundColor,
42
+ border: {
43
+ size: Theme.Widget.Border.Size,
44
+ color: Theme.Widget.Border.Color,
45
+ },
46
+ radius: Theme.Widget.Border.Radius
47
+ },
48
+ text: { color: Theme.Widget.FontColor },
49
+ }));
50
+
51
+ this.#hoverStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.textBoxStyle, {
52
+ box: {
53
+ color: Theme.Widget.Hover.BackgroundColor,
54
+ border: {
55
+ size: Theme.Widget.Border.Size,
56
+ color: Theme.Widget.Border.Color,
57
+ },
58
+ radius: Theme.Widget.Border.Radius
59
+ },
60
+ text: { color: Theme.Widget.FontColor },
61
+ }));
62
+
63
+ this.Set(config?.value === undefined ? "" : config?.value);
64
+ this.#callback = config?.callback;
65
+ if (this.#nodeProperty !== undefined) {
66
+ this.#node.addPropertyChangeListener(this.#nodeProperty, (oldVal, newVal) => {
67
+ this.Set(newVal);
68
+ });
69
+ }
70
+ }
71
+
72
+ Size(): Vector2 {
73
+ return { "x": width, "y": height }
74
+ }
75
+
76
+ Set(value: string): void {
77
+ if (this.#value === value) {
78
+ return;
79
+ }
80
+ this.#value = value;
81
+
82
+ if (this.#nodeProperty !== undefined) {
83
+ this.#node.setProperty(this.#nodeProperty, this.#value)
84
+ }
85
+
86
+ if (this.#callback !== undefined) {
87
+ this.#callback(this.#value);
88
+ }
89
+ }
90
+
91
+ ClickStart(): void {
92
+ }
93
+
94
+ ClickEnd(): void {
95
+ const popup = SetStringPopup({
96
+ title: "Set String",
97
+ startingValue: this.#value,
98
+ onUpdate: (value: string): void => {
99
+ this.Set(value);
100
+ },
101
+ });
102
+ popup.Show();
103
+ }
104
+
105
+ Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
106
+ const box = {
107
+ Position: { x: 0, y: 0 },
108
+ Size: {
109
+ x: width * scale,
110
+ y: height * scale
111
+ }
112
+ };
113
+ CopyVector2(box.Position, position);
114
+
115
+
116
+ let style: TextBoxStyle = this.#idleStyle;
117
+
118
+ if (mousePosition !== undefined) {
119
+ if (InBox(box, mousePosition)) {
120
+ style = this.#hoverStyle;
121
+ }
122
+ }
123
+
124
+ style.DrawUnderline(ctx, box, scale, fitString(ctx, this.#value, box.Size.x - (20 * scale)))
125
+
126
+ return box;
127
+ }
128
+ }
@@ -0,0 +1,92 @@
1
+ import { Theme } from "../theme";
2
+ import { Box, InBox } from "../types/box";
3
+ import { CopyVector2, Vector2, Zero } from "../types/vector2";
4
+ import { FlowNode } from '../node';
5
+ import { Text } from "../types/text";
6
+ import { TextStyle, TextStyleConfig, TextStyleFallback } from "../styles/text";
7
+
8
+ export interface TextWidgetConfig {
9
+ property?: string;
10
+
11
+ value?: string;
12
+
13
+ textBoxStyle?: TextStyleConfig;
14
+
15
+ callback?: (newString: string) => void;
16
+ }
17
+
18
+ export class TextWidget {
19
+
20
+ #value: Text;
21
+
22
+ #callback?: (newString: string) => void;
23
+
24
+ #node: FlowNode
25
+
26
+ #nodeProperty: string | undefined;
27
+
28
+ #size: Vector2;
29
+
30
+ constructor(node: FlowNode, config?: TextWidgetConfig) {
31
+ this.#size = Zero();
32
+ this.#node = node;
33
+ this.#nodeProperty = config?.property;
34
+ this.#value = new Text("", TextStyleFallback(config?.textBoxStyle, {
35
+ font: Theme.FontFamily,
36
+ color: Theme.Widget.FontColor,
37
+ size: Theme.Note.FontSize,
38
+ }))
39
+
40
+ this.Set(config?.value === undefined ? "" : config?.value);
41
+ this.#callback = config?.callback;
42
+ if (this.#nodeProperty !== undefined) {
43
+ this.#node.addPropertyChangeListener(this.#nodeProperty, (oldVal, newVal) => {
44
+ this.Set(newVal);
45
+ });
46
+ }
47
+ }
48
+
49
+ Size(): Vector2 {
50
+ return this.#size;
51
+ }
52
+
53
+ Set(value: string): void {
54
+ if (this.#value.get() === value) {
55
+ return;
56
+ }
57
+ this.#value.set(value);
58
+
59
+ if (this.#nodeProperty !== undefined) {
60
+ this.#node.setProperty(this.#nodeProperty, value)
61
+ }
62
+
63
+ if (this.#callback !== undefined) {
64
+ this.#callback(value);
65
+ }
66
+ }
67
+
68
+ ClickStart(): void {
69
+ // Left blank for implementing Widget interface
70
+ }
71
+
72
+ ClickEnd(): void {
73
+ // Left blank for implementing Widget interface
74
+ }
75
+
76
+
77
+ Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
78
+ this.#value.size(ctx, scale, this.#size);
79
+ const box = {
80
+ Position: { x: 0, y: 0 },
81
+ Size: this.#size
82
+ };
83
+ CopyVector2(box.Position, position);
84
+ this.#value.size(ctx, 1, this.#size);
85
+
86
+ ctx.textAlign = "left";
87
+ this.#value.render(ctx, scale, box.Position);
88
+ // this.#idleStyle.DrawUnderline(ctx, box, scale, fitString(ctx, this.#value, box.Size.x - (20 * scale)))
89
+
90
+ return box;
91
+ }
92
+ }