@elicdavis/node-flow 0.1.19 → 0.2.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.
- package/dist/index.d.mts +386 -227
- package/dist/index.d.ts +386 -227
- package/dist/index.js +1884 -1076
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1867 -1075
- 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 +12 -10
- package/src/index.ts +26 -12
- package/src/markdown/entry.ts +11 -15
- package/src/markdown/syntaxParser.ts +2 -1
- package/src/node.ts +162 -111
- package/src/nodes/subsystem.ts +123 -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 +84 -0
- package/src/widgets/number.ts +122 -154
- package/src/widgets/row.ts +138 -0
- package/src/widgets/slider.ts +101 -216
- package/src/widgets/string.ts +39 -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 +180 -34
- 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/hover_after_removal.test.ts +72 -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/property_binding.test.ts +49 -0
- package/test/support.ts +175 -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 +333 -0
- package/test/wire_follows_camera.test.ts +57 -0
package/src/widgets/image.ts
CHANGED
|
@@ -1,165 +1,107 @@
|
|
|
1
|
-
import { requestRender } from "../render_scheduler";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export interface ImageWidgetConfig {
|
|
8
|
-
image?: string;
|
|
9
|
-
maxWidth?: number
|
|
10
|
-
maxHeight?: number
|
|
11
|
-
blob?: Blob;
|
|
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
|
-
this.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
console.log("error loading image:", url, event);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
Size(): Vector2 {
|
|
113
|
-
if (this.#image === undefined) {
|
|
114
|
-
return { "x": 0, "y": 0 }
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
let adjust = 1;
|
|
118
|
-
if (this.#image.width > this.#maxWidth) {
|
|
119
|
-
adjust = this.#maxWidth / this.#image.width
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (this.#image.height > this.#maxHeight) {
|
|
123
|
-
let heightAdjust = this.#maxHeight / this.#image.height
|
|
124
|
-
if (heightAdjust < adjust) {
|
|
125
|
-
adjust = heightAdjust;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
"x": adjust * this.#image.width,
|
|
131
|
-
"y": adjust * this.#image.height
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
ClickStart(): void {
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
ClickEnd(): void {
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
|
|
142
|
-
const size = this.Size();
|
|
143
|
-
const box: Box = {
|
|
144
|
-
Position: { x: 0, y: 0 },
|
|
145
|
-
Size: {
|
|
146
|
-
x: size.x * scale,
|
|
147
|
-
y: size.y * scale,
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
CopyVector2(box.Position, position);
|
|
151
|
-
|
|
152
|
-
if (!this.#image) {
|
|
153
|
-
return box;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
ctx.drawImage(
|
|
157
|
-
this.#image,
|
|
158
|
-
position.x,
|
|
159
|
-
position.y,
|
|
160
|
-
box.Size.x,
|
|
161
|
-
box.Size.y
|
|
162
|
-
);
|
|
163
|
-
return box;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
1
|
+
import { requestRender } from "../render_scheduler";
|
|
2
|
+
import { CursorStyle } from "../styles/cursor";
|
|
3
|
+
import { Box } from "../types/box";
|
|
4
|
+
import { Vector2 } from "../types/vector2";
|
|
5
|
+
import { WidgetBase } from "./widget";
|
|
6
|
+
|
|
7
|
+
export interface ImageWidgetConfig {
|
|
8
|
+
image?: string;
|
|
9
|
+
maxWidth?: number
|
|
10
|
+
maxHeight?: number
|
|
11
|
+
blob?: Blob;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ImageWidget extends WidgetBase {
|
|
15
|
+
|
|
16
|
+
#url: string | undefined;
|
|
17
|
+
|
|
18
|
+
#ownedUrl: string | undefined;
|
|
19
|
+
|
|
20
|
+
#image: HTMLImageElement | undefined;
|
|
21
|
+
|
|
22
|
+
#maxWidth: number;
|
|
23
|
+
|
|
24
|
+
#maxHeight: number;
|
|
25
|
+
|
|
26
|
+
constructor(config?: ImageWidgetConfig) {
|
|
27
|
+
super();
|
|
28
|
+
this.#maxWidth = config?.maxWidth === undefined ? 150 : config?.maxWidth;
|
|
29
|
+
this.#maxHeight = config?.maxHeight === undefined ? 150 : config?.maxHeight;
|
|
30
|
+
if (config?.image) {
|
|
31
|
+
this.SetUrl(config?.image);
|
|
32
|
+
} else if (config?.blob) {
|
|
33
|
+
this.SetBlob(config?.blob);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
SetBlob(blob: Blob) {
|
|
38
|
+
const url = URL.createObjectURL(blob);
|
|
39
|
+
this.SetUrl(url);
|
|
40
|
+
this.#ownedUrl = url;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
SetUrl(url: string) {
|
|
44
|
+
if (this.#ownedUrl !== undefined) {
|
|
45
|
+
URL.revokeObjectURL(this.#ownedUrl);
|
|
46
|
+
this.#ownedUrl = undefined;
|
|
47
|
+
}
|
|
48
|
+
this.#image = undefined;
|
|
49
|
+
this.#url = url;
|
|
50
|
+
|
|
51
|
+
const img = document.createElement("img");
|
|
52
|
+
img.src = url;
|
|
53
|
+
img.onload = () => {
|
|
54
|
+
if (this.#url !== url) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.#image = img;
|
|
58
|
+
requestRender();
|
|
59
|
+
};
|
|
60
|
+
img.onerror = (event) => {
|
|
61
|
+
console.log("error loading image:", url, event);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Cursor(): CursorStyle {
|
|
66
|
+
return CursorStyle.Default;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Size(): Vector2 {
|
|
70
|
+
if (this.#image === undefined) {
|
|
71
|
+
return { "x": 0, "y": 0 }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let adjust = 1;
|
|
75
|
+
if (this.#image.width > this.#maxWidth) {
|
|
76
|
+
adjust = this.#maxWidth / this.#image.width
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this.#image.height > this.#maxHeight) {
|
|
80
|
+
let heightAdjust = this.#maxHeight / this.#image.height
|
|
81
|
+
if (heightAdjust < adjust) {
|
|
82
|
+
adjust = heightAdjust;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
"x": adjust * this.#image.width,
|
|
88
|
+
"y": adjust * this.#image.height
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected render(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
|
|
93
|
+
if (!this.#image) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const size = this.Size();
|
|
97
|
+
const w = size.x * scale;
|
|
98
|
+
const h = size.y * scale;
|
|
99
|
+
ctx.drawImage(
|
|
100
|
+
this.#image,
|
|
101
|
+
box.Position.x + ((box.Size.x - w) / 2),
|
|
102
|
+
box.Position.y,
|
|
103
|
+
w,
|
|
104
|
+
h
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { requestRender } from "../render_scheduler";
|
|
2
|
+
import { Theme } from "../theme";
|
|
3
|
+
import { Box } from "../types/box";
|
|
4
|
+
|
|
5
|
+
export interface InPlaceEditConfig {
|
|
6
|
+
canvas: HTMLCanvasElement | null;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Where to sit, in canvas pixels.
|
|
10
|
+
*/
|
|
11
|
+
box: Box;
|
|
12
|
+
|
|
13
|
+
scale: number;
|
|
14
|
+
|
|
15
|
+
value: string;
|
|
16
|
+
|
|
17
|
+
onCommit: (text: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Lays a text input over a widget. Enter or clicking away keeps what was
|
|
22
|
+
* typed; Escape throws it away.
|
|
23
|
+
*/
|
|
24
|
+
export function editInPlace(config: InPlaceEditConfig): HTMLInputElement | null {
|
|
25
|
+
const canvas = config.canvas;
|
|
26
|
+
if (canvas === null) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rect = canvas.getBoundingClientRect();
|
|
31
|
+
const sx = canvas.width > 0 ? rect.width / canvas.width : 1;
|
|
32
|
+
const sy = canvas.height > 0 ? rect.height / canvas.height : 1;
|
|
33
|
+
|
|
34
|
+
const input = document.createElement("input");
|
|
35
|
+
input.type = "text";
|
|
36
|
+
input.value = config.value;
|
|
37
|
+
input.style.cssText = [
|
|
38
|
+
"position: fixed",
|
|
39
|
+
`left: ${rect.left + (config.box.Position.x * sx)}px`,
|
|
40
|
+
`top: ${rect.top + (config.box.Position.y * sy)}px`,
|
|
41
|
+
`width: ${config.box.Size.x * sx}px`,
|
|
42
|
+
`height: ${config.box.Size.y * sy}px`,
|
|
43
|
+
"box-sizing: border-box",
|
|
44
|
+
"margin: 0",
|
|
45
|
+
`padding: 0 ${Theme.Widget.Padding * config.scale}px`,
|
|
46
|
+
`font: ${Theme.Widget.FontSize * config.scale * sy}px ${Theme.FontFamily}`,
|
|
47
|
+
`color: ${Theme.Widget.FontColor}`,
|
|
48
|
+
`background: ${Theme.Widget.Hover.BackgroundColor}`,
|
|
49
|
+
`border: ${Theme.Widget.Border.Size * config.scale}px solid ${Theme.Widget.Border.Color}`,
|
|
50
|
+
`border-radius: ${Theme.Widget.Border.Radius * config.scale}px`,
|
|
51
|
+
"outline: none",
|
|
52
|
+
"z-index: 1000",
|
|
53
|
+
].join(";");
|
|
54
|
+
|
|
55
|
+
let done = false;
|
|
56
|
+
const finish = (keep: boolean) => {
|
|
57
|
+
if (done) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
done = true;
|
|
61
|
+
input.remove();
|
|
62
|
+
if (keep) {
|
|
63
|
+
config.onCommit(input.value);
|
|
64
|
+
}
|
|
65
|
+
requestRender();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
input.addEventListener("keydown", (event: KeyboardEvent) => {
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
if (event.key === "Enter") {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
finish(true);
|
|
73
|
+
} else if (event.key === "Escape") {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
finish(false);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
input.addEventListener("blur", () => finish(true));
|
|
79
|
+
|
|
80
|
+
document.body.appendChild(input);
|
|
81
|
+
input.focus();
|
|
82
|
+
input.select();
|
|
83
|
+
return input;
|
|
84
|
+
}
|
package/src/widgets/number.ts
CHANGED
|
@@ -1,154 +1,122 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
export interface NumberWidgetConfig {
|
|
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
|
-
this
|
|
66
|
-
|
|
67
|
-
this.#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (this.#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.Set(input.valueAsNumber);
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
popup.Show();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
|
|
133
|
-
const box = {
|
|
134
|
-
Position: { x: 0, y: 0 },
|
|
135
|
-
Size: {
|
|
136
|
-
x: width * scale,
|
|
137
|
-
y: height * scale
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
CopyVector2(box.Position, position);
|
|
141
|
-
|
|
142
|
-
let style: TextBoxStyle = this.#idleBoxStyle;
|
|
143
|
-
|
|
144
|
-
if (mousePosition !== undefined) {
|
|
145
|
-
if (InBox(box, mousePosition)) {
|
|
146
|
-
style = this.#highlightBoxStyle;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
style.DrawUnderline(ctx, box, scale, this.#text)
|
|
151
|
-
|
|
152
|
-
return box;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
1
|
+
import { FlowNode } from "../node";
|
|
2
|
+
import { CursorStyle } from "../styles/cursor";
|
|
3
|
+
import { Box } from "../types/box";
|
|
4
|
+
import { drawField } from "./field";
|
|
5
|
+
import { editInPlace } from "./inplace";
|
|
6
|
+
import { ValueWidget, ValueWidgetConfig } from "./value";
|
|
7
|
+
import { WidgetPointer } from "./widget";
|
|
8
|
+
|
|
9
|
+
export interface NumberWidgetConfig extends ValueWidgetConfig<number> {
|
|
10
|
+
/**
|
|
11
|
+
* How much the value moves per pixel dragged. Left out, it scales with
|
|
12
|
+
* the value's size.
|
|
13
|
+
*/
|
|
14
|
+
step?: number;
|
|
15
|
+
|
|
16
|
+
integer?: boolean;
|
|
17
|
+
|
|
18
|
+
min?: number;
|
|
19
|
+
|
|
20
|
+
max?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DRAG_THRESHOLD = 3;
|
|
24
|
+
|
|
25
|
+
export class NumberWidget extends ValueWidget<number> {
|
|
26
|
+
|
|
27
|
+
#step: number | undefined;
|
|
28
|
+
|
|
29
|
+
#integer: boolean;
|
|
30
|
+
|
|
31
|
+
#min: number;
|
|
32
|
+
|
|
33
|
+
#max: number;
|
|
34
|
+
|
|
35
|
+
#startX = 0;
|
|
36
|
+
|
|
37
|
+
#startValue = 0;
|
|
38
|
+
|
|
39
|
+
#dragging = false;
|
|
40
|
+
|
|
41
|
+
#dragged = false;
|
|
42
|
+
|
|
43
|
+
constructor(node: FlowNode, config?: NumberWidgetConfig) {
|
|
44
|
+
super(node, config, 0);
|
|
45
|
+
this.#step = config?.step;
|
|
46
|
+
this.#integer = config?.integer === true;
|
|
47
|
+
this.#min = config?.min ?? -Infinity;
|
|
48
|
+
this.#max = config?.max ?? Infinity;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Cursor(): CursorStyle {
|
|
52
|
+
return CursorStyle.EWResize;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected render(ctx: CanvasRenderingContext2D, box: Box, scale: number): void {
|
|
56
|
+
drawField(ctx, box, scale, {
|
|
57
|
+
input: true,
|
|
58
|
+
hovered: this.hovered() || this.pressed(),
|
|
59
|
+
label: this.label(),
|
|
60
|
+
value: formatNumber(this.value()),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected onPointerDown(pointer: WidgetPointer): void {
|
|
65
|
+
this.#startX = pointer.position.x;
|
|
66
|
+
this.#startValue = this.value();
|
|
67
|
+
this.#dragging = false;
|
|
68
|
+
this.#dragged = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
protected onPointerMove(pointer: WidgetPointer): void {
|
|
72
|
+
const dx = pointer.position.x - this.#startX;
|
|
73
|
+
if (!this.#dragging && Math.abs(dx) < DRAG_THRESHOLD) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.#dragging = true;
|
|
77
|
+
this.input(this.#clean(this.#startValue + (dx * this.#stepFor(this.#startValue))));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
protected onPointerUp(): void {
|
|
81
|
+
this.#dragged = this.#dragging;
|
|
82
|
+
if (this.#dragging) {
|
|
83
|
+
this.#dragging = false;
|
|
84
|
+
this.commit(this.value(), this.#startValue);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected onClick(): void {
|
|
89
|
+
if (this.#dragged) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const before = this.value();
|
|
93
|
+
editInPlace({
|
|
94
|
+
canvas: this.canvas(),
|
|
95
|
+
box: this.box(),
|
|
96
|
+
scale: this.scale(),
|
|
97
|
+
value: String(before),
|
|
98
|
+
onCommit: (text) => {
|
|
99
|
+
const parsed = Number.parseFloat(text);
|
|
100
|
+
if (Number.isFinite(parsed)) {
|
|
101
|
+
this.commit(this.#clean(parsed), before);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#stepFor(value: number): number {
|
|
108
|
+
if (this.#step !== undefined) {
|
|
109
|
+
return this.#step;
|
|
110
|
+
}
|
|
111
|
+
return Math.max(Math.abs(value) * 0.01, this.#integer ? 0.1 : 0.01);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#clean(value: number): number {
|
|
115
|
+
const rounded = this.#integer ? Math.round(value) : value;
|
|
116
|
+
return Math.min(this.#max, Math.max(this.#min, rounded));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function formatNumber(value: number): string {
|
|
121
|
+
return '' + parseFloat(value.toPrecision(6));
|
|
122
|
+
}
|