@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/widgets/button.ts
CHANGED
|
@@ -1,107 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { height, width } from "./widget";
|
|
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
|
-
this.#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
text: { color: Theme.Widget.FontColor },
|
|
54
|
-
}));
|
|
55
|
-
this.#clickStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.clickStyle, {
|
|
56
|
-
box: {
|
|
57
|
-
color: Theme.Widget.Button.Click.BackgroundColor,
|
|
58
|
-
border: {
|
|
59
|
-
size: Theme.Widget.Border.Size,
|
|
60
|
-
color: Theme.Widget.Border.Color,
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
text: { color: Theme.Widget.FontColor },
|
|
64
|
-
}))
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
Size(): Vector2 {
|
|
68
|
-
return { "x": width, "y": height }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
|
|
72
|
-
const box = {
|
|
73
|
-
Position: { x: 0, y: 0 },
|
|
74
|
-
Size: {
|
|
75
|
-
x: width * scale,
|
|
76
|
-
y: height * scale
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
CopyVector2(box.Position, position);
|
|
80
|
-
|
|
81
|
-
let style: TextBoxStyle = this.#idleStyle;
|
|
82
|
-
|
|
83
|
-
if (mousePosition !== undefined && !this.#gettingClicked) {
|
|
84
|
-
if (InBox(box, mousePosition)) {
|
|
85
|
-
style = this.#hoverStyle;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (this.#gettingClicked) {
|
|
90
|
-
style = this.#clickStyle;
|
|
91
|
-
}
|
|
92
|
-
style.Draw(ctx, box, scale, this.#text);
|
|
93
|
-
|
|
94
|
-
return box
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
ClickStart(): void {
|
|
98
|
-
this.#gettingClicked = true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
ClickEnd(): void {
|
|
102
|
-
this.#gettingClicked = false;
|
|
103
|
-
if (this.#callback !== undefined) {
|
|
104
|
-
this.#callback();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
1
|
+
import { requestRender } from "../render_scheduler";
|
|
2
|
+
import { Box } from "../types/box";
|
|
3
|
+
import { Vector2 } from "../types/vector2";
|
|
4
|
+
import { drawField } from "./field";
|
|
5
|
+
import { height, width, WidgetBase } from "./widget";
|
|
6
|
+
|
|
7
|
+
export interface ButtonWidgetConfig {
|
|
8
|
+
text?: string;
|
|
9
|
+
callback?: () => void;
|
|
10
|
+
width?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ButtonWidget extends WidgetBase {
|
|
14
|
+
|
|
15
|
+
#text: string;
|
|
16
|
+
|
|
17
|
+
#callback?: () => void;
|
|
18
|
+
|
|
19
|
+
#width: number;
|
|
20
|
+
|
|
21
|
+
constructor(config?: ButtonWidgetConfig) {
|
|
22
|
+
super();
|
|
23
|
+
this.#text = config?.text ?? "Button";
|
|
24
|
+
this.#callback = config?.callback;
|
|
25
|
+
this.#width = config?.width ?? width;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Size(): Vector2 {
|
|
29
|
+
return { x: this.#width, y: height };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
text(): string {
|
|
33
|
+
return this.#text;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setText(text: string): void {
|
|
37
|
+
this.#text = text;
|
|
38
|
+
requestRender();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected render(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
|
|
42
|
+
drawField(ctx, box, scale, {
|
|
43
|
+
hovered: this.hovered(),
|
|
44
|
+
pressed: this.pressed(),
|
|
45
|
+
value: this.#text,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected onClick(): void {
|
|
50
|
+
this.#callback?.();
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/widgets/color.ts
CHANGED
|
@@ -1,150 +1,91 @@
|
|
|
1
|
-
import { HexToColor } from "../utils/color";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
-
// Update Styling
|
|
93
|
-
this.#contrast = contrastColor(this.#value);
|
|
94
|
-
this.#textBoxStyle.setBoxColor(this.#value);
|
|
95
|
-
this.#textBoxStyle.setBorderColor(this.#contrast);
|
|
96
|
-
this.#textBoxStyle.setTextColor(this.#contrast);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
ClickStart(): void {
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
ClickEnd(): void {
|
|
103
|
-
let input: HTMLInputElement | null = null;
|
|
104
|
-
|
|
105
|
-
const popup = new Popup({
|
|
106
|
-
title: "Set Color",
|
|
107
|
-
options: ["Set", "Cancel"],
|
|
108
|
-
content: () => {
|
|
109
|
-
const container = document.createElement('div');
|
|
110
|
-
|
|
111
|
-
input = document.createElement('input')
|
|
112
|
-
input.type = "color";
|
|
113
|
-
input.value = this.#value;
|
|
114
|
-
input.name = "color"
|
|
115
|
-
container.append(input);
|
|
116
|
-
|
|
117
|
-
const label = document.createElement("label");
|
|
118
|
-
label.htmlFor = "color";
|
|
119
|
-
label.textContent = "Color";
|
|
120
|
-
container.append(label);
|
|
121
|
-
|
|
122
|
-
return container;
|
|
123
|
-
},
|
|
124
|
-
onClose: (button: string | null): void => {
|
|
125
|
-
if (button !== "Set" || input === null) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
this.Set(input.value);
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
popup.Show();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
|
|
137
|
-
const box = {
|
|
138
|
-
Position: { x: 0, y: 0 },
|
|
139
|
-
Size: {
|
|
140
|
-
x: width * scale,
|
|
141
|
-
y: height * scale
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
CopyVector2(box.Position, position);
|
|
145
|
-
|
|
146
|
-
this.#textBoxStyle.Draw(ctx, box, scale, this.#value)
|
|
147
|
-
|
|
148
|
-
return box;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
1
|
+
import { HexToColor } from "../utils/color";
|
|
2
|
+
import { Box } from "../types/box";
|
|
3
|
+
import { drawField } from "./field";
|
|
4
|
+
import { FlowNode } from "../node";
|
|
5
|
+
import { ValueWidget, ValueWidgetConfig } from "./value";
|
|
6
|
+
|
|
7
|
+
export interface ColorWidgetConfig extends ValueWidgetConfig<string> { }
|
|
8
|
+
|
|
9
|
+
function contrastColor(color: string): string {
|
|
10
|
+
const c = HexToColor(color);
|
|
11
|
+
if (c === null) {
|
|
12
|
+
return "black";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if ((c.r + c.g + c.b) / 3 > 0.5) {
|
|
16
|
+
return "black"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return "white";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let picker: HTMLInputElement | null = null;
|
|
23
|
+
|
|
24
|
+
function sharedPicker(): HTMLInputElement {
|
|
25
|
+
if (picker === null || !picker.isConnected) {
|
|
26
|
+
picker = document.createElement("input");
|
|
27
|
+
picker.type = "color";
|
|
28
|
+
document.body.appendChild(picker);
|
|
29
|
+
}
|
|
30
|
+
return picker;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class ColorWidget extends ValueWidget<string> {
|
|
34
|
+
|
|
35
|
+
#contrast: string;
|
|
36
|
+
|
|
37
|
+
constructor(node: FlowNode, config?: ColorWidgetConfig) {
|
|
38
|
+
super(node, config, "#000000");
|
|
39
|
+
this.#contrast = contrastColor(this.value());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected valueChanged(): void {
|
|
43
|
+
this.#contrast = contrastColor(this.value());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected render(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
|
|
47
|
+
drawField(ctx, box, scale, {
|
|
48
|
+
input: true,
|
|
49
|
+
hovered: this.hovered(),
|
|
50
|
+
label: this.label(),
|
|
51
|
+
value: this.value(),
|
|
52
|
+
background: this.value(),
|
|
53
|
+
border: this.#contrast,
|
|
54
|
+
textColor: this.#contrast,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected onClick(): void {
|
|
59
|
+
const canvas = this.canvas();
|
|
60
|
+
if (canvas === null) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const before = this.value();
|
|
65
|
+
// The browser's picker has no alpha, so the old alpha is carried over.
|
|
66
|
+
const alpha = before.slice(7);
|
|
67
|
+
const box = this.box();
|
|
68
|
+
const rect = canvas.getBoundingClientRect();
|
|
69
|
+
const sx = canvas.width > 0 ? rect.width / canvas.width : 1;
|
|
70
|
+
const sy = canvas.height > 0 ? rect.height / canvas.height : 1;
|
|
71
|
+
|
|
72
|
+
const input = sharedPicker();
|
|
73
|
+
input.style.cssText = [
|
|
74
|
+
"position: fixed",
|
|
75
|
+
`left: ${rect.left + (box.Position.x * sx)}px`,
|
|
76
|
+
`top: ${rect.top + (box.Position.y * sy)}px`,
|
|
77
|
+
`width: ${box.Size.x * sx}px`,
|
|
78
|
+
`height: ${box.Size.y * sy}px`,
|
|
79
|
+
"opacity: 0",
|
|
80
|
+
"pointer-events: none",
|
|
81
|
+
"border: 0",
|
|
82
|
+
"padding: 0",
|
|
83
|
+
].join(";");
|
|
84
|
+
input.value = before.slice(0, 7);
|
|
85
|
+
input.oninput = () => this.input(input.value + alpha);
|
|
86
|
+
input.onchange = () => this.commit(input.value + alpha, before);
|
|
87
|
+
// Reading the rect lays the input out first; the picker opens at its last laid-out spot.
|
|
88
|
+
input.getBoundingClientRect();
|
|
89
|
+
input.click();
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/widgets/factory.ts
CHANGED
|
@@ -1,46 +1,89 @@
|
|
|
1
|
-
import { Widget } from "./widget";
|
|
2
|
-
import { NumberWidget, NumberWidgetConfig } from './number';
|
|
3
|
-
import { ButtonWidget, ButtonWidgetConfig } from './button';
|
|
4
|
-
import { ColorWidget, ColorWidgetConfig } from './color';
|
|
5
|
-
import { SliderWidget, SliderWidgetConfig } from './slider';
|
|
6
|
-
import { StringWidget, StringWidgetConfig } from './string';
|
|
7
|
-
import { ToggleWidget, ToggleWidgetConfig } from './toggle';
|
|
8
|
-
import { FlowNode } from "../node";
|
|
9
|
-
import { ImageWidget, ImageWidgetConfig } from "./image";
|
|
10
|
-
import { TextWidget, TextWidgetConfig } from "./text";
|
|
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
|
-
|
|
1
|
+
import { Widget } from "./widget";
|
|
2
|
+
import { NumberWidget, NumberWidgetConfig } from './number';
|
|
3
|
+
import { ButtonWidget, ButtonWidgetConfig } from './button';
|
|
4
|
+
import { ColorWidget, ColorWidgetConfig } from './color';
|
|
5
|
+
import { SliderWidget, SliderWidgetConfig } from './slider';
|
|
6
|
+
import { StringWidget, StringWidgetConfig } from './string';
|
|
7
|
+
import { ToggleWidget, ToggleWidgetConfig } from './toggle';
|
|
8
|
+
import { FlowNode } from "../node";
|
|
9
|
+
import { ImageWidget, ImageWidgetConfig } from "./image";
|
|
10
|
+
import { TextWidget, TextWidgetConfig } from "./text";
|
|
11
|
+
import { RowWidget, RowWidgetConfig } from "./row";
|
|
12
|
+
|
|
13
|
+
export interface RowWidgetFactoryConfig extends RowWidgetConfig {
|
|
14
|
+
widgets: Array<WidgetConfig>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface WidgetConfigs {
|
|
18
|
+
button: ButtonWidgetConfig;
|
|
19
|
+
number: NumberWidgetConfig;
|
|
20
|
+
color: ColorWidgetConfig;
|
|
21
|
+
slider: SliderWidgetConfig;
|
|
22
|
+
string: StringWidgetConfig;
|
|
23
|
+
toggle: ToggleWidgetConfig;
|
|
24
|
+
image: ImageWidgetConfig;
|
|
25
|
+
text: TextWidgetConfig;
|
|
26
|
+
row: RowWidgetFactoryConfig;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WidgetTypes {
|
|
30
|
+
button: ButtonWidget;
|
|
31
|
+
number: NumberWidget;
|
|
32
|
+
color: ColorWidget;
|
|
33
|
+
slider: SliderWidget;
|
|
34
|
+
string: StringWidget;
|
|
35
|
+
toggle: ToggleWidget;
|
|
36
|
+
image: ImageWidget;
|
|
37
|
+
text: TextWidget;
|
|
38
|
+
row: RowWidget;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type WidgetConfig = {
|
|
42
|
+
[K in keyof WidgetConfigs]: { type: K; config?: WidgetConfigs[K] }
|
|
43
|
+
}[keyof WidgetConfigs];
|
|
44
|
+
|
|
45
|
+
export type WidgetBuilder = (node: FlowNode, config?: any) => Widget;
|
|
46
|
+
|
|
47
|
+
export class WidgetFactory {
|
|
48
|
+
|
|
49
|
+
#registeredWidgets: Map<string, WidgetBuilder>
|
|
50
|
+
|
|
51
|
+
constructor() {
|
|
52
|
+
this.#registeredWidgets = new Map<string, WidgetBuilder>();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
register(widgetType: string, builder: WidgetBuilder): void {
|
|
56
|
+
this.#registeredWidgets.set(widgetType, builder);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
create<K extends keyof WidgetConfigs>(node: FlowNode, widgetType: K, config?: WidgetConfigs[K]): WidgetTypes[K];
|
|
60
|
+
create(node: FlowNode, widgetType: string, config?: unknown): Widget;
|
|
61
|
+
create(node: FlowNode, widgetType: string, config?: unknown): Widget {
|
|
62
|
+
const builder = this.#registeredWidgets.get(widgetType)
|
|
63
|
+
if (builder === undefined) {
|
|
64
|
+
throw new Error("no builder registered for widget: " + widgetType);
|
|
65
|
+
}
|
|
66
|
+
return builder(node, config);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
createFromConfig(node: FlowNode, config: WidgetConfig): Widget {
|
|
70
|
+
return this.create(node, config.type, config.config);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const GlobalWidgetFactory = new WidgetFactory();
|
|
75
|
+
|
|
76
|
+
GlobalWidgetFactory.register("button", (node: FlowNode, config?: ButtonWidgetConfig) => new ButtonWidget(config));
|
|
77
|
+
GlobalWidgetFactory.register("number", (node: FlowNode, config?: NumberWidgetConfig) => new NumberWidget(node, config));
|
|
78
|
+
GlobalWidgetFactory.register("color", (node: FlowNode, config?: ColorWidgetConfig) => new ColorWidget(node, config));
|
|
79
|
+
GlobalWidgetFactory.register("slider", (node: FlowNode, config?: SliderWidgetConfig) => new SliderWidget(node, config));
|
|
80
|
+
GlobalWidgetFactory.register("string", (node: FlowNode, config?: StringWidgetConfig) => new StringWidget(node, config));
|
|
81
|
+
GlobalWidgetFactory.register("toggle", (node: FlowNode, config?: ToggleWidgetConfig) => new ToggleWidget(node, config));
|
|
82
|
+
GlobalWidgetFactory.register("image", (node: FlowNode, config?: ImageWidgetConfig) => new ImageWidget(config));
|
|
83
|
+
GlobalWidgetFactory.register("text", (node: FlowNode, config?: TextWidgetConfig) => new TextWidget(node, config));
|
|
84
|
+
GlobalWidgetFactory.register("row", (node: FlowNode, config?: RowWidgetFactoryConfig) => new RowWidget(
|
|
85
|
+
(config?.widgets ?? []).map((child) => GlobalWidgetFactory.createFromConfig(node, child)),
|
|
86
|
+
config,
|
|
87
|
+
));
|
|
88
|
+
|
|
89
|
+
export { GlobalWidgetFactory };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Theme } from "../theme";
|
|
2
|
+
import { drawBevel } from "../styles/bevel";
|
|
3
|
+
import { Box } from "../types/box";
|
|
4
|
+
import { fitString } from "../utils/string";
|
|
5
|
+
|
|
6
|
+
export interface FieldLook {
|
|
7
|
+
hovered: boolean;
|
|
8
|
+
pressed?: boolean;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Something typed or dragged into, as opposed to something pressed.
|
|
12
|
+
*/
|
|
13
|
+
input?: boolean;
|
|
14
|
+
|
|
15
|
+
label?: string;
|
|
16
|
+
value: string;
|
|
17
|
+
background?: string;
|
|
18
|
+
border?: string;
|
|
19
|
+
textColor?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function widgetFont(scale: number): string {
|
|
23
|
+
return `${Theme.Widget.FontSize * scale}px ${Theme.FontFamily}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fieldBackground(look: FieldLook): string {
|
|
27
|
+
if (look.background !== undefined) {
|
|
28
|
+
return look.background;
|
|
29
|
+
}
|
|
30
|
+
if (look.input) {
|
|
31
|
+
return look.hovered ? Theme.Widget.Input.HoverBackgroundColor : Theme.Widget.Input.BackgroundColor;
|
|
32
|
+
}
|
|
33
|
+
if (look.pressed) {
|
|
34
|
+
return Theme.Widget.Button.Click.BackgroundColor;
|
|
35
|
+
}
|
|
36
|
+
return look.hovered ? Theme.Widget.Hover.BackgroundColor : Theme.Widget.BackgroundColor;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function drawFieldBox(ctx: CanvasRenderingContext2D, box: Box, scale: number, look: FieldLook): void {
|
|
40
|
+
ctx.fillStyle = fieldBackground(look);
|
|
41
|
+
ctx.beginPath();
|
|
42
|
+
ctx.roundRect(box.Position.x, box.Position.y, box.Size.x, box.Size.y, Theme.Widget.Border.Radius * scale);
|
|
43
|
+
ctx.fill();
|
|
44
|
+
|
|
45
|
+
if (Theme.Widget.Border.Size > 0) {
|
|
46
|
+
ctx.strokeStyle = look.border ?? Theme.Widget.Border.Color;
|
|
47
|
+
ctx.lineWidth = Theme.Widget.Border.Size * scale;
|
|
48
|
+
ctx.stroke();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (Theme.Widget.Bevel !== null) {
|
|
52
|
+
drawBevel(ctx, box, scale, Theme.Widget.Bevel, look.input === true || look.pressed === true);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A boxed value with an optional label: the label on the left and the value
|
|
58
|
+
* on the right, or the value alone in the middle.
|
|
59
|
+
*/
|
|
60
|
+
export function drawField(ctx: CanvasRenderingContext2D, box: Box, scale: number, look: FieldLook): void {
|
|
61
|
+
drawFieldBox(ctx, box, scale, look);
|
|
62
|
+
drawFieldText(ctx, box, scale, look);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function drawFieldText(ctx: CanvasRenderingContext2D, box: Box, scale: number, look: FieldLook): void {
|
|
66
|
+
const padding = Theme.Widget.Padding * scale;
|
|
67
|
+
const middle = box.Position.y + (box.Size.y / 2);
|
|
68
|
+
const textColor = look.textColor ?? (look.input ? Theme.Widget.Input.FontColor : Theme.Widget.FontColor);
|
|
69
|
+
|
|
70
|
+
ctx.font = widgetFont(scale);
|
|
71
|
+
ctx.textBaseline = "middle";
|
|
72
|
+
|
|
73
|
+
if (look.label === undefined || look.label === "") {
|
|
74
|
+
ctx.textAlign = "center";
|
|
75
|
+
ctx.fillStyle = textColor;
|
|
76
|
+
ctx.fillText(
|
|
77
|
+
fitString(ctx, look.value, box.Size.x - (padding * 2)),
|
|
78
|
+
box.Position.x + (box.Size.x / 2),
|
|
79
|
+
middle,
|
|
80
|
+
);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const room = box.Size.x - (padding * 2);
|
|
85
|
+
const label = fitString(ctx, look.label, room / 2);
|
|
86
|
+
const labelWidth = ctx.measureText(label).width;
|
|
87
|
+
|
|
88
|
+
ctx.textAlign = "left";
|
|
89
|
+
ctx.fillStyle = look.textColor ?? Theme.Widget.LabelColor;
|
|
90
|
+
ctx.fillText(label, box.Position.x + padding, middle);
|
|
91
|
+
|
|
92
|
+
ctx.textAlign = "right";
|
|
93
|
+
ctx.fillStyle = textColor;
|
|
94
|
+
ctx.fillText(
|
|
95
|
+
fitString(ctx, look.value, room - labelWidth - padding),
|
|
96
|
+
box.Position.x + box.Size.x - padding,
|
|
97
|
+
middle,
|
|
98
|
+
);
|
|
99
|
+
}
|