@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.
- package/.github/workflows/deploy.yml +57 -0
- package/README.md +159 -0
- package/dev.tsconfig.json +18 -0
- package/docs/promotional-image.png +0 -0
- package/esbuild.ts +32 -0
- package/index.html +311 -0
- package/package.json +50 -0
- package/src/camera.ts +30 -0
- package/src/connection.ts +249 -0
- package/src/contextMenu.ts +367 -0
- package/src/draggable.ts +13 -0
- package/src/elements/base.ts +192 -0
- package/src/elements/container.ts +119 -0
- package/src/elements/interfaces.ts +50 -0
- package/src/elements/text.ts +86 -0
- package/src/graph.ts +535 -0
- package/src/graphSubsystem.ts +31 -0
- package/src/index.ts +28 -0
- package/src/input.ts +153 -0
- package/src/markdown/entry.ts +323 -0
- package/src/markdown/lexicalParser.ts +148 -0
- package/src/markdown/markdown.ts +14 -0
- package/src/markdown/syntaxParser.ts +344 -0
- package/src/markdown/token.ts +43 -0
- package/src/metadata.ts +3 -0
- package/src/node.ts +1130 -0
- package/src/nodes/factory.ts +87 -0
- package/src/nodes/publisher.ts +98 -0
- package/src/nodes/subsystem.ts +760 -0
- package/src/notes/note.ts +295 -0
- package/src/notes/subsystem.ts +262 -0
- package/src/organize.ts +181 -0
- package/src/pass/subsystem.ts +64 -0
- package/src/performance.ts +9 -0
- package/src/popup.ts +81 -0
- package/src/popups/form.ts +63 -0
- package/src/popups/string.ts +33 -0
- package/src/port.ts +223 -0
- package/src/quickMenu.ts +330 -0
- package/src/styles/box.ts +104 -0
- package/src/styles/canvasTextAlign.ts +8 -0
- package/src/styles/canvasTextBaseline.ts +9 -0
- package/src/styles/cursor.ts +18 -0
- package/src/styles/stroke.ts +39 -0
- package/src/styles/text.ts +90 -0
- package/src/styles/textBox.ts +82 -0
- package/src/theme.ts +78 -0
- package/src/types/box.ts +126 -0
- package/src/types/list.ts +41 -0
- package/src/types/pool.ts +66 -0
- package/src/types/text.ts +136 -0
- package/src/types/vector2.ts +34 -0
- package/src/utils/color.ts +136 -0
- package/src/utils/html.ts +21 -0
- package/src/utils/math.ts +8 -0
- package/src/utils/string.ts +98 -0
- package/src/widgets/button.ts +107 -0
- package/src/widgets/color.ts +151 -0
- package/src/widgets/factory.ts +46 -0
- package/src/widgets/image.ts +163 -0
- package/src/widgets/number.ts +153 -0
- package/src/widgets/slider.ts +216 -0
- package/src/widgets/string.ts +128 -0
- package/src/widgets/text.ts +92 -0
- package/src/widgets/toggle.ts +205 -0
- package/src/widgets/widget.ts +12 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +10 -0
package/src/theme.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export const Theme = {
|
|
2
|
+
FontFamily: "Courier New",
|
|
3
|
+
Graph: {
|
|
4
|
+
BackgroundColor: "#07212a",
|
|
5
|
+
},
|
|
6
|
+
Node: {
|
|
7
|
+
FontColor: "#afb9bb",
|
|
8
|
+
BackgroundColor: "#0c2b35",
|
|
9
|
+
Title: {
|
|
10
|
+
Color: "#154050",
|
|
11
|
+
FontColor: "#afb9bb",
|
|
12
|
+
Padding: 5
|
|
13
|
+
},
|
|
14
|
+
BorderRadius: 15,
|
|
15
|
+
Border: {
|
|
16
|
+
Idle: "#1c1c1c",
|
|
17
|
+
MouseOver: "#8e8e8e",
|
|
18
|
+
Grabbed: "white",
|
|
19
|
+
Selected: "#6e6e6e"
|
|
20
|
+
},
|
|
21
|
+
Port: {
|
|
22
|
+
FontColor: "#afb9bb"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
BoxSelect: {
|
|
26
|
+
Color: "white",
|
|
27
|
+
Size: 1,
|
|
28
|
+
Radius: 2,
|
|
29
|
+
LineDashLength: 5,
|
|
30
|
+
},
|
|
31
|
+
Widget: {
|
|
32
|
+
FontColor: "#afb9bb",
|
|
33
|
+
BackgroundColor: "#0f313c",
|
|
34
|
+
Border: {
|
|
35
|
+
Color: "#154050",
|
|
36
|
+
Size: 2,
|
|
37
|
+
Radius: 2
|
|
38
|
+
},
|
|
39
|
+
Hover: {
|
|
40
|
+
BackgroundColor: "#195366"
|
|
41
|
+
},
|
|
42
|
+
Slider: {
|
|
43
|
+
FillColor: "#07212A",
|
|
44
|
+
},
|
|
45
|
+
Button: {
|
|
46
|
+
Click: {
|
|
47
|
+
BackgroundColor: "#1f637a"
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
ContextMenu: {
|
|
52
|
+
BackgroundColor: "#07212A",
|
|
53
|
+
HighlightColor: "#205A6D",
|
|
54
|
+
FontColor: "#afb9bb",
|
|
55
|
+
},
|
|
56
|
+
Note: {
|
|
57
|
+
FontColor: "#afb9bb",
|
|
58
|
+
FontSize: 16,
|
|
59
|
+
EntrySpacing: 20,
|
|
60
|
+
LineSpacing: 5,
|
|
61
|
+
DotSize: 3,
|
|
62
|
+
HeaderLineWidth: 2,
|
|
63
|
+
H1: {
|
|
64
|
+
FontSize: 32,
|
|
65
|
+
},
|
|
66
|
+
H2: {
|
|
67
|
+
FontSize: 16,
|
|
68
|
+
},
|
|
69
|
+
H3: {
|
|
70
|
+
FontSize: 16,
|
|
71
|
+
},
|
|
72
|
+
CodeBlock: {
|
|
73
|
+
BackgroundColor: "#0c2b35",
|
|
74
|
+
Padding: 16,
|
|
75
|
+
BorderRadius: 4,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/types/box.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { VectorPool } from "./pool";
|
|
2
|
+
import { AddVector2, CopyVector2, SubVector2, Vector2, Zero } from "./vector2";
|
|
3
|
+
|
|
4
|
+
export interface Box {
|
|
5
|
+
Position: Vector2
|
|
6
|
+
Size: Vector2
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function CopyBox(dst: Box, src: Box): void {
|
|
10
|
+
dst.Position.x = src.Position.x;
|
|
11
|
+
dst.Position.y = src.Position.y;
|
|
12
|
+
dst.Size.x = src.Size.x;
|
|
13
|
+
dst.Size.y = src.Size.y;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeMinMax(min: Vector2, max: Vector2) {
|
|
17
|
+
if (min.x > max.x) {
|
|
18
|
+
let temp = max.x;
|
|
19
|
+
max.x = min.x;
|
|
20
|
+
min.x = temp;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (min.y > max.y) {
|
|
24
|
+
let temp = max.y;
|
|
25
|
+
max.y = min.y;
|
|
26
|
+
min.y = temp;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function BoxIntersection(a: Box, b: Box): boolean {
|
|
31
|
+
let intersects = false;
|
|
32
|
+
|
|
33
|
+
VectorPool.run(() => {
|
|
34
|
+
const aMin = VectorPool.get();
|
|
35
|
+
const aMax = VectorPool.get();
|
|
36
|
+
CopyVector2(aMin, a.Position);
|
|
37
|
+
AddVector2(aMax, aMin, a.Size);
|
|
38
|
+
normalizeMinMax(aMin, aMax);
|
|
39
|
+
|
|
40
|
+
const bMin = VectorPool.get();
|
|
41
|
+
const bMax = VectorPool.get();
|
|
42
|
+
CopyVector2(bMin, b.Position);
|
|
43
|
+
AddVector2(bMax, bMin, b.Size);
|
|
44
|
+
normalizeMinMax(bMin, bMax);
|
|
45
|
+
|
|
46
|
+
intersects = (aMin.x <= bMax.x) && (aMax.x >= bMin.x) &&
|
|
47
|
+
(aMin.y <= bMax.y) && (aMax.y >= bMin.y);
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return intersects;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// TODO: This is probably incorrect. Needs more testing
|
|
54
|
+
// export function ExpandBox(boxToExpand: Box, box: Box): void {
|
|
55
|
+
// const newPos: Vector2 = Zero();
|
|
56
|
+
// const newSize: Vector2 = Zero();
|
|
57
|
+
|
|
58
|
+
// CopyVector2(newPos, boxToExpand.Position);
|
|
59
|
+
// CopyVector2(newSize, boxToExpand.Size);
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
// const positionOffset: Vector2 = Zero();
|
|
63
|
+
|
|
64
|
+
// // Get Position Adjustment
|
|
65
|
+
// SubVector2(positionOffset, box.Position, boxToExpand.Position);
|
|
66
|
+
|
|
67
|
+
// // We only care about adjustments that make us bigger.
|
|
68
|
+
// positionOffset.x = Math.min(positionOffset.x, 0);
|
|
69
|
+
// positionOffset.y = Math.min(positionOffset.y, 0);
|
|
70
|
+
|
|
71
|
+
// // Adjust our position
|
|
72
|
+
// AddVector2(boxToExpand.Position, boxToExpand.Position, positionOffset);
|
|
73
|
+
|
|
74
|
+
// // Increase our size to take into account new position
|
|
75
|
+
// SubVector2(newSize, newSize, positionOffset);
|
|
76
|
+
|
|
77
|
+
// const sizeIncrease: Vector2 = Zero();
|
|
78
|
+
|
|
79
|
+
// const curEndPos: Vector2 = {
|
|
80
|
+
// x: boxToExpand.Position.x + boxToExpand.Size.x,
|
|
81
|
+
// y: boxToExpand.Position.y + boxToExpand.Size.y,
|
|
82
|
+
// };
|
|
83
|
+
|
|
84
|
+
// const boxEndPos: Vector2 = {
|
|
85
|
+
// x: box.Position.x + box.Size.x,
|
|
86
|
+
// y: box.Position.y + box.Size.y,
|
|
87
|
+
// };
|
|
88
|
+
|
|
89
|
+
// SubVector2(sizeIncrease, boxEndPos, curEndPos);
|
|
90
|
+
|
|
91
|
+
// // We only care about increases to our size
|
|
92
|
+
// sizeIncrease.x = Math.max(sizeIncrease.x, 0);
|
|
93
|
+
// sizeIncrease.y = Math.max(sizeIncrease.y, 0);
|
|
94
|
+
|
|
95
|
+
// // Increase our size to take into account new position
|
|
96
|
+
// SubVector2(newSize, newSize, sizeIncrease)
|
|
97
|
+
|
|
98
|
+
// // Finally, Set result
|
|
99
|
+
// CopyVector2(boxToExpand.Position, newPos)
|
|
100
|
+
// CopyVector2(boxToExpand.Size, newSize)
|
|
101
|
+
// }
|
|
102
|
+
|
|
103
|
+
export function BoxCenter(box: Box, out: Vector2): Vector2 {
|
|
104
|
+
out.x = box.Position.x + (box.Size.x / 2);
|
|
105
|
+
out.y = box.Position.y + (box.Size.y / 2);
|
|
106
|
+
return out
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function InBox(box: Box, position: Vector2): boolean {
|
|
110
|
+
const min = box.Position
|
|
111
|
+
if (position.x < min.x) {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
if (position.y < min.y) {
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (position.x > min.x + box.Size.x) {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
if (position.y > min.y + box.Size.y) {
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class List<Type> {
|
|
2
|
+
|
|
3
|
+
#arr: Array<Type>;
|
|
4
|
+
|
|
5
|
+
#count: number;
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
this.#arr = new Array<Type>();
|
|
9
|
+
this.#count = 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public Count(): number {
|
|
13
|
+
return this.#count;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public Clear(): void {
|
|
17
|
+
this.#count = 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public At(index: number): Type {
|
|
21
|
+
return this.#arr[index];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public Push(value: Type): void {
|
|
25
|
+
if (this.#arr.length === this.#count) {
|
|
26
|
+
this.#arr.push(value);
|
|
27
|
+
} else {
|
|
28
|
+
this.#arr[this.#count] = value;
|
|
29
|
+
}
|
|
30
|
+
this.#count++;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public ToArray(): Array<Type> {
|
|
34
|
+
let arr = new Array<Type>(this.Count());
|
|
35
|
+
for(let i = 0; i < this.#count; i ++) {
|
|
36
|
+
arr[i] = this.#arr[i]
|
|
37
|
+
}
|
|
38
|
+
return arr;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Vector2 } from "./vector2";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class Pool<Type> {
|
|
6
|
+
|
|
7
|
+
#arr: Array<Type>;
|
|
8
|
+
|
|
9
|
+
#count: number;
|
|
10
|
+
|
|
11
|
+
#builder: () => Type;
|
|
12
|
+
|
|
13
|
+
#reset: (v: Type) => void;
|
|
14
|
+
|
|
15
|
+
#runningDepth: number;
|
|
16
|
+
|
|
17
|
+
constructor(builder: () => Type, reset: (v: Type) => void) {
|
|
18
|
+
this.#arr = new Array<Type>();
|
|
19
|
+
this.#count = 0;
|
|
20
|
+
this.#builder = builder;
|
|
21
|
+
this.#reset = reset;
|
|
22
|
+
this.#runningDepth = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public runIf(condition: boolean, fn: () => void) {
|
|
26
|
+
if (condition) {
|
|
27
|
+
this.run(fn);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#running(): boolean {
|
|
32
|
+
return this.#runningDepth > 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public run(fn: () => void) {
|
|
36
|
+
const start = this.#count;
|
|
37
|
+
this.#runningDepth += 1;
|
|
38
|
+
fn();
|
|
39
|
+
this.#runningDepth -= 1;
|
|
40
|
+
this.#count = start;
|
|
41
|
+
// console.log(this.#arr.length);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get(): Type {
|
|
45
|
+
if(!this.#running()) {
|
|
46
|
+
throw new Error("can't use pool outside of running context");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let value: Type;
|
|
50
|
+
if (this.#arr.length === this.#count) {
|
|
51
|
+
value = this.#builder();
|
|
52
|
+
this.#arr.push(this.#builder());
|
|
53
|
+
} else {
|
|
54
|
+
value = this.#arr[this.#count];
|
|
55
|
+
this.#reset(value);
|
|
56
|
+
}
|
|
57
|
+
this.#count++;
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const VectorPool = new Pool<Vector2>(
|
|
64
|
+
() => ({ x: 0, y: 0 }),
|
|
65
|
+
(v) => { v.x = 0; v.y = 0; }
|
|
66
|
+
)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { FontWeight, TextStyle, TextStyleConfig } from "../styles/text";
|
|
2
|
+
import { splitString, splitStringIntoLines } from "../utils/string";
|
|
3
|
+
import { CopyVector2, ScaleVector, Vector2, Zero } from "./vector2";
|
|
4
|
+
|
|
5
|
+
export class Text {
|
|
6
|
+
|
|
7
|
+
#measured: boolean
|
|
8
|
+
|
|
9
|
+
#size: Vector2;
|
|
10
|
+
|
|
11
|
+
#style: TextStyle;
|
|
12
|
+
|
|
13
|
+
#value: string
|
|
14
|
+
|
|
15
|
+
constructor(value: string, style?: TextStyleConfig) {
|
|
16
|
+
this.#value = value;
|
|
17
|
+
this.#measured = false;
|
|
18
|
+
this.#size = Zero();
|
|
19
|
+
this.#style = new TextStyle(style);
|
|
20
|
+
|
|
21
|
+
if (!document.fonts.check(`16px "${this.#style.getFont()}"`)) {
|
|
22
|
+
document.fonts.addEventListener("loadingdone", (event) => {
|
|
23
|
+
this.#measured = false;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set(newValue: string): void {
|
|
29
|
+
this.#value = newValue;
|
|
30
|
+
this.#measured = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get(): string {
|
|
34
|
+
return this.#value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
breakIntoLines(ctx: CanvasRenderingContext2D, maxWidth: number): Array<Text> {
|
|
38
|
+
const results = new Array<Text>();
|
|
39
|
+
|
|
40
|
+
this.#style.setupStyle(ctx, 1);
|
|
41
|
+
const entries = splitStringIntoLines(ctx, this.#value, maxWidth);
|
|
42
|
+
if (entries.length === 1) {
|
|
43
|
+
return [this];
|
|
44
|
+
}
|
|
45
|
+
for (let i = 0; i < entries.length; i++) {
|
|
46
|
+
const text = new Text(entries[i])
|
|
47
|
+
text.#style = this.#style;
|
|
48
|
+
results.push(text)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
split(char: string): Array<Text> {
|
|
55
|
+
const entries = this.#value.split(char)
|
|
56
|
+
const results = new Array<Text>();
|
|
57
|
+
for (let i = 0; i < entries.length; i++) {
|
|
58
|
+
const text = new Text(entries[i])
|
|
59
|
+
text.#style = this.#style;
|
|
60
|
+
results.push(text)
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
splitAtIndex(index: number): Array<Text> {
|
|
66
|
+
const results = [
|
|
67
|
+
new Text(this.#value.substring(0, index)),
|
|
68
|
+
new Text(this.#value.substring(index, 0)),
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
for (let i = 0; i < results.length; i++) {
|
|
73
|
+
results[i].#style = this.#style;
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
splitAtWidth(ctx: CanvasRenderingContext2D, maxWidth: number): Array<Text> {
|
|
79
|
+
this.#style.setupStyle(ctx, 1);
|
|
80
|
+
const entries = splitString(ctx, this.#value, maxWidth);
|
|
81
|
+
if (entries.length === 1) {
|
|
82
|
+
return [this];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const results = new Array<Text>();
|
|
86
|
+
for (let i = 0; i < entries.length; i++) {
|
|
87
|
+
const text = new Text(entries[i])
|
|
88
|
+
text.#style = this.#style;
|
|
89
|
+
results.push(text)
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#measure(ctx: CanvasRenderingContext2D): void {
|
|
95
|
+
if (this.#measured) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.#style.setupStyle(ctx, 1);
|
|
100
|
+
const measurements = ctx.measureText(this.#value);
|
|
101
|
+
this.#size.x = measurements.width;
|
|
102
|
+
this.#size.y = measurements.actualBoundingBoxAscent + measurements.actualBoundingBoxDescent;
|
|
103
|
+
this.#measured = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
size(ctx: CanvasRenderingContext2D, scale: number, out: Vector2): void {
|
|
107
|
+
this.#measure(ctx);
|
|
108
|
+
CopyVector2(out, this.#size);
|
|
109
|
+
ScaleVector(out, scale);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setColor(color: string): void {
|
|
113
|
+
this.#style.setColor(color);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getColor(): string {
|
|
117
|
+
return this.#style.getColor();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
setSize(size: number): void {
|
|
121
|
+
this.#style.setSize(size);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setWeight(weight: FontWeight): void {
|
|
125
|
+
this.#style.setWeight(weight);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
render(ctx: CanvasRenderingContext2D, scale: number, position: Vector2): void {
|
|
129
|
+
this.#style.setupStyle(ctx, scale)
|
|
130
|
+
ctx.fillText(this.#value, position.x, position.y);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
style(): TextStyle {
|
|
134
|
+
return this.#style;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface Vector2 {
|
|
2
|
+
x: number
|
|
3
|
+
y: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function CopyVector2(dst: Vector2, src: Vector2): void {
|
|
7
|
+
dst.x = src.x;
|
|
8
|
+
dst.y = src.y;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function SubVector2(dst: Vector2, a: Vector2, b: Vector2): void {
|
|
12
|
+
dst.x = a.x - b.x;
|
|
13
|
+
dst.y = a.y - b.y;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function AddVector2(dst: Vector2, a: Vector2, b: Vector2): void {
|
|
17
|
+
dst.x = a.x + b.x;
|
|
18
|
+
dst.y = a.y + b.y;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ScaleVector(dst: Vector2, scale: number): void {
|
|
22
|
+
dst.x *= scale;
|
|
23
|
+
dst.y *= scale;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Zero(): Vector2 {
|
|
27
|
+
return { x: 0, y: 0 };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function Distance(a: Vector2, b: Vector2): number {
|
|
31
|
+
const xd = b.x - a.x
|
|
32
|
+
const yd = b.y - a.y
|
|
33
|
+
return Math.sqrt((xd*xd)+(yd*yd))
|
|
34
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Clamp01 } from './math';
|
|
2
|
+
|
|
3
|
+
export interface Color {
|
|
4
|
+
r: number, // 0 - 1
|
|
5
|
+
g: number, // 0 - 1
|
|
6
|
+
b: number, // 0 - 1
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface HSV {
|
|
10
|
+
h: number, // angle in degrees
|
|
11
|
+
s: number, // 0 - 1
|
|
12
|
+
v: number, // 0 - 1
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function componentToHex(c: number) {
|
|
16
|
+
var hex = c.toString(16);
|
|
17
|
+
return hex.length == 1 ? "0" + hex : hex;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function RgbToHex(c: Color): string {
|
|
21
|
+
return "#" + componentToHex(Math.round(c.r * 255)) + componentToHex(Math.round(c.g * 255)) + componentToHex(Math.round(c.b * 255));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function HexToColor(hex: string): Color | null {
|
|
25
|
+
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
26
|
+
return result ? {
|
|
27
|
+
r: parseInt(result[1], 16) / 255,
|
|
28
|
+
g: parseInt(result[2], 16) / 255,
|
|
29
|
+
b: parseInt(result[3], 16) / 255
|
|
30
|
+
} : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export function HSV2RGB(hsv: HSV, out: Color): void {
|
|
35
|
+
if (hsv.s <= 0.0) { // < is bogus, just shuts up warnings
|
|
36
|
+
out.r = hsv.v;
|
|
37
|
+
out.g = hsv.v;
|
|
38
|
+
out.b = hsv.v;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let hh = hsv.h;
|
|
42
|
+
if (hh >= 360.0) {
|
|
43
|
+
hh = 0.0;
|
|
44
|
+
}
|
|
45
|
+
hh /= 60.0;
|
|
46
|
+
|
|
47
|
+
const i = Math.round(hh);
|
|
48
|
+
const ff = hh - i;
|
|
49
|
+
const p = hsv.v * (1.0 - hsv.s);
|
|
50
|
+
const q = hsv.v * (1.0 - (hsv.s * ff));
|
|
51
|
+
const t = hsv.v * (1.0 - (hsv.s * (1.0 - ff)));
|
|
52
|
+
|
|
53
|
+
switch (i) {
|
|
54
|
+
case 0:
|
|
55
|
+
out.r = hsv.v;
|
|
56
|
+
out.g = t;
|
|
57
|
+
out.b = p;
|
|
58
|
+
break;
|
|
59
|
+
case 1:
|
|
60
|
+
out.r = q;
|
|
61
|
+
out.g = hsv.v;
|
|
62
|
+
out.b = p;
|
|
63
|
+
break;
|
|
64
|
+
case 2:
|
|
65
|
+
out.r = p;
|
|
66
|
+
out.g = hsv.v;
|
|
67
|
+
out.b = t;
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
case 3:
|
|
71
|
+
out.r = p;
|
|
72
|
+
out.g = q;
|
|
73
|
+
out.b = hsv.v;
|
|
74
|
+
break;
|
|
75
|
+
case 4:
|
|
76
|
+
out.r = t;
|
|
77
|
+
out.g = p;
|
|
78
|
+
out.b = hsv.v;
|
|
79
|
+
break;
|
|
80
|
+
// case 5:
|
|
81
|
+
default:
|
|
82
|
+
out.r = hsv.v;
|
|
83
|
+
out.g = p;
|
|
84
|
+
out.b = q;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
out.r = Clamp01(out.r)
|
|
89
|
+
out.g = Clamp01(out.g)
|
|
90
|
+
out.b = Clamp01(out.b)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function RGB2HSV(rgb: Color, out: HSV): void {
|
|
94
|
+
out.h = 0;
|
|
95
|
+
out.s = 0;
|
|
96
|
+
out.v = 0;
|
|
97
|
+
let min: number = 0;
|
|
98
|
+
let max: number = 0;
|
|
99
|
+
let delta: number = 0;
|
|
100
|
+
|
|
101
|
+
min = rgb.r < rgb.g ? rgb.r : rgb.g;
|
|
102
|
+
min = min < rgb.b ? min : rgb.b;
|
|
103
|
+
|
|
104
|
+
max = rgb.r > rgb.g ? rgb.r : rgb.g;
|
|
105
|
+
max = max > rgb.b ? max : rgb.b;
|
|
106
|
+
|
|
107
|
+
out.v = max; // v
|
|
108
|
+
delta = max - min;
|
|
109
|
+
if (delta < 0.00001) {
|
|
110
|
+
out.s = 0;
|
|
111
|
+
out.h = 0; // undefined, maybe nan?
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (max > 0.0) { // NOTE: if Max is == 0, this divide would cause a crash
|
|
115
|
+
out.s = (delta / max); // s
|
|
116
|
+
} else {
|
|
117
|
+
// if max is 0, then r = g = b = 0
|
|
118
|
+
// s = 0, h is undefined
|
|
119
|
+
out.s = 0.0;
|
|
120
|
+
out.h = NaN; // its now undefined
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (rgb.r >= max) // > is bogus, just keeps compilor happy
|
|
124
|
+
out.h = (rgb.g - rgb.b) / delta; // between yellow & magenta
|
|
125
|
+
else
|
|
126
|
+
if (rgb.g >= max)
|
|
127
|
+
out.h = 2.0 + (rgb.b - rgb.r) / delta; // between cyan & yellow
|
|
128
|
+
else
|
|
129
|
+
out.h = 4.0 + (rgb.r - rgb.g) / delta; // between magenta & cyan
|
|
130
|
+
|
|
131
|
+
out.h *= 60.0; // degrees
|
|
132
|
+
|
|
133
|
+
if (out.h < 0.0)
|
|
134
|
+
out.h += 360.0;
|
|
135
|
+
|
|
136
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function HtmlFromString(htmlString: string): Element {
|
|
2
|
+
var div = document.createElement('div');
|
|
3
|
+
div.innerHTML = htmlString;
|
|
4
|
+
const child = div.firstElementChild;
|
|
5
|
+
if (child === null || child === undefined) {
|
|
6
|
+
throw new Error("unable to create html from string")
|
|
7
|
+
}
|
|
8
|
+
return child;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface CSS {
|
|
12
|
+
[name: string]: any
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function CssToString(css: CSS): string {
|
|
16
|
+
let cssString = ""
|
|
17
|
+
for (let entry in css) {
|
|
18
|
+
cssString += entry + ":" + css[entry] + ";";
|
|
19
|
+
}
|
|
20
|
+
return cssString;
|
|
21
|
+
}
|