mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-12 23:38:02 -05:00
refactor: remove legacy package
BREAKING CHANGE: remove legacy package
This commit is contained in:
95
package-lock.json
generated
95
package-lock.json
generated
@@ -4919,10 +4919,6 @@
|
||||
"resolved": "packages/docs",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@motion-canvas/legacy": {
|
||||
"resolved": "packages/legacy",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@motion-canvas/template": {
|
||||
"resolved": "packages/template",
|
||||
"link": true
|
||||
@@ -6168,11 +6164,6 @@
|
||||
"integrity": "sha512-fOwvpvQYStpb/zHMx0Cauwywu9yLDmzWiiQBC7gJyq5tYLUXFZvDG7VK1B7WBxxjBJNKFOZ0zLoOQn8vmATbhw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/prismjs": {
|
||||
"version": "1.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz",
|
||||
"integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ=="
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
@@ -6276,14 +6267,6 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.141.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.141.0.tgz",
|
||||
"integrity": "sha512-OJdKDgTPVBUgc+s74DYoy4aLznbFFC38Xm4ElmU1YwGNgR7GGFVvFCX7lpVgOsT6S1zSJtGdajTsOYE8/xY9nA==",
|
||||
"dependencies": {
|
||||
"@types/webxr": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
|
||||
@@ -6295,11 +6278,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
|
||||
"integrity": "sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA=="
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
@@ -14290,25 +14268,6 @@
|
||||
"integrity": "sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/konva": {
|
||||
"version": "8.3.10",
|
||||
"resolved": "https://registry.npmjs.org/konva/-/konva-8.3.10.tgz",
|
||||
"integrity": "sha512-5zOynjWBG9wWgpA634SDH+764eyoISpmHLTOCfQ3GFN8OBVd83Genk6H0R4D3hXV0kEGIFAv7RDcSVDtQpPOMw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/lavrton"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/konva"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/lavrton"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/latest-version": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
|
||||
@@ -20514,11 +20473,6 @@
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.141.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.141.0.tgz",
|
||||
"integrity": "sha512-JaSDAPWuk4RTzG5BYRQm8YZbERUxTfTDVouWgHMisS2to4E5fotMS9F2zPFNOIJyEFTTQDDKPpsgZVThKU3pXA=="
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
@@ -22544,6 +22498,7 @@
|
||||
"packages/legacy": {
|
||||
"name": "@motion-canvas/legacy",
|
||||
"version": "12.0.0-alpha.2",
|
||||
"extraneous": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.0",
|
||||
@@ -22568,8 +22523,8 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/legacy": "*"
|
||||
"@motion-canvas/2d": "*",
|
||||
"@motion-canvas/core": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
@@ -26396,25 +26351,11 @@
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
},
|
||||
"@motion-canvas/legacy": {
|
||||
"version": "file:packages/legacy",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/three": "^0.141.0",
|
||||
"colorjs.io": "^0.3.0",
|
||||
"konva": "^8.3.9",
|
||||
"prismjs": "^1.28.0",
|
||||
"three": "^0.141.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.9"
|
||||
}
|
||||
},
|
||||
"@motion-canvas/template": {
|
||||
"version": "file:packages/template",
|
||||
"requires": {
|
||||
"@motion-canvas/2d": "*",
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/legacy": "*",
|
||||
"@motion-canvas/ui": "*",
|
||||
"@motion-canvas/vite-plugin": "*",
|
||||
"vite": "^3.0.9"
|
||||
@@ -27416,11 +27357,6 @@
|
||||
"integrity": "sha512-fOwvpvQYStpb/zHMx0Cauwywu9yLDmzWiiQBC7gJyq5tYLUXFZvDG7VK1B7WBxxjBJNKFOZ0zLoOQn8vmATbhw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prismjs": {
|
||||
"version": "1.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz",
|
||||
"integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ=="
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
@@ -27524,14 +27460,6 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/three": {
|
||||
"version": "0.141.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.141.0.tgz",
|
||||
"integrity": "sha512-OJdKDgTPVBUgc+s74DYoy4aLznbFFC38Xm4ElmU1YwGNgR7GGFVvFCX7lpVgOsT6S1zSJtGdajTsOYE8/xY9nA==",
|
||||
"requires": {
|
||||
"@types/webxr": "*"
|
||||
}
|
||||
},
|
||||
"@types/tough-cookie": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
|
||||
@@ -27543,11 +27471,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"@types/webxr": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
|
||||
"integrity": "sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA=="
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
@@ -33379,11 +33302,6 @@
|
||||
"integrity": "sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"konva": {
|
||||
"version": "8.3.10",
|
||||
"resolved": "https://registry.npmjs.org/konva/-/konva-8.3.10.tgz",
|
||||
"integrity": "sha512-5zOynjWBG9wWgpA634SDH+764eyoISpmHLTOCfQ3GFN8OBVd83Genk6H0R4D3hXV0kEGIFAv7RDcSVDtQpPOMw=="
|
||||
},
|
||||
"latest-version": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
|
||||
@@ -37987,11 +37905,6 @@
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
|
||||
},
|
||||
"three": {
|
||||
"version": "0.141.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.141.0.tgz",
|
||||
"integrity": "sha512-JaSDAPWuk4RTzG5BYRQm8YZbERUxTfTDVouWgHMisS2to4E5fotMS9F2zPFNOIJyEFTTQDDKPpsgZVThKU3pXA=="
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import {threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import {Node, NodeProps} from './Node';
|
||||
import {TwoDView} from '../scenes';
|
||||
import {View2D} from '../scenes';
|
||||
|
||||
export interface LayoutProps extends NodeProps {
|
||||
layout?: LayoutMode;
|
||||
@@ -444,7 +444,7 @@ export class Layout extends Node {
|
||||
public constructor({tagName = 'div', ...props}: LayoutProps) {
|
||||
super(props);
|
||||
|
||||
this.element = TwoDView.document.createElement(tagName);
|
||||
this.element = View2D.document.createElement(tagName);
|
||||
this.element.style.display = 'flex';
|
||||
this.element.style.boxSizing = 'border-box';
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '@motion-canvas/core/lib/utils';
|
||||
import {ComponentChild, ComponentChildren} from './types';
|
||||
import {Promisable} from '@motion-canvas/core/lib/threading';
|
||||
import {TwoDView, use2DView} from '../scenes';
|
||||
import {View2D, use2DView} from '../scenes';
|
||||
import {TimingFunction} from '@motion-canvas/core/lib/tweening';
|
||||
import {threadable} from '@motion-canvas/core/lib/decorators';
|
||||
|
||||
@@ -187,7 +187,7 @@ export class Node implements Promisable<Node> {
|
||||
|
||||
public constructor({children, ...rest}: NodeProps) {
|
||||
initialize(this, {defaults: rest});
|
||||
this.append(children);
|
||||
this.add(children);
|
||||
use2DView()?.registerNode(this);
|
||||
}
|
||||
|
||||
@@ -274,11 +274,11 @@ export class Node implements Promisable<Node> {
|
||||
}
|
||||
|
||||
@computed()
|
||||
public view(): TwoDView | null {
|
||||
public view(): View2D | null {
|
||||
return this.parent()?.view() ?? null;
|
||||
}
|
||||
|
||||
public append(node: ComponentChildren): this {
|
||||
public add(node: ComponentChildren): this {
|
||||
const nodes: ComponentChild[] = Array.isArray(node) ? node : [node];
|
||||
for (const node of nodes) {
|
||||
if (node instanceof Node) {
|
||||
|
||||
@@ -3,21 +3,21 @@ import {
|
||||
Scene,
|
||||
SceneRenderEvent,
|
||||
} from '@motion-canvas/core/lib/scenes';
|
||||
import {TwoDView} from './TwoDView';
|
||||
import {View2D} from './View2D';
|
||||
import {useScene} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
export function use2DView(): TwoDView | null {
|
||||
export function use2DView(): View2D | null {
|
||||
const scene = useScene();
|
||||
if (scene instanceof TwoDScene) {
|
||||
if (scene instanceof Scene2D) {
|
||||
return scene.getView();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export class TwoDScene extends GeneratorScene<TwoDView> {
|
||||
private readonly view = new TwoDView();
|
||||
export class Scene2D extends GeneratorScene<View2D> {
|
||||
private readonly view = new View2D();
|
||||
|
||||
public getView(): TwoDView {
|
||||
public getView(): View2D {
|
||||
return this.view;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import {Layout, Node} from '../components';
|
||||
|
||||
export class TwoDView extends Layout {
|
||||
export class View2D extends Layout {
|
||||
public static frameID = 'motion-canvas-2d-frame';
|
||||
public static document: Document;
|
||||
|
||||
static {
|
||||
let frame = document.querySelector<HTMLIFrameElement>(
|
||||
`#${TwoDView.frameID}`,
|
||||
);
|
||||
let frame = document.querySelector<HTMLIFrameElement>(`#${View2D.frameID}`);
|
||||
if (!frame) {
|
||||
frame = document.createElement('iframe');
|
||||
frame.id = TwoDView.frameID;
|
||||
frame.id = View2D.frameID;
|
||||
frame.style.position = 'absolute';
|
||||
frame.style.pointerEvents = 'none';
|
||||
frame.style.top = '0';
|
||||
@@ -38,7 +36,7 @@ export class TwoDView extends Layout {
|
||||
fontStyle: 'normal',
|
||||
});
|
||||
|
||||
TwoDView.document.body.append(this.element);
|
||||
View2D.document.body.append(this.element);
|
||||
this.applyFlex();
|
||||
}
|
||||
|
||||
@@ -92,7 +90,7 @@ export class TwoDView extends Layout {
|
||||
this.updateLayout();
|
||||
}
|
||||
|
||||
public override view(): TwoDView | null {
|
||||
public override view(): View2D | null {
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './make2DScene';
|
||||
export * from './TwoDScene';
|
||||
export * from './TwoDView';
|
||||
export * from './makeScene2D';
|
||||
export * from './Scene2D';
|
||||
export * from './View2D';
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import {
|
||||
DescriptionOf,
|
||||
ThreadGeneratorFactory,
|
||||
} from '@motion-canvas/core/lib/scenes';
|
||||
import {TwoDView} from './TwoDView';
|
||||
import {TwoDScene} from './TwoDScene';
|
||||
|
||||
export function make2DScene(
|
||||
runner: ThreadGeneratorFactory<TwoDView>,
|
||||
): DescriptionOf<TwoDScene> {
|
||||
return {
|
||||
klass: TwoDScene,
|
||||
config: runner,
|
||||
};
|
||||
}
|
||||
15
packages/2d/src/scenes/makeScene2D.ts
Normal file
15
packages/2d/src/scenes/makeScene2D.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
DescriptionOf,
|
||||
ThreadGeneratorFactory,
|
||||
} from '@motion-canvas/core/lib/scenes';
|
||||
import {View2D} from './View2D';
|
||||
import {Scene2D} from './Scene2D';
|
||||
|
||||
export function makeScene2D(
|
||||
runner: ThreadGeneratorFactory<View2D>,
|
||||
): DescriptionOf<Scene2D> {
|
||||
return {
|
||||
klass: Scene2D,
|
||||
config: runner,
|
||||
};
|
||||
}
|
||||
@@ -81,7 +81,7 @@ const MANIFEST = JSON.parse(
|
||||
const templateDir = path.resolve(
|
||||
fileURLToPath(import.meta.url),
|
||||
'..',
|
||||
`template-konva-${response.language}`,
|
||||
`template-2d-${response.language}`,
|
||||
);
|
||||
copyDirectory(templateDir, response.path);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/legacy": "*"
|
||||
"@motion-canvas/2d": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
@@ -1,7 +1,7 @@
|
||||
import {makeKonvaScene} from '@motion-canvas/legacy/lib/scenes';
|
||||
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
|
||||
import {waitFor} from '@motion-canvas/core/lib/flow';
|
||||
|
||||
export default makeKonvaScene(function* (view) {
|
||||
export default makeScene2D(function* (view) {
|
||||
// Create your animations here
|
||||
|
||||
yield* waitFor(5);
|
||||
6
packages/create/template-2d-js/vite.config.js
Normal file
6
packages/create/template-2d-js/vite.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import {defineConfig} from 'vite';
|
||||
import motionCanvas from '@motion-canvas/vite-plugin';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [motionCanvas()],
|
||||
});
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/legacy": "*"
|
||||
"@motion-canvas/2d": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
1
packages/create/template-2d-ts/src/motion-canvas.d.ts
vendored
Normal file
1
packages/create/template-2d-ts/src/motion-canvas.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="@motion-canvas/core/project" />
|
||||
@@ -1,7 +1,7 @@
|
||||
import {makeKonvaScene} from '@motion-canvas/legacy/lib/scenes';
|
||||
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
|
||||
import {waitFor} from '@motion-canvas/core/lib/flow';
|
||||
|
||||
export default makeKonvaScene(function* (view) {
|
||||
export default makeScene2D(function* (view) {
|
||||
// Create your animations here
|
||||
|
||||
yield* waitFor(5);
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@motion-canvas/legacy/tsconfig.project.json",
|
||||
"extends": "@motion-canvas/2d/tsconfig.project.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src"
|
||||
},
|
||||
@@ -1,7 +1,6 @@
|
||||
import {defineConfig} from 'vite';
|
||||
import motionCanvas from '@motion-canvas/vite-plugin';
|
||||
import legacyRenderer from '@motion-canvas/legacy/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [motionCanvas(), legacyRenderer()],
|
||||
plugins: [motionCanvas()],
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import {defineConfig} from 'vite';
|
||||
import motionCanvas from '@motion-canvas/vite-plugin';
|
||||
import legacyRenderer from '@motion-canvas/legacy/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
motionCanvas({
|
||||
project: './src/project.js',
|
||||
}),
|
||||
legacyRenderer(),
|
||||
],
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
/// <reference types="@motion-canvas/legacy/project" />
|
||||
@@ -1,52 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [12.0.0-alpha.2](https://github.com/motion-canvas/motion-canvas/compare/v12.0.0-alpha.1...v12.0.0-alpha.2) (2022-09-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* better naming conventions ([#62](https://github.com/motion-canvas/motion-canvas/issues/62)) ([a9d764f](https://github.com/motion-canvas/motion-canvas/commit/a9d764fbceb639497ef45f44c90f9b6e408213d3))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* change names of timing and interpolation functions
|
||||
|
||||
`TweenFunction` is now called `InterpolationFunction`.
|
||||
Individual functions are now called `[type]Lerp` instead of `[type]Tween`.
|
||||
For instance: `colorTween` is now `colorLerp`.
|
||||
|
||||
`InterpolationFunction` is now called `TimingFunction`.
|
||||
This name is better aligned with the CSS spec.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [12.0.0-alpha.1](https://github.com/motion-canvas/motion-canvas/compare/v12.0.0-alpha.0...v12.0.0-alpha.1) (2022-08-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **legacy:** add missing files ([#61](https://github.com/motion-canvas/motion-canvas/issues/61)) ([fad87d5](https://github.com/motion-canvas/motion-canvas/commit/fad87d5aa5500e7c63cb914fc51044db6225502e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [12.0.0-alpha.0](https://github.com/motion-canvas/motion-canvas/compare/v11.1.0...v12.0.0-alpha.0) (2022-08-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* extract konva to separate package ([#60](https://github.com/motion-canvas/motion-canvas/issues/60)) ([4ecad3c](https://github.com/motion-canvas/motion-canvas/commit/4ecad3ca2732bd5147af670c230f8f959129a707))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* change to import paths
|
||||
|
||||
See [the migration guide](https://motion-canvas.github.io/guides/migration/12.0.0) for more info.
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "@motion-canvas/legacy",
|
||||
"version": "12.0.0-alpha.2",
|
||||
"description": "The legacy Motion Canvas renderer based on the Konva library",
|
||||
"main": "lib/scenes/index.js",
|
||||
"author": "motion-canvas",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://npm.pkg.github.com/motion-canvas"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/motion-canvas/motion-canvas.git"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"vite",
|
||||
"project.d.ts",
|
||||
"tsconfig.project.json"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@motion-canvas/core": "*",
|
||||
"vite": "3.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/three": "^0.141.0",
|
||||
"colorjs.io": "^0.3.0",
|
||||
"konva": "^8.3.9",
|
||||
"prismjs": "^1.28.0",
|
||||
"three": "^0.141.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.9"
|
||||
}
|
||||
}
|
||||
13
packages/legacy/project.d.ts
vendored
13
packages/legacy/project.d.ts
vendored
@@ -1,13 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-namespace */
|
||||
/// <reference types="@motion-canvas/core/project" />
|
||||
/// <reference types="@motion-canvas/legacy/lib/patches/Factory" />
|
||||
/// <reference types="@motion-canvas/legacy/lib/patches/Node" />
|
||||
/// <reference types="@motion-canvas/legacy/lib/patches/Shape" />
|
||||
/// <reference types="@motion-canvas/legacy/lib/patches/Container" />
|
||||
|
||||
declare namespace JSX {
|
||||
type ElementClass = import('konva/lib/Node').Node;
|
||||
interface ElementChildrenAttribute {
|
||||
children: unknown;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Animation utilities.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
export * from './show';
|
||||
export * from './interpolationFunctions';
|
||||
export * from './surfaceFrom';
|
||||
export * from './surfaceTransition';
|
||||
@@ -1,81 +0,0 @@
|
||||
import {PossibleSpacing, Rect, Size, Spacing, Vector2} from '../types';
|
||||
import {map} from '@motion-canvas/core/lib/tweening';
|
||||
|
||||
export function vector2dLerp(from: Vector2, to: Vector2, value: number) {
|
||||
return {
|
||||
x: map(from.x, to.x, value),
|
||||
y: map(from.y, to.y, value),
|
||||
};
|
||||
}
|
||||
|
||||
export function sizeLerp(from: Size, to: Size, value: number) {
|
||||
return {
|
||||
width: map(from.width, to.width, value),
|
||||
height: map(from.height, to.height, value),
|
||||
};
|
||||
}
|
||||
|
||||
export function spacingLerp(
|
||||
from: Spacing,
|
||||
to: Spacing,
|
||||
value: number,
|
||||
): PossibleSpacing {
|
||||
return [
|
||||
map(from.top, to.top, value),
|
||||
map(from.right, to.right, value),
|
||||
map(from.bottom, to.bottom, value),
|
||||
map(from.left, to.left, value),
|
||||
];
|
||||
}
|
||||
|
||||
export function rectArcLerp(
|
||||
from: Partial<Rect>,
|
||||
to: Partial<Rect>,
|
||||
value: number,
|
||||
reverse?: boolean,
|
||||
ratio?: number,
|
||||
) {
|
||||
ratio ??= calculateRatio(from, to);
|
||||
|
||||
let flip = reverse;
|
||||
if (ratio > 1) {
|
||||
ratio = 1 / ratio;
|
||||
} else {
|
||||
flip = !flip;
|
||||
}
|
||||
|
||||
const normalized = flip ? Math.acos(1 - value) : Math.asin(value);
|
||||
const radians = map(normalized, map(0, Math.PI / 2, value), ratio);
|
||||
|
||||
let xValue = Math.sin(radians);
|
||||
let yValue = 1 - Math.cos(radians);
|
||||
if (reverse) {
|
||||
[xValue, yValue] = [yValue, xValue];
|
||||
}
|
||||
|
||||
return {
|
||||
x: map(from.x ?? 0, to.x ?? 0, xValue),
|
||||
y: map(from.y ?? 0, to.y ?? 0, yValue),
|
||||
width: map(from.width ?? 0, to.width ?? 0, xValue),
|
||||
height: map(from.height ?? 0, to.height ?? 0, yValue),
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateRatio(from: Partial<Rect>, to: Partial<Rect>): number {
|
||||
let numberOfValues = 0;
|
||||
let ratio = 0;
|
||||
if (from.x) {
|
||||
ratio += Math.abs((from.x - to.x) / (from.y - to.y));
|
||||
numberOfValues++;
|
||||
}
|
||||
if (from.width) {
|
||||
ratio += Math.abs((from.width - to.width) / (from.height - to.height));
|
||||
numberOfValues++;
|
||||
}
|
||||
|
||||
if (numberOfValues) {
|
||||
ratio /= numberOfValues;
|
||||
}
|
||||
|
||||
return isNaN(ratio) ? 1 : ratio;
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import type {Node} from 'konva/lib/Node';
|
||||
import {Surface} from '../components';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {originPosition} from '../types';
|
||||
import {all} from '@motion-canvas/core/lib/flow';
|
||||
import {Vector2d} from 'konva/lib/types';
|
||||
import {
|
||||
easeInOutCubic,
|
||||
easeOutCubic,
|
||||
easeOutExpo,
|
||||
map,
|
||||
tween,
|
||||
} from '@motion-canvas/core/lib/tweening';
|
||||
import {decorate, threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
|
||||
decorate(showTop, threadable());
|
||||
/**
|
||||
* Show the given node by sliding it up.
|
||||
*
|
||||
* @param node - The node to animate.
|
||||
*/
|
||||
export function* showTop(node: Node): ThreadGenerator {
|
||||
const to = node.offsetY();
|
||||
const from = to - 40;
|
||||
node.show();
|
||||
node.cache();
|
||||
node.offsetY(from);
|
||||
node.opacity(0);
|
||||
|
||||
yield* all(
|
||||
node.opacity(1, 0.5, easeOutExpo),
|
||||
node.offsetY(to, 0.5, easeOutExpo),
|
||||
);
|
||||
node.clearCache();
|
||||
}
|
||||
|
||||
decorate(showSurfaceVertically, threadable());
|
||||
/**
|
||||
* Show the given surface by expanding its mask vertically.
|
||||
*
|
||||
* @param surface - The surface to animate.
|
||||
*/
|
||||
export function* showSurfaceVertically(surface: Surface): ThreadGenerator {
|
||||
const mask = surface.getMask();
|
||||
surface.show();
|
||||
surface.setMask({...mask, height: 0});
|
||||
yield* tween(0.5, value => {
|
||||
surface.setMask({
|
||||
...mask,
|
||||
height: map(0, mask.height, easeOutCubic(value)),
|
||||
});
|
||||
});
|
||||
surface.setMask(null);
|
||||
}
|
||||
|
||||
decorate(showCircle, threadable());
|
||||
/**
|
||||
* Show the given surface using a circle mask.
|
||||
*
|
||||
* @param surface - The surface to animate.
|
||||
* @param duration - The duration of the animation.
|
||||
* @param origin - The center of the circle mask.
|
||||
*/
|
||||
export function* showCircle(
|
||||
surface: Surface,
|
||||
duration = 0.6,
|
||||
origin?: Origin | Vector2d,
|
||||
): ThreadGenerator {
|
||||
const position =
|
||||
typeof origin === 'object'
|
||||
? origin
|
||||
: originPosition(origin ?? surface.getOrigin());
|
||||
const mask = surface.getAbsoluteCircleMask({
|
||||
circleMask: {
|
||||
...position,
|
||||
radius: 1,
|
||||
},
|
||||
});
|
||||
surface.show();
|
||||
surface.setCircleMask(mask);
|
||||
const target = mask.radius;
|
||||
mask.radius = 0;
|
||||
|
||||
yield* tween(duration, value => {
|
||||
mask.radius = easeInOutCubic(value, 0, target);
|
||||
});
|
||||
surface.setCircleMask(null);
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import type {Vector2d} from 'konva/lib/types';
|
||||
import type {ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import type {Surface, SurfaceMask} from '../components';
|
||||
import {
|
||||
colorLerp,
|
||||
easeInOutCubic,
|
||||
easeInOutQuint,
|
||||
tween,
|
||||
} from '@motion-canvas/core/lib/tweening';
|
||||
import {decorate, threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {calculateRatio, rectArcLerp} from './interpolationFunctions';
|
||||
|
||||
/**
|
||||
* Configuration for {@link surfaceFrom}.
|
||||
*/
|
||||
export interface SurfaceFromConfig {
|
||||
/**
|
||||
* Whether the transition arc should be reversed.
|
||||
*
|
||||
* @remarks
|
||||
* See {@link rectArcLerp} for more detail.
|
||||
*/
|
||||
reverse?: boolean;
|
||||
/**
|
||||
* A function called when the initial surface is updated.
|
||||
*
|
||||
* @param surface - The initial surface.
|
||||
* @param value - Completion of the entire transition.
|
||||
*
|
||||
* @returns `true` if the default changes made by {@link surfaceFrom}
|
||||
* should be prevented.
|
||||
*/
|
||||
onUpdate?: (surface: Surface, value: number) => boolean | void;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
decorate(surfaceFrom, threadable());
|
||||
/**
|
||||
* Animate the mask of the surface from the initial state to its current state.
|
||||
*
|
||||
* @param surface - The surface to animate.
|
||||
* @param mask - The initial mask.
|
||||
* @param position - The initial position.
|
||||
* @param config - Additional configuration.
|
||||
*/
|
||||
export function* surfaceFrom(
|
||||
surface: Surface,
|
||||
mask: Partial<SurfaceMask> = {width: 0, height: 0},
|
||||
position?: Vector2d,
|
||||
config: SurfaceFromConfig = {},
|
||||
): ThreadGenerator {
|
||||
const toMask = surface.getMask();
|
||||
const fromMask = {...toMask, ...mask};
|
||||
const toPosition = surface.getPosition();
|
||||
|
||||
if (position) {
|
||||
surface.position(position);
|
||||
}
|
||||
surface.show().setMask(fromMask);
|
||||
|
||||
const ratio =
|
||||
(calculateRatio(surface.getPosition(), toPosition) +
|
||||
calculateRatio(fromMask, toMask)) /
|
||||
2;
|
||||
|
||||
yield* tween(config.duration ?? 0.6, value => {
|
||||
surface.setMask({
|
||||
...fromMask,
|
||||
...rectArcLerp(
|
||||
fromMask,
|
||||
toMask,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
radius: easeInOutCubic(value, fromMask.radius, toMask.radius),
|
||||
color: colorLerp(fromMask.color, toMask.color, easeInOutQuint(value)),
|
||||
});
|
||||
if (position) {
|
||||
surface.setPosition(
|
||||
rectArcLerp(
|
||||
position,
|
||||
toPosition,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!config.onUpdate?.(surface, value)) {
|
||||
surface.getChild().opacity(value);
|
||||
}
|
||||
});
|
||||
|
||||
surface.setMask(null);
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
import {Surface} from '../components';
|
||||
import {
|
||||
clampRemap,
|
||||
colorLerp,
|
||||
easeInOutCubic,
|
||||
easeInOutQuint,
|
||||
tween,
|
||||
} from '@motion-canvas/core/lib/tweening';
|
||||
import {decorate, threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import {calculateRatio, rectArcLerp} from './interpolationFunctions';
|
||||
|
||||
/**
|
||||
* Configuration for {@link surfaceTransition}.
|
||||
*
|
||||
* @remarks
|
||||
* For {@link SurfaceTransitionConfig.onInitialSurfaceUpdate | `onInitialSurfaceUpdate`}
|
||||
* and {@link SurfaceTransitionConfig.onTargetSurfaceUpdate | `onTargetSurfaceUpdate`}
|
||||
* callbacks, the `value` and `relativeValue` arguments represent the absolute
|
||||
* and relative completion of the transition:
|
||||
* ```text
|
||||
*
|
||||
* start onSurfaceChange end
|
||||
* │ │ │
|
||||
* │ 0 <──────────┼─────────────────> 1 │ <- value
|
||||
* │ 0 <──────> 1 │ 0 <─────────────> 1 │ <- relativeValue
|
||||
* ──────┴──── from ────┴──────── to ─────────┴───────────────────────>
|
||||
* time
|
||||
* ```
|
||||
*/
|
||||
export interface SurfaceTransitionConfig {
|
||||
/**
|
||||
* Whether the transition arc should be reversed.
|
||||
*
|
||||
* @remarks
|
||||
* See {@link rectArcLerp} for more detail.
|
||||
*/
|
||||
reverse?: boolean;
|
||||
/**
|
||||
* A function called when the currently displayed surface changes.
|
||||
*
|
||||
* @param surface - The new surface.
|
||||
*/
|
||||
onSurfaceChange?: (surface: Surface) => void;
|
||||
/**
|
||||
* A function called when the initial surface is updated.
|
||||
*
|
||||
* @param surface - The initial surface.
|
||||
* @param value - Completion of the entire transition.
|
||||
* @param relativeValue - Relative completion of the transition.
|
||||
*
|
||||
* @returns `true` if the default changes made by {@link surfaceTransition}
|
||||
* should be prevented
|
||||
*/
|
||||
onInitialSurfaceUpdate?: (
|
||||
surface: Surface,
|
||||
value: number,
|
||||
relativeValue: number,
|
||||
) => true | void;
|
||||
/**
|
||||
* A function called when the target surface is updated.
|
||||
*
|
||||
* @param surface - The target surface.
|
||||
* @param value - Completion of the entire transition.
|
||||
* @param relativeValue - Relative completion of the transition.
|
||||
*
|
||||
* @returns `true` if the default changes made by {@link surfaceTransition}
|
||||
* should be prevented
|
||||
*/
|
||||
onTargetSurfaceUpdate?: (
|
||||
surface: Surface,
|
||||
value: number,
|
||||
relativeValue: number,
|
||||
) => true | void;
|
||||
/**
|
||||
* Duration at which the surfaces are swapped.
|
||||
*/
|
||||
transitionTime?: number;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
decorate(surfaceTransition, threadable());
|
||||
/**
|
||||
* Morph one surface into another.
|
||||
*
|
||||
* @param initial - The initial surface.
|
||||
* @param target - The target surface.
|
||||
* @param config - Additional configuration.
|
||||
*/
|
||||
export function* surfaceTransition(
|
||||
initial: Surface,
|
||||
target: Surface,
|
||||
config: SurfaceTransitionConfig = {},
|
||||
): ThreadGenerator {
|
||||
const from = initial.getMask();
|
||||
|
||||
const transitionTime = config.transitionTime ?? 1 / 3;
|
||||
const to = target.getMask();
|
||||
const toPos = target.getPosition();
|
||||
const fromPos = initial.getPosition();
|
||||
|
||||
const fromDelta = initial.getOriginDelta(target.getOrigin());
|
||||
const fromNewPos = {
|
||||
x: fromPos.x + fromDelta.x,
|
||||
y: fromPos.y + fromDelta.y,
|
||||
};
|
||||
const toDelta = target.getOriginDelta(initial.getOrigin());
|
||||
const toNewPos = {
|
||||
x: toPos.x + toDelta.x,
|
||||
y: toPos.y + toDelta.y,
|
||||
};
|
||||
|
||||
const ratio =
|
||||
(calculateRatio(fromNewPos, toPos) + calculateRatio(from, to)) / 2;
|
||||
|
||||
target.hide();
|
||||
config.onSurfaceChange?.(initial);
|
||||
|
||||
let check = true;
|
||||
let relativeValue = 0;
|
||||
yield* tween(config.duration ?? 0.6, value => {
|
||||
if (value > transitionTime) {
|
||||
relativeValue = clampRemap(transitionTime, 1, 0, 1, value);
|
||||
if (check) {
|
||||
target.show();
|
||||
initial.hide();
|
||||
}
|
||||
target.setMask({
|
||||
...from,
|
||||
...rectArcLerp(from, to, easeInOutQuint(value), config.reverse, ratio),
|
||||
radius: easeInOutCubic(value, from.radius, target.radius()),
|
||||
color: colorLerp(
|
||||
from.color,
|
||||
target.background(),
|
||||
easeInOutQuint(value),
|
||||
),
|
||||
});
|
||||
target.setPosition(
|
||||
rectArcLerp(
|
||||
fromNewPos,
|
||||
toPos,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
);
|
||||
if (!config.onTargetSurfaceUpdate?.(target, value, relativeValue)) {
|
||||
target.getChild().opacity(relativeValue);
|
||||
}
|
||||
|
||||
if (check) {
|
||||
config.onSurfaceChange?.(target);
|
||||
check = false;
|
||||
}
|
||||
} else {
|
||||
relativeValue = clampRemap(0, transitionTime, 1, 0, value);
|
||||
initial.setMask({
|
||||
...from,
|
||||
...rectArcLerp(from, to, easeInOutQuint(value), config.reverse, ratio),
|
||||
radius: easeInOutCubic(value, from.radius, target.radius()),
|
||||
color: colorLerp(
|
||||
from.color,
|
||||
target.background(),
|
||||
easeInOutQuint(value),
|
||||
),
|
||||
});
|
||||
initial.setPosition(
|
||||
rectArcLerp(
|
||||
fromPos,
|
||||
toNewPos,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
);
|
||||
|
||||
if (!config.onInitialSurfaceUpdate?.(target, value, relativeValue)) {
|
||||
initial.getChild().opacity(relativeValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initial.position(fromPos).setMask(null);
|
||||
target.position(toPos).setMask(null);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import type {Node} from 'konva/lib/Node';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginDelta} from '../types';
|
||||
import {useScene} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
interface AlignConfig {
|
||||
origin?: Origin;
|
||||
children: Node | Node[];
|
||||
}
|
||||
|
||||
export function Align(config: AlignConfig) {
|
||||
const origin = config.origin ?? Origin.Middle;
|
||||
if (origin === Origin.Middle) {
|
||||
console.warn(
|
||||
'<Align> with origin set to Middle does nothing and can be omitted',
|
||||
);
|
||||
return <>{config.children}</>;
|
||||
}
|
||||
|
||||
const scene = useScene();
|
||||
const position = getOriginDelta(scene.getSize(), Origin.Middle, origin);
|
||||
|
||||
if (Array.isArray(config.children)) {
|
||||
for (const child of config.children) {
|
||||
child.move(position);
|
||||
}
|
||||
} else {
|
||||
config.children.move(position);
|
||||
}
|
||||
|
||||
return <>{config.children}</>;
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {Sprite} from './Sprite';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {LinearLayout} from './LinearLayout';
|
||||
import {Center} from '@motion-canvas/core/lib/types';
|
||||
import {Surface, SurfaceConfig} from './Surface';
|
||||
import {getStyle, Style} from '../styles';
|
||||
import {ImageDataSource} from '@motion-canvas/core/lib/media';
|
||||
|
||||
export interface AnimationClipConfig extends SurfaceConfig {
|
||||
animation: ImageDataSource[];
|
||||
skin?: ImageDataSource;
|
||||
frame?: number;
|
||||
style?: Partial<Style>;
|
||||
}
|
||||
|
||||
@KonvaNode()
|
||||
export class AnimationClip extends Surface {
|
||||
@getset(null, AnimationClip.prototype.update)
|
||||
public animation: GetSet<AnimationClipConfig['animation'], this>;
|
||||
|
||||
@getset(null, AnimationClip.prototype.update)
|
||||
public skin: GetSet<AnimationClipConfig['skin'], this>;
|
||||
|
||||
@getset(0, AnimationClip.prototype.updateStyle)
|
||||
public frame: GetSet<AnimationClipConfig['frame'], this>;
|
||||
|
||||
@getset(null, AnimationClip.prototype.updateStyle)
|
||||
public style: GetSet<AnimationClipConfig['style'], this>;
|
||||
|
||||
public constructor(config?: AnimationClipConfig) {
|
||||
super({
|
||||
direction: Center.Horizontal,
|
||||
...config,
|
||||
});
|
||||
this.setChild(new LinearLayout({direction: Center.Horizontal, padd: 5}));
|
||||
this.update();
|
||||
}
|
||||
|
||||
private update() {
|
||||
const animation = this.animation();
|
||||
const layout = this.getChild<LinearLayout>();
|
||||
if (!animation || !layout) return;
|
||||
|
||||
const skin = this.skin();
|
||||
for (let i = 0; i < animation.length; i++) {
|
||||
let sprite: Sprite;
|
||||
let surface: Surface;
|
||||
if (layout.children.length > i) {
|
||||
surface = <Surface>layout.children[i];
|
||||
sprite = surface.getChild<Sprite>();
|
||||
sprite.animation(animation);
|
||||
sprite.skin(skin);
|
||||
} else {
|
||||
surface = new Surface({
|
||||
padd: 22,
|
||||
margin: 5,
|
||||
});
|
||||
sprite = new Sprite({
|
||||
animation,
|
||||
skin,
|
||||
width: 96,
|
||||
height: 96,
|
||||
});
|
||||
surface.setChild(sprite);
|
||||
layout.add(surface);
|
||||
}
|
||||
}
|
||||
for (let i = layout.children.length - 1; i >= animation.length; i--) {
|
||||
layout.children[i].destroy();
|
||||
}
|
||||
this.updateStyle();
|
||||
}
|
||||
|
||||
private updateStyle() {
|
||||
const style = getStyle(this);
|
||||
const layout = this.getChild<LinearLayout>();
|
||||
const animation = this.animation();
|
||||
if (!animation || !layout) return;
|
||||
|
||||
const children = this.getChild<LinearLayout>().children;
|
||||
const frame = this.frame() % animation.length;
|
||||
|
||||
this.background(style.background);
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const surface = <Surface>children[i];
|
||||
surface.background(
|
||||
frame === i ? style.backgroundLight : 'rgba(0, 0, 0, 0)',
|
||||
);
|
||||
surface.getChild<Sprite>().frame(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {GetSet, Vector2d} from 'konva/lib/types';
|
||||
import {clamp} from 'three/src/math/MathUtils';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
|
||||
export interface ArrowConfig extends ShapeConfig {
|
||||
radius?: number;
|
||||
points?: number[];
|
||||
startArrow?: boolean;
|
||||
start?: number;
|
||||
endArrow?: boolean;
|
||||
arrowSize?: number;
|
||||
end?: number;
|
||||
}
|
||||
|
||||
abstract class Segment {
|
||||
public abstract draw(
|
||||
context: Context,
|
||||
start: number,
|
||||
end: number,
|
||||
move: boolean,
|
||||
): [Vector2d, Vector2d, Vector2d, Vector2d];
|
||||
|
||||
public abstract get arcLength(): number;
|
||||
}
|
||||
|
||||
class LineSegment extends Segment {
|
||||
private readonly length: number;
|
||||
private readonly vector: Vector2d;
|
||||
private readonly tangent: Vector2d;
|
||||
|
||||
public constructor(private from: Vector2d, private to: Vector2d) {
|
||||
super();
|
||||
this.vector = {
|
||||
x: this.to.x - this.from.x,
|
||||
y: this.to.y - this.from.y,
|
||||
};
|
||||
this.length = Math.sqrt(
|
||||
this.vector.x * this.vector.x + this.vector.y * this.vector.y,
|
||||
);
|
||||
this.tangent = {
|
||||
x: -this.vector.y / this.length,
|
||||
y: this.vector.x / this.length,
|
||||
};
|
||||
}
|
||||
|
||||
public get arcLength(): number {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public draw(
|
||||
context: Context,
|
||||
start = 0,
|
||||
end = 1,
|
||||
move = false,
|
||||
): [Vector2d, Vector2d, Vector2d, Vector2d] {
|
||||
const from = {
|
||||
x: this.from.x + this.vector.x * start,
|
||||
y: this.from.y + this.vector.y * start,
|
||||
};
|
||||
const to = {
|
||||
x: this.from.x + this.vector.x * end,
|
||||
y: this.from.y + this.vector.y * end,
|
||||
};
|
||||
if (move) context.moveTo(from.x, from.y);
|
||||
context.lineTo(to.x, to.y);
|
||||
|
||||
return [
|
||||
from,
|
||||
{
|
||||
x: -this.tangent.x,
|
||||
y: -this.tangent.y,
|
||||
},
|
||||
to,
|
||||
this.tangent,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class CircleSegment extends Segment {
|
||||
private readonly length: number;
|
||||
private readonly delta: number;
|
||||
|
||||
public constructor(
|
||||
private center: Vector2d,
|
||||
private radius: number,
|
||||
private startAngle: number,
|
||||
private deltaAngle: number,
|
||||
private counter: boolean,
|
||||
) {
|
||||
super();
|
||||
this.delta = this.counter ? this.deltaAngle : this.deltaAngle + Math.PI * 2;
|
||||
this.length = Math.abs(this.delta * radius);
|
||||
}
|
||||
|
||||
public get arcLength(): number {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public draw(
|
||||
context: Context,
|
||||
from: number,
|
||||
to: number,
|
||||
): [Vector2d, Vector2d, Vector2d, Vector2d] {
|
||||
const startAngle = this.startAngle + this.delta * from;
|
||||
const endAngle = this.startAngle + this.delta * to;
|
||||
|
||||
context.arc(
|
||||
this.center.x,
|
||||
this.center.y,
|
||||
this.radius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
this.counter,
|
||||
);
|
||||
|
||||
const startTangent = {
|
||||
x: Math.cos(startAngle),
|
||||
y: Math.sin(startAngle),
|
||||
};
|
||||
const endTangent = {
|
||||
x: Math.cos(endAngle),
|
||||
y: Math.sin(endAngle),
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
x: this.center.x + this.radius * startTangent.x,
|
||||
y: this.center.y + this.radius * startTangent.y,
|
||||
},
|
||||
this.counter
|
||||
? {
|
||||
x: -startTangent.x,
|
||||
y: -startTangent.y,
|
||||
}
|
||||
: startTangent,
|
||||
{
|
||||
x: this.center.x + this.radius * endTangent.x,
|
||||
y: this.center.y + this.radius * endTangent.y,
|
||||
},
|
||||
this.counter
|
||||
? endTangent
|
||||
: {
|
||||
x: -endTangent.x,
|
||||
y: -endTangent.y,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@KonvaNode()
|
||||
export class Arrow extends Shape<ArrowConfig> {
|
||||
@getset(8, Node.prototype.markDirty)
|
||||
public radius: GetSet<number, this>;
|
||||
@getset([], Node.prototype.markDirty)
|
||||
public points: GetSet<number[], this>;
|
||||
@getset(0)
|
||||
public start: GetSet<number, this>;
|
||||
@getset(1)
|
||||
public end: GetSet<number, this>;
|
||||
@getset(false)
|
||||
public startArrow: GetSet<boolean, this>;
|
||||
@getset(false)
|
||||
public endArrow: GetSet<boolean, this>;
|
||||
@getset(16)
|
||||
public arrowSize: GetSet<number, this>;
|
||||
|
||||
private segments: Segment[] = [];
|
||||
private arcLength = 0;
|
||||
|
||||
public _sceneFunc(context: Context) {
|
||||
let start = this.start() * this.arcLength;
|
||||
let end = this.end() * this.arcLength;
|
||||
if (start > end) {
|
||||
[start, end] = [end, start];
|
||||
}
|
||||
const offset = start;
|
||||
const distance = end - start;
|
||||
const arrowSize = this.arrowSize();
|
||||
const arrowScale =
|
||||
(distance > arrowSize ? arrowSize : distance < 0 ? 0 : distance) /
|
||||
arrowSize;
|
||||
|
||||
context.beginPath();
|
||||
let length = 0;
|
||||
let firstPoint = null;
|
||||
let firstTangent = null;
|
||||
let lastPoint = null;
|
||||
let lastTangent = null;
|
||||
for (const segment of this.segments) {
|
||||
length += segment.arcLength;
|
||||
const relativeStart =
|
||||
(start - length + segment.arcLength) / segment.arcLength;
|
||||
const relativeEnd =
|
||||
(end - length + segment.arcLength) / segment.arcLength;
|
||||
|
||||
const clampedStart =
|
||||
relativeStart > 1 ? 1 : relativeStart < 0 ? 0 : relativeStart;
|
||||
const clampedEnd =
|
||||
relativeEnd > 1 ? 1 : relativeEnd < 0 ? 0 : relativeEnd;
|
||||
|
||||
if (length < start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [first, fTangent, last, lTangent] = segment.draw(
|
||||
context,
|
||||
clampedStart,
|
||||
clampedEnd,
|
||||
firstPoint === null,
|
||||
);
|
||||
|
||||
if (firstPoint === null) {
|
||||
firstPoint = first;
|
||||
firstTangent = fTangent;
|
||||
}
|
||||
|
||||
lastPoint = last;
|
||||
lastTangent = lTangent;
|
||||
if (length > end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.dashOffset(offset);
|
||||
context.strokeShape(this);
|
||||
context.beginPath();
|
||||
|
||||
if (this.attrs.endArrow && lastPoint && arrowScale > 0.0001) {
|
||||
this.drawArrow(context, lastPoint, lastTangent, arrowScale);
|
||||
}
|
||||
|
||||
if (this.attrs.startArrow && firstPoint && arrowScale > 0.0001) {
|
||||
this.drawArrow(context, firstPoint, firstTangent, arrowScale);
|
||||
}
|
||||
context.closePath();
|
||||
this.fill(this.stroke());
|
||||
context.fillShape(this);
|
||||
}
|
||||
|
||||
public getStart(): number {
|
||||
return clamp(this.attrs.start ?? 0, 0, 1);
|
||||
}
|
||||
|
||||
public getEnd(): number {
|
||||
return clamp(this.attrs.end ?? 1, 0, 1);
|
||||
}
|
||||
|
||||
private drawArrow(
|
||||
context: Context,
|
||||
center: Vector2d,
|
||||
tangent: Vector2d,
|
||||
size: number,
|
||||
) {
|
||||
const arrowSize = this.arrowSize() * size;
|
||||
const offset = this.strokeWidth() / 2;
|
||||
// noinspection JSSuspiciousNameCombination
|
||||
const normal = {
|
||||
x: -tangent.y,
|
||||
y: tangent.x,
|
||||
};
|
||||
|
||||
center.x -= normal.x * offset * size;
|
||||
center.y -= normal.y * offset * size;
|
||||
|
||||
context.moveTo(center.x, center.y);
|
||||
context.lineTo(
|
||||
center.x + (normal.x + tangent.x) * arrowSize,
|
||||
center.y + (normal.y + tangent.y) * arrowSize,
|
||||
);
|
||||
context.lineTo(
|
||||
center.x + (normal.x - tangent.x) * arrowSize,
|
||||
center.y + (normal.y - tangent.y) * arrowSize,
|
||||
);
|
||||
context.lineTo(center.x, center.y);
|
||||
context.closePath();
|
||||
}
|
||||
|
||||
public recalculateLayout() {
|
||||
this.arcLength = 0;
|
||||
this.segments = [];
|
||||
|
||||
const points: number[] = this.points();
|
||||
const radius = this.radius();
|
||||
|
||||
let lastX = points[0];
|
||||
let lastY = points[1];
|
||||
for (let i = 5; i < points.length; i += 2) {
|
||||
const startX = points[i - 5];
|
||||
const startY = points[i - 4];
|
||||
const centerX = points[i - 3];
|
||||
const centerY = points[i - 2];
|
||||
const endX = points[i - 1];
|
||||
const endY = points[i];
|
||||
|
||||
const toStartX = startX - centerX;
|
||||
const toStartY = startY - centerY;
|
||||
const toEndX = endX - centerX;
|
||||
const toEndY = endY - centerY;
|
||||
|
||||
const correctAngle = Math.atan2(
|
||||
toEndY * toStartX - toEndX * toStartY,
|
||||
toEndX * toStartX + toEndY * toStartY,
|
||||
);
|
||||
const startAngle = Math.atan2(-toStartY, toStartX);
|
||||
const endAngle = Math.atan2(-toEndY, toEndX);
|
||||
const angle = startAngle - correctAngle / 2;
|
||||
|
||||
const length = radius / Math.abs(Math.sin(correctAngle / 2));
|
||||
const circleX = length * Math.cos(angle);
|
||||
const circleY = length * -Math.sin(angle);
|
||||
|
||||
const deltaLength = radius / Math.abs(Math.tan(correctAngle / 2));
|
||||
const startDeltaX = deltaLength * Math.cos(startAngle);
|
||||
const startDeltaY = deltaLength * -Math.sin(startAngle);
|
||||
|
||||
const endDeltaX = deltaLength * Math.cos(endAngle);
|
||||
const endDeltaY = deltaLength * -Math.sin(endAngle);
|
||||
|
||||
const start = {x: centerX + startDeltaX, y: centerY + startDeltaY};
|
||||
const center = {x: centerX + circleX, y: centerY + circleY};
|
||||
const centerToStart = {x: start.x - center.x, y: start.y - center.y};
|
||||
const perpendicularAngle = -Math.atan2(-centerToStart.y, centerToStart.x);
|
||||
|
||||
const line = new LineSegment({x: lastX, y: lastY}, start);
|
||||
const circle = new CircleSegment(
|
||||
center,
|
||||
radius,
|
||||
perpendicularAngle,
|
||||
correctAngle - Math.PI,
|
||||
correctAngle > 0,
|
||||
);
|
||||
|
||||
this.segments.push(line);
|
||||
this.segments.push(circle);
|
||||
|
||||
this.arcLength += line.arcLength;
|
||||
this.arcLength += circle.arcLength;
|
||||
|
||||
lastX = centerX + endDeltaX;
|
||||
lastY = centerY + endDeltaY;
|
||||
}
|
||||
|
||||
const line = new LineSegment(
|
||||
{x: lastX, y: lastY},
|
||||
{x: points[points.length - 2], y: points[points.length - 1]},
|
||||
);
|
||||
this.segments.push(line);
|
||||
this.arcLength += line.arcLength;
|
||||
super.recalculateLayout();
|
||||
}
|
||||
|
||||
public getArrowSize(): number {
|
||||
return this.attrs.arrowSize ?? this.strokeWidth() * 2;
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import {LinearLayout, LinearLayoutConfig} from './LinearLayout';
|
||||
import {Rect} from 'konva/lib/shapes/Rect';
|
||||
import {Range, RangeConfig} from './Range';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {parseColor} from 'mix-color';
|
||||
import {Surface} from './Surface';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {Style} from '../styles';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {clampRemap} from '@motion-canvas/core/lib/tweening';
|
||||
import {makeRef} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
export interface ColorPickerConfig extends LinearLayoutConfig {
|
||||
previewColor?: string;
|
||||
dissolve?: number;
|
||||
style?: Style;
|
||||
}
|
||||
|
||||
const colorRangeConfig: RangeConfig = {
|
||||
width: 280,
|
||||
height: 60,
|
||||
range: [0, 255],
|
||||
precision: 0,
|
||||
margin: [10, 40],
|
||||
value: 0,
|
||||
};
|
||||
|
||||
@KonvaNode()
|
||||
export class ColorPicker extends Surface {
|
||||
@getset(null)
|
||||
public style: GetSet<ColorPickerConfig['style'], this>;
|
||||
@getset('#000000', ColorPicker.prototype.updateColor)
|
||||
public previewColor: GetSet<ColorPickerConfig['previewColor'], this>;
|
||||
@getset(0, ColorPicker.prototype.updateDissolve)
|
||||
public dissolve: GetSet<ColorPickerConfig['dissolve'], this>;
|
||||
|
||||
public readonly preview: Rect;
|
||||
public readonly r: Range;
|
||||
public readonly g: Range;
|
||||
public readonly b: Range;
|
||||
public readonly a: Range;
|
||||
|
||||
public parsedColor: ReturnType<typeof parseColor>;
|
||||
|
||||
public constructor(config?: ColorPickerConfig) {
|
||||
super(config);
|
||||
|
||||
this.setChild(
|
||||
<LinearLayout origin={Origin.Top}>
|
||||
<Rect
|
||||
ref={makeRef(this, 'preview')}
|
||||
width={360}
|
||||
height={200}
|
||||
fill={'yellow'}
|
||||
cornerRadius={[8, 8, 0, 0]}
|
||||
/>
|
||||
<Range
|
||||
ref={makeRef(this, 'r')}
|
||||
{...colorRangeConfig}
|
||||
label={'R:'}
|
||||
margin={[40, 40, 10]}
|
||||
/>
|
||||
<Range ref={makeRef(this, 'g')} {...colorRangeConfig} label={'G:'} />
|
||||
<Range ref={makeRef(this, 'b')} {...colorRangeConfig} label={'B:'} />
|
||||
<Range
|
||||
ref={makeRef(this, 'a')}
|
||||
{...colorRangeConfig}
|
||||
label={'A:'}
|
||||
margin={[10, 40, 40]}
|
||||
/>
|
||||
</LinearLayout>,
|
||||
);
|
||||
|
||||
this.updateColor();
|
||||
this.updateDissolve();
|
||||
}
|
||||
|
||||
private updateColor() {
|
||||
if (!this.a) return;
|
||||
|
||||
const style = this.style();
|
||||
const preview = this.previewColor();
|
||||
|
||||
this.style({
|
||||
...style,
|
||||
foreground: preview,
|
||||
});
|
||||
this.parsedColor = parseColor(preview);
|
||||
this.parsedColor.a = Math.round(this.parsedColor.a * 255);
|
||||
|
||||
this.r.value(this.parsedColor.r);
|
||||
this.g.value(this.parsedColor.g);
|
||||
this.b.value(this.parsedColor.b);
|
||||
this.a.value(this.parsedColor.a);
|
||||
this.preview.fill(preview);
|
||||
}
|
||||
|
||||
public setStyle(value: Style): this {
|
||||
this.attrs.style = value;
|
||||
this.background(value.background);
|
||||
return this;
|
||||
}
|
||||
|
||||
private updateDissolve() {
|
||||
if (!this.r) return;
|
||||
|
||||
const opacity = clampRemap(0, 0.5, 1, 0, this.dissolve());
|
||||
this.r.opacity(opacity);
|
||||
this.g.opacity(opacity);
|
||||
this.b.opacity(opacity);
|
||||
this.a.opacity(opacity);
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public recalculateLayout() {
|
||||
if (this.preview) {
|
||||
const rangeWidth = this.preview.width() - 80;
|
||||
this.r.width(rangeWidth);
|
||||
this.g.width(rangeWidth);
|
||||
this.b.width(rangeWidth);
|
||||
this.a.width(rangeWidth);
|
||||
}
|
||||
super.recalculateLayout();
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Center} from '@motion-canvas/core/lib/types';
|
||||
import {KonvaNode} from '../decorators';
|
||||
import {Pin} from './Pin';
|
||||
import {Group} from 'konva/lib/Group';
|
||||
import {ContainerConfig} from 'konva/lib/Container';
|
||||
import {Arrow} from './Arrow';
|
||||
import {map} from '@motion-canvas/core/lib/tweening';
|
||||
import {useKonvaView} from '../scenes';
|
||||
|
||||
export interface ConnectionConfig extends ContainerConfig {
|
||||
start?: Pin;
|
||||
end?: Pin;
|
||||
startTarget?: Node;
|
||||
endTarget?: Node;
|
||||
crossing?: Node;
|
||||
arrow?: Arrow;
|
||||
}
|
||||
|
||||
interface Measurement {
|
||||
direction: -1 | 0 | 1;
|
||||
range: [number, number];
|
||||
rangeOffset: [number, number];
|
||||
from: number;
|
||||
to: number;
|
||||
preferNegative: boolean;
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
if (min > max) [min, max] = [max, min];
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
|
||||
@KonvaNode()
|
||||
export class Connection extends Group {
|
||||
public readonly start: Pin;
|
||||
public readonly end: Pin;
|
||||
public readonly crossing: Node = null;
|
||||
public readonly arrow: Arrow;
|
||||
|
||||
public constructor(config?: ConnectionConfig) {
|
||||
super(config);
|
||||
|
||||
this.start = config?.start ?? new Pin();
|
||||
if (!this.start.getParent()) {
|
||||
this.add(this.start);
|
||||
}
|
||||
|
||||
this.end = config?.end ?? new Pin();
|
||||
if (!this.end.getParent()) {
|
||||
this.add(this.end);
|
||||
}
|
||||
|
||||
if (config?.crossing) {
|
||||
this.crossing = config.crossing;
|
||||
}
|
||||
|
||||
this.arrow = config?.arrow ?? new Arrow();
|
||||
if (!this.arrow.getParent()) {
|
||||
this.add(this.arrow);
|
||||
}
|
||||
|
||||
if (config?.startTarget) {
|
||||
this.start.target(config?.startTarget);
|
||||
}
|
||||
if (config?.endTarget) {
|
||||
this.end.target(config?.endTarget);
|
||||
}
|
||||
}
|
||||
|
||||
private static measurePosition(
|
||||
positionA: number,
|
||||
sizeA: number,
|
||||
positionB: number,
|
||||
sizeB: number,
|
||||
): Measurement {
|
||||
// FIXME use layout margin
|
||||
const padding = 20;
|
||||
const length = 20;
|
||||
const offset = (padding + length) * 2;
|
||||
|
||||
let preferNegative = false;
|
||||
let direction: -1 | 0 | 1;
|
||||
let range: [number, number];
|
||||
let rangeOffset: [number, number];
|
||||
|
||||
if (positionA - sizeA - offset > positionB + sizeB) {
|
||||
direction = -1;
|
||||
range = [positionA - sizeA - padding, positionB + sizeB + padding];
|
||||
rangeOffset = [range[0] - length, range[1] + length];
|
||||
} else if (positionA + sizeA + offset < positionB - sizeB) {
|
||||
direction = 1;
|
||||
range = [positionA + sizeA + padding, positionB - sizeB - padding];
|
||||
rangeOffset = [range[0] + length, range[1] - length];
|
||||
} else {
|
||||
direction = 0;
|
||||
|
||||
if (
|
||||
Math.abs(positionA + sizeA - positionB - sizeB) >
|
||||
Math.abs(positionA - sizeA - positionB + sizeB)
|
||||
) {
|
||||
range = [positionA - sizeA - padding, positionB - sizeB - padding];
|
||||
rangeOffset = [range[0] - length, range[1] - length];
|
||||
} else {
|
||||
preferNegative = true;
|
||||
range = [positionA + sizeA + padding, positionB + sizeB + padding];
|
||||
rangeOffset = [range[0] + length, range[1] + length];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
direction,
|
||||
range,
|
||||
rangeOffset,
|
||||
from: positionA,
|
||||
to: positionB,
|
||||
preferNegative,
|
||||
};
|
||||
}
|
||||
|
||||
private static 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],
|
||||
);
|
||||
}
|
||||
|
||||
if (measurement.direction === 0) {
|
||||
return measurement.preferNegative
|
||||
? Math.max(
|
||||
measurement.rangeOffset[0],
|
||||
measurement.rangeOffset[1],
|
||||
clampedCrossing,
|
||||
)
|
||||
: Math.min(
|
||||
measurement.rangeOffset[0],
|
||||
measurement.rangeOffset[1],
|
||||
clampedCrossing,
|
||||
);
|
||||
}
|
||||
|
||||
return fractionX
|
||||
? map(measurement.rangeOffset[0], measurement.rangeOffset[1], crossing)
|
||||
: clampedCrossing;
|
||||
}
|
||||
|
||||
public isDirty(): boolean {
|
||||
return (
|
||||
this.attrs.dirty ||
|
||||
this.start.wasDirty() ||
|
||||
this.end.wasDirty() ||
|
||||
this.crossing?.wasDirty()
|
||||
);
|
||||
}
|
||||
|
||||
public updateLayout() {
|
||||
this.start.updateLayout();
|
||||
this.end.updateLayout();
|
||||
|
||||
this.attrs.wasDirty = false;
|
||||
if (this.isDirty()) {
|
||||
this.recalculateLayout();
|
||||
this.attrs.dirty = false;
|
||||
this.attrs.wasDirty = true;
|
||||
}
|
||||
|
||||
this.arrow.updateLayout();
|
||||
}
|
||||
|
||||
public recalculateLayout() {
|
||||
if (!this.start || !this.end || !this.arrow) {
|
||||
this.arrow?.points([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const view = useKonvaView();
|
||||
const crossing = this.crossing
|
||||
? this.crossing.getAbsolutePosition(view)
|
||||
: {x: 0.5, y: 0.5};
|
||||
|
||||
const fromRect = this.start.getClientRect({relativeTo: view});
|
||||
fromRect.width /= 2;
|
||||
fromRect.height /= 2;
|
||||
fromRect.x += fromRect.width;
|
||||
fromRect.y += fromRect.height;
|
||||
const toRect = this.end.getClientRect({relativeTo: view});
|
||||
|
||||
toRect.width /= 2;
|
||||
toRect.height /= 2;
|
||||
toRect.x += toRect.width;
|
||||
toRect.y += toRect.height;
|
||||
|
||||
const points: number[] = [];
|
||||
const horizontal = Connection.measurePosition(
|
||||
fromRect.x,
|
||||
fromRect.width,
|
||||
toRect.x,
|
||||
toRect.width,
|
||||
);
|
||||
const vertical = Connection.measurePosition(
|
||||
fromRect.y,
|
||||
fromRect.height,
|
||||
toRect.y,
|
||||
toRect.height,
|
||||
);
|
||||
|
||||
if (this.start.direction() === Center.Vertical) {
|
||||
points.push(fromRect.x, vertical.range[0]);
|
||||
} else {
|
||||
points.push(horizontal.range[0], fromRect.y);
|
||||
}
|
||||
|
||||
let endDirection = this.end.direction();
|
||||
if (this.start.direction() !== endDirection) {
|
||||
if (endDirection === Center.Vertical && vertical.direction === 0) {
|
||||
endDirection = this.start.direction();
|
||||
} else if (
|
||||
endDirection === Center.Horizontal &&
|
||||
horizontal.direction === 0
|
||||
) {
|
||||
endDirection = this.start.direction();
|
||||
}
|
||||
}
|
||||
|
||||
let distance = 100;
|
||||
if (this.start.direction() === endDirection) {
|
||||
if (endDirection === Center.Vertical) {
|
||||
distance = Math.abs(toRect.x - fromRect.x);
|
||||
if (distance >= 1) {
|
||||
const y = Connection.calculateCrossing(crossing.y, vertical);
|
||||
points.push(fromRect.x, y);
|
||||
points.push(toRect.x, y);
|
||||
}
|
||||
} else {
|
||||
distance = Math.abs(toRect.y - fromRect.y);
|
||||
if (distance >= 1) {
|
||||
const x = Connection.calculateCrossing(crossing.x, horizontal);
|
||||
points.push(x, fromRect.y);
|
||||
points.push(x, toRect.y);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (endDirection === Center.Vertical) {
|
||||
points.push(toRect.x, fromRect.y);
|
||||
} else {
|
||||
points.push(fromRect.x, toRect.y);
|
||||
}
|
||||
}
|
||||
|
||||
if (endDirection === Center.Vertical) {
|
||||
points.push(toRect.x, vertical.range[1]);
|
||||
} else {
|
||||
points.push(horizontal.range[1], toRect.y);
|
||||
}
|
||||
|
||||
this.arrow.radius(Math.min(8, distance / 2));
|
||||
this.arrow.points(points);
|
||||
super.recalculateLayout();
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {CanvasHelper} from '../helpers';
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
|
||||
export interface GridConfig extends ShapeConfig {
|
||||
gridSize?: number;
|
||||
subdivision?: boolean;
|
||||
checker?: boolean;
|
||||
}
|
||||
|
||||
@KonvaNode()
|
||||
export class Grid extends Shape {
|
||||
@getset(16, Grid.prototype.recalculate)
|
||||
public gridSize: GetSet<number, this>;
|
||||
@getset(false)
|
||||
public checker: GetSet<GridConfig['checker'], this>;
|
||||
|
||||
@getset(false)
|
||||
public subdivision: GetSet<GridConfig['subdivision'], this>;
|
||||
|
||||
private path: Path2D;
|
||||
private checkerPath: Path2D;
|
||||
|
||||
public constructor(config?: GridConfig) {
|
||||
super(config);
|
||||
this._strokeFunc = context => {
|
||||
if (!this.path) this.recalculate();
|
||||
context.stroke(this.path);
|
||||
|
||||
if (this.subdivision()) {
|
||||
const offset = this.gridSize() / 2;
|
||||
const dash = offset / 8;
|
||||
context.setLineDash([
|
||||
0,
|
||||
dash / 2,
|
||||
dash,
|
||||
dash,
|
||||
dash,
|
||||
dash,
|
||||
dash,
|
||||
dash,
|
||||
dash / 2,
|
||||
]);
|
||||
context.translate(offset, offset);
|
||||
context.stroke(this.path);
|
||||
}
|
||||
};
|
||||
this._fillFunc = context => {
|
||||
if (!this.checkerPath) this.recalculate();
|
||||
const size = this.size();
|
||||
context._context.clip(
|
||||
CanvasHelper.roundRectPath(
|
||||
new Path2D(),
|
||||
size.width / -2,
|
||||
size.height / -2,
|
||||
size.width,
|
||||
size.height,
|
||||
8,
|
||||
),
|
||||
);
|
||||
context.fill(this.checkerPath);
|
||||
};
|
||||
}
|
||||
|
||||
private recalculate() {
|
||||
this.path = new Path2D();
|
||||
this.checkerPath = new Path2D();
|
||||
|
||||
let gridSize = this.gridSize();
|
||||
if (gridSize < 1) {
|
||||
console.warn('Too small grid size: ', gridSize);
|
||||
gridSize = 1;
|
||||
}
|
||||
const size = this.getSize();
|
||||
size.width = size.width / 2 + gridSize;
|
||||
size.height = size.height / 2 + gridSize;
|
||||
|
||||
let i = 0;
|
||||
for (let x = -size.width; x <= size.width; x += gridSize) {
|
||||
this.path.moveTo(x, -size.height);
|
||||
this.path.lineTo(x, size.height);
|
||||
|
||||
for (
|
||||
let y = -size.height + (i % 2 ? 0 : gridSize);
|
||||
y <= size.height;
|
||||
y += gridSize * 2
|
||||
) {
|
||||
this.checkerPath.rect(x, y, gridSize, gridSize);
|
||||
this.checkerPath.rect(x, y, gridSize, gridSize);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
for (let y = -size.height; y <= size.height; y += gridSize) {
|
||||
this.path.moveTo(-size.width, y);
|
||||
this.path.lineTo(size.width, y);
|
||||
}
|
||||
}
|
||||
|
||||
public _sceneFunc(context: Context) {
|
||||
if (this.checker()) {
|
||||
context.fillShape(this);
|
||||
} else {
|
||||
context.strokeShape(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import {KonvaNode} from '../decorators';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
|
||||
const FILL = [
|
||||
new Path2D(
|
||||
'M16.56,8.94L7.62,0L6.21,1.41l2.38,2.38L3.44,8.94c-0.59,0.59-0.59,1.54,0,2.12l5.5,5.5C9.23,16.85,9.62,17,10,17 s0.77-0.15,1.06-0.44l5.5-5.5C17.15,10.48,17.15,9.53,16.56,8.94z M5.21,10L10,5.21L14.79,10H5.21z M19,11.5c0,0-2,2.17-2,3.5 c0,1.1,0.9,2,2,2s2-0.9,2-2C21,13.67,19,11.5,19,11.5z M2',
|
||||
),
|
||||
];
|
||||
|
||||
const BRUSH = [
|
||||
new Path2D(
|
||||
'M7 14c-1.66 0-3 1.34-3 3 0 1.31-1.16 2-2 2 .92 1.22 2.49 2 4 2 2.21 0 4-1.79 4-4 0-1.66-1.34-3-3-3zm13.71-9.37l-1.34-1.34c-.39-.39-1.02-.39-1.41 0L9 12.25 11.75 15l8.96-8.96c.39-.39.39-1.02 0-1.41z',
|
||||
),
|
||||
];
|
||||
|
||||
const UNITY = [
|
||||
new Path2D(
|
||||
'M 46.523438 17.292969 L 61.820313 26.125 C 62.375 26.429688 62.386719 27.296875 61.820313 27.605469 L 43.636719 38.101563 C 43.089844 38.417969 42.4375 38.394531 41.921875 38.101563 L 23.742188 27.605469 C 23.1875 27.300781 23.171875 26.429688 23.742188 26.121094 L 39.039063 17.292969 L 39.039063 0 L 0 22.539063 L 0 67.617188 L 0 67.410156 L 0 67.617188 L 14.972656 58.972656 L 14.972656 41.308594 C 14.964844 40.675781 15.707031 40.230469 16.257813 40.570313 L 34.4375 51.070313 C 34.988281 51.386719 35.292969 51.960938 35.292969 52.554688 L 35.292969 73.546875 C 35.308594 74.175781 34.566406 74.625 34.019531 74.289063 L 18.71875 65.457031 L 3.742188 74.101563 L 42.78125 96.640625 L 81.820313 74.101563 L 66.84375 65.457031 L 51.550781 74.289063 C 51.007813 74.613281 50.25 74.191406 50.269531 73.546875 L 50.269531 52.550781 C 50.269531 51.917969 50.613281 51.363281 51.125 51.066406 L 69.304688 40.570313 C 69.847656 40.242188 70.609375 40.664063 70.589844 41.3125 L 70.589844 58.972656 L 85.5625 67.617188 L 85.5625 22.539063 L 46.523438 0 L 46.523438 17.292969 ',
|
||||
),
|
||||
];
|
||||
|
||||
const OBJECT = [
|
||||
new Path2D(
|
||||
'M49.71,20.94c-.36-1.31-1.05-2.51-2-3.48-.5-.51-1.07-.95-1.71-1.32l-12-6.92c-1.24-.72-2.62-1.08-4-1.08s-2.76,.36-4,1.08l-12,6.92c-.64,.37-1.21,.81-1.71,1.32-.95,.97-1.64,2.17-2,3.48-.19,.68-.29,1.4-.29,2.13v13.86c0,2.86,1.52,5.5,4,6.93l12,6.92c.64,.37,1.31,.64,2,.82,.65,.17,1.33,.26,2,.26s1.35-.09,2-.26c.69-.18,1.36-.45,2-.82l12-6.92c2.48-1.43,4-4.07,4-6.93v-13.86c0-.73-.1-1.45-.29-2.13ZM28,12.68c.61-.35,1.3-.54,2-.54s1.39,.19,2,.54l12,6.93-14,8.08-14-8.08,12-6.93Zm-12,27.71c-1.23-.71-2-2.04-2-3.46v-13.85l14,8.07v16.17l-12-6.93Zm30-3.46c0,1.42-.77,2.75-2,3.46l-12,6.93V31.15l14-8.08v13.86Z',
|
||||
),
|
||||
];
|
||||
|
||||
export enum IconType {
|
||||
Fill,
|
||||
Brush,
|
||||
Unity,
|
||||
Object,
|
||||
}
|
||||
|
||||
interface IconConfig extends ShapeConfig {
|
||||
type?: IconType;
|
||||
}
|
||||
|
||||
@KonvaNode({centroid: false})
|
||||
export class Icon extends Shape {
|
||||
private readonly paths: Path2D[];
|
||||
|
||||
public constructor(config?: IconConfig) {
|
||||
super(config);
|
||||
|
||||
switch (config?.type ?? IconType.Fill) {
|
||||
case IconType.Brush:
|
||||
this.paths = BRUSH;
|
||||
break;
|
||||
case IconType.Fill:
|
||||
this.paths = FILL;
|
||||
break;
|
||||
case IconType.Unity:
|
||||
this.paths = UNITY;
|
||||
break;
|
||||
case IconType.Object:
|
||||
this.paths = OBJECT;
|
||||
break;
|
||||
}
|
||||
|
||||
this._fillFunc = context => {
|
||||
for (const path of this.paths) {
|
||||
context.fill(path);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public _sceneFunc(context: Context) {
|
||||
context.fillShape(this);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import {Size} from '../types';
|
||||
import {Group} from 'konva/lib/Group';
|
||||
|
||||
export class LayeredLayout extends Group {
|
||||
public getLayoutSize(): Size {
|
||||
return this.getChildrenRect({skipTransform: true});
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import {Text, TextConfig} from 'konva/lib/shapes/Text';
|
||||
import {GetSet, IRect, Vector2d} from 'konva/lib/types';
|
||||
import {ShapeGetClientRectConfig} from 'konva/lib/Shape';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginOffset} from '../types';
|
||||
import {Size, Spacing} from '../types';
|
||||
import {
|
||||
Animator,
|
||||
tween,
|
||||
textLerp,
|
||||
TimingFunction,
|
||||
} from '@motion-canvas/core/lib/tweening';
|
||||
import {threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {getset} from '../decorators';
|
||||
|
||||
export interface LayoutTextConfig extends TextConfig {
|
||||
minWidth?: number;
|
||||
}
|
||||
|
||||
export class LayoutText extends Text {
|
||||
@getset('', undefined, LayoutText.prototype.textTween)
|
||||
public text: GetSet<LayoutTextConfig['text'], this>;
|
||||
|
||||
private overrideWidth: number | null = null;
|
||||
private isConstructed = false;
|
||||
|
||||
public constructor(config?: LayoutTextConfig) {
|
||||
super({
|
||||
padd: new Spacing(30),
|
||||
align: 'center',
|
||||
verticalAlign: 'middle',
|
||||
fontSize: 28,
|
||||
fontFamily: 'JetBrains Mono',
|
||||
fill: 'rgba(30, 30, 30, 0.87)',
|
||||
...config,
|
||||
});
|
||||
this.isConstructed = true;
|
||||
}
|
||||
|
||||
public getLayoutSize(custom?: LayoutTextConfig): Size {
|
||||
const padding = this.getPadd();
|
||||
let size: Size;
|
||||
if (custom?.text) {
|
||||
const text = this.text();
|
||||
this.text(custom.text);
|
||||
size = {
|
||||
width: this.textWidth,
|
||||
height: this.textHeight,
|
||||
};
|
||||
this.text(text);
|
||||
} else {
|
||||
size = {
|
||||
width: this.textWidth,
|
||||
height: this.textHeight,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
width: Math.max(
|
||||
custom?.minWidth ?? this.getMinWidth(),
|
||||
this.overrideWidth ?? size.width + padding.x,
|
||||
),
|
||||
height: this.height() + padding.y,
|
||||
};
|
||||
}
|
||||
|
||||
public setMinWidth(value: number): this {
|
||||
this.attrs.minWidth = value;
|
||||
this.markDirty();
|
||||
return this;
|
||||
}
|
||||
|
||||
public getMinWidth(): number {
|
||||
return this.attrs.minWidth ?? 0;
|
||||
}
|
||||
|
||||
public setText(text: string): this {
|
||||
super.setText(text);
|
||||
this.markDirty();
|
||||
return this;
|
||||
}
|
||||
|
||||
public getOriginOffset(custom?: LayoutTextConfig): Vector2d {
|
||||
const padding = this.getPadd();
|
||||
const size = this.getLayoutSize({minWidth: 0, ...custom});
|
||||
const offset = getOriginOffset(size, custom?.origin ?? this.getOrigin());
|
||||
offset.x += size.width / 2 - padding.left;
|
||||
offset.y += size.height / 2 - padding.top;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public get animate(): Animator<string, this> {
|
||||
return new Animator<string, this>(this, 'text', this.textTween);
|
||||
}
|
||||
|
||||
@threadable()
|
||||
private *textTween(
|
||||
fromText: string,
|
||||
text: string,
|
||||
time: number,
|
||||
timingFunction: TimingFunction,
|
||||
onEnd: Callback,
|
||||
) {
|
||||
const fromWidth = this.getLayoutSize({text: fromText, minWidth: 0}).width;
|
||||
const toWidth = this.getLayoutSize({text, minWidth: 0}).width;
|
||||
|
||||
this.overrideWidth = fromWidth;
|
||||
yield* tween(time, value => {
|
||||
this.overrideWidth = timingFunction(value, fromWidth, toWidth);
|
||||
this.setText(textLerp(fromText, text, timingFunction(value)));
|
||||
});
|
||||
this.overrideWidth = null;
|
||||
|
||||
this.setText(text);
|
||||
onEnd();
|
||||
}
|
||||
|
||||
public getClientRect(config?: ShapeGetClientRectConfig): IRect {
|
||||
const realSize = this.getLayoutSize({minWidth: 0});
|
||||
const size = this.getLayoutSize();
|
||||
const offset = this.getOriginOffset({origin: Origin.TopLeft});
|
||||
|
||||
const rect: IRect = {
|
||||
x: offset.x + (realSize.width - size.width) / 2,
|
||||
y: offset.y,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
};
|
||||
|
||||
if (!config?.skipTransform) {
|
||||
return this._transformedRect(rect, config?.relativeTo);
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import {Group} from 'konva/lib/Group';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {Shape} from 'konva/lib/Shape';
|
||||
import {Center, Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginOffset} from '../types';
|
||||
import {Size} from '../types';
|
||||
import {ContainerConfig} from 'konva/lib/Container';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
|
||||
export interface LinearLayoutConfig extends ContainerConfig {
|
||||
direction?: Center;
|
||||
}
|
||||
|
||||
@KonvaNode()
|
||||
export class LinearLayout extends Group {
|
||||
@getset(Center.Vertical, Node.prototype.markDirty)
|
||||
public direction: GetSet<Center, this>;
|
||||
|
||||
private contentSize: Size;
|
||||
|
||||
public constructor(config?: LinearLayoutConfig) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
public getLayoutSize(): Size {
|
||||
return this.getPadd().expand({
|
||||
width: this.contentSize?.width ?? 0,
|
||||
height: this.contentSize?.height ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
//TODO Recalculate upon removing children as well.
|
||||
public add(...children: (Group | Shape)[]): this {
|
||||
super.add(...children);
|
||||
this.recalculateLayout();
|
||||
return this;
|
||||
}
|
||||
|
||||
public recalculateLayout() {
|
||||
if (!this.children) return;
|
||||
|
||||
const direction = this.direction();
|
||||
this.contentSize = {width: 0, height: 0};
|
||||
|
||||
for (const child of this.children) {
|
||||
const size = child.getLayoutSize();
|
||||
const margin = child.getMargin();
|
||||
const scale = child.getAbsoluteScale(this);
|
||||
|
||||
const boxSize = {
|
||||
x: (size.width + margin.x) * scale.x,
|
||||
y: (size.height + margin.y) * scale.y,
|
||||
};
|
||||
|
||||
if (direction === Center.Vertical) {
|
||||
this.contentSize.width = Math.max(this.contentSize.width, boxSize.x);
|
||||
this.contentSize.height += boxSize.y;
|
||||
} else {
|
||||
this.contentSize.height = Math.max(this.contentSize.height, boxSize.y);
|
||||
this.contentSize.width += boxSize.x;
|
||||
}
|
||||
}
|
||||
|
||||
let length =
|
||||
direction === Center.Vertical
|
||||
? this.contentSize.height / -2
|
||||
: this.contentSize.width / -2;
|
||||
|
||||
for (const child of this.children) {
|
||||
const size = child.getLayoutSize();
|
||||
const margin = child.getMargin();
|
||||
const scale = child.getAbsoluteScale(this);
|
||||
const offset = child.getOriginDelta(Origin.TopLeft);
|
||||
const parentOffset = getOriginOffset(
|
||||
margin.shrink(this.contentSize),
|
||||
child.origin(),
|
||||
);
|
||||
if (direction === Center.Vertical) {
|
||||
child.position({
|
||||
x: parentOffset.x,
|
||||
y: length + (-offset.y + margin.top) * scale.y,
|
||||
});
|
||||
length += (size.height + margin.y) * scale.y;
|
||||
} else {
|
||||
child.position({
|
||||
x: length + (-offset.x + margin.left) * scale.x,
|
||||
y: parentOffset.y,
|
||||
});
|
||||
length += (size.width + margin.x) * scale.x;
|
||||
}
|
||||
}
|
||||
super.recalculateLayout();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import {Group} from 'konva/lib/Group';
|
||||
import {Container, ContainerConfig} from 'konva/lib/Container';
|
||||
import {Center, flipOrigin, Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginDelta} from '../types';
|
||||
import {GetSet, IRect} from 'konva/lib/types';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {useKonvaView} from '../scenes';
|
||||
|
||||
export interface PinConfig extends ContainerConfig {
|
||||
target?: Node;
|
||||
attach?: Node;
|
||||
direction?: Center;
|
||||
}
|
||||
|
||||
@KonvaNode()
|
||||
export class Pin extends Group {
|
||||
@getset(null, Node.prototype.markDirty)
|
||||
public target: GetSet<PinConfig['target'], this>;
|
||||
@getset(null, Node.prototype.markDirty)
|
||||
public attach: GetSet<PinConfig['attach'], this>;
|
||||
@getset(null, Pin.prototype.markDirty)
|
||||
public direction: GetSet<PinConfig['direction'], this>;
|
||||
|
||||
public constructor(config?: PinConfig) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
public getDirection(): Center {
|
||||
return (
|
||||
this.attrs.direction ??
|
||||
(this.attach() ? Center.Vertical : Center.Horizontal)
|
||||
);
|
||||
}
|
||||
|
||||
public isDirty(): boolean {
|
||||
return super.isDirty() || this.target()?.wasDirty();
|
||||
}
|
||||
|
||||
public recalculateLayout() {
|
||||
const attach = this.attach();
|
||||
if (attach) {
|
||||
const attachDirection = flipOrigin(attach.getOrigin(), this.direction());
|
||||
const rect = this.getClientRect({
|
||||
relativeTo: useKonvaView(),
|
||||
});
|
||||
const offset = getOriginDelta(rect, Origin.TopLeft, attachDirection);
|
||||
attach.position({
|
||||
x: rect.x + offset.x,
|
||||
y: rect.y + offset.y,
|
||||
});
|
||||
}
|
||||
super.recalculateLayout();
|
||||
}
|
||||
|
||||
public getClientRect(config?: {
|
||||
skipTransform?: boolean;
|
||||
skipShadow?: boolean;
|
||||
skipStroke?: boolean;
|
||||
relativeTo?: Container;
|
||||
}): IRect {
|
||||
return this.target()?.getClientRect(config) ?? super.getClientRect(config);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import {Reference, useRef} from '@motion-canvas/core/lib/utils';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {LayoutText, LayoutTextConfig} from './LayoutText';
|
||||
import {Pin} from './Pin';
|
||||
import {Center, Origin} from '@motion-canvas/core/lib/types';
|
||||
|
||||
interface PinnedLabelConfig extends LayoutTextConfig {
|
||||
children: string;
|
||||
target: Reference<Node>;
|
||||
ref?: Reference<LayoutText>;
|
||||
direction?: Center;
|
||||
}
|
||||
|
||||
export function PinnedLabel(config: PinnedLabelConfig) {
|
||||
const {children, target, ref, direction, ...rest} = config;
|
||||
const reference = ref ?? useRef<LayoutText>();
|
||||
return (
|
||||
<>
|
||||
<LayoutText
|
||||
ref={reference}
|
||||
text={children}
|
||||
padd={[30, 0]}
|
||||
fill={'rgba(255, 255, 255, 0.54'}
|
||||
origin={Origin.BottomLeft}
|
||||
{...rest}
|
||||
/>
|
||||
<Pin
|
||||
target={target.value}
|
||||
attach={reference.value}
|
||||
direction={direction ?? Center.Vertical}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {CanvasHelper} from '../helpers';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {getFontColor, getStyle, Style} from '../styles';
|
||||
import {remap} from '@motion-canvas/core/lib/tweening';
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
|
||||
export interface RangeConfig extends ShapeConfig {
|
||||
range?: [number, number];
|
||||
value?: number;
|
||||
precision?: number;
|
||||
label?: string;
|
||||
style?: Style;
|
||||
}
|
||||
|
||||
@KonvaNode()
|
||||
export class Range extends Shape {
|
||||
@getset(null)
|
||||
public style: GetSet<RangeConfig['style'], this>;
|
||||
@getset([0, 1])
|
||||
public range: GetSet<RangeConfig['range'], this>;
|
||||
@getset(0.5)
|
||||
public value: GetSet<RangeConfig['value'], this>;
|
||||
@getset(0)
|
||||
public precision: GetSet<RangeConfig['precision'], this>;
|
||||
@getset(null)
|
||||
public label: GetSet<RangeConfig['label'], this>;
|
||||
|
||||
public constructor(config?: RangeConfig) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
public _sceneFunc(context: Context) {
|
||||
const style = getStyle(this);
|
||||
|
||||
const ctx = context._context;
|
||||
const size = this.getSize();
|
||||
const value = this.value();
|
||||
const range = this.range();
|
||||
const precision = this.precision();
|
||||
const label = this.label();
|
||||
const text = value.toLocaleString('en-EN', {
|
||||
minimumFractionDigits: precision,
|
||||
maximumFractionDigits: precision,
|
||||
});
|
||||
const minText = range[0].toLocaleString('en-EN', {
|
||||
minimumFractionDigits: precision,
|
||||
maximumFractionDigits: precision,
|
||||
});
|
||||
|
||||
const position = {
|
||||
x: size.width / -2,
|
||||
y: size.height / -2,
|
||||
};
|
||||
|
||||
if (label) {
|
||||
position.x += 60;
|
||||
size.width -= 60;
|
||||
}
|
||||
|
||||
ctx.fillStyle = style.backgroundLight;
|
||||
CanvasHelper.roundRect(
|
||||
ctx,
|
||||
position.x,
|
||||
position.y,
|
||||
size.width,
|
||||
size.height,
|
||||
8,
|
||||
);
|
||||
ctx.fill();
|
||||
|
||||
ctx.font = style.bodyFont;
|
||||
const textSize = ctx.measureText(minText);
|
||||
|
||||
const width = remap(
|
||||
range[0],
|
||||
range[1],
|
||||
textSize.width + 40,
|
||||
size.width,
|
||||
value,
|
||||
);
|
||||
ctx.fillStyle = style.foreground;
|
||||
CanvasHelper.roundRect(ctx, position.x, position.y, width, size.height, 8);
|
||||
ctx.fill();
|
||||
|
||||
ctx.font = style.bodyFont;
|
||||
ctx.fillStyle = getFontColor(style.foreground);
|
||||
ctx.fillText(text, position.x + 20, 10);
|
||||
|
||||
if (label) {
|
||||
ctx.fillStyle = getFontColor(style.backgroundLight);
|
||||
ctx.fillText(label, position.x - 60, 10);
|
||||
}
|
||||
}
|
||||
|
||||
public _hitFunc(context: Context) {
|
||||
const size = this.getSize();
|
||||
const position = {
|
||||
x: size.width / -2,
|
||||
y: size.height / -2,
|
||||
};
|
||||
|
||||
context.beginPath();
|
||||
context.rect(position.x, position.y, size.width, size.height);
|
||||
context.closePath();
|
||||
context.fillShape(this);
|
||||
}
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {GetSet, Vector2d} from 'konva/lib/types';
|
||||
import {waitFor} from '@motion-canvas/core/lib/flow';
|
||||
import {threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {cached, KonvaNode, getset} from '../decorators';
|
||||
import {GeneratorHelper} from '@motion-canvas/core/lib/helpers';
|
||||
import {TimingFunction, map, tween} from '@motion-canvas/core/lib/tweening';
|
||||
import {cancel, ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import {parseColor} from 'mix-color';
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
import {getImageData, ImageDataSource} from '@motion-canvas/core/lib/media';
|
||||
|
||||
export const SPRITE_CHANGE_EVENT = 'spriteChange';
|
||||
|
||||
export interface SpriteConfig extends ShapeConfig {
|
||||
animation: ImageDataSource[];
|
||||
skin?: ImageDataSource;
|
||||
mask?: ImageDataSource;
|
||||
playing?: boolean;
|
||||
fps?: number;
|
||||
maskBlend?: number;
|
||||
frame?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for animated sprites.
|
||||
*
|
||||
* @remarks
|
||||
* Allows to use custom alpha masks and skins.
|
||||
*/
|
||||
@KonvaNode()
|
||||
export class Sprite extends Shape {
|
||||
@getset(null, Sprite.prototype.updateAnimation)
|
||||
public animation: GetSet<SpriteConfig['animation'], this>;
|
||||
@getset(0, Sprite.prototype.updateAnimation)
|
||||
public frame: GetSet<SpriteConfig['frame'], this>;
|
||||
@getset(false)
|
||||
public playing: GetSet<SpriteConfig['playing'], this>;
|
||||
@getset(10)
|
||||
public fps: GetSet<SpriteConfig['fps'], this>;
|
||||
|
||||
/**
|
||||
* An image used for 2D UV mapping.
|
||||
*/
|
||||
@getset(null, Sprite.prototype.updateSkin)
|
||||
public skin: GetSet<SpriteConfig['skin'], this>;
|
||||
|
||||
/**
|
||||
* An image that defines which parts of the sprite should be visible.
|
||||
*
|
||||
* @remarks
|
||||
* The red channel is used for sampling.
|
||||
*/
|
||||
@getset(null, Sprite.prototype.updateMask, Sprite.prototype.maskTween)
|
||||
public mask: GetSet<SpriteConfig['mask'], this>;
|
||||
|
||||
/**
|
||||
* The blend between the original opacity and the opacity calculated using the
|
||||
* mask.
|
||||
*/
|
||||
@getset(0, Sprite.prototype.updateFrame)
|
||||
public maskBlend: GetSet<SpriteConfig['maskBlend'], this>;
|
||||
|
||||
private task: ThreadGenerator | null = null;
|
||||
private baseMask: ImageDataSource;
|
||||
private baseMaskBlend = 0;
|
||||
private synced = false;
|
||||
|
||||
private readonly computeCanvas: HTMLCanvasElement;
|
||||
private readonly context: CanvasRenderingContext2D;
|
||||
|
||||
public constructor(config?: SpriteConfig) {
|
||||
super(config);
|
||||
this.computeCanvas = document.createElement('canvas');
|
||||
this.context = this.computeCanvas.getContext('2d');
|
||||
}
|
||||
|
||||
protected _sceneFunc(context: Context) {
|
||||
const size = this.getSize();
|
||||
let source: ImageDataSource = this.computeCanvas;
|
||||
if (this.requiresProcessing()) {
|
||||
// Make sure the compute canvas is up-to-date
|
||||
this.getFrameData();
|
||||
} else {
|
||||
const animation = this.animation();
|
||||
const frame = this.frame();
|
||||
source = animation[frame % animation.length];
|
||||
}
|
||||
|
||||
context.save();
|
||||
context._context.imageSmoothingEnabled = false;
|
||||
context.drawImage(
|
||||
source,
|
||||
0,
|
||||
0,
|
||||
source.width,
|
||||
source.height,
|
||||
size.width / -2,
|
||||
size.height / -2,
|
||||
size.width,
|
||||
size.height,
|
||||
);
|
||||
context.restore();
|
||||
}
|
||||
|
||||
private updateSkin() {
|
||||
this._clearCache(this.getSkinData);
|
||||
this.updateFrame();
|
||||
}
|
||||
|
||||
private updateAnimation() {
|
||||
this._clearCache(this.getRawFrameData);
|
||||
this.updateFrame();
|
||||
}
|
||||
|
||||
private updateMask() {
|
||||
this._clearCache(this.getMaskData);
|
||||
this.updateFrame();
|
||||
}
|
||||
|
||||
private updateFrame() {
|
||||
this._clearCache(this.getFrameData);
|
||||
}
|
||||
|
||||
@cached('Sprite.skinData')
|
||||
private getSkinData() {
|
||||
const skin = this.skin();
|
||||
return skin ? getImageData(skin) : null;
|
||||
}
|
||||
|
||||
@cached('Sprite.maskData')
|
||||
private getMaskData() {
|
||||
const mask = this.mask();
|
||||
return mask ? getImageData(mask) : null;
|
||||
}
|
||||
|
||||
@cached('Sprite.baseMaskData')
|
||||
private getBaseMaskData() {
|
||||
return this.baseMask ? getImageData(this.baseMask) : null;
|
||||
}
|
||||
|
||||
@cached('Sprite.rawFrameData')
|
||||
private getRawFrameData() {
|
||||
const animation = this.animation();
|
||||
const frameId = this.frame() % animation.length;
|
||||
return getImageData(animation[frameId]);
|
||||
}
|
||||
|
||||
@cached('Sprite.frameData')
|
||||
private getFrameData() {
|
||||
if (!this.requiresProcessing()) {
|
||||
return this.getRawFrameData();
|
||||
}
|
||||
|
||||
const skin = this.skin();
|
||||
const mask = this.mask();
|
||||
const blend = this.maskBlend();
|
||||
const rawFrameData = this.getRawFrameData();
|
||||
const frameData = this.context.createImageData(rawFrameData);
|
||||
|
||||
this.computeCanvas.width = rawFrameData.width;
|
||||
this.computeCanvas.height = rawFrameData.height;
|
||||
|
||||
if (skin) {
|
||||
const skinData = this.getSkinData();
|
||||
for (let y = 0; y < rawFrameData.height; y++) {
|
||||
for (let x = 0; x < rawFrameData.width; x++) {
|
||||
const id = this.positionToId({x, y});
|
||||
const skinX = rawFrameData.data[id];
|
||||
const skinY = rawFrameData.data[id + 1];
|
||||
const skinId = ((skin.height - 1 - skinY) * skin.width + skinX) * 4;
|
||||
|
||||
frameData.data[id] = skinData.data[skinId];
|
||||
frameData.data[id + 1] = skinData.data[skinId + 1];
|
||||
frameData.data[id + 2] = skinData.data[skinId + 2];
|
||||
frameData.data[id + 3] = Math.round(
|
||||
(rawFrameData.data[id + 3] / 255) *
|
||||
(skinData.data[skinId + 3] / 255) *
|
||||
255,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
frameData.data.set(rawFrameData.data);
|
||||
}
|
||||
|
||||
if (mask || this.baseMask) {
|
||||
const maskData = this.getMaskData();
|
||||
const baseMaskData = this.getBaseMaskData();
|
||||
for (let y = 0; y < rawFrameData.height; y++) {
|
||||
for (let x = 0; x < rawFrameData.width; x++) {
|
||||
const id = this.positionToId({x, y});
|
||||
const maskValue = map(
|
||||
maskData?.data[id] ?? 255,
|
||||
baseMaskData?.data[id] ?? 255,
|
||||
this.baseMaskBlend,
|
||||
);
|
||||
frameData.data[id + 3] *= map(1, maskValue / 255, blend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.context.putImageData(frameData, 0, 0);
|
||||
this.fire(SPRITE_CHANGE_EVENT);
|
||||
|
||||
return frameData;
|
||||
}
|
||||
|
||||
private requiresProcessing(): boolean {
|
||||
return !!(this.skin() || this.mask() || this.baseMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* A generator that runs this sprite's animation.
|
||||
*
|
||||
* @remarks
|
||||
* For the animation to actually play, the {@link Sprite.playing} value has to
|
||||
* be set to `true`.
|
||||
*
|
||||
* Should be run concurrently:
|
||||
* ```ts
|
||||
* yield sprite.play();
|
||||
* ```
|
||||
*/
|
||||
public play(): ThreadGenerator {
|
||||
const runTask = this.playRunner();
|
||||
if (this.task) {
|
||||
const previousTask = this.task;
|
||||
this.task = (function* (): ThreadGenerator {
|
||||
cancel(previousTask);
|
||||
yield* runTask;
|
||||
})();
|
||||
GeneratorHelper.makeThreadable(this.task, runTask);
|
||||
} else {
|
||||
this.task = runTask;
|
||||
}
|
||||
|
||||
return this.task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the given animation once.
|
||||
*
|
||||
* @param animation - The animation to play.
|
||||
* @param next - An optional animation that should be switched to next. If not
|
||||
* present the sprite will go back to the previous animation.
|
||||
*/
|
||||
@threadable()
|
||||
public *playOnce(
|
||||
animation: ImageDataSource[],
|
||||
next: ImageDataSource[] = null,
|
||||
): ThreadGenerator {
|
||||
next ??= this.animation();
|
||||
this.animation(animation);
|
||||
for (let i = 0; i < animation.length; i++) {
|
||||
this.frame(i);
|
||||
yield* waitFor(1 / this.fps());
|
||||
}
|
||||
this.animation(next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the current {@link Sprite.play} generator.
|
||||
*
|
||||
* @remarks
|
||||
* Should be used instead of manually canceling the generator.
|
||||
*/
|
||||
@threadable()
|
||||
public *stop() {
|
||||
if (this.task) {
|
||||
cancel(this.task);
|
||||
this.task = null;
|
||||
}
|
||||
}
|
||||
|
||||
@threadable('spriteAnimationRunner')
|
||||
private *playRunner(): ThreadGenerator {
|
||||
this.frame(0);
|
||||
while (this.task !== null) {
|
||||
if (this.playing()) {
|
||||
this.synced = true;
|
||||
this.frame(this.frame() + 1);
|
||||
} else {
|
||||
this.synced = false;
|
||||
}
|
||||
yield* waitFor(1 / this.fps());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the given frame is shown.
|
||||
*
|
||||
* @param frame - The frame to wait for.
|
||||
*/
|
||||
@threadable()
|
||||
public *waitForFrame(frame: number): ThreadGenerator {
|
||||
let limit = 1000;
|
||||
while (
|
||||
this.frame() % this.animation().length !== frame &&
|
||||
limit > 0 &&
|
||||
!this.synced
|
||||
) {
|
||||
limit--;
|
||||
yield;
|
||||
}
|
||||
|
||||
if (limit === 0) {
|
||||
console.warn(`Sprite.waitForFrame cancelled`);
|
||||
}
|
||||
}
|
||||
|
||||
@threadable()
|
||||
private *maskTween(
|
||||
from: ImageDataSource,
|
||||
to: ImageDataSource,
|
||||
time: number,
|
||||
timingFunction: TimingFunction,
|
||||
onEnd: () => void,
|
||||
): ThreadGenerator {
|
||||
this.baseMask = from;
|
||||
this._clearCache(this.getBaseMaskData);
|
||||
|
||||
this.baseMaskBlend = 1;
|
||||
this.mask(to);
|
||||
|
||||
yield* tween(time, value => {
|
||||
this.baseMaskBlend = timingFunction(1 - value);
|
||||
this.updateFrame();
|
||||
});
|
||||
this.baseMask = null;
|
||||
this.baseMaskBlend = 0;
|
||||
onEnd();
|
||||
}
|
||||
|
||||
public getColorAt(position: Vector2d): string {
|
||||
const id = this.positionToId(position);
|
||||
const frameData = this.getFrameData();
|
||||
return `rgba(${frameData.data[id]
|
||||
.toString()
|
||||
.padStart(3, ' ')}, ${frameData.data[id + 1]
|
||||
.toString()
|
||||
.padStart(3, ' ')}, ${frameData.data[id + 2]
|
||||
.toString()
|
||||
.padStart(3, ' ')}, ${frameData.data[id + 3] / 255})`;
|
||||
}
|
||||
|
||||
public getParsedColorAt(position: Vector2d): ReturnType<typeof parseColor> {
|
||||
const id = this.positionToId(position);
|
||||
const frameData = this.getFrameData();
|
||||
return {
|
||||
r: frameData.data[id],
|
||||
g: frameData.data[id + 1],
|
||||
b: frameData.data[id + 2],
|
||||
a: frameData.data[id + 3],
|
||||
};
|
||||
}
|
||||
|
||||
public positionToId(position: Vector2d): number {
|
||||
const frameData = this.getRawFrameData();
|
||||
return (position.y * frameData.width + position.x) * 4;
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
import {ContainerConfig} from 'konva/lib/Container';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {getOriginDelta, Size} from '../types';
|
||||
import {CanvasHelper} from '../helpers';
|
||||
import {easeOutExpo, linear, tween} from '@motion-canvas/core/lib/tweening';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Reference} from '@motion-canvas/core/lib/utils';
|
||||
import {Group} from 'konva/lib/Group';
|
||||
import {Shape} from 'konva/lib/Shape';
|
||||
import {Canvas} from 'konva/lib/Canvas';
|
||||
|
||||
export interface SurfaceMask {
|
||||
width: number;
|
||||
height: number;
|
||||
radius: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface CircleMask {
|
||||
radius: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface SurfaceConfig extends ContainerConfig {
|
||||
ref?: Reference<Surface>;
|
||||
radius?: number;
|
||||
circleMask?: CircleMask;
|
||||
background?: string;
|
||||
child?: Node;
|
||||
rescaleChild?: boolean;
|
||||
shadow?: boolean;
|
||||
overflow?: boolean;
|
||||
}
|
||||
|
||||
@KonvaNode()
|
||||
export class Surface extends Group {
|
||||
@getset(8)
|
||||
public radius: GetSet<SurfaceConfig['radius'], this>;
|
||||
@getset('#00000000')
|
||||
public background: GetSet<SurfaceConfig['background'], this>;
|
||||
@getset(null)
|
||||
public child: GetSet<SurfaceConfig['child'], this>;
|
||||
@getset(true)
|
||||
public rescaleChild: GetSet<SurfaceConfig['rescaleChild'], this>;
|
||||
@getset(false)
|
||||
public shadow: GetSet<SurfaceConfig['shadow'], this>;
|
||||
@getset(false)
|
||||
public overflow: GetSet<SurfaceConfig['overflow'], this>;
|
||||
|
||||
private surfaceMask: SurfaceMask | null = null;
|
||||
private circleMask: CircleMask | null = null;
|
||||
private layoutData: Size;
|
||||
private rippleTween = 0;
|
||||
|
||||
public constructor(config?: SurfaceConfig) {
|
||||
super(config);
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public setChild(value: Shape | Group): this {
|
||||
this.attrs.child?.remove();
|
||||
this.attrs.child = value;
|
||||
if (value) {
|
||||
this.add(value);
|
||||
}
|
||||
this.markDirty();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public getChild<T extends Node>(): T {
|
||||
return <T>this.attrs.child;
|
||||
}
|
||||
|
||||
public setCircleMask(value: CircleMask | null): this {
|
||||
this.circleMask = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public getCircleMask(): CircleMask | null {
|
||||
return this.circleMask ?? null;
|
||||
}
|
||||
|
||||
public clone(obj?: unknown): this {
|
||||
const child = this.child();
|
||||
this.child(null);
|
||||
const clone: this = Node.prototype.clone.call(this, obj);
|
||||
this.child(child);
|
||||
if (child) {
|
||||
clone.setChild(child.clone());
|
||||
}
|
||||
this.getChildren().forEach(node => {
|
||||
if (node !== child) {
|
||||
clone.add(node.clone());
|
||||
}
|
||||
});
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public getLayoutSize(): Size {
|
||||
return {
|
||||
width: this.surfaceMask?.width ?? this.layoutData?.width ?? 0,
|
||||
height: this.surfaceMask?.height ?? this.layoutData?.height ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link ripple} instead.
|
||||
*/
|
||||
public doRipple() {
|
||||
return this.ripple();
|
||||
}
|
||||
|
||||
@threadable()
|
||||
public *ripple() {
|
||||
if (this.surfaceMask) return;
|
||||
yield* tween(1, value => {
|
||||
this.rippleTween = value;
|
||||
});
|
||||
this.rippleTween = 0;
|
||||
}
|
||||
|
||||
public setMask(data: SurfaceMask) {
|
||||
if (data === null) {
|
||||
this.surfaceMask = null;
|
||||
this.markDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
const child = this.child();
|
||||
const contentSize = child.getLayoutSize();
|
||||
const contentMargin = child.getMargin();
|
||||
const scale = Math.min(
|
||||
1,
|
||||
data.width / (contentSize.width + contentMargin.x),
|
||||
);
|
||||
|
||||
this.surfaceMask = data;
|
||||
if (this.rescaleChild()) {
|
||||
child.scaleX(scale);
|
||||
child.scaleY(scale);
|
||||
}
|
||||
child.position(getOriginDelta(data, Origin.Middle, child.getOrigin()));
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public getMask(): SurfaceMask {
|
||||
return {
|
||||
...this.layoutData,
|
||||
radius: this.radius(),
|
||||
color: this.background(),
|
||||
};
|
||||
}
|
||||
|
||||
public recalculateLayout() {
|
||||
if (this.surfaceMask) return;
|
||||
|
||||
this.layoutData ??= {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
|
||||
const child = this.child();
|
||||
if (child) {
|
||||
const size = child.getLayoutSize();
|
||||
const margin = child.getMargin();
|
||||
const scale = child.getAbsoluteScale(this);
|
||||
const padding = this.getPadd();
|
||||
|
||||
this.layoutData = {
|
||||
width: (size.width + margin.x + padding.x) * scale.x,
|
||||
height: (size.height + margin.y + padding.y) * scale.y,
|
||||
};
|
||||
|
||||
const offset = child.getOriginDelta(Origin.Middle);
|
||||
child.position({
|
||||
x: -offset.x,
|
||||
y: -offset.y,
|
||||
});
|
||||
}
|
||||
|
||||
super.recalculateLayout();
|
||||
}
|
||||
|
||||
public _drawChildren(drawMethod: string, canvas: Canvas, top: Node): void {
|
||||
const context = canvas && canvas.getContext();
|
||||
|
||||
context.save();
|
||||
const transform = this.getAbsoluteTransform(top);
|
||||
let m = transform.getMatrix();
|
||||
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
||||
const opacity = this.getAbsoluteOpacity();
|
||||
|
||||
const size = this.surfaceMask ?? this.layoutData;
|
||||
const radius = this.surfaceMask?.radius ?? this.radius();
|
||||
if (this.rippleTween > 0) {
|
||||
const width = size.width + easeOutExpo(this.rippleTween, 0, 100);
|
||||
const height = size.height + easeOutExpo(this.rippleTween, 0, 100);
|
||||
const rippleRadius = radius + easeOutExpo(this.rippleTween, 0, 50);
|
||||
|
||||
context.save();
|
||||
context._context.fillStyle = this.surfaceMask?.color ?? this.background();
|
||||
context._context.globalAlpha = linear(this.rippleTween, opacity / 2, 0);
|
||||
CanvasHelper.roundRect(
|
||||
context._context,
|
||||
-width / 2,
|
||||
-height / 2,
|
||||
width,
|
||||
height,
|
||||
rippleRadius,
|
||||
);
|
||||
context._context.fill();
|
||||
context.restore();
|
||||
}
|
||||
|
||||
if (this.circleMask) {
|
||||
context._context.beginPath();
|
||||
context._context.arc(
|
||||
this.circleMask.x,
|
||||
this.circleMask.y,
|
||||
this.circleMask.radius,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
);
|
||||
context._context.closePath();
|
||||
context._context.clip();
|
||||
}
|
||||
|
||||
context.save();
|
||||
context._context.fillStyle = this.surfaceMask?.color ?? this.background();
|
||||
context._context.globalAlpha = opacity;
|
||||
if (this.shadow()) {
|
||||
context._context.shadowColor = 'rgba(0, 0, 0, 0.32)';
|
||||
context._context.shadowOffsetY = 10;
|
||||
context._context.shadowBlur = 40;
|
||||
}
|
||||
|
||||
const path = CanvasHelper.roundRectPath(
|
||||
new Path2D(),
|
||||
-size.width / 2,
|
||||
-size.height / 2,
|
||||
size.width,
|
||||
size.height,
|
||||
radius,
|
||||
);
|
||||
context._context.fill(path);
|
||||
context.restore();
|
||||
|
||||
if (!this.overflow() || this.surfaceMask) {
|
||||
context._context.clip(path);
|
||||
}
|
||||
|
||||
m = transform.copy().invert().getMatrix();
|
||||
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
||||
|
||||
super._drawChildren(drawMethod, canvas, top);
|
||||
|
||||
context.restore();
|
||||
}
|
||||
|
||||
public getAbsoluteCircleMask(custom?: SurfaceConfig): CircleMask {
|
||||
const mask = custom?.circleMask ?? this.circleMask ?? null;
|
||||
if (mask === null) return null;
|
||||
const size = this.getLayoutSize();
|
||||
const position = {
|
||||
x: (size.width * mask.x) / 2,
|
||||
y: (size.height * mask.y) / 2,
|
||||
};
|
||||
const farthestEdge = {
|
||||
x: Math.abs(position.x) + size.width / 2,
|
||||
y: Math.abs(position.y) + size.height / 2,
|
||||
};
|
||||
const distance = Math.sqrt(
|
||||
farthestEdge.x * farthestEdge.x + farthestEdge.y * farthestEdge.y,
|
||||
);
|
||||
|
||||
return {
|
||||
...position,
|
||||
radius: distance * mask.radius,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
import {PossibleSpacing, Size} from '../types';
|
||||
import {Util} from 'konva/lib/Util';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import * as THREE from 'three';
|
||||
import {CanvasHelper} from '../helpers';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {KonvaNode, getset} from '../decorators';
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
|
||||
export interface ThreeViewConfig extends ShapeConfig {
|
||||
canvasSize: Size;
|
||||
cameraScale?: number;
|
||||
quality?: number;
|
||||
skipFrames?: number;
|
||||
scene?: THREE.Scene;
|
||||
camera?: THREE.Camera;
|
||||
radius?: PossibleSpacing;
|
||||
background?: string;
|
||||
}
|
||||
|
||||
interface Pool<T> {
|
||||
borrow(): T;
|
||||
dispose(object: T): void;
|
||||
}
|
||||
|
||||
class RendererPool implements Pool<THREE.WebGLRenderer> {
|
||||
private pool: THREE.WebGLRenderer[] = [];
|
||||
|
||||
public borrow(): THREE.WebGLRenderer {
|
||||
if (this.pool.length) {
|
||||
return this.pool.pop();
|
||||
} else {
|
||||
return new THREE.WebGLRenderer({
|
||||
canvas: Util.createCanvasElement(),
|
||||
antialias: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(renderer: THREE.WebGLRenderer) {
|
||||
this.pool.push(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
const rendererPool = new RendererPool();
|
||||
|
||||
@KonvaNode()
|
||||
export class ThreeView extends Shape {
|
||||
@getset(null)
|
||||
public scene: GetSet<ThreeViewConfig['scene'], this>;
|
||||
@getset(null)
|
||||
public camera: GetSet<ThreeViewConfig['camera'], this>;
|
||||
@getset({width: 0, height: 0}, ThreeView.prototype.handleCanvasSizeChange)
|
||||
public canvasSize: GetSet<ThreeViewConfig['canvasSize'], this>;
|
||||
@getset(1, ThreeView.prototype.handleCanvasSizeChange)
|
||||
public cameraScale: GetSet<ThreeViewConfig['cameraScale'], this>;
|
||||
@getset(1, ThreeView.prototype.handleCanvasSizeChange)
|
||||
public quality: GetSet<ThreeViewConfig['quality'], this>;
|
||||
@getset(0)
|
||||
public skipFrames: GetSet<ThreeViewConfig['skipFrames'], this>;
|
||||
@getset(0)
|
||||
public radius: GetSet<ThreeViewConfig['radius'], this>;
|
||||
|
||||
private readonly renderer: THREE.WebGLRenderer;
|
||||
private readonly context: WebGLRenderingContext;
|
||||
|
||||
private renderedFrames = 0;
|
||||
|
||||
public constructor(config?: ThreeViewConfig) {
|
||||
super(config);
|
||||
this.renderer = rendererPool.borrow();
|
||||
this.context = this.renderer.getContext();
|
||||
|
||||
this.handleCanvasSizeChange();
|
||||
}
|
||||
|
||||
public setBackground(value: string): this {
|
||||
const scene = this.scene();
|
||||
if (scene) {
|
||||
scene.background = new THREE.Color(value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public getBackground(): string {
|
||||
const background = this.scene()?.background;
|
||||
return background instanceof THREE.Color
|
||||
? background.getHexString()
|
||||
: '#000000';
|
||||
}
|
||||
|
||||
public destroy(): this {
|
||||
rendererPool.dispose(this.renderer);
|
||||
|
||||
return super.destroy();
|
||||
}
|
||||
|
||||
private handleCanvasSizeChange() {
|
||||
if (!this.renderer) return;
|
||||
|
||||
const size = {...this.canvasSize()};
|
||||
const camera = this.camera();
|
||||
|
||||
const ratio = size.width / size.height;
|
||||
const scale = this.cameraScale() / 2;
|
||||
if (camera instanceof THREE.OrthographicCamera) {
|
||||
camera.left = -ratio * scale;
|
||||
camera.right = ratio * scale;
|
||||
camera.bottom = -scale;
|
||||
camera.top = scale;
|
||||
camera.updateProjectionMatrix();
|
||||
} else if (camera instanceof THREE.PerspectiveCamera) {
|
||||
camera.aspect = ratio;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
size.width *= this.quality();
|
||||
size.height *= this.quality();
|
||||
this.renderer.setSize(size.width, size.height);
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public getLayoutSize(): Size {
|
||||
return this.canvasSize();
|
||||
}
|
||||
|
||||
public _sceneFunc(context: Context) {
|
||||
const scale = this.quality();
|
||||
const size = {...this.canvasSize()};
|
||||
|
||||
if (this.renderedFrames < 1) {
|
||||
this.renderedFrames = this.skipFrames();
|
||||
this.renderer.render(this.scene(), this.camera());
|
||||
} else {
|
||||
this.renderedFrames--;
|
||||
}
|
||||
|
||||
context._context.imageSmoothingEnabled = false;
|
||||
context._context.clip(
|
||||
CanvasHelper.roundRectPath(
|
||||
new Path2D(),
|
||||
size.width / -2,
|
||||
size.height / -2,
|
||||
size.width,
|
||||
size.height,
|
||||
this.radius(),
|
||||
),
|
||||
);
|
||||
context._context.drawImage(
|
||||
this.renderer.domElement,
|
||||
0,
|
||||
0,
|
||||
size.width * scale,
|
||||
size.height * scale,
|
||||
size.width / -2,
|
||||
size.height / -2,
|
||||
size.width,
|
||||
size.height,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import {Shape, ShapeConfig} from 'konva/lib/Shape';
|
||||
import {threadable} from '@motion-canvas/core/lib/decorators';
|
||||
import {getset} from '../decorators';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {cancel, ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import {waitFor} from '@motion-canvas/core/lib/flow';
|
||||
import {GeneratorHelper} from '@motion-canvas/core/lib/helpers';
|
||||
import {CanvasHelper} from '../helpers';
|
||||
|
||||
interface VideoConfig extends ShapeConfig {
|
||||
frames: ImageBitmap[];
|
||||
frame?: number;
|
||||
fps?: number;
|
||||
playing?: number;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
export class Video extends Shape {
|
||||
@getset([])
|
||||
public frames: GetSet<VideoConfig['frames'], this>;
|
||||
@getset(0)
|
||||
public frame: GetSet<VideoConfig['frame'], this>;
|
||||
@getset(30)
|
||||
public fps: GetSet<VideoConfig['fps'], this>;
|
||||
@getset(true)
|
||||
public playing: GetSet<VideoConfig['playing'], this>;
|
||||
@getset(8)
|
||||
public radius: GetSet<VideoConfig['radius'], this>;
|
||||
|
||||
private task: ThreadGenerator | null = null;
|
||||
|
||||
public constructor(config?: VideoConfig) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
public _sceneFunc(context: Context) {
|
||||
const frames = this.frames();
|
||||
context._context.clip(
|
||||
CanvasHelper.roundRectPath(
|
||||
new Path2D(),
|
||||
0,
|
||||
0,
|
||||
this.width(),
|
||||
this.height(),
|
||||
this.radius(),
|
||||
),
|
||||
);
|
||||
if (frames.length) {
|
||||
context.drawImage(frames[this.frame() % frames.length], 0, 0);
|
||||
} else {
|
||||
context._context.fillStyle = '#666666';
|
||||
context.fillRect(0, 0, this.width(), this.height());
|
||||
}
|
||||
}
|
||||
|
||||
public play(): ThreadGenerator {
|
||||
const runTask = this.playRunner();
|
||||
if (this.task) {
|
||||
const previousTask = this.task;
|
||||
this.task = (function* (): ThreadGenerator {
|
||||
cancel(previousTask);
|
||||
yield* runTask;
|
||||
})();
|
||||
GeneratorHelper.makeThreadable(this.task, runTask);
|
||||
} else {
|
||||
this.task = runTask;
|
||||
}
|
||||
|
||||
return this.task;
|
||||
}
|
||||
|
||||
@threadable()
|
||||
public *stop() {
|
||||
if (this.task) {
|
||||
cancel(this.task);
|
||||
this.task = null;
|
||||
}
|
||||
}
|
||||
|
||||
@threadable('videoRunner')
|
||||
private *playRunner(): ThreadGenerator {
|
||||
while (this.task !== null) {
|
||||
if (this.playing()) {
|
||||
this.frame(this.frame() + 1);
|
||||
}
|
||||
yield* waitFor(1 / this.fps());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,437 +0,0 @@
|
||||
import {cached, KonvaNode, getset} from '../../decorators';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import PrismJS from 'prismjs';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {Text, TextConfig} from 'konva/lib/shapes/Text';
|
||||
import {Util} from 'konva/lib/Util';
|
||||
import {easeInExpo, easeOutExpo, tween} from '@motion-canvas/core/lib/tweening';
|
||||
import {CodeTheme, CodeTokens} from './CodeTheme';
|
||||
import {JS_CODE_THEME} from '../../themes';
|
||||
import {ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import {threadable} from '@motion-canvas/core/lib/decorators';
|
||||
|
||||
type CodePoint = [number, number];
|
||||
type CodeRange = [CodePoint, CodePoint];
|
||||
|
||||
interface CodeConfig extends TextConfig {
|
||||
selection?: CodeRange[];
|
||||
theme?: CodeTheme;
|
||||
numbers?: boolean | number;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
const FALLBACK_COLOR = '#FF00FF';
|
||||
|
||||
@KonvaNode({centroid: false})
|
||||
export class Code extends Text {
|
||||
@getset([])
|
||||
public selection: GetSet<CodeConfig['selection'], this>;
|
||||
@getset(JS_CODE_THEME)
|
||||
public theme: GetSet<CodeConfig['theme'], this>;
|
||||
@getset(false)
|
||||
public numbers: GetSet<CodeConfig['numbers'], this>;
|
||||
@getset('js')
|
||||
public language: GetSet<CodeConfig['language'], this>;
|
||||
|
||||
private readonly textCanvas: HTMLCanvasElement;
|
||||
private readonly textCtx: CanvasRenderingContext2D;
|
||||
private readonly selectionCanvas: HTMLCanvasElement;
|
||||
private readonly selectionCtx: CanvasRenderingContext2D;
|
||||
|
||||
private outline = 0;
|
||||
private unselectedOpacity = 1;
|
||||
|
||||
public constructor(config?: CodeConfig) {
|
||||
super({
|
||||
fontFamily: 'JetBrains Mono, monospace',
|
||||
fontSize: 28,
|
||||
lineHeight: 2,
|
||||
...config,
|
||||
});
|
||||
this.textCanvas = Util.createCanvasElement();
|
||||
this.textCtx = this.textCanvas.getContext('2d');
|
||||
this.selectionCanvas = Util.createCanvasElement();
|
||||
this.selectionCtx = this.selectionCanvas.getContext('2d');
|
||||
}
|
||||
|
||||
public _sceneFunc(context: Context) {
|
||||
const padding = this.getPadd();
|
||||
|
||||
context.translate(padding.left, padding.right);
|
||||
this.drawSelection(context._context);
|
||||
this.drawText(context._context);
|
||||
if (this.numbers() !== false) {
|
||||
this.drawLineNumbers(context._context);
|
||||
}
|
||||
}
|
||||
|
||||
public setText(text: string): this {
|
||||
super.setText(text);
|
||||
this.markDirty();
|
||||
|
||||
this._clearCache(this.getLines);
|
||||
this._clearCache(this.getTokens);
|
||||
this._clearCache(this.getNormalizedSelection);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setLanguage(language: string): this {
|
||||
this.attrs.language = language;
|
||||
this._clearCache(this.getTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setSelection(value: CodeRange[]): this {
|
||||
this.attrs.selection = value;
|
||||
this._clearCache(this.getNormalizedSelection);
|
||||
return this;
|
||||
}
|
||||
|
||||
@cached('Code.lines')
|
||||
private getLines(): string[] {
|
||||
return this.text().split('\n');
|
||||
}
|
||||
|
||||
@cached('Code.tokens')
|
||||
private getTokens(): (PrismJS.Token | string)[] {
|
||||
const language = this.language();
|
||||
if (language in PrismJS.languages) {
|
||||
const env = {
|
||||
code: this.text(),
|
||||
grammar: PrismJS.languages[language],
|
||||
language: language,
|
||||
tokens: PrismJS.tokenize(this.text(), PrismJS.languages[language]),
|
||||
};
|
||||
// the after-tokenize hook will update env tokens for the jsx language
|
||||
PrismJS.hooks.run('after-tokenize', env);
|
||||
return env.tokens;
|
||||
} else {
|
||||
console.warn(
|
||||
`Missing language: ${language}.`,
|
||||
`Make sure that 'prismjs/components/prism-${language}' has been imported.`,
|
||||
);
|
||||
return PrismJS.tokenize(this.text(), PrismJS.languages.plain);
|
||||
}
|
||||
}
|
||||
|
||||
@cached('Code.selection')
|
||||
private getNormalizedSelection(): CodeRange[] {
|
||||
const lines = this.getLines();
|
||||
const normalized: CodeRange[] = [];
|
||||
const selection = [...this.selection()];
|
||||
for (const range of selection) {
|
||||
let [[startLine, startColumn], [endLine, endColumn]] = range;
|
||||
if (startLine > endLine) {
|
||||
[startLine, endLine] = [endLine, startLine];
|
||||
}
|
||||
if (startLine === endLine && startColumn > endColumn) {
|
||||
[startColumn, endColumn] = [endColumn, startColumn];
|
||||
}
|
||||
|
||||
if (startLine >= lines.length) {
|
||||
startLine = lines.length - 1;
|
||||
}
|
||||
if (endLine >= lines.length) {
|
||||
endLine = lines.length - 1;
|
||||
}
|
||||
if (endColumn >= lines[endLine].length) {
|
||||
endColumn = Math.max(lines[endLine].length, 1);
|
||||
}
|
||||
|
||||
if (startLine !== endLine) {
|
||||
const nextLineOffset =
|
||||
startLine + 1 === endLine
|
||||
? endColumn
|
||||
: Math.max(1, lines[startLine + 1].length);
|
||||
|
||||
if (nextLineOffset <= startColumn) {
|
||||
normalized.push([
|
||||
[startLine + 1, 0],
|
||||
[endLine, endColumn],
|
||||
]);
|
||||
endLine = startLine;
|
||||
endColumn = lines[startLine].length;
|
||||
}
|
||||
}
|
||||
|
||||
normalized.push([
|
||||
[startLine, startColumn],
|
||||
[endLine, endColumn],
|
||||
]);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private drawSelection(context: CanvasRenderingContext2D) {
|
||||
const letterWidth = this.measureSize(' ').width;
|
||||
const lineHeight = this.fontSize() * this.lineHeight();
|
||||
const selection = this.getNormalizedSelection();
|
||||
const outline = this.outline;
|
||||
const lines = this.getLines();
|
||||
|
||||
context.beginPath();
|
||||
for (const range of selection) {
|
||||
const [[startLine, startColumn], [endLine, endColumn]] = range;
|
||||
let offset =
|
||||
startLine === endLine
|
||||
? endColumn * letterWidth
|
||||
: Math.max(1, lines[startLine].length) * letterWidth;
|
||||
context.moveTo(
|
||||
startColumn * letterWidth - outline,
|
||||
(startLine + 0.5) * lineHeight,
|
||||
);
|
||||
context.arcTo(
|
||||
startColumn * letterWidth - outline,
|
||||
startLine * lineHeight - outline,
|
||||
offset + outline,
|
||||
startLine * lineHeight - outline,
|
||||
8,
|
||||
);
|
||||
|
||||
context.arcTo(
|
||||
offset + outline,
|
||||
startLine * lineHeight - outline,
|
||||
offset + outline,
|
||||
(startLine + 1) * lineHeight,
|
||||
8,
|
||||
);
|
||||
|
||||
for (let i = startLine + 1; i <= endLine; i++) {
|
||||
const lineOffset =
|
||||
Math.max(1, i === endLine ? endColumn : lines[i].length) *
|
||||
letterWidth;
|
||||
const linePadding = lineOffset > offset ? -outline : outline;
|
||||
context.arcTo(
|
||||
offset + outline,
|
||||
i * lineHeight + linePadding,
|
||||
lineOffset + outline,
|
||||
i * lineHeight + linePadding,
|
||||
8,
|
||||
);
|
||||
offset = lineOffset;
|
||||
context.arcTo(
|
||||
offset + outline,
|
||||
i * lineHeight + linePadding,
|
||||
offset + outline,
|
||||
(i + 1) * lineHeight + linePadding,
|
||||
8,
|
||||
);
|
||||
}
|
||||
|
||||
const endOffset = startLine === endLine ? startColumn * letterWidth : 0;
|
||||
context.arcTo(
|
||||
offset + outline,
|
||||
(endLine + 1) * lineHeight + outline,
|
||||
endOffset - outline,
|
||||
(endLine + 1) * lineHeight + outline,
|
||||
8,
|
||||
);
|
||||
context.arcTo(
|
||||
endOffset - outline,
|
||||
(endLine + 1) * lineHeight + outline,
|
||||
endOffset - outline,
|
||||
endLine * lineHeight + outline,
|
||||
8,
|
||||
);
|
||||
if (startLine !== endLine) {
|
||||
context.arcTo(
|
||||
endOffset - outline,
|
||||
(startLine + 1) * lineHeight - outline,
|
||||
startColumn * letterWidth - outline,
|
||||
(startLine + 1) * lineHeight - outline,
|
||||
8,
|
||||
);
|
||||
context.arcTo(
|
||||
startColumn * letterWidth - outline,
|
||||
(startLine + 1) * lineHeight - outline,
|
||||
startColumn * letterWidth - outline,
|
||||
startLine * lineHeight - outline,
|
||||
8,
|
||||
);
|
||||
}
|
||||
context.lineTo(
|
||||
startColumn * letterWidth - outline,
|
||||
(startLine + 0.5) * lineHeight,
|
||||
);
|
||||
}
|
||||
|
||||
context.closePath();
|
||||
context.fillStyle = '#242424';
|
||||
context.globalAlpha = this.getAbsoluteOpacity();
|
||||
context.fill();
|
||||
}
|
||||
|
||||
private drawText(context: CanvasRenderingContext2D) {
|
||||
const letterWidth = this.measureSize(' ').width;
|
||||
const lineHeight = this.fontSize() * this.lineHeight();
|
||||
const theme = this.theme();
|
||||
|
||||
context.font = this._getContextFont();
|
||||
context.textBaseline = 'middle';
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
const draw = (token: string | PrismJS.Token, colors: CodeTokens) => {
|
||||
if (typeof token === 'string') {
|
||||
context.fillStyle = colors.punctuation ?? FALLBACK_COLOR;
|
||||
const lines = token.split('\n');
|
||||
let isFirst = true;
|
||||
for (const line of lines) {
|
||||
if (!isFirst) {
|
||||
x = 0;
|
||||
y++;
|
||||
}
|
||||
isFirst = false;
|
||||
|
||||
const trim = line.length - line.trimStart().length;
|
||||
context.globalAlpha = this.getOpacityAtPoint(x + trim, y);
|
||||
context.fillText(line, x * letterWidth, (y + 0.5) * lineHeight);
|
||||
x += line.length;
|
||||
}
|
||||
} else if (
|
||||
typeof token.content === 'string' &&
|
||||
// FIXME Handle newlines no matter the token type
|
||||
token.type !== 'plain-text'
|
||||
) {
|
||||
if (!(token.type in colors)) {
|
||||
console.warn(`Unstyled token type:`, token.type);
|
||||
}
|
||||
context.fillStyle = colors[token.type] ?? FALLBACK_COLOR;
|
||||
context.globalAlpha = this.getOpacityAtPoint(x, y);
|
||||
context.fillText(
|
||||
token.content,
|
||||
x * letterWidth,
|
||||
(y + 0.5) * lineHeight,
|
||||
);
|
||||
x += token.length;
|
||||
} else if (Array.isArray(token.content)) {
|
||||
const subTheme = theme[token.type] ?? colors;
|
||||
for (const subToken of token.content) {
|
||||
draw(subToken, subTheme);
|
||||
}
|
||||
} else {
|
||||
const subTheme = theme[token.type] ?? colors;
|
||||
draw(token.content, subTheme);
|
||||
}
|
||||
};
|
||||
|
||||
for (const token of this.getTokens()) {
|
||||
draw(token, theme.default);
|
||||
}
|
||||
}
|
||||
|
||||
private drawLineNumbers(context: CanvasRenderingContext2D) {
|
||||
const theme = this.theme();
|
||||
const numbers = this.numbers();
|
||||
const lines = this.getLines();
|
||||
const lineHeight = this.fontSize() * this.lineHeight();
|
||||
|
||||
context.save();
|
||||
context.fillStyle = theme.default.comment ?? FALLBACK_COLOR;
|
||||
context.globalAlpha = this.getAbsoluteOpacity();
|
||||
context.textAlign = 'right';
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const number = typeof numbers === 'number' ? numbers + i : i;
|
||||
context.fillText(number.toString(), -20, (i + 0.5) * lineHeight);
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
|
||||
private getOpacityAtPoint(x: number, y: number): number {
|
||||
return this.isSelected(x, y)
|
||||
? this.getAbsoluteOpacity()
|
||||
: this.getAbsoluteOpacity() * this.unselectedOpacity;
|
||||
}
|
||||
|
||||
private isSelected(x: number, y: number): boolean {
|
||||
if (this.selection().length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!this.selection().find(
|
||||
([[startLine, startColumn], [endLine, endColumn]]) => {
|
||||
return (
|
||||
((y === startLine && x >= startColumn) || y > startLine) &&
|
||||
((y === endLine && x < endColumn) || y < endLine)
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public selectLines(from: number, to?: number): this {
|
||||
this.selection([
|
||||
[
|
||||
[from, 0],
|
||||
[to ?? from, Infinity],
|
||||
],
|
||||
]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public selectWord(line: number, from: number, length?: number): this {
|
||||
this.selection([
|
||||
[
|
||||
[line, from],
|
||||
[line, from + (length ?? Infinity)],
|
||||
],
|
||||
]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public selectRange(
|
||||
startLine: number,
|
||||
startColumn: number,
|
||||
endLine: number,
|
||||
endColumn: number,
|
||||
): this {
|
||||
this.selection([
|
||||
[
|
||||
[startLine, startColumn],
|
||||
[endLine, endColumn],
|
||||
],
|
||||
]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public clearSelection(): this {
|
||||
this.selection([]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public hasSelection(): boolean {
|
||||
return this.selection().length > 0;
|
||||
}
|
||||
|
||||
public apply() {
|
||||
this.outline = 0;
|
||||
this.unselectedOpacity = this.hasSelection() ? 0.32 : 1;
|
||||
}
|
||||
|
||||
@threadable('animateCode')
|
||||
public *animate(): ThreadGenerator {
|
||||
const hasSelection = this.hasSelection();
|
||||
const currentOpacity = this.unselectedOpacity;
|
||||
|
||||
yield* tween(0.5, value => {
|
||||
this.outline = easeOutExpo(value, -8, 0);
|
||||
this.unselectedOpacity = easeOutExpo(
|
||||
value,
|
||||
currentOpacity,
|
||||
hasSelection ? 0.32 : 1,
|
||||
);
|
||||
});
|
||||
this.apply();
|
||||
}
|
||||
|
||||
@threadable()
|
||||
public *animateClearSelection() {
|
||||
const currentOpacity = this.unselectedOpacity;
|
||||
yield* tween(0.5, value => {
|
||||
this.outline = easeInExpo(value, 0, -8);
|
||||
this.unselectedOpacity = easeInExpo(value, currentOpacity, 1);
|
||||
});
|
||||
this.clearSelection();
|
||||
this.apply();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
export type CodeTokens = Record<string, string>;
|
||||
|
||||
export type CodeTheme<T extends CodeTokens = CodeTokens> = {
|
||||
[Key: string]: T;
|
||||
default: T;
|
||||
};
|
||||
|
||||
export type JSCodeTokens = CodeTokens & {
|
||||
boolean?: string;
|
||||
'class-name'?: string;
|
||||
comment?: string;
|
||||
constant?: string;
|
||||
function?: string;
|
||||
'function-variable'?: string;
|
||||
hashbang?: string;
|
||||
keyword?: string;
|
||||
'literal-property'?: string;
|
||||
number?: string;
|
||||
operator?: string;
|
||||
parameter?: string;
|
||||
punctuation?: string;
|
||||
regex?: string;
|
||||
string?: string;
|
||||
'string-property'?: string;
|
||||
'template-string'?: string;
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './Code';
|
||||
export * from './CodeTheme';
|
||||
@@ -1,17 +0,0 @@
|
||||
export * from './Align';
|
||||
export * from './AnimationClip';
|
||||
export * from './Arrow';
|
||||
export * from './ColorPicker';
|
||||
export * from './Connection';
|
||||
export * from './Grid';
|
||||
export * from './Icon';
|
||||
export * from './LayeredLayout';
|
||||
export * from './LayoutText';
|
||||
export * from './LinearLayout';
|
||||
export * from './Pin';
|
||||
export * from './PinnedLabel';
|
||||
export * from './Range';
|
||||
export * from './Sprite';
|
||||
export * from './Surface';
|
||||
export * from './ThreeView';
|
||||
export * from './Video';
|
||||
@@ -1,9 +0,0 @@
|
||||
export function KonvaNode(config?: {
|
||||
name?: string;
|
||||
centroid?: boolean;
|
||||
}): ClassDecorator {
|
||||
return function (target) {
|
||||
target.prototype.className = config?.name ?? target.name;
|
||||
target.prototype._centroid = config?.centroid ?? true;
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import type {Node} from 'konva/lib/Node';
|
||||
|
||||
export function cached(key: string): MethodDecorator {
|
||||
return function (target, propertyKey, descriptor: PropertyDescriptor) {
|
||||
const original = descriptor.value;
|
||||
descriptor.value = function (this: Node) {
|
||||
return this._getCache(key, original);
|
||||
};
|
||||
descriptor.value.prototype.cachedKey = key;
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import {Factory} from 'konva/lib/Factory';
|
||||
import {TweenProvider} from '@motion-canvas/core/lib/tweening';
|
||||
|
||||
export function getset<T = unknown>(
|
||||
defaultValue?: T,
|
||||
after?: Callback,
|
||||
tween?: TweenProvider<T>,
|
||||
): PropertyDecorator {
|
||||
return function (target, propertyKey) {
|
||||
Factory.addGetter(target.constructor, propertyKey, defaultValue);
|
||||
Factory.addSetter(target.constructor, propertyKey, undefined, after);
|
||||
// @ts-ignore
|
||||
Factory.addOverloadedGetterSetter(target.constructor, propertyKey, tween);
|
||||
};
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './cached';
|
||||
export * from './getset';
|
||||
export * from './KonvaNode';
|
||||
9
packages/legacy/src/globals.d.ts
vendored
9
packages/legacy/src/globals.d.ts
vendored
@@ -1,9 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-namespace */
|
||||
/// <reference types="@motion-canvas/core/project" />
|
||||
|
||||
declare namespace JSX {
|
||||
type ElementClass = import('konva/lib/Node').Node;
|
||||
interface ElementChildrenAttribute {
|
||||
children: unknown;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import type {Context} from 'konva/lib/Context';
|
||||
import {PossibleSpacing, Spacing} from '../types';
|
||||
|
||||
export const CanvasHelper = {
|
||||
roundRect<T extends CanvasRenderingContext2D | Context>(
|
||||
ctx: T,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
radius: PossibleSpacing,
|
||||
): T {
|
||||
ctx.beginPath();
|
||||
this.roundRectPath(ctx, x, y, width, height, radius);
|
||||
ctx.closePath();
|
||||
|
||||
return ctx;
|
||||
},
|
||||
|
||||
roundRectPath<T extends CanvasRenderingContext2D | Context | Path2D>(
|
||||
ctx: T,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
radius: PossibleSpacing,
|
||||
): T {
|
||||
const spacing = new Spacing(radius);
|
||||
const maxRadius = Math.min(height / 2, width / 2);
|
||||
spacing.left = Math.min(spacing.left, maxRadius);
|
||||
spacing.right = Math.min(spacing.right, maxRadius);
|
||||
spacing.top = Math.min(spacing.top, maxRadius);
|
||||
spacing.bottom = Math.min(spacing.bottom, maxRadius);
|
||||
|
||||
ctx.moveTo(x + spacing.left, y);
|
||||
ctx.arcTo(x + width, y, x + width, y + height, spacing.top);
|
||||
ctx.arcTo(x + width, y + height, x, y + height, spacing.right);
|
||||
ctx.arcTo(x, y + height, x, y, spacing.bottom);
|
||||
ctx.arcTo(x, y, x + width, y, spacing.left);
|
||||
|
||||
return ctx;
|
||||
},
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './CanvasHelper';
|
||||
@@ -1,63 +0,0 @@
|
||||
import type {NodeConfig} from 'konva/lib/Node';
|
||||
import type {Shape} from 'konva/lib/Shape';
|
||||
import type {Reference} from '@motion-canvas/core/lib/utils';
|
||||
import {Container} from 'konva/lib/Container';
|
||||
import {Surface} from './components';
|
||||
|
||||
function isConstructor(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
fn: Function,
|
||||
): fn is new (...args: unknown[]) => unknown {
|
||||
return !!fn.prototype?.name;
|
||||
}
|
||||
|
||||
type ChildrenConfig = {
|
||||
[key in keyof JSX.ElementChildrenAttribute]:
|
||||
| JSX.ElementClass
|
||||
| JSX.ElementClass[];
|
||||
};
|
||||
|
||||
type ReferenceConfig = {
|
||||
ref?: Reference<JSX.ElementClass>;
|
||||
};
|
||||
|
||||
export const Fragment = Symbol.for('Fragment');
|
||||
export function jsx(
|
||||
type:
|
||||
| (new (config?: NodeConfig) => JSX.ElementClass)
|
||||
| ((config: NodeConfig) => JSX.ElementClass)
|
||||
| typeof Fragment,
|
||||
config: NodeConfig & ChildrenConfig & ReferenceConfig,
|
||||
): JSX.ElementClass | JSX.ElementClass[] {
|
||||
const {children, ref, ...rest} = config;
|
||||
const flatChildren = Array.isArray(children) ? children.flat() : [children];
|
||||
|
||||
if (type === Fragment) {
|
||||
return flatChildren;
|
||||
}
|
||||
|
||||
if (!isConstructor(type)) {
|
||||
return type(config);
|
||||
}
|
||||
|
||||
const node = new type(rest);
|
||||
if (children) {
|
||||
if (node instanceof Surface) {
|
||||
node.setChild(<Shape>flatChildren[0]);
|
||||
} else if (node instanceof Container) {
|
||||
node.add(...flatChildren);
|
||||
}
|
||||
}
|
||||
|
||||
if (ref) {
|
||||
if (Array.isArray(ref)) {
|
||||
console.warn('Reference arrays are deprecated. Use makeRef() instead.');
|
||||
ref[0][ref[1]] = node;
|
||||
} else {
|
||||
ref.value = node;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
export {jsx as jsxs};
|
||||
@@ -1,36 +0,0 @@
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Container} from 'konva/lib/Container';
|
||||
import {IRect} from 'konva/lib/types';
|
||||
|
||||
declare module 'konva/lib/Container' {
|
||||
export interface Container {
|
||||
getChildrenRect(config?: {
|
||||
skipTransform?: boolean;
|
||||
skipShadow?: boolean;
|
||||
skipStroke?: boolean;
|
||||
relativeTo?: Container<Node>;
|
||||
}): IRect;
|
||||
}
|
||||
}
|
||||
|
||||
Container.prototype.updateLayout = function (this: Container): void {
|
||||
for (const child of this.children) {
|
||||
child.updateLayout();
|
||||
if (child.wasDirty()) {
|
||||
this.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
Node.prototype.updateLayout.call(this);
|
||||
};
|
||||
|
||||
Container.prototype._centroid = true;
|
||||
|
||||
const super_setChildrenIndices = Container.prototype._setChildrenIndices;
|
||||
Container.prototype._setChildrenIndices = function (this: Container) {
|
||||
super_setChildrenIndices.call(this);
|
||||
this.markDirty();
|
||||
};
|
||||
|
||||
Container.prototype.getChildrenRect = Container.prototype.getClientRect;
|
||||
Container.prototype.getClientRect = Node.prototype.getClientRect;
|
||||
@@ -1,64 +0,0 @@
|
||||
import type {Node} from 'konva/lib/Node';
|
||||
import {Factory} from 'konva/lib/Factory';
|
||||
import {ANIMATE} from '@motion-canvas/core/lib';
|
||||
import {
|
||||
Animator,
|
||||
TimingFunction,
|
||||
InterpolationFunction,
|
||||
TweenProvider,
|
||||
} from '@motion-canvas/core/lib/tweening';
|
||||
import {ThreadGenerator} from '@motion-canvas/core/lib/threading';
|
||||
import {Vector2d} from 'konva/lib/types';
|
||||
|
||||
declare module 'konva/lib/Factory' {
|
||||
export interface Factory {
|
||||
addOverloadedGetterSetter(
|
||||
constructor: new (...args: unknown[]) => unknown,
|
||||
attr: string,
|
||||
tween?: TweenProvider<unknown>,
|
||||
): void;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'konva/lib/types' {
|
||||
export interface GetSet<Type, This extends Node> {
|
||||
(): Type;
|
||||
(value: Type): This;
|
||||
(value: typeof ANIMATE): Animator<Type, This>;
|
||||
<Rest extends unknown[]>(
|
||||
value: Type,
|
||||
time: number,
|
||||
timingFunction?: TimingFunction,
|
||||
interpolationFunction?: InterpolationFunction<Type, Rest>,
|
||||
...rest: Rest
|
||||
): ThreadGenerator;
|
||||
}
|
||||
}
|
||||
|
||||
Factory.addOverloadedGetterSetter = function addOverloadedGetterSetter(
|
||||
constructor: new (...args: unknown[]) => unknown,
|
||||
attr: string,
|
||||
tween?: TweenProvider<unknown>,
|
||||
) {
|
||||
const capitalizedAttr = attr.charAt(0).toUpperCase() + attr.slice(1);
|
||||
const setter = 'set' + capitalizedAttr;
|
||||
const getter = 'get' + capitalizedAttr;
|
||||
|
||||
constructor.prototype[attr] = function <Rest extends unknown[]>(
|
||||
value?: Vector2d | typeof ANIMATE,
|
||||
time?: number,
|
||||
timingFunction?: TimingFunction,
|
||||
interpolationFunction?: InterpolationFunction<unknown, Rest>,
|
||||
...rest: Rest
|
||||
) {
|
||||
if (value === ANIMATE) {
|
||||
return new Animator<unknown, Node>(this, attr, tween);
|
||||
}
|
||||
if (time !== undefined) {
|
||||
return new Animator<unknown, Node>(this, attr, tween)
|
||||
.key(value, time, timingFunction, interpolationFunction, ...rest)
|
||||
.run();
|
||||
}
|
||||
return value === undefined ? this[getter]() : this[setter](value);
|
||||
};
|
||||
};
|
||||
@@ -1,370 +0,0 @@
|
||||
import type {Style} from '../styles';
|
||||
import {Node, NodeConfig} from 'konva/lib/Node';
|
||||
import {Origin} from '@motion-canvas/core/lib/types';
|
||||
import {PossibleSpacing, Size, Spacing, getOriginDelta} from '../types';
|
||||
import {GetSet, IRect, Vector2d} from 'konva/lib/types';
|
||||
import {Factory} from 'konva/lib/Factory';
|
||||
import {Container} from 'konva/lib/Container';
|
||||
import {NODE_ID} from '@motion-canvas/core/lib';
|
||||
import {useScene} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
declare module 'konva/lib/Node' {
|
||||
export interface Node {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_centroid: boolean;
|
||||
|
||||
style?: GetSet<Partial<Style>, this>;
|
||||
|
||||
/**
|
||||
* The empty space between the borders of this node and its content.
|
||||
*
|
||||
* Analogous to CSS padding.
|
||||
*/
|
||||
padd: GetSet<PossibleSpacing, this>;
|
||||
|
||||
/**
|
||||
* The empty space between the borders of this node and surrounding nodes.
|
||||
*
|
||||
* Analogous to CSS margin.
|
||||
*/
|
||||
margin: GetSet<PossibleSpacing, this>;
|
||||
|
||||
/**
|
||||
* The origin of this node.
|
||||
*
|
||||
* By default, each node has its origin in the middle.
|
||||
*
|
||||
* Analogous to CSS margin.
|
||||
*/
|
||||
origin: GetSet<Origin, this>;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
setX(value: number): this;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
setY(value: number): this;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
setWidth(width: number): void;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
setHeight(height: number): void;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
setPadd(value: PossibleSpacing): this;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
setMargin(value: PossibleSpacing): this;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
setOrigin(value: Origin): this;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
getPadd(): Spacing;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
getMargin(): Spacing;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
getOrigin(): Origin;
|
||||
|
||||
/**
|
||||
* Get the size of this node used for layout calculations.
|
||||
*
|
||||
* @remarks
|
||||
* The returned size should include the padding.
|
||||
* A node can use the size of its children to derive its own dimensions.
|
||||
*
|
||||
* @param custom - Custom node configuration to use during the calculations.
|
||||
* When present, the method will return the layout size that
|
||||
* the node would have, if it had these options configured.
|
||||
*/
|
||||
getLayoutSize(custom?: NodeConfig): Size;
|
||||
|
||||
/**
|
||||
* Get the vector from the local origin of this node to its current origin.
|
||||
*
|
||||
* @remarks
|
||||
* The local origin is the center of coordinates of the canvas when drawing
|
||||
* the node. Centroid nodes will have their local origin at the center.
|
||||
* Other shapes will have it in the top left corner.
|
||||
*
|
||||
* The current origin is configured via {@link Node.origin}.
|
||||
*
|
||||
* @param custom - Custom node configuration to use during the calculations.
|
||||
* When present, the method will return the origin offset
|
||||
* that the node would have, if it had these options
|
||||
* configured.
|
||||
*/
|
||||
getOriginOffset(custom?: NodeConfig): Vector2d;
|
||||
|
||||
/**
|
||||
* Get the vector from the current origin of this node to the `newOrigin`.
|
||||
*
|
||||
* @param newOrigin - The origin to which the delta should be calculated.
|
||||
*
|
||||
* @param custom - Custom node configuration to use during the calculations.
|
||||
* When present, the method will return the origin offset
|
||||
* that the node would have, if it had these options
|
||||
* configured.
|
||||
*/
|
||||
getOriginDelta(newOrigin: Origin, custom?: NodeConfig): Vector2d;
|
||||
|
||||
/**
|
||||
* Update the layout of this node and all its children.
|
||||
*
|
||||
* @remarks
|
||||
* If the node is considered dirty the {@link recalculateLayout} method will
|
||||
* be called.
|
||||
*/
|
||||
updateLayout(): void;
|
||||
|
||||
/**
|
||||
* Perform any computations necessary to update the layout of this node.
|
||||
*/
|
||||
recalculateLayout(): void;
|
||||
|
||||
/**
|
||||
* Mark this node as dirty.
|
||||
*
|
||||
* @remarks
|
||||
* It will cause the layout of this node and all its ancestors to be
|
||||
* recalculated before drawing the next frame.
|
||||
*/
|
||||
markDirty(force?: boolean): void;
|
||||
|
||||
/**
|
||||
* Check if this node is dirty.
|
||||
*/
|
||||
isDirty(): boolean;
|
||||
|
||||
/**
|
||||
* Check if the layout of this node has been recalculated during the current
|
||||
* layout process.
|
||||
*
|
||||
* @remarks
|
||||
* Containers can use this method to check if their children has changed.
|
||||
*/
|
||||
wasDirty(): boolean;
|
||||
|
||||
subscribe(event: string, handler: () => void): () => void;
|
||||
|
||||
_clearCache(attr?: string | Callback): void;
|
||||
}
|
||||
|
||||
export interface NodeConfig {
|
||||
margin?: PossibleSpacing;
|
||||
padd?: PossibleSpacing;
|
||||
origin?: Origin;
|
||||
}
|
||||
}
|
||||
|
||||
Node.prototype.setPadd = function (this: Node, value: PossibleSpacing) {
|
||||
this.attrs.padd = new Spacing(value);
|
||||
this.markDirty();
|
||||
return this;
|
||||
};
|
||||
|
||||
Node.prototype.setMargin = function (this: Node, value: PossibleSpacing) {
|
||||
this.attrs.margin = new Spacing(value);
|
||||
this.markDirty();
|
||||
return this;
|
||||
};
|
||||
|
||||
Node.prototype.setOrigin = function (this: Node, value: Origin) {
|
||||
this.attrs.origin = value;
|
||||
this.markDirty();
|
||||
return this;
|
||||
};
|
||||
|
||||
Node.prototype.getLayoutSize = function (
|
||||
this: Node,
|
||||
custom?: NodeConfig,
|
||||
): Size {
|
||||
const padding =
|
||||
custom?.padd === null || custom?.padd === undefined
|
||||
? this.getPadd()
|
||||
: new Spacing(custom.padd);
|
||||
|
||||
return padding.expand(this.getSize());
|
||||
};
|
||||
|
||||
Node.prototype.getOriginOffset = function (
|
||||
this: Node,
|
||||
custom?: NodeConfig,
|
||||
): Vector2d {
|
||||
return getOriginDelta(
|
||||
this.getLayoutSize(custom),
|
||||
this._centroid ? Origin.Middle : Origin.TopLeft,
|
||||
custom?.origin ?? this.getOrigin(),
|
||||
);
|
||||
};
|
||||
|
||||
Node.prototype.getOriginDelta = function (
|
||||
this: Node,
|
||||
newOrigin?: Origin,
|
||||
custom?: NodeConfig,
|
||||
): Vector2d {
|
||||
return getOriginDelta(
|
||||
this.getLayoutSize(custom),
|
||||
custom?.origin ?? this.getOrigin(),
|
||||
newOrigin,
|
||||
);
|
||||
};
|
||||
|
||||
Node.prototype.updateLayout = function (this: Node): void {
|
||||
this.attrs.wasDirty = false;
|
||||
if (this.isDirty()) {
|
||||
this.recalculateLayout();
|
||||
this.attrs.dirty = false;
|
||||
this.attrs.wasDirty = true;
|
||||
}
|
||||
};
|
||||
|
||||
Node.prototype.recalculateLayout = function (this: Node): void {
|
||||
// do nothing
|
||||
};
|
||||
|
||||
Node.prototype.markDirty = function (this: Node, force = false): void {
|
||||
this.attrs.dirty = true;
|
||||
if (
|
||||
force ||
|
||||
// When the layout size changes and the origin is other than default,
|
||||
// the transform will also most likely change.
|
||||
(this._centroid ? Origin.Middle : Origin.TopLeft) !== this.origin()
|
||||
) {
|
||||
this._clearCache('transform');
|
||||
this._clearCache('absoluteTransform');
|
||||
}
|
||||
};
|
||||
|
||||
Node.prototype.isDirty = function (this: Node): boolean {
|
||||
return this.attrs.dirty;
|
||||
};
|
||||
|
||||
Node.prototype.wasDirty = function (this: Node): boolean {
|
||||
return this.attrs.wasDirty;
|
||||
};
|
||||
|
||||
Node.prototype.subscribe = function (
|
||||
this: Node,
|
||||
event: string,
|
||||
handler: () => void,
|
||||
): () => void {
|
||||
this.on(event, handler);
|
||||
return () => this.off(event, handler);
|
||||
};
|
||||
|
||||
Node.prototype.getClientRect = function (
|
||||
this: Node,
|
||||
config?: {
|
||||
skipTransform?: boolean;
|
||||
skipShadow?: boolean;
|
||||
skipStroke?: boolean;
|
||||
relativeTo?: Container;
|
||||
},
|
||||
): IRect {
|
||||
const size = this.getLayoutSize();
|
||||
const offset = this.getOriginOffset({origin: Origin.TopLeft});
|
||||
|
||||
const rect: IRect = {
|
||||
x: offset.x,
|
||||
y: offset.y,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
};
|
||||
|
||||
if (!config?.skipTransform) {
|
||||
return this._transformedRect(rect, config?.relativeTo);
|
||||
}
|
||||
|
||||
return rect;
|
||||
};
|
||||
|
||||
const super_setX = Node.prototype.setX;
|
||||
Node.prototype.setX = function (this: Node, value: number) {
|
||||
if (this.attrs.x !== value) this.markDirty();
|
||||
return super_setX.call(this, value);
|
||||
};
|
||||
|
||||
const super_setY = Node.prototype.setY;
|
||||
Node.prototype.setY = function (this: Node, value: number) {
|
||||
if (this.attrs.y !== value) this.markDirty();
|
||||
return super_setY.call(this, value);
|
||||
};
|
||||
|
||||
const super_setWidth = Node.prototype.setWidth;
|
||||
Node.prototype.setWidth = function (this: Node, value: number) {
|
||||
if (this.attrs.width !== value) {
|
||||
this.markDirty();
|
||||
}
|
||||
return super_setWidth.call(this, value);
|
||||
};
|
||||
|
||||
const super_setHeight = Node.prototype.setHeight;
|
||||
Node.prototype.setHeight = function (this: Node, value: number) {
|
||||
if (this.attrs.height !== value) {
|
||||
this.markDirty();
|
||||
}
|
||||
return super_setHeight.call(this, value);
|
||||
};
|
||||
|
||||
const super__getTransform = Node.prototype._getTransform;
|
||||
Node.prototype._getTransform = function (this: Node) {
|
||||
const m = super__getTransform.call(this);
|
||||
const offset = this.getOriginOffset();
|
||||
if (offset.x !== 0 || offset.y !== 0) {
|
||||
m.translate(-1 * offset.x, -1 * offset.y);
|
||||
}
|
||||
m.dirty = false;
|
||||
return m;
|
||||
};
|
||||
|
||||
const super_setAttrs = Node.prototype.setAttrs;
|
||||
Node.prototype.setAttrs = function (this: Node, config: unknown) {
|
||||
if (!(NODE_ID in this.attrs)) {
|
||||
const scene: any = useScene();
|
||||
if (scene && 'generateNodeId' in scene) {
|
||||
const type = this.className;
|
||||
this.attrs[NODE_ID] = scene.generateNodeId(type);
|
||||
}
|
||||
}
|
||||
return super_setAttrs.call(this, config);
|
||||
};
|
||||
|
||||
const super__clearCache = Node.prototype._clearCache;
|
||||
Node.prototype._clearCache = function (this: Node, attr?: string | Callback) {
|
||||
if (typeof attr === 'function') {
|
||||
if (attr.prototype?.cachedKey) {
|
||||
this._cache.delete(attr.prototype.cachedKey);
|
||||
}
|
||||
} else {
|
||||
super__clearCache.call(this, attr);
|
||||
}
|
||||
};
|
||||
|
||||
Factory.addGetterSetter(Node, 'padd', new Spacing());
|
||||
Factory.addGetterSetter(Node, 'margin', new Spacing());
|
||||
Factory.addGetterSetter(Node, 'origin', Origin.Middle);
|
||||
@@ -1,16 +0,0 @@
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Shape, ShapeGetClientRectConfig} from 'konva/lib/Shape';
|
||||
|
||||
declare module 'konva/lib/Shape' {
|
||||
export interface Shape {
|
||||
getShapeRect(config?: ShapeGetClientRectConfig): {
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Shape.prototype.getShapeRect = Shape.prototype.getClientRect;
|
||||
Shape.prototype.getClientRect = Node.prototype.getClientRect;
|
||||
@@ -1,190 +0,0 @@
|
||||
import {Container} from 'konva/lib/Container';
|
||||
import {
|
||||
GeneratorScene,
|
||||
Inspectable,
|
||||
InspectedElement,
|
||||
InspectedAttributes,
|
||||
InspectedSize,
|
||||
Scene,
|
||||
SceneDescription,
|
||||
SceneRenderEvent,
|
||||
ThreadGeneratorFactory,
|
||||
} from '@motion-canvas/core/lib/scenes';
|
||||
import {HitCanvas, SceneCanvas} from 'konva/lib/Canvas';
|
||||
import {Shape, shapes} from 'konva/lib/Shape';
|
||||
import {Group} from 'konva/lib/Group';
|
||||
import {useScene} from '@motion-canvas/core/lib/utils';
|
||||
import {Util} from 'konva/lib/Util';
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Konva} from 'konva/lib/Global';
|
||||
import {NODE_ID} from '@motion-canvas/core/lib';
|
||||
import {Rect, Vector2} from '@motion-canvas/core/lib/types';
|
||||
|
||||
Konva.autoDrawEnabled = false;
|
||||
|
||||
const sceneCanvasMap = new Map<HTMLCanvasElement, SceneCanvas>();
|
||||
|
||||
export function useKonvaView(): KonvaView {
|
||||
const scene = useScene();
|
||||
if (scene instanceof KonvaScene) {
|
||||
return scene.view;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a descriptor for a Konva scene.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // example.scene.ts
|
||||
*
|
||||
* export default makeKonvaScene(function* example(view) {
|
||||
* yield* view.transition();
|
||||
* // perform animation
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param factory - The generator function for this scene.
|
||||
*/
|
||||
export function makeKonvaScene(
|
||||
factory: ThreadGeneratorFactory<KonvaView>,
|
||||
): SceneDescription {
|
||||
return {
|
||||
config: factory,
|
||||
klass: KonvaScene,
|
||||
};
|
||||
}
|
||||
|
||||
class KonvaView extends Container {
|
||||
public constructor(private readonly scene: KonvaScene) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start transitioning out of the current scene.
|
||||
*/
|
||||
public canFinish() {
|
||||
this.scene.enterCanTransitionOut();
|
||||
}
|
||||
|
||||
public updateLayout() {
|
||||
super.updateLayout();
|
||||
let limit = 10;
|
||||
while (this.wasDirty() && limit > 0) {
|
||||
super.updateLayout();
|
||||
limit--;
|
||||
}
|
||||
|
||||
if (limit === 0) {
|
||||
console.warn('Layout iteration limit exceeded');
|
||||
}
|
||||
}
|
||||
|
||||
public add(...children: (Shape | Group)[]): this {
|
||||
super.add(...children.flat());
|
||||
this.updateLayout();
|
||||
return this;
|
||||
}
|
||||
|
||||
public _validateAdd() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export class KonvaScene
|
||||
extends GeneratorScene<KonvaView>
|
||||
implements Inspectable
|
||||
{
|
||||
public readonly view = new KonvaView(this);
|
||||
private hitCanvas = new HitCanvas({pixelRatio: 1});
|
||||
|
||||
public getView(): KonvaView {
|
||||
return this.view;
|
||||
}
|
||||
|
||||
public update() {
|
||||
this.view.updateLayout();
|
||||
}
|
||||
|
||||
public render(context: CanvasRenderingContext2D, canvas: HTMLCanvasElement) {
|
||||
let sceneCanvas = sceneCanvasMap.get(canvas);
|
||||
if (!sceneCanvas) {
|
||||
sceneCanvas = new SceneCanvas({
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
pixelRatio: 1,
|
||||
});
|
||||
sceneCanvas._canvas = canvas;
|
||||
sceneCanvas.getContext()._context = context;
|
||||
}
|
||||
|
||||
context.save();
|
||||
this.renderLifecycle.dispatch([SceneRenderEvent.BeforeRender, context]);
|
||||
context.save();
|
||||
this.renderLifecycle.dispatch([SceneRenderEvent.BeginRender, context]);
|
||||
this.view.drawScene(sceneCanvas);
|
||||
this.renderLifecycle.dispatch([SceneRenderEvent.FinishRender, context]);
|
||||
context.restore();
|
||||
this.renderLifecycle.dispatch([SceneRenderEvent.AfterRender, context]);
|
||||
context.restore();
|
||||
}
|
||||
|
||||
public reset(previousScene: Scene = null) {
|
||||
this.view.x(0).y(0).opacity(1).show();
|
||||
this.view.destroyChildren();
|
||||
return super.reset(previousScene);
|
||||
}
|
||||
|
||||
//#region Inspectable Interface
|
||||
|
||||
public inspectPosition(x: number, y: number): InspectedElement | null {
|
||||
this.hitCanvas.setSize(this.getSize().width, this.getSize().height);
|
||||
this.project.transformCanvas(this.hitCanvas.context._context);
|
||||
this.view.drawHit(this.hitCanvas, this.view);
|
||||
|
||||
const color = this.hitCanvas.context.getImageData(x, y, 1, 1).data;
|
||||
if (color[3] < 255) return null;
|
||||
const key = Util._rgbToHex(color[0], color[1], color[2]);
|
||||
return shapes[`#${key}`] ?? null;
|
||||
}
|
||||
|
||||
public validateInspection(
|
||||
element: InspectedElement | null,
|
||||
): InspectedElement | null {
|
||||
if (!(element instanceof Node)) return null;
|
||||
if (element.isAncestorOf(this.view)) return element;
|
||||
const id = element.attrs[NODE_ID];
|
||||
return (
|
||||
this.view.findOne((node: Node) => node.attrs[NODE_ID] === id) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
public inspectAttributes(
|
||||
element: InspectedElement,
|
||||
): InspectedAttributes | null {
|
||||
if (!(element instanceof Node)) return null;
|
||||
return element.attrs;
|
||||
}
|
||||
|
||||
public inspectBoundingBox(element: InspectedElement): InspectedSize {
|
||||
if (!(element instanceof Node)) return {};
|
||||
|
||||
const rect = element.getClientRect({relativeTo: this.view});
|
||||
const scale = element.getAbsoluteScale(this.view);
|
||||
const position = element.getAbsolutePosition(this.view);
|
||||
const offset = element.getOriginOffset();
|
||||
|
||||
return {
|
||||
rect,
|
||||
contentRect: element.getPadd().scale(scale).shrink(rect),
|
||||
marginRect: element.getMargin().scale(scale).expand(rect),
|
||||
position: {
|
||||
x: position.x + offset.x,
|
||||
y: position.y + offset.y,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './KonvaScene';
|
||||
@@ -1,46 +0,0 @@
|
||||
import type {Node} from 'konva/lib/Node';
|
||||
import Color from 'colorjs.io';
|
||||
|
||||
export interface Style {
|
||||
labelFont: string;
|
||||
bodyFont: string;
|
||||
background: string;
|
||||
backgroundLight: string;
|
||||
foreground: string;
|
||||
foregroundLight: string;
|
||||
}
|
||||
|
||||
export const MISSING_STYLE: Style = {
|
||||
labelFont: '20px "Times New Roman"',
|
||||
bodyFont: '20px Arial',
|
||||
background: '#FF00FF',
|
||||
backgroundLight: '#FF00FF',
|
||||
foreground: '#FF00FF',
|
||||
foregroundLight: '#FF00FF',
|
||||
};
|
||||
|
||||
export function getStyle(node: Node): Style {
|
||||
let mergedStyle = {};
|
||||
|
||||
do {
|
||||
const style = node.style?.() ?? null;
|
||||
if (style) {
|
||||
mergedStyle = {
|
||||
...style,
|
||||
...mergedStyle,
|
||||
};
|
||||
}
|
||||
|
||||
node = node.getParent();
|
||||
} while (node);
|
||||
|
||||
return {
|
||||
...MISSING_STYLE,
|
||||
...mergedStyle,
|
||||
};
|
||||
}
|
||||
|
||||
export function getFontColor(background: string) {
|
||||
const color = new Color(background);
|
||||
return color.lab.l > 50 ? 'rgba(0, 0, 0, 0.87)' : 'rgba(255, 255, 255, 0.87)';
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './Style';
|
||||
@@ -1,35 +0,0 @@
|
||||
import {CodeTheme, JSCodeTokens} from '../components/code';
|
||||
|
||||
const KEYWORD = '#ff6470';
|
||||
const TEXT = '#ACB3BF';
|
||||
const FUNCTION = '#ffc66d';
|
||||
const STRING = '#99C47A';
|
||||
const NUMBER = '#68ABDF';
|
||||
const PROPERTY = '#AC7BB5';
|
||||
const COMMENT = '#5c5e60';
|
||||
|
||||
export const JS_CODE_THEME: CodeTheme<JSCodeTokens> = {
|
||||
default: {
|
||||
boolean: KEYWORD,
|
||||
constant: KEYWORD,
|
||||
keyword: KEYWORD,
|
||||
'class-name': NUMBER,
|
||||
operator: TEXT,
|
||||
punctuation: TEXT,
|
||||
'script-punctuation': TEXT,
|
||||
function: FUNCTION,
|
||||
string: STRING,
|
||||
number: NUMBER,
|
||||
'literal-property': PROPERTY,
|
||||
comment: COMMENT,
|
||||
'function-variable': FUNCTION,
|
||||
},
|
||||
parameter: {
|
||||
'literal-property': TEXT,
|
||||
operator: TEXT,
|
||||
punctuation: NUMBER,
|
||||
},
|
||||
'attr-name': {
|
||||
punctuation: FUNCTION,
|
||||
},
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './code';
|
||||
@@ -1,46 +0,0 @@
|
||||
import type {Vector2} from './Vector';
|
||||
import type {Size} from './Size';
|
||||
import {Direction, Origin} from '@motion-canvas/core/lib/types';
|
||||
|
||||
export function originPosition(
|
||||
origin: Origin | Direction,
|
||||
width = 1,
|
||||
height = 1,
|
||||
): Vector2 {
|
||||
const position: Vector2 = {x: 0, y: 0};
|
||||
|
||||
if (origin === Origin.Middle) {
|
||||
return position;
|
||||
}
|
||||
|
||||
if (origin & Direction.Left) {
|
||||
position.x = -width;
|
||||
} else if (origin & Direction.Right) {
|
||||
position.x = width;
|
||||
}
|
||||
|
||||
if (origin & Direction.Top) {
|
||||
position.y = -height;
|
||||
} else if (origin & Direction.Bottom) {
|
||||
position.y = height;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
export function getOriginOffset(size: Size, origin: Origin): Vector2 {
|
||||
return originPosition(origin, size.width / 2, size.height / 2);
|
||||
}
|
||||
|
||||
export function getOriginDelta(size: Size, from: Origin, to: Origin) {
|
||||
const fromOffset = getOriginOffset(size, from);
|
||||
if (to === Origin.Middle) {
|
||||
return {x: -fromOffset.x, y: -fromOffset.y};
|
||||
}
|
||||
|
||||
const toOffset = getOriginOffset(size, to);
|
||||
return {
|
||||
x: toOffset.x - fromOffset.x,
|
||||
y: toOffset.y - fromOffset.y,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export interface Rect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import type {Size} from './Size';
|
||||
import type {Rect} from './Rect';
|
||||
import type {Vector2} from './Vector';
|
||||
|
||||
interface ISpacing {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
}
|
||||
|
||||
export type PossibleSpacing =
|
||||
| ISpacing
|
||||
| number
|
||||
| [number, number]
|
||||
| [number, number, number]
|
||||
| [number, number, number, number];
|
||||
|
||||
export class Spacing implements ISpacing {
|
||||
public top = 0;
|
||||
public right = 0;
|
||||
public bottom = 0;
|
||||
public left = 0;
|
||||
|
||||
public get x(): number {
|
||||
return this.left + this.right;
|
||||
}
|
||||
|
||||
public get y(): number {
|
||||
return this.top + this.bottom;
|
||||
}
|
||||
|
||||
public constructor(value?: PossibleSpacing) {
|
||||
if (value !== undefined) {
|
||||
this.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
public set(value: PossibleSpacing): this {
|
||||
if (Array.isArray(value)) {
|
||||
switch (value.length) {
|
||||
case 2:
|
||||
this.top = this.bottom = value[0];
|
||||
this.right = this.left = value[1];
|
||||
break;
|
||||
case 3:
|
||||
this.top = value[0];
|
||||
this.right = this.left = value[1];
|
||||
this.bottom = value[2];
|
||||
break;
|
||||
case 4:
|
||||
this.top = value[0];
|
||||
this.right = value[1];
|
||||
this.bottom = value[2];
|
||||
this.left = value[3];
|
||||
break;
|
||||
}
|
||||
} else if (typeof value === 'object') {
|
||||
this.top = value.top ?? 0;
|
||||
this.right = value.right ?? 0;
|
||||
this.bottom = value.bottom ?? 0;
|
||||
this.left = value.left ?? 0;
|
||||
return this;
|
||||
} else {
|
||||
this.top = this.right = this.bottom = this.left = value;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public expand<T extends Size | Rect>(value: T): T {
|
||||
const result = {...value};
|
||||
|
||||
result.width += this.x;
|
||||
result.height += this.y;
|
||||
if ('x' in result) {
|
||||
result.x -= this.left;
|
||||
result.y -= this.top;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public shrink<T extends Size | Rect>(value: T): T {
|
||||
const result = {...value};
|
||||
|
||||
result.width -= this.x;
|
||||
result.height -= this.y;
|
||||
if ('x' in result) {
|
||||
result.x += this.left;
|
||||
result.y += this.top;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public scale(scale: Vector2): Spacing {
|
||||
return new Spacing([
|
||||
this.top * scale.y,
|
||||
this.right * scale.x,
|
||||
this.bottom * scale.y,
|
||||
this.left * scale.x,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface Vector2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Vector3 {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './Origin';
|
||||
export * from './Rect';
|
||||
export * from './Size';
|
||||
export * from './Spacing';
|
||||
export * from './Vector';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './slide';
|
||||
@@ -1,23 +0,0 @@
|
||||
import type {Container} from 'konva/lib/Container';
|
||||
import type {Vector2} from '../types';
|
||||
|
||||
export function slide(container: Container, offset: Vector2): void;
|
||||
export function slide(container: Container, x: number, y?: number): void;
|
||||
export function slide(
|
||||
container: Container,
|
||||
offset: number | Vector2,
|
||||
y = 0,
|
||||
): void {
|
||||
if (typeof offset === 'number') {
|
||||
offset = {x: offset, y};
|
||||
} else {
|
||||
offset = {...offset};
|
||||
}
|
||||
|
||||
container.move(offset);
|
||||
offset.x *= -1;
|
||||
offset.y *= -1;
|
||||
for (const child of container.children) {
|
||||
child.move(offset);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"outDir": "lib",
|
||||
"inlineSourceMap": true,
|
||||
"noImplicitAny": true,
|
||||
"module": "esnext",
|
||||
"target": "es2020",
|
||||
"allowJs": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@motion-canvas/legacy/lib",
|
||||
"paths": {
|
||||
"@motion-canvas/legacy/lib/jsx-runtime": ["jsx-runtime.ts"]
|
||||
},
|
||||
"types": ["node", "prismjs", "three"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "@motion-canvas/core/tsconfig.project.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@motion-canvas/legacy/lib"
|
||||
}
|
||||
}
|
||||
3
packages/legacy/vite/index.d.ts
vendored
3
packages/legacy/vite/index.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
import type {Plugin} from 'vite';
|
||||
declare const _default: () => Plugin;
|
||||
export default _default;
|
||||
@@ -1,22 +0,0 @@
|
||||
module.exports = () => ({
|
||||
name: 'motion-canvas:legacy',
|
||||
transform(code, id) {
|
||||
if (id.endsWith('?project')) {
|
||||
return (
|
||||
`import '@motion-canvas/legacy/lib/patches/Factory';` +
|
||||
`import '@motion-canvas/legacy/lib/patches/Node';` +
|
||||
`import '@motion-canvas/legacy/lib/patches/Shape';` +
|
||||
`import '@motion-canvas/legacy/lib/patches/Container';` +
|
||||
code
|
||||
);
|
||||
}
|
||||
},
|
||||
config() {
|
||||
return {
|
||||
esbuild: {
|
||||
jsx: 'automatic',
|
||||
jsxImportSource: '@motion-canvas/legacy/lib',
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
2
packages/template/src/motion-canvas.d.ts
vendored
2
packages/template/src/motion-canvas.d.ts
vendored
@@ -1 +1 @@
|
||||
/// <reference types="@motion-canvas/legacy/project" />
|
||||
/// <reference types="@motion-canvas/core/project" />
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {makeKonvaScene} from '@motion-canvas/legacy/lib/scenes';
|
||||
import {makeScene2D} from '@motion-canvas/2d';
|
||||
import {Circle} from '@motion-canvas/2d/lib/components';
|
||||
import {waitFor, waitUntil} from '@motion-canvas/core/lib/flow';
|
||||
import {useRef} from '@motion-canvas/core/lib/utils';
|
||||
import {Circle} from 'konva/lib/shapes/Circle';
|
||||
import {Vector2} from '@motion-canvas/core/lib/types';
|
||||
|
||||
export default makeKonvaScene(function* (view) {
|
||||
export default makeScene2D(function* (view) {
|
||||
const circle = useRef<Circle>();
|
||||
|
||||
view.add(
|
||||
@@ -11,7 +12,7 @@ export default makeKonvaScene(function* (view) {
|
||||
);
|
||||
|
||||
yield* waitUntil('circle');
|
||||
yield* circle.value.scale({x: 2, y: 2}, 2);
|
||||
yield* circle.value.scale(Vector2.fromScalar(2), 2);
|
||||
|
||||
yield* waitFor(5);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@motion-canvas/legacy/tsconfig.project.json",
|
||||
"extends": "@motion-canvas/2d/tsconfig.project.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"types": ["node", "prismjs", "three", "dom-webcodecs"]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {defineConfig} from 'vite';
|
||||
import motionCanvas from '@motion-canvas/vite-plugin';
|
||||
import legacyRenderer from '@motion-canvas/legacy/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [motionCanvas(), legacyRenderer()],
|
||||
plugins: [motionCanvas()],
|
||||
});
|
||||
|
||||
@@ -339,6 +339,10 @@ export default ({
|
||||
server: {
|
||||
port: 9000,
|
||||
},
|
||||
esbuild: {
|
||||
jsx: 'automatic',
|
||||
jsxImportSource: '@motion-canvas/2d/lib',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user