@elicdavis/node-flow 0.1.20 → 0.2.1
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/dist/index.d.mts +393 -226
- package/dist/index.d.ts +393 -226
- package/dist/index.js +1905 -1058
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1888 -1057
- package/dist/index.mjs.map +1 -1
- package/index.html +1134 -329
- package/package.json +1 -1
- package/src/connection.ts +30 -5
- package/src/contextMenu.ts +66 -34
- package/src/graph.ts +19 -14
- package/src/index.ts +26 -12
- package/src/markdown/entry.ts +11 -15
- package/src/markdown/syntaxParser.ts +2 -1
- package/src/node.ts +160 -101
- package/src/nodes/subsystem.ts +113 -29
- package/src/notes/note.ts +9 -1
- package/src/organize.ts +540 -111
- package/src/popup.ts +4 -2
- package/src/port.ts +83 -6
- package/src/styles/bevel.ts +31 -0
- package/src/styles/cursor.ts +1 -0
- package/src/styles/text.ts +4 -4
- package/src/theme.ts +179 -92
- package/src/types/text.ts +5 -1
- package/src/utils/color.ts +1 -1
- package/src/widgets/button.ts +52 -107
- package/src/widgets/color.ts +91 -150
- package/src/widgets/factory.ts +89 -46
- package/src/widgets/field.ts +99 -0
- package/src/widgets/image.ts +107 -165
- package/src/widgets/inplace.ts +136 -0
- package/src/widgets/number.ts +115 -154
- package/src/widgets/row.ts +138 -0
- package/src/widgets/slider.ts +101 -216
- package/src/widgets/string.ts +32 -130
- package/src/widgets/text.ts +81 -92
- package/src/widgets/toggle.ts +62 -207
- package/src/widgets/value.ts +127 -0
- package/src/widgets/widget.ts +173 -6
- package/test/context_menu.test.ts +82 -0
- package/test/cursor.test.ts +17 -0
- package/test/hidden_port.test.ts +125 -0
- package/test/markdown.test.ts +2 -2
- package/test/markdown_code_block.test.ts +58 -0
- package/test/multi_input.test.ts +124 -0
- package/test/organize.test.ts +117 -1
- package/test/support.ts +188 -0
- package/test/theme.test.ts +86 -0
- package/test/widget_click.test.ts +111 -97
- package/test/widget_construction.test.ts +5 -4
- package/test/widgets.test.ts +386 -0
- package/test/wire_follows_camera.test.ts +57 -0
package/src/popup.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Theme } from "./theme";
|
|
2
|
+
|
|
1
3
|
export interface PopupConfig {
|
|
2
4
|
title: string;
|
|
3
5
|
options: Array<string>;
|
|
@@ -58,11 +60,11 @@ export class Popup {
|
|
|
58
60
|
|
|
59
61
|
Show(): void {
|
|
60
62
|
this.#popup = document.createElement('div');
|
|
61
|
-
this.#popup.style.cssText =
|
|
63
|
+
this.#popup.style.cssText = `position:absolute;top:0;width:100%;display:flex;height:100%;justify-content:center;align-items:center;background-color:${Theme.Popup.Backdrop};`;
|
|
62
64
|
document.body.appendChild(this.#popup);
|
|
63
65
|
|
|
64
66
|
const container = document.createElement('div');
|
|
65
|
-
container.style.cssText =
|
|
67
|
+
container.style.cssText = `background-color:${Theme.Popup.BackgroundColor};color:${Theme.Popup.FontColor};font-family:${Theme.Popup.FontFamily};border-radius:${Theme.Popup.BorderRadius}px;padding:16px;`;
|
|
66
68
|
this.#popup.appendChild(container);
|
|
67
69
|
|
|
68
70
|
const title = document.createElement("h2");
|
package/src/port.ts
CHANGED
|
@@ -51,6 +51,11 @@ export interface PortConfig {
|
|
|
51
51
|
*/
|
|
52
52
|
acceptedTypes?: Array<string>;
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Leaves the port off the node until something is connected to it.
|
|
56
|
+
*/
|
|
57
|
+
hidden?: boolean;
|
|
58
|
+
|
|
54
59
|
emptyStyle?: PortStyle;
|
|
55
60
|
filledStyle?: PortStyle;
|
|
56
61
|
onConnectionAdded?: ConnectionChangeCallback;
|
|
@@ -88,6 +93,28 @@ export function portConfigTypes(config: PortConfig): Array<string> {
|
|
|
88
93
|
return [config.type === undefined ? "" : config.type];
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
export const ARRAY_SLOT_PITCH = 12;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Which gap between a port's wires a point is nearest: 0 is above the first,
|
|
100
|
+
* `count` is below the last.
|
|
101
|
+
*/
|
|
102
|
+
export function arrayInsertIndex(box: Box, count: number, y: number): number {
|
|
103
|
+
if (count === 0) {
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
const index = Math.round((y - box.Position.y) / (box.Size.y / count));
|
|
107
|
+
return Math.min(count, Math.max(0, index));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function arraySlotCenter(box: Box, count: number, index: number): Vector2 {
|
|
111
|
+
const slots = Math.max(1, count);
|
|
112
|
+
return {
|
|
113
|
+
x: box.Position.x + (box.Size.x / 2),
|
|
114
|
+
y: box.Position.y + ((index + 0.5) * box.Size.y / slots),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
91
118
|
// Calculate a color hash for an arbirary type
|
|
92
119
|
function fallbackColor(type: string, s: number): string {
|
|
93
120
|
let value = 0;
|
|
@@ -128,6 +155,8 @@ export class Port {
|
|
|
128
155
|
|
|
129
156
|
#array: boolean;
|
|
130
157
|
|
|
158
|
+
#hidden: boolean;
|
|
159
|
+
|
|
131
160
|
#configuredEmptyFill: string | undefined;
|
|
132
161
|
|
|
133
162
|
#configuredFilledFill: string | undefined;
|
|
@@ -148,6 +177,7 @@ export class Port {
|
|
|
148
177
|
this.#acceptedTypes = config?.acceptedTypes === undefined ? [] : config.acceptedTypes;
|
|
149
178
|
this.#description = config?.description === undefined ? "" : config.description;
|
|
150
179
|
this.#array = config?.array === true;
|
|
180
|
+
this.#hidden = config?.hidden === true;
|
|
151
181
|
this.#configuredEmptyFill = config?.emptyStyle?.fillColor;
|
|
152
182
|
this.#configuredFilledFill = config?.filledStyle?.fillColor;
|
|
153
183
|
|
|
@@ -260,9 +290,13 @@ export class Port {
|
|
|
260
290
|
this.#refreshDataType();
|
|
261
291
|
}
|
|
262
292
|
|
|
263
|
-
addConnection(connection: Connection): void {
|
|
264
|
-
const c =
|
|
265
|
-
|
|
293
|
+
addConnection(connection: Connection, index?: number): void {
|
|
294
|
+
const c = index === undefined
|
|
295
|
+
? this.#connections.length
|
|
296
|
+
: Math.min(Math.max(index, 0), this.#connections.length);
|
|
297
|
+
const was = this.hidden();
|
|
298
|
+
this.#connections.splice(c, 0, connection);
|
|
299
|
+
this.#visibilityMayHaveChanged(was);
|
|
266
300
|
for (let i = 0; i < this.#onConnectionAdded.length; i++) {
|
|
267
301
|
this.#onConnectionAdded[i](connection, c, this, this.#portType, this.#node);
|
|
268
302
|
}
|
|
@@ -287,6 +321,36 @@ export class Port {
|
|
|
287
321
|
return this.#connections;
|
|
288
322
|
}
|
|
289
323
|
|
|
324
|
+
/**
|
|
325
|
+
* How many wires the port has room for: one per connection on an array
|
|
326
|
+
* input, so each can be told apart and picked up on its own.
|
|
327
|
+
*/
|
|
328
|
+
slots(): number {
|
|
329
|
+
if (this.#portType !== PortType.InputArray) {
|
|
330
|
+
return 1;
|
|
331
|
+
}
|
|
332
|
+
return Math.max(1, this.#connections.length);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
setHidden(hidden: boolean): void {
|
|
336
|
+
const was = this.hidden();
|
|
337
|
+
this.#hidden = hidden;
|
|
338
|
+
this.#visibilityMayHaveChanged(was);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#visibilityMayHaveChanged(was: boolean): void {
|
|
342
|
+
if (was !== this.hidden() || this.#portType === PortType.InputArray) {
|
|
343
|
+
this.#node?.invalidateLayout();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* A port asked to hide still shows while anything is connected to it.
|
|
349
|
+
*/
|
|
350
|
+
hidden(): boolean {
|
|
351
|
+
return this.#hidden && this.#connections.length === 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
290
354
|
addConnectionRemovedListener(callback: ConnectionChangeCallback) {
|
|
291
355
|
if (callback === undefined) {
|
|
292
356
|
return;
|
|
@@ -297,7 +361,9 @@ export class Port {
|
|
|
297
361
|
clearConnection(connection: Connection): void {
|
|
298
362
|
const index = this.#connections.indexOf(connection);
|
|
299
363
|
if (index > -1) {
|
|
364
|
+
const was = this.hidden();
|
|
300
365
|
this.#connections.splice(index, 1);
|
|
366
|
+
this.#visibilityMayHaveChanged(was);
|
|
301
367
|
for (let i = 0; i < this.#onConnectionRemoved.length; i++) {
|
|
302
368
|
this.#onConnectionRemoved[i](connection, index, this, this.#portType, this.#node);
|
|
303
369
|
}
|
|
@@ -373,23 +439,34 @@ export class Port {
|
|
|
373
439
|
})
|
|
374
440
|
}
|
|
375
441
|
|
|
442
|
+
const slots = this.slots();
|
|
443
|
+
const height = slots === 1 ? scaledRadius * 2 : slots * ARRAY_SLOT_PITCH * camera.zoom;
|
|
376
444
|
this.#box.Position.x = position.x - scaledRadius;
|
|
377
|
-
this.#box.Position.y = position.y -
|
|
445
|
+
this.#box.Position.y = position.y - (height / 2);
|
|
378
446
|
this.#box.Size.x = scaledRadius * 2;
|
|
379
|
-
this.#box.Size.y =
|
|
447
|
+
this.#box.Size.y = height;
|
|
380
448
|
|
|
381
449
|
ctx.strokeStyle = style.borderColor as string;
|
|
382
450
|
ctx.fillStyle = style.fillColor as string;
|
|
383
451
|
|
|
384
452
|
ctx.beginPath();
|
|
385
453
|
if (this.#portType === PortType.InputArray) {
|
|
386
|
-
ctx.
|
|
454
|
+
ctx.roundRect(this.#box.Position.x, this.#box.Position.y, this.#box.Size.x, this.#box.Size.y, slots === 1 ? 0 : scaledRadius);
|
|
387
455
|
} else {
|
|
388
456
|
ctx.arc(position.x, position.y, scaledRadius, 0, 2 * Math.PI);
|
|
389
457
|
}
|
|
390
458
|
ctx.fill();
|
|
391
459
|
ctx.stroke();
|
|
392
460
|
|
|
461
|
+
if (slots > 1) {
|
|
462
|
+
ctx.fillStyle = style.borderColor as string;
|
|
463
|
+
for (let i = 0; i < slots; i++) {
|
|
464
|
+
const slot = arraySlotCenter(this.#box, slots, i);
|
|
465
|
+
ctx.beginPath();
|
|
466
|
+
ctx.arc(slot.x, slot.y, scaledRadius * 0.4, 0, 2 * Math.PI);
|
|
467
|
+
ctx.fill();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
393
470
|
|
|
394
471
|
return this.#box;
|
|
395
472
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { BevelColors } from "../theme";
|
|
2
|
+
import { Box } from "../types/box";
|
|
3
|
+
|
|
4
|
+
export function drawBevel(ctx: CanvasRenderingContext2D, box: Box, scale: number, colors: BevelColors, sunken: boolean): void {
|
|
5
|
+
const width = Math.max(1, 2 * scale);
|
|
6
|
+
const half = width / 2;
|
|
7
|
+
const left = box.Position.x + half;
|
|
8
|
+
const top = box.Position.y + half;
|
|
9
|
+
const right = box.Position.x + box.Size.x - half;
|
|
10
|
+
const bottom = box.Position.y + box.Size.y - half;
|
|
11
|
+
|
|
12
|
+
ctx.save();
|
|
13
|
+
ctx.lineWidth = width;
|
|
14
|
+
ctx.lineCap = "square";
|
|
15
|
+
|
|
16
|
+
ctx.strokeStyle = sunken ? colors.Dark : colors.Light;
|
|
17
|
+
ctx.beginPath();
|
|
18
|
+
ctx.moveTo(left, bottom);
|
|
19
|
+
ctx.lineTo(left, top);
|
|
20
|
+
ctx.lineTo(right, top);
|
|
21
|
+
ctx.stroke();
|
|
22
|
+
|
|
23
|
+
ctx.strokeStyle = sunken ? colors.Light : colors.Dark;
|
|
24
|
+
ctx.beginPath();
|
|
25
|
+
ctx.moveTo(right, top);
|
|
26
|
+
ctx.lineTo(right, bottom);
|
|
27
|
+
ctx.lineTo(left, bottom);
|
|
28
|
+
ctx.stroke();
|
|
29
|
+
|
|
30
|
+
ctx.restore();
|
|
31
|
+
}
|
package/src/styles/cursor.ts
CHANGED
package/src/styles/text.ts
CHANGED
|
@@ -38,7 +38,7 @@ export class TextStyle {
|
|
|
38
38
|
|
|
39
39
|
#color: string;
|
|
40
40
|
|
|
41
|
-
#font: string;
|
|
41
|
+
#font: string | undefined;
|
|
42
42
|
|
|
43
43
|
#weight: FontWeight;
|
|
44
44
|
|
|
@@ -47,14 +47,14 @@ export class TextStyle {
|
|
|
47
47
|
constructor(config: TextStyleConfig | undefined) {
|
|
48
48
|
this.#size = config?.size === undefined ? DefaultSize : config.size;
|
|
49
49
|
this.#color = config?.color === undefined ? DefaultColor : config.color;
|
|
50
|
-
this.#font = config?.font
|
|
50
|
+
this.#font = config?.font;
|
|
51
51
|
this.#weight = config?.weight === undefined ? FontWeight.Normal : config.weight;
|
|
52
52
|
this.#fontStyle = config?.style === undefined ? FontStyle.Normal : config.style;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
setupStyle(ctx: CanvasRenderingContext2D, scale: number): void {
|
|
56
56
|
ctx.fillStyle = this.#color;
|
|
57
|
-
ctx.font = this.#fontStyle + " " + this.#weight + " " + (this.#size * scale) + "px " + this
|
|
57
|
+
ctx.font = this.#fontStyle + " " + this.#weight + " " + (this.#size * scale) + "px " + this.getFont();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
getSize(): number {
|
|
@@ -62,7 +62,7 @@ export class TextStyle {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
getFont(): string {
|
|
65
|
-
return this.#font;
|
|
65
|
+
return this.#font ?? Theme.FontFamily;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
getWeight(): FontWeight {
|
package/src/theme.ts
CHANGED
|
@@ -1,92 +1,179 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
1
|
+
import { requestRender } from "./render_scheduler";
|
|
2
|
+
|
|
3
|
+
export interface BevelColors {
|
|
4
|
+
Light: string;
|
|
5
|
+
Dark: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const defaults = {
|
|
9
|
+
FontFamily: "Courier New",
|
|
10
|
+
Graph: {
|
|
11
|
+
BackgroundColor: "#07212a",
|
|
12
|
+
DotColor: "rgb(41, 54, 57)",
|
|
13
|
+
},
|
|
14
|
+
Node: {
|
|
15
|
+
FontColor: "#afb9bb",
|
|
16
|
+
BackgroundColor: "#0c2b35",
|
|
17
|
+
Title: {
|
|
18
|
+
Color: "#154050",
|
|
19
|
+
FontColor: "#afb9bb",
|
|
20
|
+
Padding: 5
|
|
21
|
+
},
|
|
22
|
+
BorderRadius: 15,
|
|
23
|
+
Border: {
|
|
24
|
+
Idle: "#1c1c1c",
|
|
25
|
+
MouseOver: "#8e8e8e",
|
|
26
|
+
Grabbed: "white",
|
|
27
|
+
Selected: "#6e6e6e"
|
|
28
|
+
},
|
|
29
|
+
Bevel: null as BevelColors | null,
|
|
30
|
+
Port: {
|
|
31
|
+
FontColor: "#afb9bb"
|
|
32
|
+
},
|
|
33
|
+
Message: {
|
|
34
|
+
InfoColor: "#afb9bb",
|
|
35
|
+
WarnColor: "#f1f663ff",
|
|
36
|
+
ErrorColor: "#FF0000"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
BoxSelect: {
|
|
40
|
+
Color: "white",
|
|
41
|
+
Size: 1,
|
|
42
|
+
Radius: 2,
|
|
43
|
+
LineDashLength: 5,
|
|
44
|
+
},
|
|
45
|
+
Widget: {
|
|
46
|
+
FontColor: "#afb9bb",
|
|
47
|
+
LabelColor: "#7f9497",
|
|
48
|
+
FontSize: 14,
|
|
49
|
+
Padding: 6,
|
|
50
|
+
Gap: 4,
|
|
51
|
+
BackgroundColor: "#0f313c",
|
|
52
|
+
Border: {
|
|
53
|
+
Color: "#154050",
|
|
54
|
+
Size: 2,
|
|
55
|
+
Radius: 2
|
|
56
|
+
},
|
|
57
|
+
Bevel: null as BevelColors | null,
|
|
58
|
+
Hover: {
|
|
59
|
+
BackgroundColor: "#195366"
|
|
60
|
+
},
|
|
61
|
+
Input: {
|
|
62
|
+
BackgroundColor: "#0f313c",
|
|
63
|
+
HoverBackgroundColor: "#195366",
|
|
64
|
+
FontColor: "#afb9bb",
|
|
65
|
+
},
|
|
66
|
+
Slider: {
|
|
67
|
+
FillColor: "#07212A",
|
|
68
|
+
},
|
|
69
|
+
Button: {
|
|
70
|
+
Click: {
|
|
71
|
+
BackgroundColor: "#1f637a"
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
ContextMenu: {
|
|
76
|
+
BackgroundColor: "#07212A",
|
|
77
|
+
HighlightColor: "#205A6D",
|
|
78
|
+
HighlightFontColor: "#afb9bb",
|
|
79
|
+
FontColor: "#afb9bb",
|
|
80
|
+
DisabledFontColor: "#4a585c",
|
|
81
|
+
BorderRadius: 5,
|
|
82
|
+
},
|
|
83
|
+
Minimap: {
|
|
84
|
+
BackgroundColor: "rgba(10, 16, 18, 0.85)",
|
|
85
|
+
BorderColor: "#1c1c1c",
|
|
86
|
+
BorderRadius: 4,
|
|
87
|
+
Padding: 6,
|
|
88
|
+
NodeColor: "#6b7f85",
|
|
89
|
+
ViewportColor: "rgba(175, 185, 187, 0.25)",
|
|
90
|
+
},
|
|
91
|
+
Popup: {
|
|
92
|
+
BackgroundColor: "#000000",
|
|
93
|
+
FontColor: "#ffffff",
|
|
94
|
+
FontFamily: "Verdana",
|
|
95
|
+
BorderRadius: 8,
|
|
96
|
+
Backdrop: "#00000070",
|
|
97
|
+
},
|
|
98
|
+
Note: {
|
|
99
|
+
FontColor: "#afb9bb",
|
|
100
|
+
FontSize: 16,
|
|
101
|
+
EntrySpacing: 20,
|
|
102
|
+
LineSpacing: 5,
|
|
103
|
+
DotSize: 3,
|
|
104
|
+
HeaderLineWidth: 2,
|
|
105
|
+
H1: {
|
|
106
|
+
FontSize: 32,
|
|
107
|
+
},
|
|
108
|
+
H2: {
|
|
109
|
+
FontSize: 16,
|
|
110
|
+
},
|
|
111
|
+
H3: {
|
|
112
|
+
FontSize: 16,
|
|
113
|
+
},
|
|
114
|
+
CodeBlock: {
|
|
115
|
+
BackgroundColor: "#0c2b35",
|
|
116
|
+
FontColor: "#afb9bb",
|
|
117
|
+
Padding: 16,
|
|
118
|
+
BorderRadius: 4,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export type ThemeValues = typeof defaults;
|
|
124
|
+
|
|
125
|
+
type DeepPartial<T> = {
|
|
126
|
+
[K in keyof T]?: T[K] extends object | null
|
|
127
|
+
? (null extends T[K] ? T[K] : DeepPartial<T[K]>)
|
|
128
|
+
: T[K];
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export type ThemeConfig = DeepPartial<ThemeValues>;
|
|
132
|
+
|
|
133
|
+
function clone<T>(value: T): T {
|
|
134
|
+
return JSON.parse(JSON.stringify(value));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function merge(target: any, source: any): any {
|
|
138
|
+
if (source === undefined) {
|
|
139
|
+
return target;
|
|
140
|
+
}
|
|
141
|
+
if (source === null || typeof source !== "object" || target === null || typeof target !== "object") {
|
|
142
|
+
return source;
|
|
143
|
+
}
|
|
144
|
+
for (const key of Object.keys(source)) {
|
|
145
|
+
target[key] = merge(target[key], source[key]);
|
|
146
|
+
}
|
|
147
|
+
return target;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const DefaultTheme: Readonly<ThemeValues> = Object.freeze(clone(defaults));
|
|
151
|
+
|
|
152
|
+
export const Theme: ThemeValues = clone(defaults);
|
|
153
|
+
|
|
154
|
+
let version = 0;
|
|
155
|
+
|
|
156
|
+
const listeners = new Set<() => void>();
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Replaces the theme: the defaults, with anything in config laid over them.
|
|
160
|
+
* Everything already on screen picks the change up on its next draw.
|
|
161
|
+
*/
|
|
162
|
+
export function setTheme(config?: ThemeConfig): void {
|
|
163
|
+
const next = merge(clone(defaults), config);
|
|
164
|
+
for (const key of Object.keys(next) as Array<keyof ThemeValues>) {
|
|
165
|
+
(Theme as any)[key] = next[key];
|
|
166
|
+
}
|
|
167
|
+
version++;
|
|
168
|
+
listeners.forEach((listener) => listener());
|
|
169
|
+
requestRender();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function themeVersion(): number {
|
|
173
|
+
return version;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function onThemeChange(listener: () => void): () => void {
|
|
177
|
+
listeners.add(listener);
|
|
178
|
+
return () => listeners.delete(listener);
|
|
179
|
+
}
|
package/src/types/text.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requestRender } from "../render_scheduler";
|
|
2
|
+
import { themeVersion } from "../theme";
|
|
2
3
|
import { FontWeight, TextStyle, TextStyleConfig } from "../styles/text";
|
|
3
4
|
import { splitString, splitStringIntoLines } from "../utils/string";
|
|
4
5
|
import { CopyVector2, ScaleVector, Vector2, Zero } from "./vector2";
|
|
@@ -12,6 +13,8 @@ export class Text {
|
|
|
12
13
|
|
|
13
14
|
#measured: boolean
|
|
14
15
|
|
|
16
|
+
#measuredForTheme = -1;
|
|
17
|
+
|
|
15
18
|
#size: Vector2;
|
|
16
19
|
|
|
17
20
|
#style: TextStyle;
|
|
@@ -110,9 +113,10 @@ export class Text {
|
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
#measure(ctx: CanvasRenderingContext2D): void {
|
|
113
|
-
if (this.#measured) {
|
|
116
|
+
if (this.#measured && this.#measuredForTheme === themeVersion()) {
|
|
114
117
|
return;
|
|
115
118
|
}
|
|
119
|
+
this.#measuredForTheme = themeVersion();
|
|
116
120
|
|
|
117
121
|
|
|
118
122
|
this.#style.setupStyle(ctx, 1);
|
package/src/utils/color.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function RgbToHex(c: Color): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function HexToColor(hex: string): Color | null {
|
|
25
|
-
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})
|
|
25
|
+
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})(?:[a-f\d]{2})?$/i.exec(hex);
|
|
26
26
|
return result ? {
|
|
27
27
|
r: parseInt(result[1], 16) / 255,
|
|
28
28
|
g: parseInt(result[2], 16) / 255,
|