@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,330 @@
1
+ import { ContextMenuConfig, ContextMenuItemConfig } from "./contextMenu";
2
+ import { ContainerRenderElement as ContainerElement } from "./elements/container";
3
+ import { IRenderElement } from "./elements/interfaces";
4
+ import { TextAlign, TextElement as TextElement, VerticalAlign } from "./elements/text";
5
+ import { FontWeight } from "./styles/text";
6
+ import { Theme } from "./theme";
7
+ import { Text } from './types/text';
8
+ import { ScaleVector, Vector2 } from './types/vector2';
9
+ import { Display } from './elements/base';
10
+
11
+ class QuickMenuGroup {
12
+
13
+ #text: TextElement;
14
+
15
+ public items: Array<QuickMenuItem>;
16
+
17
+ constructor(text: TextElement, items: Array<QuickMenuItem>) {
18
+ this.#text = text;
19
+ this.items = items;
20
+ }
21
+
22
+ updateRendering(terms: Array<string>): void {
23
+ let shouldRender = false;
24
+ for (let i = 0; i < this.items.length; i++) {
25
+ const kept = this.items[i].filter(terms);
26
+ this.items[i].render(kept);
27
+ if (kept) {
28
+ shouldRender = true;
29
+ }
30
+ }
31
+
32
+ this.#text.setDisplay(shouldRender ? Display.Flex : Display.None);
33
+ }
34
+
35
+ }
36
+
37
+ class QuickMenuItem {
38
+ #searchText: string;
39
+ #text: TextElement;
40
+ #context: ContextMenuItemConfig
41
+
42
+ constructor(searchText: string, text: TextElement, context: ContextMenuItemConfig) {
43
+ this.#searchText = searchText.toLowerCase();
44
+ this.#text = text;
45
+ this.#context = context;
46
+ }
47
+
48
+ filter(terms: Array<string>): boolean {
49
+ if (terms.length === 0) {
50
+ return true;
51
+ }
52
+ for (let i = 0; i < terms.length; i++) {
53
+ if (terms[i] === "") {
54
+ continue;
55
+ }
56
+
57
+ if (this.#searchText.indexOf(terms[i]) === -1) {
58
+ return false;
59
+ }
60
+ }
61
+ return true;
62
+ }
63
+
64
+ render(render: boolean): void {
65
+ this.#text.setDisplay(render ? Display.Flex : Display.None);
66
+ }
67
+
68
+ rendering(): boolean {
69
+ return this.#text.getDisplay() === Display.Flex;
70
+ }
71
+
72
+ highlight(highlight: boolean): void {
73
+ this.#text.setBackgroundColor(highlight ? Theme.ContextMenu.HighlightColor : "");
74
+ }
75
+
76
+ select(): void {
77
+ if (this.#context.callback) {
78
+ this.#context.callback()
79
+ }
80
+ }
81
+ }
82
+
83
+ function RecurseBuildContainers(
84
+ path: string,
85
+ config: ContextMenuConfig,
86
+ renderElements: Array<IRenderElement>,
87
+ groups: Array<QuickMenuGroup>
88
+ ): void {
89
+
90
+ let currentPath = config?.name ?? "";
91
+ if (path !== "") {
92
+ currentPath = path + " / " + currentPath;
93
+ }
94
+
95
+ if (config?.items && config.items.length > 0) {
96
+ const titleText = new TextElement(
97
+ new Text(
98
+ currentPath,
99
+ {
100
+ color: Theme.ContextMenu.FontColor,
101
+ weight: FontWeight.Bold,
102
+ size: 12,
103
+ }
104
+ ),
105
+ {
106
+ Align: TextAlign.Center,
107
+ VerticalAlign: VerticalAlign.Center,
108
+ Padding: 16
109
+ }
110
+ );
111
+ renderElements.push(titleText);
112
+
113
+ const quickItems = new Array<QuickMenuItem>();
114
+
115
+ for (let i = 0; i < config.items.length; i++) {
116
+ const configItem = config.items[i];
117
+
118
+ const itemText = new TextElement(
119
+ new Text(
120
+ configItem.name ?? "Item",
121
+ {
122
+ color: Theme.ContextMenu.FontColor,
123
+ }
124
+ ),
125
+ {
126
+ Padding: 8
127
+ }
128
+ );
129
+
130
+ quickItems.push(new QuickMenuItem(
131
+ currentPath + (configItem.name ?? ""),
132
+ itemText,
133
+ configItem
134
+ ));
135
+
136
+ renderElements.push(itemText);
137
+ }
138
+
139
+ groups.push(new QuickMenuGroup(titleText, quickItems));
140
+ }
141
+
142
+ if (config?.subMenus) {
143
+ for (let i = 0; i < config?.subMenus.length; i++) {
144
+ const configItem = config?.subMenus[i];
145
+ RecurseBuildContainers(currentPath, configItem, renderElements, groups);
146
+ }
147
+ }
148
+ }
149
+
150
+ export class QuickMenu {
151
+
152
+ #menuGroups: Array<QuickMenuGroup>
153
+
154
+ #container: ContainerElement
155
+
156
+ #searchText: Text;
157
+
158
+ #currentSelection: number;
159
+
160
+ constructor(config?: ContextMenuConfig) {
161
+ this.#menuGroups = [];
162
+ this.#currentSelection = 0;
163
+
164
+ this.#searchText = new Text(
165
+ "",
166
+ {
167
+ color: Theme.ContextMenu.FontColor,
168
+ }
169
+ );
170
+
171
+ const elements = [
172
+ new TextElement(
173
+ new Text(
174
+ "Search",
175
+ {
176
+ color: Theme.ContextMenu.FontColor,
177
+ }
178
+ ),
179
+ {
180
+ Padding: {
181
+ Bottom: 0,
182
+ Left: 8,
183
+ Right: 8,
184
+ Top: 8
185
+ },
186
+ }
187
+ ),
188
+
189
+ // Search Box...
190
+ new ContainerElement(
191
+ [
192
+ new TextElement(
193
+ this.#searchText,
194
+ undefined
195
+ ),
196
+ ],
197
+ {
198
+ Border: {
199
+ Radius: 2,
200
+ },
201
+ BackgroundColor: "#003847",
202
+ Padding: 8,
203
+ Margin: 8,
204
+ MinHeight: 46,
205
+ }
206
+ ),
207
+ ]
208
+
209
+ if (config) {
210
+ RecurseBuildContainers("", config, elements, this.#menuGroups);
211
+ }
212
+
213
+ this.#container = new ContainerElement(
214
+ elements,
215
+ {
216
+ BackgroundColor: Theme.ContextMenu.BackgroundColor,
217
+ Border: {
218
+ Thickness: 1,
219
+ Radius: 4
220
+ }
221
+ }
222
+ )
223
+
224
+ this.#updateHighlighting();
225
+ }
226
+
227
+ execute(): void {
228
+ let selection = this.#currentSelection;
229
+
230
+ for (let groupIndex = 0; groupIndex < this.#menuGroups.length; groupIndex++) {
231
+ const group = this.#menuGroups[groupIndex];
232
+
233
+ for (let itemIndex = 0; itemIndex < group.items.length; itemIndex++) {
234
+ if (group.items[itemIndex].rendering()) {
235
+ if (selection === 0) {
236
+ group.items[itemIndex].select();
237
+ return;
238
+ }
239
+ selection--;
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ #updateHighlighting(): void {
246
+ const searchText = this.#searchText
247
+ .get()
248
+ .trim()
249
+ .toLowerCase();
250
+
251
+ let terms = new Array<string>();
252
+ if (searchText !== "") {
253
+ var potentialTerms = searchText.split(" ");
254
+ for (let i = 0; i < potentialTerms.length; i++) {
255
+ const cleaned = potentialTerms[i].trim();
256
+ if (cleaned !== "") {
257
+ terms.push(cleaned);
258
+ }
259
+ }
260
+ }
261
+
262
+ let selection = this.#currentSelection;
263
+
264
+ for (let groupIndex = 0; groupIndex < this.#menuGroups.length; groupIndex++) {
265
+ const group = this.#menuGroups[groupIndex];
266
+ group.updateRendering(terms);
267
+
268
+ for (let itemIndex = 0; itemIndex < group.items.length; itemIndex++) {
269
+ if (group.items[itemIndex].rendering()) {
270
+ group.items[itemIndex].highlight(selection === 0)
271
+ selection--;
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ keyboardEvent(event: KeyboardEvent): void {
278
+
279
+ let intercepted = true;
280
+
281
+ if (event.code === "Backspace") {
282
+ this.#currentSelection = 0;
283
+ const text = this.#searchText.get();
284
+ this.#searchText.set(text.slice(0, text.length - 1));
285
+ } else if (/^[a-zA-Z0-9]$/.test(event.key) || event.key === " ") {
286
+ this.#currentSelection = 0;
287
+ this.#searchText.set(this.#searchText.get() + event.key);
288
+ } else if (event.key === "ArrowUp") {
289
+ this.#currentSelection = Math.max(0, this.#currentSelection - 1);
290
+ } else if (event.key === "ArrowDown") {
291
+ this.#currentSelection ++;
292
+ } else {
293
+ intercepted = false;
294
+ }
295
+
296
+ if (intercepted) {
297
+ this.#updateHighlighting();
298
+ event.preventDefault();
299
+ }
300
+
301
+ // console.log(event);
302
+ }
303
+
304
+ renderSize = { x: 0, y: 0 };
305
+
306
+ public render(ctx: CanvasRenderingContext2D, pos: Vector2, graphScale: number, mousePosition: Vector2 | undefined): null {
307
+ this.#container.calcSize(ctx, this.renderSize, { x: -1, y: -1 });
308
+ // ScaleVector(this.renderSize, graphScale);
309
+
310
+ // Clamp the position so it's not spilling off the canvas
311
+ if (pos.y + Math.min(this.renderSize.y, 400) > ctx.canvas.clientHeight) {
312
+ pos.y = ctx.canvas.clientHeight - Math.min(this.renderSize.y, 400);
313
+ }
314
+
315
+ if (pos.x + this.renderSize.x > ctx.canvas.clientWidth) {
316
+ pos.x = ctx.canvas.clientWidth - this.renderSize.x;
317
+ }
318
+
319
+ if (pos.y < 0) {
320
+ pos.y = 0;
321
+ }
322
+
323
+ if (pos.x < 0) {
324
+ pos.x = 0;
325
+ }
326
+
327
+ this.#container.render(ctx, pos, 1, this.renderSize);
328
+ return null;
329
+ }
330
+ }
@@ -0,0 +1,104 @@
1
+ import { Box } from "../types/box";
2
+ import { StrokeStyle, StrokeStyleConfig, StrokeStyleWithFallback } from "./stroke";
3
+
4
+ export interface BoxStyleConfig {
5
+ border?: StrokeStyleConfig;
6
+ color?: string;
7
+ radius?: number;
8
+ }
9
+
10
+ export function BoxStyleWithFallback(input?: BoxStyleConfig, fallback?: BoxStyleConfig): BoxStyleConfig {
11
+ return {
12
+ radius: input?.radius === undefined ? fallback?.radius : input?.radius,
13
+ color: input?.color === undefined ? fallback?.color : input?.color,
14
+ border: StrokeStyleWithFallback(input?.border, fallback?.border)
15
+ };
16
+ }
17
+
18
+ export class BoxStyle {
19
+ #border: StrokeStyle | null;
20
+
21
+ #color: string;
22
+
23
+ #radius: number;
24
+
25
+ constructor(config?: BoxStyleConfig) {
26
+ this.#color = config?.color === undefined ? "#CCCCCC" : config.color;
27
+ this.#border = config?.border === undefined ? null : new StrokeStyle(config.border);
28
+ this.#radius = config?.radius === undefined ? 2 : config.radius;
29
+ }
30
+
31
+ #box(ctx: CanvasRenderingContext2D, box: Box, scale: number, radius: any): void {
32
+ ctx.fillStyle = this.#color
33
+ this.#border?.setupStyle(ctx, scale);
34
+ ctx.beginPath();
35
+ ctx.roundRect(
36
+ box.Position.x,
37
+ box.Position.y,
38
+ box.Size.x,
39
+ box.Size.y,
40
+ radius
41
+ );
42
+ ctx.fill();
43
+ }
44
+
45
+ #draw(ctx: CanvasRenderingContext2D, box: Box, scale: number, radius: any): void {
46
+ this.#box(ctx, box, scale, radius);
47
+ ctx.stroke();
48
+ }
49
+
50
+ Outline(ctx: CanvasRenderingContext2D, box: Box, scale: number, radius: any): void {
51
+ this.#border?.setupStyle(ctx, scale);
52
+ ctx.beginPath();
53
+ ctx.roundRect(
54
+ box.Position.x,
55
+ box.Position.y,
56
+ box.Size.x,
57
+ box.Size.y,
58
+ radius
59
+ );
60
+ ctx.stroke();
61
+ }
62
+
63
+ Draw(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
64
+ this.#draw(ctx, box, scale, this.#radius * scale)
65
+ }
66
+
67
+ DrawRoundedTopOnly(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
68
+ this.#draw(ctx, box, scale, [this.#radius * scale * 2, this.#radius * scale * 2, 0, 0])
69
+ }
70
+
71
+ DrawUnderline(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
72
+ this.#box(ctx, box, scale, [this.#radius * scale * 2, this.#radius * scale * 2, 0, 0]);
73
+ ctx.beginPath();
74
+ ctx.moveTo(box.Position.x, box.Position.y + box.Size.y)
75
+ ctx.lineTo(box.Position.x + box.Size.x, box.Position.y + box.Size.y);
76
+ ctx.stroke();
77
+ }
78
+
79
+ borderSize(): number {
80
+ if (this.#border === null) {
81
+ return 0;
82
+ }
83
+ return this.#border.size();
84
+ }
85
+
86
+ radius(): number {
87
+ return this.#radius;
88
+ }
89
+
90
+ setColor(color: string): void {
91
+ this.#color = color;
92
+ }
93
+
94
+ setBorderColor(color: string): void {
95
+ if (this.#border === null) {
96
+ this.#border = new StrokeStyle({
97
+ color: color
98
+ })
99
+ } else {
100
+ this.#border.setColor(color);
101
+ }
102
+ }
103
+
104
+ }
@@ -0,0 +1,8 @@
1
+
2
+ export const enum TextAlign {
3
+ Center = "center",
4
+ End = "end",
5
+ Left = "left",
6
+ Right = "right",
7
+ Start = "start"
8
+ }
@@ -0,0 +1,9 @@
1
+
2
+ export enum TextBaseline {
3
+ Alphabetic = "alphabetic",
4
+ Bottom = "bottom",
5
+ Hanging = "hanging",
6
+ Ideographic = "ideographic",
7
+ Middle = "middle",
8
+ Top = "top",
9
+ }
@@ -0,0 +1,18 @@
1
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
2
+ export enum CursorStyle {
3
+ Default = 'default',
4
+ Pointer = 'pointer',
5
+ Grab = 'grab',
6
+ Grabbing = 'grabbing',
7
+ Wait = 'wait',
8
+ Help = 'help',
9
+ Crosshair = 'crosshair',
10
+ Cell = 'cell',
11
+ Text = 'text',
12
+ Move = 'move',
13
+ NotAllowed = 'not-allowed',
14
+ ZoomIn = 'zoom-in',
15
+ ZoomOut = 'zoom-out',
16
+ None = 'none',
17
+ ContextMenu = 'context-menu',
18
+ }
@@ -0,0 +1,39 @@
1
+ export interface StrokeStyleConfig {
2
+ size?: number;
3
+ color?: string;
4
+ }
5
+
6
+ export function StrokeStyleWithFallback(input?: StrokeStyleConfig, fallback?: StrokeStyleConfig): StrokeStyleConfig {
7
+ return {
8
+ color: input?.color === undefined ? fallback?.color : input?.color,
9
+ size: input?.size === undefined ? fallback?.size : input?.size,
10
+ };
11
+ }
12
+
13
+ export class StrokeStyle {
14
+ #size: number;
15
+
16
+ #color: string;
17
+
18
+ constructor(config?: StrokeStyleConfig) {
19
+ this.#size = config?.size === undefined ? 0.5 : config.size;
20
+ this.#color = config?.color === undefined ? "black" : config.color;
21
+ }
22
+
23
+ setupStyle(ctx: CanvasRenderingContext2D, scale: number): void {
24
+ ctx.strokeStyle = this.#color;
25
+ ctx.lineWidth = scale * this.#size;
26
+ }
27
+
28
+ setColor(newColor: string): void {
29
+ this.#color = newColor;
30
+ }
31
+
32
+ setSize(newSize: number): void {
33
+ this.#size = newSize;
34
+ }
35
+
36
+ size(): number {
37
+ return this.#size;
38
+ }
39
+ }
@@ -0,0 +1,90 @@
1
+ import { Theme } from "../theme";
2
+ import { Vector2 } from "../types/vector2";
3
+
4
+ export enum FontWeight {
5
+ Normal = "",
6
+ Bold = "bold"
7
+ }
8
+
9
+ export enum FontStyle {
10
+ Normal = "",
11
+ Italic = "italic"
12
+ }
13
+
14
+ export interface TextStyleConfig {
15
+ size?: number;
16
+ color?: string;
17
+ font?: string;
18
+ weight?: FontWeight;
19
+ style?: FontStyle;
20
+ }
21
+
22
+ const DefaultSize: number = 16;
23
+ const DefaultColor: string = "black";
24
+
25
+ export function TextStyleFallback(input?: TextStyleConfig, fallback?: TextStyleConfig): TextStyleConfig {
26
+ return {
27
+ color: input?.color === undefined ? fallback?.color : input?.color,
28
+ size: input?.size === undefined ? fallback?.size : input?.size,
29
+ font: input?.font === undefined ? fallback?.font : input?.font,
30
+ style: input?.style === undefined ? fallback?.style : input?.style,
31
+ weight: input?.weight === undefined ? fallback?.weight : input?.weight,
32
+ };
33
+ }
34
+
35
+ export class TextStyle {
36
+
37
+ #size: number;
38
+
39
+ #color: string;
40
+
41
+ #font: string;
42
+
43
+ #weight: FontWeight;
44
+
45
+ #fontStyle: FontStyle;
46
+
47
+ constructor(config: TextStyleConfig | undefined) {
48
+ this.#size = config?.size === undefined ? DefaultSize : config.size;
49
+ this.#color = config?.color === undefined ? DefaultColor : config.color;
50
+ this.#font = config?.font === undefined ? Theme.FontFamily : config.font;
51
+ this.#weight = config?.weight === undefined ? FontWeight.Normal : config.weight;
52
+ this.#fontStyle = config?.style === undefined ? FontStyle.Normal : config.style;
53
+ }
54
+
55
+ setupStyle(ctx: CanvasRenderingContext2D, scale: number): void {
56
+ ctx.fillStyle = this.#color;
57
+ ctx.font = this.#fontStyle + " " + this.#weight + " " + (this.#size * scale) + "px " + this.#font;
58
+ }
59
+
60
+ getSize(): number {
61
+ return this.#size;
62
+ }
63
+
64
+ getFont(): string {
65
+ return this.#font;
66
+ }
67
+
68
+ measure(ctx: CanvasRenderingContext2D, scale: number, text: string, out: Vector2): void {
69
+ this.setupStyle(ctx, scale);
70
+ const measurements = ctx.measureText(text)
71
+ out.x = measurements.width;
72
+ out.y = measurements.actualBoundingBoxAscent + measurements.actualBoundingBoxDescent;
73
+ }
74
+
75
+ setColor(color: string): void {
76
+ this.#color = color;
77
+ }
78
+
79
+ getColor(): string {
80
+ return this.#color ;
81
+ }
82
+
83
+ setSize(size: number): void {
84
+ this.#size = size;
85
+ }
86
+
87
+ setWeight(weight: FontWeight): void {
88
+ this.#weight = weight;
89
+ }
90
+ }
@@ -0,0 +1,82 @@
1
+ import { Box } from "../types/box";
2
+ import { BoxStyle, BoxStyleConfig, BoxStyleWithFallback } from "./box";
3
+ import { TextAlign } from "./canvasTextAlign";
4
+ import { TextBaseline } from "./canvasTextBaseline";
5
+ import { TextStyle, TextStyleConfig, TextStyleFallback } from './text';
6
+
7
+
8
+ export interface TextBoxStyleConfig {
9
+ box?: BoxStyleConfig;
10
+ text?: TextStyleConfig;
11
+
12
+ textAlign?: CanvasTextAlign;
13
+ textBaseline?: CanvasTextBaseline;
14
+ }
15
+
16
+ export function TextBoxStyleWithFallback(input?: TextBoxStyleConfig, fallback?: TextBoxStyleConfig): TextBoxStyleConfig {
17
+ return {
18
+ box: BoxStyleWithFallback(input?.box, fallback?.box),
19
+ text: TextStyleFallback(input?.text, fallback?.text),
20
+
21
+ textAlign: input?.textAlign === undefined ? fallback?.textAlign : input?.textAlign,
22
+ textBaseline: input?.textBaseline === undefined ? fallback?.textBaseline : input?.textBaseline,
23
+ };
24
+ }
25
+
26
+ export class TextBoxStyle {
27
+ #box: BoxStyle;
28
+
29
+ #text: TextStyle;
30
+
31
+ #textAlign: CanvasTextAlign;
32
+
33
+ #textBaseline: CanvasTextBaseline;
34
+
35
+ constructor(config?: TextBoxStyleConfig) {
36
+ this.#box = new BoxStyle(config?.box);
37
+ this.#text = new TextStyle(config?.text);
38
+
39
+ this.#textAlign = config?.textAlign === undefined ? TextAlign.Center : config.textAlign;
40
+ this.#textBaseline = config?.textBaseline === undefined ? TextBaseline.Middle : config.textBaseline;
41
+ }
42
+
43
+ Draw(ctx: CanvasRenderingContext2D, box: Box, scale: number, text: string) {
44
+ this.#box.Draw(ctx, box, scale);
45
+
46
+ ctx.textAlign = this.#textAlign;
47
+ ctx.textBaseline = this.#textBaseline;
48
+ this.#text.setupStyle(ctx, scale);
49
+
50
+ ctx.fillText(
51
+ text,
52
+ box.Position.x + (box.Size.x / 2),
53
+ box.Position.y + (box.Size.y / 2),
54
+ );
55
+ }
56
+
57
+ DrawUnderline(ctx: CanvasRenderingContext2D, box: Box, scale: number, text: string) {
58
+ this.#box.DrawUnderline(ctx, box, scale);
59
+
60
+ ctx.textAlign = this.#textAlign;
61
+ ctx.textBaseline = this.#textBaseline;
62
+ this.#text.setupStyle(ctx, scale);
63
+
64
+ ctx.fillText(
65
+ text,
66
+ box.Position.x + (box.Size.x / 2),
67
+ box.Position.y + (box.Size.y / 2),
68
+ );
69
+ }
70
+
71
+ setTextColor(color: string): void {
72
+ this.#text.setColor(color);
73
+ }
74
+
75
+ setBoxColor(color: string): void {
76
+ this.#box.setColor(color);
77
+ }
78
+
79
+ setBorderColor(color: string): void {
80
+ this.#box.setBorderColor(color);
81
+ }
82
+ }