@elicdavis/node-flow 0.1.8 → 0.1.10

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/index.html CHANGED
@@ -296,6 +296,20 @@ node.addOutput({ name: "sum", type: "float32" })
296
296
  outputs: [
297
297
  { name: "sum", type: "float32", description: "The sum of all connected nodes to our 'numbers' port" }
298
298
  ],
299
+ messages: [
300
+ {
301
+ message: "I am a message attatched to the node",
302
+ alwaysShow: true,
303
+ },
304
+ {
305
+ message: "I am a message that only shows when you're hovering over the node!",
306
+ },
307
+ {
308
+ message: "I am another message attatched to the node with type \"error\"",
309
+ type: "error",
310
+ alwaysShow: true,
311
+ }
312
+ ],
299
313
  });
300
314
 
301
315
  graph.addNode(sumNode)
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@elicdavis/node-flow",
3
3
  "description": "build node graphs",
4
4
  "author": "Eli C Davis",
5
- "version": "0.1.8",
5
+ "version": "0.1.10",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "nodes",
package/src/graph.ts CHANGED
@@ -21,7 +21,7 @@ import { QuickMenu } from './quickMenu';
21
21
  export type GraphRenderer = (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, position: Vector2, scale: number) => void;
22
22
 
23
23
  function BuildBackgroundRenderer(backgroundColor: string): GraphRenderer {
24
- return (canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, position: Vector2, scale: number) => {
24
+ return (canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, position: Vector2, scale: number): void => {
25
25
  context.fillStyle = backgroundColor;
26
26
  context.fillRect(0, 0, canvas.width, canvas.height);
27
27
 
package/src/node.ts CHANGED
@@ -35,7 +35,6 @@ type TitleChangeCallback = (node: FlowNode, oldTitle: string, newTitle: string)
35
35
  type InfoChangeCallback = (node: FlowNode, oldInfo: string, newInfo: string) => void
36
36
 
37
37
 
38
-
39
38
  export interface WidgetConfig {
40
39
  type?: string,
41
40
  config?: any
@@ -56,11 +55,41 @@ export interface FlowNodeStyle {
56
55
  portText?: TextStyleConfig;
57
56
  }
58
57
 
58
+ export enum MessageType {
59
+ Info = "info",
60
+ Warning = "warning",
61
+ Error = "error"
62
+ };
63
+
64
+ function MessageTypeColor(messageType: MessageType): string {
65
+ switch (messageType) {
66
+ case MessageType.Info:
67
+ return Theme.Node.Message.InfoColor;
68
+
69
+ case MessageType.Warning:
70
+ return Theme.Node.Message.WarnColor;
71
+
72
+ case MessageType.Error:
73
+ return Theme.Node.Message.ErrorColor;
74
+
75
+ default:
76
+ throw new Error(`unrecognized message type ${messageType}`)
77
+ }
78
+ }
79
+
80
+ export interface NodeMessageConfig {
81
+ message: string;
82
+ type?: MessageType;
83
+ alwaysShow?: boolean;
84
+ color?: string;
85
+ }
86
+
59
87
  export interface FlowNodeConfig {
60
88
  position?: Vector2;
61
89
  title?: string;
62
90
  subTitle?: string;
63
91
  info?: string;
92
+ messages?: Array<NodeMessageConfig>
64
93
  locked?: boolean;
65
94
  data?: Metadata;
66
95
  contextMenu?: ContextMenuConfig;
@@ -111,6 +140,35 @@ export interface NodeIntersection {
111
140
 
112
141
  }
113
142
 
143
+ class MessageRenderer {
144
+
145
+ #text: Text;
146
+
147
+ #alwaysShow: boolean;
148
+
149
+ constructor(config: NodeMessageConfig) {
150
+ this.#alwaysShow = config.alwaysShow ? config.alwaysShow : false;
151
+ this.#text = new Text(config.message,
152
+ {
153
+ color: config.color ? config.color : MessageTypeColor(config.type ? config.type : MessageType.Info),
154
+ },
155
+ {
156
+ LineSpacing: 2.5,
157
+ MaxWidth: 200
158
+ }
159
+ );
160
+ }
161
+
162
+ render(ctx: CanvasRenderingContext2D, scale: number, position: Vector2, hovering: boolean): number {
163
+ if (!this.#alwaysShow && !hovering) {
164
+ return 0;
165
+ }
166
+ ctx.textAlign = TextAlign.Center;
167
+ this.#text.render(ctx, scale, position);
168
+ return this.#text.height(ctx) * scale;
169
+ }
170
+ }
171
+
114
172
  export class FlowNode {
115
173
 
116
174
  #position: Vector2;
@@ -123,6 +181,8 @@ export class FlowNode {
123
181
 
124
182
  #infoText: string;
125
183
 
184
+ #messages: Array<MessageRenderer>
185
+
126
186
  #input: Array<Port>;
127
187
 
128
188
  #output: Array<Port>;
@@ -203,6 +263,14 @@ export class FlowNode {
203
263
  this.#contextMenu = config?.contextMenu === undefined ? null : config.contextMenu;
204
264
  this.#metadata = config?.metadata;
205
265
 
266
+ this.#messages = [];
267
+ if (config?.messages) {
268
+ for (let i = 0; i < config.messages.length; i++) {
269
+ const message = config.messages[i];
270
+ this.#messages.push(new MessageRenderer(message));
271
+ }
272
+ }
273
+
206
274
  this.#selected = false;
207
275
  this.#onSelect = new Array<() => void>();
208
276
  this.#onUnselect = new Array<() => void>();
@@ -879,7 +947,6 @@ export class FlowNode {
879
947
  console.warn("setInfo instruction ignored, as node has been marked un-editable");
880
948
  }
881
949
 
882
-
883
950
  let cleaned = newInfo;
884
951
  if (cleaned === null || cleaned === undefined) {
885
952
  cleaned = "";
@@ -935,6 +1002,14 @@ export class FlowNode {
935
1002
  return boxStyle;
936
1003
  }
937
1004
 
1005
+ addMessage(message: NodeMessageConfig): void {
1006
+ this.#messages.push(new MessageRenderer(message));
1007
+ }
1008
+
1009
+ clearMessages(): void {
1010
+ this.#messages = [];
1011
+ }
1012
+
938
1013
  render(ctx: CanvasRenderingContext2D, camera: Camera, state: NodeState, mousePosition: Vector2 | undefined, postProcess: PassSubsystem): void {
939
1014
  VectorPool.run(() => {
940
1015
  const tempMeasurement = VectorPool.get();
@@ -1113,6 +1188,7 @@ export class FlowNode {
1113
1188
  startY += tempMeasurement.y + scaledElementSpacing;
1114
1189
  }
1115
1190
 
1191
+ // Widgets
1116
1192
  for (let i = 0; i < this.#widgets.length; i++) {
1117
1193
  const widget = this.#widgets[i];
1118
1194
  const widgetSize = widget.Size();
@@ -1125,6 +1201,15 @@ export class FlowNode {
1125
1201
  this.#widgetPositions.Push(widget.Draw(ctx, position, camera.zoom, mousePosition));
1126
1202
  startY += (widgetSize.y * camera.zoom) + scaledElementSpacing;
1127
1203
  }
1204
+
1205
+ // Messages
1206
+ const messageStart = VectorPool.get();
1207
+ messageStart.x = nodeBounds.Position.x + (nodeBounds.Size.x / 2);
1208
+ messageStart.y = nodeBounds.Position.y + nodeBounds.Size.y + (15 * camera.zoom);
1209
+ for (let i = 0; i < this.#messages.length; i++) {
1210
+ const message = this.#messages[i];
1211
+ messageStart.y += message.render(ctx, camera.zoom, messageStart, !(state === NodeState.Idle)) + (10 * camera.zoom);
1212
+ }
1128
1213
  })
1129
1214
  }
1130
1215
  }
package/src/theme.ts CHANGED
@@ -20,6 +20,11 @@ export const Theme = {
20
20
  },
21
21
  Port: {
22
22
  FontColor: "#afb9bb"
23
+ },
24
+ Message: {
25
+ InfoColor: "#afb9bb",
26
+ WarnColor: "#f1f663ff",
27
+ ErrorColor: "#FF0000"
23
28
  }
24
29
  },
25
30
  BoxSelect: {
package/src/types/text.ts CHANGED
@@ -2,6 +2,11 @@ import { FontWeight, TextStyle, TextStyleConfig } from "../styles/text";
2
2
  import { splitString, splitStringIntoLines } from "../utils/string";
3
3
  import { CopyVector2, ScaleVector, Vector2, Zero } from "./vector2";
4
4
 
5
+ export interface MultilineTextConfig {
6
+ MaxWidth?: number;
7
+ LineSpacing?: number;
8
+ }
9
+
5
10
  export class Text {
6
11
 
7
12
  #measured: boolean
@@ -12,11 +17,20 @@ export class Text {
12
17
 
13
18
  #value: string
14
19
 
15
- constructor(value: string, style?: TextStyleConfig) {
20
+ #maxWidth: number;
21
+
22
+ #lineSpacing: number;
23
+
24
+ #textToRender: Array<string>;
25
+
26
+ constructor(value: string, style?: TextStyleConfig, config?: MultilineTextConfig) {
16
27
  this.#value = value;
17
28
  this.#measured = false;
18
29
  this.#size = Zero();
19
30
  this.#style = new TextStyle(style);
31
+ this.#maxWidth = config?.MaxWidth ? config?.MaxWidth : -1;
32
+ this.#lineSpacing = config?.LineSpacing ? config?.LineSpacing : 5;
33
+ this.#textToRender = [value];
20
34
 
21
35
  if (!document.fonts.check(`16px "${this.#style.getFont()}"`)) {
22
36
  document.fonts.addEventListener("loadingdone", (event) => {
@@ -96,10 +110,28 @@ export class Text {
96
110
  return;
97
111
  }
98
112
 
113
+
99
114
  this.#style.setupStyle(ctx, 1);
100
- const measurements = ctx.measureText(this.#value);
101
- this.#size.x = measurements.width;
102
- this.#size.y = measurements.actualBoundingBoxAscent + measurements.actualBoundingBoxDescent;
115
+
116
+ if (this.#maxWidth == -1) {
117
+ const measurements = ctx.measureText(this.#value);
118
+ this.#size.x = measurements.width;
119
+ this.#size.y = measurements.actualBoundingBoxAscent + measurements.actualBoundingBoxDescent;
120
+ this.#textToRender = [this.#value];
121
+ this.#measured = true;
122
+ return;
123
+ }
124
+
125
+ this.#textToRender = splitStringIntoLines(ctx, this.#value, this.#maxWidth);
126
+
127
+ this.#size.x = 0;
128
+ this.#size.y = 0;
129
+ for (let i = 0; i < this.#textToRender.length; i++) {
130
+ const measurements = ctx.measureText(this.#textToRender[i]);
131
+ this.#size.x = Math.max(this.#size.x, measurements.width);
132
+ }
133
+
134
+ this.#size.y += ((this.#textToRender.length - 1) * this.#lineSpacing) + (this.#style.getSize() * this.#textToRender.length);
103
135
  this.#measured = true;
104
136
  }
105
137
 
@@ -109,10 +141,15 @@ export class Text {
109
141
  ScaleVector(out, scale);
110
142
  }
111
143
 
144
+ height(ctx: CanvasRenderingContext2D): number {
145
+ this.#measure(ctx);
146
+ return this.#size.y;
147
+ }
148
+
112
149
  setColor(color: string): void {
113
150
  this.#style.setColor(color);
114
151
  }
115
-
152
+
116
153
  getColor(): string {
117
154
  return this.#style.getColor();
118
155
  }
@@ -126,8 +163,17 @@ export class Text {
126
163
  }
127
164
 
128
165
  render(ctx: CanvasRenderingContext2D, scale: number, position: Vector2): void {
129
- this.#style.setupStyle(ctx, scale)
130
- ctx.fillText(this.#value, position.x, position.y);
166
+ this.#measure(ctx);
167
+ this.#style.setupStyle(ctx, scale);
168
+ let yOffset = 0;
169
+ for (let i = 0; i < this.#textToRender.length; i++) {
170
+ ctx.fillText(
171
+ this.#textToRender[i],
172
+ position.x,
173
+ position.y + yOffset
174
+ );
175
+ yOffset += (this.#style.getSize() + this.#lineSpacing) * scale
176
+ }
131
177
  }
132
178
 
133
179
  style(): TextStyle {