@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/LICENSE +21 -0
- package/dist/index.d.mts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +141 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +140 -31
- package/dist/index.mjs.map +1 -1
- package/index.html +14 -0
- package/package.json +1 -1
- package/src/graph.ts +1 -1
- package/src/node.ts +87 -2
- package/src/theme.ts +5 -0
- package/src/types/text.ts +53 -7
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
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
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
|
-
|
|
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
|
-
|
|
101
|
-
this.#
|
|
102
|
-
|
|
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.#
|
|
130
|
-
|
|
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 {
|