mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-13 15:58:10 -05:00
feat: connections
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
output
|
||||
dist
|
||||
test
|
||||
.idea
|
||||
53
package-lock.json
generated
53
package-lock.json
generated
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"name": "@aarthificial/motion-canvas",
|
||||
"version": "1.0.14",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@aarthificial/motion-canvas",
|
||||
"version": "1.0.14",
|
||||
"version": "1.2.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"canvas": "^2.9.0",
|
||||
"konva": "^8.3.2",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typescript": "^4.5.5",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.68.0",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
@@ -627,7 +628,6 @@
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
@@ -1207,7 +1207,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
@@ -2165,7 +2164,6 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5"
|
||||
},
|
||||
@@ -2216,7 +2214,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
|
||||
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
@@ -3683,6 +3680,32 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-loader": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz",
|
||||
"integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==",
|
||||
"dependencies": {
|
||||
"loader-utils": "^2.0.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"schema-utils": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"file-loader": "*",
|
||||
"webpack": "^4.0.0 || ^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"file-loader": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -4657,8 +4680,7 @@
|
||||
"big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
@@ -5104,8 +5126,7 @@
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
@@ -5814,7 +5835,6 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
@@ -5839,7 +5859,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
|
||||
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
@@ -6861,6 +6880,16 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"url-loader": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz",
|
||||
"integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==",
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"schema-utils": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@@ -7,13 +7,16 @@
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
"build": "tsc"
|
||||
"build": "tsc",
|
||||
"test:serve": "node ./tools/serve.mjs ./test/player.ts",
|
||||
"test:render": "node ./tools/serve.mjs ./test/render.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"canvas": "^2.9.0",
|
||||
"konva": "^8.3.2",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typescript": "^4.5.5",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.68.0",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
|
||||
@@ -2,9 +2,12 @@ import {Project} from './Project';
|
||||
|
||||
export function Player(factory: () => Project) {
|
||||
const project = factory();
|
||||
project.start();
|
||||
const interval = setInterval(() => {
|
||||
if (project.next()) {
|
||||
clearInterval(interval);
|
||||
project.start();
|
||||
project.next();
|
||||
// clearInterval(interval);
|
||||
}
|
||||
}, 1000 / project.framesPerSeconds);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ import {Stage, StageConfig} from 'konva/lib/Stage';
|
||||
import {Rect} from 'konva/lib/shapes/Rect';
|
||||
import {Layer} from 'konva/lib/Layer';
|
||||
import {Scene, SceneRunner} from './Scene';
|
||||
import {Vector2d} from 'konva/lib/types';
|
||||
import {Konva} from "konva/lib/Global";
|
||||
|
||||
Konva.autoDrawEnabled = false;
|
||||
|
||||
export enum ProjectSize {
|
||||
FullHD,
|
||||
@@ -13,11 +17,15 @@ const Sizes: Record<ProjectSize, [number, number]> = {
|
||||
|
||||
export class Project extends Stage {
|
||||
public readonly background: Rect;
|
||||
public readonly center: Vector2d;
|
||||
public framesPerSeconds = 60;
|
||||
public frame: number = 0;
|
||||
|
||||
private runner: Generator;
|
||||
private scenes: Scene[] = [];
|
||||
|
||||
public constructor(
|
||||
private runnerFactory: (project: Project) => Generator,
|
||||
size: ProjectSize = ProjectSize.FullHD,
|
||||
config: Partial<StageConfig> = {},
|
||||
) {
|
||||
@@ -28,6 +36,11 @@ export class Project extends Stage {
|
||||
...config,
|
||||
});
|
||||
|
||||
this.center = {
|
||||
x: Sizes[size][0] / 2,
|
||||
y: Sizes[size][1] / 2,
|
||||
};
|
||||
|
||||
this.background = new Rect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -42,19 +55,23 @@ export class Project extends Stage {
|
||||
}
|
||||
|
||||
public createScene(runner: SceneRunner) {
|
||||
const layer = new Layer();
|
||||
this.add(layer);
|
||||
const scene = new Scene(this, new Layer(), runner);
|
||||
this.add(scene.layer);
|
||||
this.scenes.push(scene);
|
||||
|
||||
return new Scene(this, layer, runner);
|
||||
return scene;
|
||||
}
|
||||
|
||||
public setRunner(factory: (project: Project) => Generator) {
|
||||
public start() {
|
||||
this.scenes.forEach(scene => scene.layer.destroy());
|
||||
this.scenes = [];
|
||||
this.frame = 0;
|
||||
this.runner = factory(this);
|
||||
this.runner = this.runnerFactory(this);
|
||||
}
|
||||
|
||||
public next(): boolean {
|
||||
const result = this.runner.next();
|
||||
this.draw();
|
||||
this.frame++;
|
||||
|
||||
return result.done;
|
||||
|
||||
@@ -7,8 +7,8 @@ export interface SceneRunner {
|
||||
|
||||
export class Scene {
|
||||
public constructor(
|
||||
private project: Project,
|
||||
private layer: Layer,
|
||||
public readonly project: Project,
|
||||
public readonly layer: Layer,
|
||||
private runner: SceneRunner,
|
||||
) {}
|
||||
|
||||
|
||||
2
src/animations/index.ts
Normal file
2
src/animations/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './move';
|
||||
export * from './show';
|
||||
@@ -1,6 +1,28 @@
|
||||
import {Node} from "konva/lib/Node";
|
||||
import {Vector2d} from "konva/lib/types";
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Vector2d} from 'konva/lib/types';
|
||||
import {tween} from '../tweening';
|
||||
|
||||
export function move(node: Node, positionTo: Vector2d) {
|
||||
const positionFrom = node.position();
|
||||
}
|
||||
export function move(node: Node, position: Vector2d): Generator;
|
||||
export function move(
|
||||
node: Node,
|
||||
positionX: number,
|
||||
positionY: number,
|
||||
): Generator;
|
||||
export function move(
|
||||
node: Node,
|
||||
positionX: number | Vector2d,
|
||||
positionY?: number,
|
||||
): Generator {
|
||||
const positionFrom = node.position();
|
||||
const positionTo =
|
||||
typeof positionX === 'number' ? {x: positionX, y: positionY} : positionX;
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(positionFrom.x - positionTo.x, 2) +
|
||||
Math.pow(positionFrom.y - positionTo.y, 2),
|
||||
);
|
||||
|
||||
return tween(distance / 1000, value =>
|
||||
node.position(value.vector2d(positionFrom, positionTo)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ class LineSegment extends Segment {
|
||||
|
||||
class CircleSegment extends Segment {
|
||||
private readonly length: number;
|
||||
private readonly delta: number;
|
||||
|
||||
public constructor(
|
||||
private center: Vector2d,
|
||||
@@ -97,7 +98,10 @@ class CircleSegment extends Segment {
|
||||
private counter: boolean,
|
||||
) {
|
||||
super();
|
||||
this.length = Math.abs(deltaAngle * radius);
|
||||
this.delta = this.counter
|
||||
? this.deltaAngle
|
||||
: this.deltaAngle + Math.PI * 2;
|
||||
this.length = Math.abs(this.delta * radius);
|
||||
}
|
||||
|
||||
get arcLength(): number {
|
||||
@@ -110,12 +114,8 @@ class CircleSegment extends Segment {
|
||||
to: number,
|
||||
move: boolean,
|
||||
): [Vector2d, Vector2d, Vector2d, Vector2d] {
|
||||
const delta = this.counter
|
||||
? this.deltaAngle
|
||||
: this.deltaAngle + Math.PI * 2;
|
||||
|
||||
const startAngle = this.startAngle + delta * from;
|
||||
const endAngle = this.startAngle + delta * to;
|
||||
const startAngle = this.startAngle + this.delta * from;
|
||||
const endAngle = this.startAngle + this.delta * to;
|
||||
|
||||
context.arc(
|
||||
this.center.x,
|
||||
@@ -160,14 +160,16 @@ class CircleSegment extends Segment {
|
||||
}
|
||||
|
||||
public getOffset(from: number): number {
|
||||
return this.counter ? 0 : -from * 1.045 * this.deltaAngle * this.radius / 2;
|
||||
// May wanna go back to (-from * 1.045 * this.delta * this.radius) / 2
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class Arrow extends Shape<ArrowConfig> {
|
||||
protected dirty = true;
|
||||
|
||||
private segments: Segment[] = [];
|
||||
private arcLength: number = 0;
|
||||
private dirty = true;
|
||||
|
||||
_sceneFunc(context: Context) {
|
||||
if (this.dirty) {
|
||||
|
||||
259
src/components/Connection.ts
Normal file
259
src/components/Connection.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import {Arrow, ArrowConfig} from './Arrow';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {_registerNode} from 'konva/lib/Global';
|
||||
import {Factory} from 'konva/lib/Factory';
|
||||
import {GetSet, Vector2d} from 'konva/lib/types';
|
||||
import {Direction} from '../types/Origin';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {TimeTween} from '../tweening';
|
||||
|
||||
export interface ConnectionPoint {
|
||||
node: Node;
|
||||
direction: Direction;
|
||||
offset: number;
|
||||
vertical?: boolean;
|
||||
}
|
||||
|
||||
export interface ConnectionConfig extends ArrowConfig {
|
||||
from: ConnectionPoint;
|
||||
to: ConnectionPoint;
|
||||
crossing: Vector2d;
|
||||
}
|
||||
|
||||
interface Measurement {
|
||||
direction: -1 | 0 | 1;
|
||||
range: [number, number];
|
||||
rangeOffset: [number, number];
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
if (min > max) [min, max] = [max, min];
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
|
||||
export class Connection extends Arrow {
|
||||
constructor(config?: ConnectionConfig) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
private measurePosition(
|
||||
positionA: number,
|
||||
sizeA: number,
|
||||
positionB: number,
|
||||
sizeB: number,
|
||||
): Measurement {
|
||||
let direction: -1 | 0 | 1;
|
||||
let range: [number, number];
|
||||
let rangeOffset: [number, number];
|
||||
|
||||
if (positionA - sizeA - 80 > positionB + sizeB) {
|
||||
direction = -1;
|
||||
range = [positionA - sizeA - 20, positionB + sizeB + 20];
|
||||
rangeOffset = [range[0] - 20, range[1] + 20];
|
||||
} else if (positionA + sizeA + 80 < positionB - sizeB) {
|
||||
direction = 1;
|
||||
range = [positionA + sizeA + 20, positionB - sizeB - 20];
|
||||
rangeOffset = [range[0] + 20, range[1] - 20];
|
||||
} else {
|
||||
direction = 0;
|
||||
range = [positionA - sizeA - 20, positionB - sizeB - 20];
|
||||
rangeOffset = [range[0] - 20, range[1] - 20];
|
||||
}
|
||||
|
||||
return {direction, range, rangeOffset, from: positionA, to: positionB};
|
||||
}
|
||||
|
||||
private calculateCrossing(
|
||||
crossing: number,
|
||||
measurement: Measurement,
|
||||
): number {
|
||||
const fractionX = crossing >= 0 && crossing <= 1;
|
||||
let clampedCrossing = crossing + measurement.from;
|
||||
if (measurement.direction !== 0 && !fractionX) {
|
||||
clampedCrossing = clamp(
|
||||
clampedCrossing,
|
||||
measurement.rangeOffset[0],
|
||||
measurement.rangeOffset[1],
|
||||
);
|
||||
}
|
||||
|
||||
return measurement.direction === 0
|
||||
? Math.min(
|
||||
measurement.rangeOffset[0],
|
||||
measurement.rangeOffset[1],
|
||||
clampedCrossing,
|
||||
)
|
||||
: fractionX
|
||||
? TimeTween.map(
|
||||
measurement.rangeOffset[0],
|
||||
measurement.rangeOffset[1],
|
||||
crossing,
|
||||
)
|
||||
: clampedCrossing;
|
||||
}
|
||||
|
||||
_sceneFunc(context: Context) {
|
||||
if (this.dirty) {
|
||||
if (!this.attrs.from || !this.attrs.to) {
|
||||
this.attrs.points = [];
|
||||
} else {
|
||||
const from: ConnectionPoint = this.attrs.from;
|
||||
const to: ConnectionPoint = this.attrs.to;
|
||||
|
||||
const fromPosition = from.node.absolutePosition();
|
||||
const fromSize = from.node.size();
|
||||
fromSize.width /= 2;
|
||||
fromSize.height /= 2;
|
||||
const toPosition = to.node.absolutePosition();
|
||||
const toSize = to.node.size();
|
||||
toSize.width /= 2;
|
||||
toSize.height /= 2;
|
||||
|
||||
this.attrs.points = [];
|
||||
const horizontal = this.measurePosition(
|
||||
fromPosition.x,
|
||||
fromSize.width,
|
||||
toPosition.x,
|
||||
toSize.width,
|
||||
);
|
||||
const vertical = this.measurePosition(
|
||||
fromPosition.y,
|
||||
fromSize.height,
|
||||
toPosition.y,
|
||||
toSize.height,
|
||||
);
|
||||
|
||||
if (from.vertical) {
|
||||
this.attrs.points.push(fromPosition.x, vertical.range[0]);
|
||||
} else {
|
||||
this.attrs.points.push(horizontal.range[0], fromPosition.y);
|
||||
}
|
||||
|
||||
let toVertical = to.vertical;
|
||||
if (
|
||||
from.vertical !== toVertical &&
|
||||
(horizontal.direction === 0 || vertical.direction === 0)
|
||||
) {
|
||||
toVertical = from.vertical;
|
||||
}
|
||||
|
||||
let distance = 100;
|
||||
if (from.vertical === toVertical) {
|
||||
if (from.vertical) {
|
||||
distance = Math.abs(toPosition.x - fromPosition.x);
|
||||
if (distance > 0) {
|
||||
const y = this.calculateCrossing(this.attrs.crossing.y, vertical);
|
||||
this.attrs.points.push(fromPosition.x, y);
|
||||
this.attrs.points.push(toPosition.x, y);
|
||||
}
|
||||
} else {
|
||||
distance = Math.abs(toPosition.y - fromPosition.y);
|
||||
if (distance > 0) {
|
||||
const x = this.calculateCrossing(
|
||||
this.attrs.crossing.x,
|
||||
horizontal,
|
||||
);
|
||||
this.attrs.points.push(x, fromPosition.y);
|
||||
this.attrs.points.push(x, toPosition.y);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (from.vertical) {
|
||||
this.attrs.points.push(fromPosition.x, toPosition.y);
|
||||
} else {
|
||||
this.attrs.points.push(toPosition.x, fromPosition.y);
|
||||
}
|
||||
}
|
||||
|
||||
if (toVertical) {
|
||||
this.attrs.points.push(toPosition.x, vertical.range[1]);
|
||||
} else {
|
||||
this.attrs.points.push(horizontal.range[1], toPosition.y);
|
||||
}
|
||||
|
||||
this.attrs.radius = Math.min(8, distance / 2);
|
||||
}
|
||||
}
|
||||
|
||||
super._sceneFunc(context);
|
||||
}
|
||||
|
||||
private previousFromNode: Node | null = null;
|
||||
public fromChanged() {
|
||||
if (this.previousFromNode === this.attrs.from?.node) return;
|
||||
this.markAsDirtyCallback ??= () => this.markAsDirty();
|
||||
|
||||
this.previousFromNode?.off(
|
||||
'absoluteTransformChange',
|
||||
this.markAsDirtyCallback,
|
||||
);
|
||||
this.previousFromNode = this.attrs.from?.node;
|
||||
this.previousFromNode?.on(
|
||||
'absoluteTransformChange',
|
||||
this.markAsDirtyCallback,
|
||||
);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
private previousToNode: Node | null = null;
|
||||
public toChanged() {
|
||||
if (this.previousToNode === this.attrs.to?.node) return;
|
||||
this.markAsDirtyCallback ??= () => this.markAsDirty();
|
||||
|
||||
this.previousToNode?.off(
|
||||
'absoluteTransformChange',
|
||||
this.markAsDirtyCallback,
|
||||
);
|
||||
this.previousToNode = this.attrs.to?.node;
|
||||
this.previousToNode?.on(
|
||||
'absoluteTransformChange',
|
||||
this.markAsDirtyCallback,
|
||||
);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
private markAsDirtyCallback = () => {
|
||||
this.markAsDirty();
|
||||
};
|
||||
|
||||
from: GetSet<ConnectionPoint, this>;
|
||||
to: GetSet<ConnectionPoint, this>;
|
||||
crossing: GetSet<Vector2d, this>;
|
||||
}
|
||||
|
||||
Connection.prototype.className = 'Connection';
|
||||
Connection.prototype._attrsAffectingSize = ['from', 'to'];
|
||||
|
||||
_registerNode(Arrow);
|
||||
|
||||
Factory.addGetterSetter(
|
||||
Connection,
|
||||
'from',
|
||||
{
|
||||
node: null,
|
||||
direction: Direction.Right,
|
||||
offset: 0,
|
||||
},
|
||||
undefined,
|
||||
Connection.prototype.fromChanged,
|
||||
);
|
||||
Factory.addGetterSetter(
|
||||
Connection,
|
||||
'to',
|
||||
{
|
||||
node: null,
|
||||
direction: Direction.Right,
|
||||
offset: 0,
|
||||
},
|
||||
undefined,
|
||||
Connection.prototype.toChanged,
|
||||
);
|
||||
Factory.addGetterSetter(
|
||||
Connection,
|
||||
'crossing',
|
||||
{x: 0, y: 0},
|
||||
undefined,
|
||||
Connection.prototype.markAsDirty,
|
||||
);
|
||||
@@ -2,6 +2,7 @@ import {Group} from 'konva/lib/Group';
|
||||
import {Rect} from 'konva/lib/shapes/Rect';
|
||||
import {Text} from 'konva/lib/shapes/Text';
|
||||
import {Vector2d} from 'konva/lib/types';
|
||||
import {ContainerConfig} from 'konva/lib/Container';
|
||||
import {Origin, Direction} from '../types/Origin';
|
||||
import {tween} from '../tweening';
|
||||
|
||||
@@ -10,8 +11,8 @@ export class ObjectNode extends Group {
|
||||
public readonly text: Text;
|
||||
private _origin: Origin = Origin.Middle;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
public constructor(config?: ContainerConfig) {
|
||||
super(config);
|
||||
|
||||
this.box = new Rect({
|
||||
x: 0,
|
||||
@@ -82,7 +83,7 @@ export class ObjectNode extends Group {
|
||||
const previousWidth = this.text.width();
|
||||
this.text.width(null);
|
||||
const width = this.text.getTextWidth();
|
||||
const height = this.text.getTextHeight();
|
||||
const height = this.text.height();
|
||||
const boxWidth = Math.ceil((width + 80) / 20) * 20;
|
||||
this.text.width(previousWidth);
|
||||
|
||||
|
||||
22
src/components/Sprite.ts
Normal file
22
src/components/Sprite.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {Util} from 'konva/lib/Util';
|
||||
|
||||
export interface SpriteConfig extends ShapeConfig {
|
||||
image: string;
|
||||
}
|
||||
|
||||
export class Sprite extends Shape {
|
||||
private image: HTMLImageElement;
|
||||
|
||||
constructor(config?: SpriteConfig) {
|
||||
super(config);
|
||||
this.image = Util.createImageElement();
|
||||
this.image.src = config?.image;
|
||||
}
|
||||
|
||||
_sceneFunc(context: Context) {
|
||||
context._context.imageSmoothingEnabled = false;
|
||||
context.drawImage(this.image, -24 * 20, -24 * 20, 24 * 40, 24 * 40);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './Arrow';
|
||||
export * from './Connection';
|
||||
export * from './ObjectNode';
|
||||
12
src/flow/any.ts
Normal file
12
src/flow/any.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export function* any(...sequences: Generator[]): Generator {
|
||||
while (sequences.length > 0) {
|
||||
for (let i = sequences.length - 1; i >= 0; i--) {
|
||||
const result = sequences[i].next();
|
||||
if (result.done) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from './all';
|
||||
export * from './any';
|
||||
export * from './loop';
|
||||
export * from './sequence';
|
||||
|
||||
5
src/flow/loop.ts
Normal file
5
src/flow/loop.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function* loop(iterations: number, factory: () => Generator) {
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
yield* factory();
|
||||
}
|
||||
}
|
||||
4
src/global.d.ts
vendored
Normal file
4
src/global.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module "*.png" {
|
||||
const value: any;
|
||||
export = value;
|
||||
}
|
||||
2
src/index.ts
Normal file
2
src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export {Sprite} from 'konva/lib/shapes/Sprite';
|
||||
export {Util} from 'konva/lib/Util';
|
||||
@@ -70,6 +70,7 @@ function build(entry) {
|
||||
|
||||
let totalSize = 0;
|
||||
const project = setup.default.default(createCanvas, Image);
|
||||
project.start();
|
||||
while (!project.next()) {
|
||||
const name = String(project.frame).padStart(6, '0');
|
||||
const content = project.toDataURL().replace(/^data:image\/png;base64,/, '');
|
||||
|
||||
@@ -23,6 +23,17 @@ const compiler = webpack({
|
||||
allowTsInNodeModules: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 8192,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
"target": "es6",
|
||||
"declaration": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"MC/*": [
|
||||
"../src/*",
|
||||
"../dist/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user