@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
package/src/graph.ts ADDED
@@ -0,0 +1,535 @@
1
+ import { CombineContextMenus, ContextEntry, ContextMenu, ContextMenuConfig } from './contextMenu';
2
+ import { Theme } from "./theme";
3
+ import { MouseObserver } from "./input";
4
+ import { FlowNode } from "./node";
5
+ import { NodeCreatedCallback, NodeFactoryConfig } from "./nodes/factory";
6
+ import { TimeExecution } from "./performance";
7
+ import { CursorStyle } from "./styles/cursor";
8
+ import { CopyVector2, Vector2, Zero } from './types/vector2';
9
+ import { Clamp01 } from "./utils/math";
10
+ import { GraphSubsystem, RenderResults } from './graphSubsystem';
11
+ import { FlowNote } from "./notes/note";
12
+ import { NoteAddedCallback, NoteDragStartCallback, NoteDragStopCallback, NoteRemovedCallback, NoteSubsystem, NoteSubsystemConfig } from "./notes/subsystem";
13
+ import { ConnectionRendererConfiguration, NodeAddedCallback, NodeRemovedCallback, NodeSubsystem } from "./nodes/subsystem";
14
+ import { Connection } from './connection';
15
+ import { Publisher } from './nodes/publisher';
16
+ import { VectorPool } from './types/pool';
17
+ import { Camera } from './camera';
18
+ import { PassSubsystem } from './pass/subsystem';
19
+ import { QuickMenu } from './quickMenu';
20
+
21
+ export type GraphRenderer = (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, position: Vector2, scale: number) => void;
22
+
23
+ function BuildBackgroundRenderer(backgroundColor: string): GraphRenderer {
24
+ return (canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, position: Vector2, scale: number) => {
25
+ context.fillStyle = backgroundColor;
26
+ context.fillRect(0, 0, canvas.width, canvas.height);
27
+
28
+ const alpha = Math.round(Clamp01(scale - 0.3) * 255)
29
+ if (alpha <= 0) {
30
+ return;
31
+ }
32
+
33
+ context.fillStyle = `rgba(41, 54, 57, ${alpha})`;
34
+ const spacing = 100;
35
+ const pi2 = 2 * Math.PI
36
+ const dotScale = 2 * scale;
37
+ for (let x = -50; x < 50; x++) {
38
+ const xPos = (x * spacing * scale) + position.x;
39
+ for (let y = -50; y < 50; y++) {
40
+ const yPos = (y * spacing * scale) + position.y;
41
+ context.beginPath();
42
+ context.arc(xPos, yPos, dotScale, 0, pi2);
43
+ context.fill();
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ const contextMenuGroup = "graph-context-menu";
50
+
51
+ export interface FlowNodeGraphConfiguration {
52
+ backgroundRenderer?: GraphRenderer;
53
+ backgroundColor?: string;
54
+ idleConnection?: ConnectionRendererConfiguration
55
+ contextMenu?: ContextMenuConfig
56
+ nodes?: NodeFactoryConfig
57
+ board?: NoteSubsystemConfig
58
+ }
59
+
60
+ interface OpenContextMenu {
61
+ Menu: ContextMenu
62
+ Position: Vector2
63
+ }
64
+
65
+ interface OpenQuickMenu {
66
+ Menu: QuickMenu
67
+ Position: Vector2
68
+ }
69
+
70
+ class GraphView {
71
+
72
+ #subsystems: Array<GraphSubsystem>;
73
+
74
+ constructor(subsystems: Array<GraphSubsystem>) {
75
+ this.#subsystems = subsystems;
76
+ }
77
+
78
+ clickStart(mousePosition: Vector2, camera: Camera, ctrlKey: boolean) {
79
+ // Since the graph is rendered from 0 => n, n is the last thing
80
+ // rendererd and is always shown to the user. So if their clicking on
81
+ // two things technically, we need to make sure it's whatever was most
82
+ // recently rendered. So we traverse n => 0.
83
+ for (let i = this.#subsystems.length - 1; i >= 0; i--) {
84
+ // If the user sucesfully clicked on something in this layer, don't
85
+ // check any more layers.
86
+ if (this.#subsystems[i].clickStart(mousePosition, camera, ctrlKey)) {
87
+ return;
88
+ }
89
+ }
90
+ }
91
+
92
+ fileDrop(file: File): boolean {
93
+ for (let i = this.#subsystems.length - 1; i >= 0; i--) {
94
+ if (this.#subsystems[i].fileDrop(file)) {
95
+ return true;
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+
101
+ openContextMenu(ctx: CanvasRenderingContext2D, position: Vector2): ContextMenuConfig {
102
+ let finalConfig: ContextMenuConfig = {};
103
+
104
+ for (let i = 0; i < this.#subsystems.length; i++) {
105
+ const subSystemMenu = this.#subsystems[i].openContextMenu(ctx, position);
106
+ if (subSystemMenu !== null) {
107
+ finalConfig = CombineContextMenus(finalConfig, subSystemMenu);
108
+ }
109
+ }
110
+
111
+ return finalConfig;
112
+ }
113
+
114
+ clickEnd(): void {
115
+ for (let i = 0; i < this.#subsystems.length; i++) {
116
+ this.#subsystems[i].clickEnd();
117
+ }
118
+ }
119
+
120
+ mouseDragEvent(delta: Vector2, scale: number): boolean {
121
+ for (let i = 0; i < this.#subsystems.length; i++) {
122
+ if (this.#subsystems[i].mouseDragEvent(delta, scale)) {
123
+ return true;
124
+ }
125
+ }
126
+ return false;
127
+ }
128
+
129
+ render(ctx: CanvasRenderingContext2D, camera: Camera, mousePosition: Vector2 | undefined): RenderResults {
130
+ const results: RenderResults = {};
131
+ for (let i = 0; i < this.#subsystems.length; i++) {
132
+ TimeExecution("Render_Subsystem_" + i, () => {
133
+ let results = this.#subsystems[i].render(ctx, camera, mousePosition);
134
+ if (results?.cursorStyle) {
135
+ results.cursorStyle = results?.cursorStyle;
136
+ }
137
+ })
138
+ }
139
+ return results;
140
+ }
141
+ }
142
+
143
+ export class NodeFlowGraph {
144
+
145
+ #ctx: CanvasRenderingContext2D;
146
+
147
+ #canvas: HTMLCanvasElement;
148
+
149
+ #backgroundRenderer: GraphRenderer
150
+
151
+ #contextMenuConfig: ContextMenuConfig;
152
+
153
+ #mousePosition: Vector2 | undefined;
154
+
155
+ #camera: Camera;
156
+
157
+ #openedContextMenu: OpenContextMenu | null;
158
+
159
+ #openQuickMenu: OpenQuickMenu | null;
160
+
161
+ #contextMenuEntryHovering: ContextEntry | null;
162
+
163
+ #views: Array<GraphView>;
164
+
165
+ #currentView: number;
166
+
167
+ #mainNodeSubsystem: NodeSubsystem;
168
+
169
+ #mainNoteSubsystem: NoteSubsystem;
170
+
171
+ constructor(canvas: HTMLCanvasElement, config?: FlowNodeGraphConfiguration) {
172
+ const postProcessPass = new PassSubsystem();
173
+
174
+ this.#mainNodeSubsystem = new NodeSubsystem(postProcessPass, {
175
+ nodes: config?.nodes,
176
+ idleConnection: config?.idleConnection
177
+ });
178
+
179
+ this.#lastFrameCursor = CursorStyle.Default;
180
+ this.#cursor = CursorStyle.Default;
181
+
182
+ this.#mainNoteSubsystem = new NoteSubsystem(config?.board);
183
+
184
+ this.#views = [
185
+ new GraphView([
186
+ this.#mainNoteSubsystem,
187
+ this.#mainNodeSubsystem,
188
+ postProcessPass
189
+ ])
190
+ ];
191
+ this.#currentView = 0;
192
+
193
+ this.#camera = new Camera();
194
+
195
+ this.#contextMenuConfig = CombineContextMenus({
196
+ items: [
197
+ {
198
+ name: "Reset View",
199
+ group: contextMenuGroup,
200
+ callback: this.#camera.reset.bind(this.#camera)
201
+ },
202
+ ],
203
+ }, config?.contextMenu);
204
+
205
+ this.#openedContextMenu = null;
206
+ this.#openQuickMenu = null;
207
+ this.#contextMenuEntryHovering = null;
208
+
209
+ this.#canvas = canvas;
210
+ const ctx = canvas.getContext("2d")
211
+ if (ctx === null) {
212
+ throw new Error("could not create canvas context")
213
+ }
214
+ this.#ctx = ctx;
215
+
216
+ if (config?.backgroundRenderer !== undefined) {
217
+ this.#backgroundRenderer = config?.backgroundRenderer
218
+ } else {
219
+ const backgroundColor = config?.backgroundColor === undefined ? Theme.Graph.BackgroundColor : config.backgroundColor;
220
+ this.#backgroundRenderer = BuildBackgroundRenderer(backgroundColor);
221
+ }
222
+
223
+ window.requestAnimationFrame(this.#render.bind(this));
224
+
225
+ this.#canvas.addEventListener('wheel', (event) => {
226
+ event.preventDefault();
227
+ this.zoom(Math.sign(event.deltaY));
228
+ }, false);
229
+
230
+ new MouseObserver(this.#canvas,
231
+ this.#mouseDragEvent.bind(this),
232
+
233
+ // Mouse move event
234
+ (mousePosition) => {
235
+ this.#mousePosition = mousePosition;
236
+ },
237
+
238
+ this.#clickStart.bind(this),
239
+ this.#clickEnd.bind(this),
240
+ this.#openContextMenu.bind(this),
241
+ this.#fileDrop.bind(this)
242
+ );
243
+
244
+ document.addEventListener(
245
+ "keydown",
246
+ (e) => {
247
+ if (document.activeElement) {
248
+ if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) return;
249
+ }
250
+
251
+ if (this.#openQuickMenu) {
252
+ if (e.code === "Escape") {
253
+ this.#openQuickMenu = null;
254
+ return;
255
+ }
256
+
257
+ if (e.code === "Enter") {
258
+ this.#openQuickMenu.Menu.execute();
259
+ this.#openQuickMenu = null;
260
+ return;
261
+ }
262
+
263
+ this.#openQuickMenu.Menu.keyboardEvent(e);
264
+ return
265
+ }
266
+
267
+ const spacePressed = e.key == " " || e.code == "Space";
268
+ if (!spacePressed) {
269
+ return;
270
+ }
271
+
272
+ if (!this.#mousePosition) {
273
+ return;
274
+ }
275
+ const contextMenuPosition = this.#sceenPositionToGraphPosition(this.#mousePosition);
276
+
277
+ let items = this.#mainNodeSubsystem.nodeFactory().newNodeSubmenus(this.#mainNodeSubsystem, contextMenuPosition);
278
+ this.#openQuickMenu = {
279
+ Menu: new QuickMenu({
280
+ subMenus: items,
281
+ }),
282
+ Position: contextMenuPosition
283
+ }
284
+ }
285
+ );
286
+ }
287
+
288
+ #fileDrop(file: File): void {
289
+
290
+ if (this.currentView().fileDrop(file)) {
291
+ return;
292
+ }
293
+
294
+ const contents = file.name.split('.');
295
+ const extension = contents[contents.length - 1];
296
+ if (extension !== "jpg" && extension !== "jpeg" && extension !== "png") {
297
+ return;
298
+ }
299
+
300
+ let pos = Zero();
301
+ if (this.#mousePosition) {
302
+ CopyVector2(pos, this.#sceenPositionToGraphPosition(this.#mousePosition));
303
+ }
304
+
305
+ this.#mainNodeSubsystem.addNode(new FlowNode({
306
+ title: contents[0],
307
+ position: pos,
308
+ widgets: [{
309
+ type: "image",
310
+ config: {
311
+ blob: file,
312
+ }
313
+ }]
314
+ }));
315
+ }
316
+
317
+ public addNoteAddedListener(callback: NoteAddedCallback): void {
318
+ this.#mainNoteSubsystem.addNoteAddedListener(callback);
319
+ }
320
+
321
+ public addNoteRemovedListener(callback: NoteRemovedCallback): void {
322
+ this.#mainNoteSubsystem.addNoteRemovedListener(callback);
323
+ }
324
+
325
+ public addNoteDragStartListener(callback: NoteDragStartCallback): void {
326
+ this.#mainNoteSubsystem.addNoteDragStartListener(callback);
327
+ }
328
+
329
+ public addNoteDragStopListener(callback: NoteDragStopCallback): void {
330
+ this.#mainNoteSubsystem.addNoteDragStopListener(callback);
331
+ }
332
+
333
+
334
+ zoom(amount: number): void {
335
+
336
+ let oldPos: Vector2 | undefined = undefined;
337
+ if (this.#mousePosition) {
338
+ oldPos = this.#sceenPositionToGraphPosition(this.#mousePosition);;
339
+ }
340
+
341
+ this.#camera.zoom += amount * this.#camera.zoom * 0.05;
342
+
343
+ if (!oldPos || !this.#mousePosition) {
344
+ return;
345
+ }
346
+ // Attempt zoom around where the current mouse is.
347
+ const newPos = this.#sceenPositionToGraphPosition(this.#mousePosition);
348
+ this.#camera.position.x += (newPos.x - oldPos.x) * this.#camera.zoom;
349
+ this.#camera.position.y += (newPos.y - oldPos.y) * this.#camera.zoom;
350
+ }
351
+
352
+ #clickStart(mousePosition: Vector2, ctrlKey: boolean): void {
353
+ if (this.#contextMenuEntryHovering !== null) {
354
+ this.#contextMenuEntryHovering.click();
355
+ this.#openedContextMenu = null;
356
+ this.#contextMenuEntryHovering = null;
357
+ return;
358
+ }
359
+ this.#openedContextMenu = null;
360
+ this.#openQuickMenu = null;
361
+ this.#contextMenuEntryHovering = null;
362
+
363
+ this.#mousePosition = mousePosition;
364
+ this.currentView().clickStart(mousePosition, this.#camera, ctrlKey);
365
+ }
366
+
367
+ currentView(): GraphView {
368
+ return this.#views[this.#currentView];
369
+ }
370
+
371
+ organize(): void {
372
+ this.#mainNodeSubsystem.organize(this.#ctx);
373
+ }
374
+
375
+ addPublisher(identifier: string, publisher: Publisher): void {
376
+ this.#mainNodeSubsystem.addPublisher(identifier, publisher);
377
+ }
378
+
379
+ getNodes(): Array<FlowNode> {
380
+ return this.#mainNodeSubsystem.getNodes();
381
+ }
382
+
383
+ connectedInputsNodeReferences(nodeIndex: number): Array<FlowNode> {
384
+ return this.#mainNodeSubsystem.connectedInputsNodeReferencesByIndex(nodeIndex);
385
+ }
386
+
387
+ connectedOutputsNodeReferences(nodeIndex: number): Array<FlowNode> {
388
+ return this.#mainNodeSubsystem.connectedOutputsNodeReferences(nodeIndex);
389
+ }
390
+
391
+ connectNodes(nodeOut: FlowNode, outPort: number, nodeIn: FlowNode, inPort: number): Connection | undefined {
392
+ return this.#mainNodeSubsystem.connectNodes(nodeOut, outPort, nodeIn, inPort);
393
+ }
394
+
395
+ addNode(node: FlowNode): void {
396
+ this.#mainNodeSubsystem.addNode(node);
397
+ }
398
+
399
+ addNote(note: FlowNote): void {
400
+ this.#mainNoteSubsystem.addNote(note);
401
+ }
402
+
403
+ public addOnNodeCreatedListener(callback: NodeCreatedCallback): void {
404
+ this.#mainNodeSubsystem.addOnNodeCreatedListener(callback);
405
+ }
406
+
407
+ public addOnNodeAddedListener(callback: NodeAddedCallback): void {
408
+ this.#mainNodeSubsystem.addOnNodeAddedListener(callback);
409
+ }
410
+
411
+ public addOnNodeRemovedListener(callback: NodeRemovedCallback): void {
412
+ this.#mainNodeSubsystem.addOnNodeRemovedListener(callback);
413
+ }
414
+
415
+ #sceenPositionToGraphPosition(screenPosition: Vector2): Vector2 {
416
+ const out = Zero();
417
+ this.#camera.screenSpaceToGraphSpace(screenPosition, out);
418
+ return out;
419
+ }
420
+
421
+ #openContextMenu(position: Vector2): void {
422
+ let finalConfig = this.#contextMenuConfig;
423
+
424
+ const contextMenuPosition = this.#sceenPositionToGraphPosition(position);
425
+
426
+ finalConfig = CombineContextMenus(finalConfig, this.currentView().openContextMenu(this.#ctx, contextMenuPosition));
427
+
428
+ this.#openedContextMenu = {
429
+ Menu: new ContextMenu(finalConfig),
430
+ Position: contextMenuPosition,
431
+ };
432
+ }
433
+
434
+
435
+ // Somethings wrong here. Needs more testing
436
+ // #fitView(): void {
437
+ // if (this.#nodes.length === 0) {
438
+ // return;
439
+ // }
440
+
441
+ // const curBox: Box = {
442
+ // Position: { x: 0, y: 0 },
443
+ // Size: { x: 0, y: 0 }
444
+ // };
445
+ // CopyBox(curBox, this.#nodes[0].calculateBounds(this.#ctx, this.#graphState.position, this.#graphState.scale));
446
+
447
+ // for (let i = 1; i < this.#nodes.length; i++) {
448
+ // ExpandBox(curBox, this.#nodes[i].calculateBounds(this.#ctx, this.#graphState.position, this.#graphState.scale));
449
+ // }
450
+
451
+ // CopyVector2(this.#graphState.position, curBox.Position);
452
+ // }
453
+
454
+ #clickEnd(): void {
455
+ this.currentView().clickEnd();
456
+ }
457
+
458
+ #mouseDragEvent(delta: Vector2): void {
459
+ let draggingSomething = this.currentView().mouseDragEvent(delta, this.#camera.zoom);
460
+ if (!draggingSomething) {
461
+ this.#camera.position.x += delta.x;
462
+ this.#camera.position.y += delta.y;
463
+ }
464
+ }
465
+
466
+ #lastFrameCursor: CursorStyle;
467
+
468
+ #cursor: CursorStyle;
469
+
470
+ #render(): void {
471
+ if (this.#canvas.parentNode !== null) {
472
+ // Stupid as any because typescript doesn't think it exists
473
+ var rect = (this.#canvas.parentNode as any).getBoundingClientRect();
474
+ this.#canvas.width = rect.width;
475
+ this.#canvas.height = rect.height;
476
+ }
477
+
478
+
479
+ this.#cursor = CursorStyle.Default;
480
+
481
+ TimeExecution("Render_Background", this.#renderBackground.bind(this));
482
+
483
+ TimeExecution("Render_View_" + this.#currentView, () => {
484
+ let results = this.currentView().render(this.#ctx, this.#camera, this.#mousePosition);
485
+ if (results?.cursorStyle) {
486
+ this.#cursor = results?.cursorStyle;
487
+ }
488
+ });
489
+
490
+ TimeExecution("Render_Context", this.#renderContextMenu.bind(this));
491
+
492
+ TimeExecution("Render_QuickMenu", this.#renderQuickMenu.bind(this))
493
+
494
+ // Only update CSS style if things have changed
495
+ // TODO: Does this actually have any measurable performance savings?
496
+ if (this.#lastFrameCursor !== this.#cursor) {
497
+ this.#canvas.style.cursor = this.#cursor;
498
+ }
499
+ this.#lastFrameCursor = this.#cursor;
500
+
501
+ window.requestAnimationFrame(this.#render.bind(this));
502
+ }
503
+
504
+ #renderBackground(): void {
505
+ this.#backgroundRenderer(this.#canvas, this.#ctx, this.#camera.position, this.#camera.zoom);
506
+ }
507
+
508
+ #renderContextMenu(): void {
509
+ VectorPool.run(() => {
510
+ if (this.#openedContextMenu !== null) {
511
+ const pos = VectorPool.get();
512
+ this.#camera.graphSpaceToScreenSpace(this.#openedContextMenu.Position, pos)
513
+ this.#contextMenuEntryHovering = this.#openedContextMenu.Menu.render(this.#ctx, pos, this.#camera.zoom, this.#mousePosition, true);
514
+
515
+ if (this.#contextMenuEntryHovering !== null) {
516
+ this.#cursor = CursorStyle.Pointer;
517
+ }
518
+ }
519
+ });
520
+ }
521
+
522
+ #renderQuickMenu(): void {
523
+ VectorPool.run(() => {
524
+ if (this.#openQuickMenu !== null) {
525
+ const pos = VectorPool.get();
526
+ this.#camera.graphSpaceToScreenSpace(this.#openQuickMenu.Position, pos)
527
+ this.#contextMenuEntryHovering = this.#openQuickMenu.Menu.render(this.#ctx, pos, this.#camera.zoom, this.#mousePosition);
528
+
529
+ if (this.#contextMenuEntryHovering !== null) {
530
+ this.#cursor = CursorStyle.Pointer;
531
+ }
532
+ }
533
+ });
534
+ }
535
+ }
@@ -0,0 +1,31 @@
1
+ import { Camera } from "./camera";
2
+ import { ContextMenuConfig } from "./contextMenu";
3
+ import { CursorStyle } from "./styles/cursor";
4
+ import { Vector2 } from "./types/vector2";
5
+
6
+ export interface RenderResults {
7
+ cursorStyle?: CursorStyle;
8
+ }
9
+
10
+ export interface GraphSubsystem {
11
+
12
+ render(ctx: CanvasRenderingContext2D, camera: Camera, mousePosition: Vector2 | undefined): RenderResults | undefined;
13
+
14
+ openContextMenu(ctx: CanvasRenderingContext2D, position: Vector2): ContextMenuConfig | null;
15
+
16
+ /**
17
+ * Event for when the graph is clicked.
18
+ *
19
+ * Returns whether or not the subsystem had one of it's elements clicked on
20
+ *
21
+ * @param mousePosition Where the mouse is on the graph
22
+ * @param ctrlKey Whether or not the ctrl key is pressed
23
+ */
24
+ clickStart(mousePosition: Vector2, camera: Camera, ctrlKey: boolean): boolean;
25
+
26
+ mouseDragEvent(delta: Vector2, scale: number): boolean;
27
+
28
+ fileDrop(file: File): boolean;
29
+
30
+ clickEnd(): void;
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { NodeFlowGraph } from './graph';
2
+ import { FlowNode, FlowNodeConfig } from './node';
3
+
4
+ export { NodeFlowGraph, FlowNode, FlowNodeConfig };
5
+
6
+ import { Theme } from './theme';
7
+ export { Theme };
8
+
9
+ import { Publisher, PublisherConfig } from './nodes/publisher';
10
+ export { Publisher, PublisherConfig };
11
+
12
+ import { FlowNote } from './notes/note';
13
+ export { FlowNote };
14
+
15
+ // Widgets
16
+ import { NumberWidget } from './widgets/number';
17
+ import { ColorWidget } from './widgets/color';
18
+ import { StringWidget } from './widgets/string';
19
+ import { ButtonWidget } from './widgets/button';
20
+ import { ToggleWidget } from './widgets/toggle';
21
+ import { SliderWidget } from './widgets/slider';
22
+ import { ImageWidget } from './widgets/image';
23
+ export {
24
+ NumberWidget, ColorWidget, StringWidget, ButtonWidget, ToggleWidget, SliderWidget, ImageWidget
25
+ };
26
+
27
+ import { GlobalWidgetFactory } from './widgets/factory';
28
+ export { GlobalWidgetFactory };