mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-11 23:07:57 -05:00
feat: add basic documentation structure (#10)
Documentation can now be accessed by visiting http://localhost:9000/api/ BREAKING CHANGE: `waitFor` and `waitUntil` were moved They should be imported from `@motion-canvas/core/lib/flow`. Fixes #2
This commit is contained in:
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
registry-url: https://npm.pkg.github.com/
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run docs
|
||||
- run: npm run release
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
lib
|
||||
api
|
||||
test
|
||||
.idea
|
||||
@@ -89,7 +89,7 @@ Otherwise, below are the steps to set it up manually:
|
||||
7. Initialize the project with your scene in `src/project.ts`:
|
||||
|
||||
```ts
|
||||
import {bootstrap} from '@motion-canvas/core/lib/bootstrap';
|
||||
import {bootstrap} from '@motion-canvas/core/lib';
|
||||
|
||||
import example from './scenes/example.scene';
|
||||
|
||||
|
||||
@@ -121,6 +121,13 @@ const server = new WebpackDevServer(
|
||||
compress: true,
|
||||
port: 9000,
|
||||
hot: true,
|
||||
static: [
|
||||
{
|
||||
directory: path.join(__dirname, '../api'),
|
||||
publicPath: '/api',
|
||||
watch: false,
|
||||
}
|
||||
],
|
||||
setupMiddlewares: middlewares => {
|
||||
middlewares.unshift({
|
||||
name: 'render',
|
||||
|
||||
181
package-lock.json
generated
181
package-lock.json
generated
@@ -43,7 +43,8 @@
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"eslint": "^8.17.0",
|
||||
"prettier": "^2.6.2",
|
||||
"semantic-release": "^19.0.2"
|
||||
"semantic-release": "^19.0.2",
|
||||
"typedoc": "^0.22.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -5155,6 +5156,12 @@
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
@@ -5353,6 +5360,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
@@ -9611,6 +9624,17 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz",
|
||||
"integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"vscode-oniguruma": "^1.6.1",
|
||||
"vscode-textmate": "5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
@@ -10455,6 +10479,68 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc": {
|
||||
"version": "0.22.17",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.17.tgz",
|
||||
"integrity": "sha512-h6+uXHVVCPDaANzjwzdsj9aePBjZiBTpiMpBBeyh1zcN2odVsDCNajz8zyKnixF93HJeGpl34j/70yoEE5BfNg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^8.0.3",
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^4.0.16",
|
||||
"minimatch": "^5.1.0",
|
||||
"shiki": "^0.10.1"
|
||||
},
|
||||
"bin": {
|
||||
"typedoc": "bin/typedoc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x || 4.7.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc/node_modules/glob": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
|
||||
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.0.1",
|
||||
"once": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc/node_modules/minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
|
||||
@@ -10578,6 +10664,18 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-oniguruma": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz",
|
||||
"integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vscode-textmate": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
|
||||
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||
@@ -14990,6 +15088,12 @@
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
@@ -15141,6 +15245,12 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
@@ -18145,6 +18255,17 @@
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
|
||||
},
|
||||
"shiki": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz",
|
||||
"integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"vscode-oniguruma": "^1.6.1",
|
||||
"vscode-textmate": "5.2.0"
|
||||
}
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
@@ -18781,6 +18902,52 @@
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"typedoc": {
|
||||
"version": "0.22.17",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.17.tgz",
|
||||
"integrity": "sha512-h6+uXHVVCPDaANzjwzdsj9aePBjZiBTpiMpBBeyh1zcN2odVsDCNajz8zyKnixF93HJeGpl34j/70yoEE5BfNg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^8.0.3",
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^4.0.16",
|
||||
"minimatch": "^5.1.0",
|
||||
"shiki": "^0.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
|
||||
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.0.1",
|
||||
"once": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
|
||||
@@ -18873,6 +19040,18 @@
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"vscode-oniguruma": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz",
|
||||
"integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==",
|
||||
"dev": true
|
||||
},
|
||||
"vscode-textmate": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
|
||||
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"watchpack": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
"lint:fix": "eslint --fix \"src/**/*.ts?(x)\"",
|
||||
"prettier": "prettier --check \"src/**/*\"",
|
||||
"prettier:fix": "prettier --write \"src/**/*\"",
|
||||
"docs": "typedoc",
|
||||
"docs:watch": "typedoc --watch",
|
||||
"release": "semantic-release"
|
||||
},
|
||||
"publishConfig": {
|
||||
@@ -27,6 +29,7 @@
|
||||
"files": [
|
||||
"lib",
|
||||
"bin",
|
||||
"api",
|
||||
"tsconfig.project.json"
|
||||
],
|
||||
"dependencies": {
|
||||
@@ -61,6 +64,7 @@
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"eslint": "^8.17.0",
|
||||
"prettier": "^2.6.2",
|
||||
"semantic-release": "^19.0.2"
|
||||
"semantic-release": "^19.0.2",
|
||||
"typedoc": "^0.22.17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export * from './motion';
|
||||
export * from './scheduling';
|
||||
export * from './sequence';
|
||||
/**
|
||||
* Animation utilities.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
export * from './show';
|
||||
export * from './surfaceFrom';
|
||||
export * from './surfaceTransition';
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import {Vector2d} from 'konva/lib/types';
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {tween, easeInOutQuint, vector2dTween} from '../tweening';
|
||||
import {ThreadGenerator} from '../threading';
|
||||
|
||||
export interface MoveConfig {
|
||||
absolute?: boolean;
|
||||
speed?: number;
|
||||
}
|
||||
|
||||
decorate(move, threadable());
|
||||
export function move(
|
||||
node: Node,
|
||||
position: Vector2d,
|
||||
config?: MoveConfig,
|
||||
): ThreadGenerator;
|
||||
export function move(
|
||||
node: Node,
|
||||
positionX: number,
|
||||
positionY: number,
|
||||
config?: MoveConfig,
|
||||
): ThreadGenerator;
|
||||
export function move(
|
||||
node: Node,
|
||||
arg0: number | Vector2d,
|
||||
arg1?: number | MoveConfig,
|
||||
arg2?: MoveConfig,
|
||||
): ThreadGenerator {
|
||||
let delta: Vector2d;
|
||||
let config: MoveConfig;
|
||||
if (typeof arg0 === 'number') {
|
||||
delta = {x: arg0, y: <number>arg1};
|
||||
config = arg2 ?? {};
|
||||
} else {
|
||||
delta = arg0;
|
||||
config = <MoveConfig>arg1 ?? {};
|
||||
}
|
||||
|
||||
const positionFrom = node.position();
|
||||
const positionTo = config.absolute
|
||||
? delta
|
||||
: {x: delta.x + positionFrom.x, y: delta.y + positionFrom.y};
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(positionFrom.x - positionTo.x, 2) +
|
||||
Math.pow(positionFrom.y - positionTo.y, 2),
|
||||
);
|
||||
|
||||
return tween(config.speed ?? distance / 1000, value =>
|
||||
node.position(
|
||||
vector2dTween(positionFrom, positionTo, easeInOutQuint(value)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,74 +1,67 @@
|
||||
import {Node} from 'konva/lib/Node';
|
||||
import type {Node} from 'konva/lib/Node';
|
||||
import {Surface} from '../components';
|
||||
import {Origin, originPosition, Spacing} from '../types';
|
||||
import {chain} from '../flow';
|
||||
import {Origin, originPosition} from '../types';
|
||||
import {all} from '../flow';
|
||||
import {Vector2d} from 'konva/lib/types';
|
||||
import {
|
||||
clampRemap,
|
||||
easeInExpo,
|
||||
easeInOutCubic,
|
||||
easeOutCubic,
|
||||
easeOutExpo,
|
||||
linear,
|
||||
map,
|
||||
rectArcTween,
|
||||
spacingTween,
|
||||
tween,
|
||||
} from '../tweening';
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {ThreadGenerator} from '../threading';
|
||||
|
||||
decorate(showTop, threadable());
|
||||
export function showTop(node: Node): [ThreadGenerator, ThreadGenerator] {
|
||||
/**
|
||||
* Show the given node by sliding it up.
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
export function* showTop(node: Node): ThreadGenerator {
|
||||
const to = node.offsetY();
|
||||
const from = to - 40;
|
||||
node.show();
|
||||
node.cache();
|
||||
node.offsetY(from);
|
||||
node.opacity(0);
|
||||
|
||||
return [
|
||||
tween(0.5, value => {
|
||||
node.opacity(Math.min(1, linear(value, 0, 2)));
|
||||
node.offsetY(easeOutExpo(value, from, to));
|
||||
}),
|
||||
tween(0.5, value => {
|
||||
node.opacity(Math.min(1, linear(value, 2, 0)));
|
||||
node.offsetY(easeInExpo(value, to, from));
|
||||
}),
|
||||
];
|
||||
yield* all(
|
||||
node.opacity(1, 0.5, easeOutExpo),
|
||||
node.offsetY(to, 0.5, easeOutExpo),
|
||||
);
|
||||
node.clearCache();
|
||||
}
|
||||
|
||||
decorate(showSurface, threadable());
|
||||
export function showSurface(surface: Surface): ThreadGenerator {
|
||||
const marginFrom = new Spacing();
|
||||
const margin = surface.getMargin();
|
||||
const toMask = surface.getMask();
|
||||
const fromMask = {
|
||||
...toMask,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
|
||||
surface.setMargin(0);
|
||||
surface.setMask(fromMask);
|
||||
|
||||
return tween(
|
||||
0.5,
|
||||
value => {
|
||||
surface.setMask({
|
||||
...toMask,
|
||||
...rectArcTween(fromMask, toMask, easeInOutCubic(value)),
|
||||
});
|
||||
surface.setMargin(
|
||||
spacingTween(marginFrom, margin, easeInOutCubic(value)),
|
||||
);
|
||||
surface.opacity(clampRemap(0.3, 1, 0, 1, value));
|
||||
},
|
||||
() => surface.setMask(null),
|
||||
);
|
||||
decorate(showSurfaceVertically, threadable());
|
||||
/**
|
||||
* Show the given surface by expanding its mask vertically.
|
||||
*
|
||||
* @param surface
|
||||
*/
|
||||
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());
|
||||
export function showCircle(
|
||||
/**
|
||||
* Show the given surface using a circle mask.
|
||||
*
|
||||
* @param surface
|
||||
* @param duration
|
||||
* @param origin The center of the circle mask.
|
||||
*/
|
||||
export function* showCircle(
|
||||
surface: Surface,
|
||||
duration = 0.6,
|
||||
origin?: Origin | Vector2d,
|
||||
@@ -83,30 +76,13 @@ export function showCircle(
|
||||
radius: 1,
|
||||
},
|
||||
});
|
||||
surface.show();
|
||||
surface.setCircleMask(mask);
|
||||
const target = mask.radius;
|
||||
mask.radius = 0;
|
||||
|
||||
return chain(
|
||||
tween(duration, value => {
|
||||
mask.radius = easeInOutCubic(value, 0, target);
|
||||
}),
|
||||
() => surface.setCircleMask(null),
|
||||
);
|
||||
}
|
||||
|
||||
export function unravelSurface(surface: Surface): ThreadGenerator {
|
||||
const mask = surface.getMask();
|
||||
surface.show();
|
||||
surface.setMask({...mask, height: 0});
|
||||
return tween(
|
||||
0.5,
|
||||
value => {
|
||||
surface.setMask({
|
||||
...mask,
|
||||
height: map(0, mask.height, easeOutCubic(value)),
|
||||
});
|
||||
},
|
||||
() => surface.setMask(null),
|
||||
);
|
||||
yield* tween(duration, value => {
|
||||
mask.radius = easeInOutCubic(value, 0, target);
|
||||
});
|
||||
surface.setCircleMask(null);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {Surface} from '../components';
|
||||
import type {Vector2d} from 'konva/lib/types';
|
||||
import type {ThreadGenerator} from '../threading';
|
||||
import type {Surface, SurfaceMask} from '../components';
|
||||
import {
|
||||
calculateRatio,
|
||||
clampRemap,
|
||||
colorTween,
|
||||
easeInOutCubic,
|
||||
easeInOutQuint,
|
||||
@@ -9,73 +10,87 @@ import {
|
||||
tween,
|
||||
} from '../tweening';
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {ThreadGenerator} from '../threading';
|
||||
|
||||
/**
|
||||
* Configuration for {@link surfaceFrom}.
|
||||
*/
|
||||
export interface SurfaceFromConfig {
|
||||
/**
|
||||
* Whether the transition arc should be reversed.
|
||||
*
|
||||
* See {@link rectArcTween} from more detail.
|
||||
*/
|
||||
reverse?: boolean;
|
||||
onOpacityChange?: (
|
||||
surface: Surface,
|
||||
value: number,
|
||||
relativeValue: number,
|
||||
) => boolean | void;
|
||||
transitionTime?: number;
|
||||
/**
|
||||
* A function called when the initial surface is updated.
|
||||
*
|
||||
* @param surface The initial surface.
|
||||
* @param value Completion of the entire transition.
|
||||
*
|
||||
* @return `true` if the default changes made by {@link surfaceFrom}
|
||||
* should be prevented.
|
||||
*/
|
||||
onUpdate?: (surface: Surface, value: number) => boolean | void;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
decorate(surfaceFrom, threadable());
|
||||
export function surfaceFrom(fromSurface: Surface) {
|
||||
const from = fromSurface.getMask();
|
||||
/**
|
||||
* Animate the mask of the surface from the initial state to its current state.
|
||||
*
|
||||
* @param surface
|
||||
* @param mask The initial mask
|
||||
* @param position The initial position
|
||||
* @param config
|
||||
*/
|
||||
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();
|
||||
|
||||
decorate(surfaceTransitionExecutor, threadable());
|
||||
function* surfaceTransitionExecutor(
|
||||
target: Surface,
|
||||
config: SurfaceFromConfig = {},
|
||||
): ThreadGenerator {
|
||||
const transitionTime = config.transitionTime ?? 1 / 3;
|
||||
const to = target.getMask();
|
||||
const toPos = target.getPosition();
|
||||
const fromDelta = fromSurface.getOriginDelta(target.getOrigin());
|
||||
target.show();
|
||||
if (position) {
|
||||
surface.position(position);
|
||||
}
|
||||
surface.show().setMask(fromMask);
|
||||
|
||||
const ratio =
|
||||
(calculateRatio(fromSurface.getPosition(), toPos) +
|
||||
calculateRatio(from, to)) /
|
||||
2;
|
||||
const ratio =
|
||||
(calculateRatio(surface.getPosition(), toPosition) +
|
||||
calculateRatio(fromMask, toMask)) /
|
||||
2;
|
||||
|
||||
yield* tween(0.6, value => {
|
||||
const relativeValue = clampRemap(transitionTime, 1, 0, 1, value);
|
||||
const fromPos = fromSurface.getPosition();
|
||||
const fromNewPos = {
|
||||
x: fromPos.x + fromDelta.x,
|
||||
y: fromPos.y + fromDelta.y,
|
||||
};
|
||||
|
||||
target.setMask({
|
||||
...from,
|
||||
...rectArcTween(from, to, easeInOutQuint(value), config.reverse, ratio),
|
||||
radius: easeInOutCubic(value, from.radius, target.radius()),
|
||||
color: colorTween(
|
||||
from.color,
|
||||
target.background(),
|
||||
easeInOutQuint(value),
|
||||
),
|
||||
});
|
||||
target.setPosition(
|
||||
yield* tween(config.duration ?? 0.6, value => {
|
||||
surface.setMask({
|
||||
...fromMask,
|
||||
...rectArcTween(
|
||||
fromMask,
|
||||
toMask,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
radius: easeInOutCubic(value, fromMask.radius, toMask.radius),
|
||||
color: colorTween(fromMask.color, toMask.color, easeInOutQuint(value)),
|
||||
});
|
||||
if (position) {
|
||||
surface.setPosition(
|
||||
rectArcTween(
|
||||
fromNewPos,
|
||||
toPos,
|
||||
position,
|
||||
toPosition,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
);
|
||||
if (!config.onOpacityChange?.(target, value, relativeValue)) {
|
||||
target.getChild().opacity(relativeValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!config.onUpdate?.(surface, value)) {
|
||||
surface.getChild().opacity(value);
|
||||
}
|
||||
});
|
||||
|
||||
target.setMask(null);
|
||||
target.show();
|
||||
}
|
||||
|
||||
return surfaceTransitionExecutor;
|
||||
surface.setMask(null);
|
||||
}
|
||||
|
||||
@@ -11,144 +11,174 @@ import {
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {ThreadGenerator} from '../threading';
|
||||
|
||||
/**
|
||||
* Configuration for {@link surfaceTransition}.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* See {@link rectArcTween} from more detail.
|
||||
*/
|
||||
reverse?: boolean;
|
||||
/**
|
||||
* A function called when the currently displayed surface changes.
|
||||
*
|
||||
* @param surface
|
||||
*/
|
||||
onSurfaceChange?: (surface: Surface) => void;
|
||||
onFromOpacityChange?: (
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return `true` if the default changes made by {@link surfaceTransition}
|
||||
* should be prevented
|
||||
*/
|
||||
onInitialSurfaceUpdate?: (
|
||||
surface: Surface,
|
||||
value: number,
|
||||
relativeValue: number,
|
||||
) => boolean | void;
|
||||
onToOpacityChange?: (
|
||||
) => 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.
|
||||
*
|
||||
* @return `true` if the default changes made by {@link surfaceTransition}
|
||||
* should be prevented
|
||||
*/
|
||||
onTargetSurfaceUpdate?: (
|
||||
surface: Surface,
|
||||
value: number,
|
||||
relativeValue: number,
|
||||
) => boolean | void;
|
||||
) => true | void;
|
||||
/**
|
||||
* Duration at which the surfaces are swapped.
|
||||
*/
|
||||
transitionTime?: number;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
decorate(surfaceTransition, threadable());
|
||||
export function surfaceTransition(fromSurfaceOriginal: Surface, clone = true) {
|
||||
const fromSurface = clone
|
||||
? fromSurfaceOriginal
|
||||
.clone()
|
||||
.moveTo(fromSurfaceOriginal.parent)
|
||||
.zIndex(fromSurfaceOriginal.zIndex())
|
||||
: fromSurfaceOriginal;
|
||||
/**
|
||||
* Morph one surface into another.
|
||||
*
|
||||
* @param initial
|
||||
* @param target
|
||||
* @param config
|
||||
*/
|
||||
export function* surfaceTransition(
|
||||
initial: Surface,
|
||||
target: Surface,
|
||||
config: SurfaceTransitionConfig = {},
|
||||
): ThreadGenerator {
|
||||
const from = initial.getMask();
|
||||
|
||||
if (clone) {
|
||||
fromSurfaceOriginal.hide();
|
||||
}
|
||||
const from = fromSurfaceOriginal.getMask();
|
||||
const transitionTime = config.transitionTime ?? 1 / 3;
|
||||
const to = target.getMask();
|
||||
const toPos = target.getPosition();
|
||||
const fromPos = initial.getPosition();
|
||||
|
||||
decorate(surfaceTransitionRunner, threadable());
|
||||
function* surfaceTransitionRunner(
|
||||
target: Surface,
|
||||
config: SurfaceTransitionConfig = {},
|
||||
): ThreadGenerator {
|
||||
const transitionTime = config.transitionTime ?? 1 / 3;
|
||||
const to = target.getMask();
|
||||
const toPos = target.getPosition();
|
||||
const fromPos = fromSurface.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,
|
||||
};
|
||||
|
||||
let relativeValue = 0;
|
||||
const fromDelta = fromSurface.getOriginDelta(target.getOrigin());
|
||||
const fromNewPos = {
|
||||
x: fromPos.x + fromDelta.x,
|
||||
y: fromPos.y + fromDelta.y,
|
||||
};
|
||||
const toDelta = target.getOriginDelta(fromSurfaceOriginal.getOrigin());
|
||||
const toNewPos = {
|
||||
x: toPos.x + toDelta.x,
|
||||
y: toPos.y + toDelta.y,
|
||||
};
|
||||
const ratio =
|
||||
(calculateRatio(fromNewPos, toPos) + calculateRatio(from, to)) / 2;
|
||||
|
||||
const ratio =
|
||||
(calculateRatio(fromNewPos, toPos) + calculateRatio(from, to)) / 2;
|
||||
target.hide();
|
||||
config.onSurfaceChange?.(initial);
|
||||
|
||||
target.hide();
|
||||
|
||||
config.onSurfaceChange?.(fromSurface);
|
||||
|
||||
let check = true;
|
||||
yield* tween(0.6, value => {
|
||||
if (value > transitionTime) {
|
||||
relativeValue = clampRemap(transitionTime, 1, 0, 1, value);
|
||||
if (check) {
|
||||
target.show();
|
||||
fromSurface.destroy();
|
||||
}
|
||||
|
||||
target.setMask({
|
||||
...from,
|
||||
...rectArcTween(
|
||||
from,
|
||||
to,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
radius: easeInOutCubic(value, from.radius, target.radius()),
|
||||
color: colorTween(
|
||||
from.color,
|
||||
target.background(),
|
||||
easeInOutQuint(value),
|
||||
),
|
||||
});
|
||||
target.setPosition(
|
||||
rectArcTween(
|
||||
fromNewPos,
|
||||
toPos,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
);
|
||||
if (!config.onToOpacityChange?.(target, value, relativeValue)) {
|
||||
target.getChild().opacity(relativeValue);
|
||||
}
|
||||
|
||||
if (check) {
|
||||
config.onSurfaceChange?.(target);
|
||||
check = false;
|
||||
}
|
||||
} else {
|
||||
relativeValue = clampRemap(0, transitionTime, 1, 0, value);
|
||||
fromSurface.setMask({
|
||||
...from,
|
||||
...rectArcTween(
|
||||
from,
|
||||
to,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
radius: easeInOutCubic(value, from.radius, target.radius()),
|
||||
color: colorTween(
|
||||
from.color,
|
||||
target.background(),
|
||||
easeInOutQuint(value),
|
||||
),
|
||||
});
|
||||
fromSurface.setPosition(
|
||||
rectArcTween(
|
||||
fromPos,
|
||||
toNewPos,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
);
|
||||
|
||||
if (!config.onFromOpacityChange?.(target, value, relativeValue)) {
|
||||
fromSurface.getChild().opacity(relativeValue);
|
||||
}
|
||||
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,
|
||||
...rectArcTween(from, to, easeInOutQuint(value), config.reverse, ratio),
|
||||
radius: easeInOutCubic(value, from.radius, target.radius()),
|
||||
color: colorTween(
|
||||
from.color,
|
||||
target.background(),
|
||||
easeInOutQuint(value),
|
||||
),
|
||||
});
|
||||
target.setPosition(
|
||||
rectArcTween(
|
||||
fromNewPos,
|
||||
toPos,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
);
|
||||
if (!config.onTargetSurfaceUpdate?.(target, value, relativeValue)) {
|
||||
target.getChild().opacity(relativeValue);
|
||||
}
|
||||
});
|
||||
|
||||
target.setMask(null);
|
||||
target.show();
|
||||
}
|
||||
if (check) {
|
||||
config.onSurfaceChange?.(target);
|
||||
check = false;
|
||||
}
|
||||
} else {
|
||||
relativeValue = clampRemap(0, transitionTime, 1, 0, value);
|
||||
initial.setMask({
|
||||
...from,
|
||||
...rectArcTween(from, to, easeInOutQuint(value), config.reverse, ratio),
|
||||
radius: easeInOutCubic(value, from.radius, target.radius()),
|
||||
color: colorTween(
|
||||
from.color,
|
||||
target.background(),
|
||||
easeInOutQuint(value),
|
||||
),
|
||||
});
|
||||
initial.setPosition(
|
||||
rectArcTween(
|
||||
fromPos,
|
||||
toNewPos,
|
||||
easeInOutQuint(value),
|
||||
config.reverse,
|
||||
ratio,
|
||||
),
|
||||
);
|
||||
|
||||
return surfaceTransitionRunner;
|
||||
if (!config.onInitialSurfaceUpdate?.(target, value, relativeValue)) {
|
||||
initial.getChild().opacity(relativeValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initial.position(fromPos).setMask(null);
|
||||
target.position(toPos).setMask(null);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {Util} from 'konva/lib/Util';
|
||||
import {GetSet, Vector2d} from 'konva/lib/types';
|
||||
import {waitFor} from '../animations';
|
||||
import {waitFor} from '../flow';
|
||||
import {getset, KonvaNode, threadable} from '../decorators';
|
||||
import {GeneratorHelper} from '../helpers';
|
||||
import {InterpolationFunction, map, tween} from '../tweening';
|
||||
|
||||
@@ -3,7 +3,7 @@ import {getset, threadable} from '../decorators';
|
||||
import {Context} from 'konva/lib/Context';
|
||||
import {GetSet} from 'konva/lib/types';
|
||||
import {cancel, ThreadGenerator} from '../threading';
|
||||
import {waitFor} from '../animations';
|
||||
import {waitFor} from '../flow';
|
||||
import {CanvasHelper, GeneratorHelper} from '../helpers';
|
||||
|
||||
interface VideoConfig extends ShapeConfig {
|
||||
|
||||
@@ -2,6 +2,21 @@ import {join, ThreadGenerator} from '../threading';
|
||||
import {decorate, threadable} from '../decorators';
|
||||
|
||||
decorate(all, threadable());
|
||||
/**
|
||||
* Run all tasks concurrently and wait for all of them to finish.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* // current time: 0s
|
||||
* yield* all(
|
||||
* rect.fill('#ff0000', 2),
|
||||
* rect.opacity(1, 1),
|
||||
* );
|
||||
* // current time: 2s
|
||||
* ```
|
||||
*
|
||||
* @param tasks
|
||||
*/
|
||||
export function* all(...tasks: ThreadGenerator[]): ThreadGenerator {
|
||||
for (const task of tasks) {
|
||||
yield task;
|
||||
|
||||
@@ -2,6 +2,21 @@ import {join, ThreadGenerator} from '../threading';
|
||||
import {decorate, threadable} from '../decorators';
|
||||
|
||||
decorate(any, threadable());
|
||||
/**
|
||||
* Run all tasks concurrently and wait for any of them to finish.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* // current time: 0s
|
||||
* yield* all(
|
||||
* rect.fill('#ff0000', 2),
|
||||
* rect.opacity(1, 1),
|
||||
* );
|
||||
* // current time: 1s
|
||||
* ```
|
||||
*
|
||||
* @param tasks
|
||||
*/
|
||||
export function* any(...tasks: ThreadGenerator[]): ThreadGenerator {
|
||||
for (const task of tasks) {
|
||||
yield task;
|
||||
|
||||
@@ -2,8 +2,43 @@ import {decorate, threadable} from '../decorators';
|
||||
import {isThreadGenerator, ThreadGenerator} from '../threading';
|
||||
|
||||
decorate(chain, threadable());
|
||||
export function* chain(...args: (ThreadGenerator | Callback)[]) {
|
||||
for (const generator of args) {
|
||||
/**
|
||||
* Run tasks one after another.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* // current time: 0s
|
||||
* yield* chain(
|
||||
* rect.fill('#ff0000', 2),
|
||||
* rect.opacity(1, 1),
|
||||
* );
|
||||
* // current time: 3s
|
||||
* ```
|
||||
*
|
||||
* Note that the same animation can be written as:
|
||||
* ```ts
|
||||
* yield* rect.fill('#ff0000', 2),
|
||||
* yield* rect.opacity(1, 1),
|
||||
* ```
|
||||
*
|
||||
* The reason `chain` exists is to make it easier to pass it to other flow
|
||||
* functions. For example:
|
||||
* ```ts
|
||||
* yield* all(
|
||||
* rect.corenerRadius(20, 3),
|
||||
* chain(
|
||||
* rect.fill('#ff0000', 2),
|
||||
* rect.opacity(1, 1),
|
||||
* ),
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @param tasks
|
||||
*/
|
||||
export function* chain(
|
||||
...tasks: (ThreadGenerator | Callback)[]
|
||||
): ThreadGenerator {
|
||||
for (const generator of tasks) {
|
||||
if (isThreadGenerator(generator)) {
|
||||
yield* generator;
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,34 @@
|
||||
import {waitFor} from '../animations';
|
||||
import {waitFor} from './scheduling';
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {isThreadGenerator, ThreadGenerator} from '../threading';
|
||||
|
||||
decorate(delay, threadable());
|
||||
/**
|
||||
* Run the given generator or callback after a specific amount of time.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* yield* delay(1, rect.fill('#ff0000', 2));
|
||||
* ```
|
||||
*
|
||||
* Note that the same animation can be written as:
|
||||
* ```ts
|
||||
* yield* waitFor(1),
|
||||
* yield* rect.fill('#ff0000', 2),
|
||||
* ```
|
||||
*
|
||||
* The reason `delay` exists is to make it easier to pass it to other flow
|
||||
* functions. For example:
|
||||
* ```ts
|
||||
* yield* all(
|
||||
* rect.opacity(1, 3),
|
||||
* delay(1, rect.fill('#ff0000', 2));
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @param time Delay in seconds
|
||||
* @param task
|
||||
*/
|
||||
export function* delay(
|
||||
time: number,
|
||||
task: ThreadGenerator | Callback,
|
||||
|
||||
@@ -2,7 +2,49 @@ import {ThreadGenerator} from '../threading';
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {useProject} from '../utils';
|
||||
|
||||
export function every(seconds: number, callback: (frame: number) => void) {
|
||||
/**
|
||||
* A callback called by {@link EveryTimer} every N seconds.
|
||||
*/
|
||||
export interface EveryCallback {
|
||||
/**
|
||||
* @param tick The amount of times the timer has ticked.
|
||||
*/
|
||||
(tick: number): void;
|
||||
}
|
||||
|
||||
export interface EveryTimer {
|
||||
/**
|
||||
* The generator responsible for running this timer.
|
||||
*/
|
||||
runner: ThreadGenerator;
|
||||
setInterval(value: number): void;
|
||||
setCallback(value: EveryCallback): void;
|
||||
|
||||
/**
|
||||
* Wait until the timer ticks.
|
||||
*/
|
||||
sync(): ThreadGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the given callback every N seconds.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* const timer = every(2, time => console.log(time));
|
||||
* yield timer.runner;
|
||||
*
|
||||
* // current time: 0s
|
||||
* yield* waitFor(5);
|
||||
* // current time: 5s
|
||||
* yield* timer.sync();
|
||||
* // current time: 6s
|
||||
* ```
|
||||
*
|
||||
* @param interval
|
||||
* @param callback
|
||||
*/
|
||||
export function every(interval: number, callback: EveryCallback): EveryTimer {
|
||||
let changed = false;
|
||||
decorate(everyRunner, threadable('every'));
|
||||
function* everyRunner(): ThreadGenerator {
|
||||
@@ -13,7 +55,7 @@ export function every(seconds: number, callback: (frame: number) => void) {
|
||||
changed = true;
|
||||
|
||||
while (true) {
|
||||
if (acc >= project.secondsToFrames(seconds)) {
|
||||
if (acc >= project.secondsToFrames(interval)) {
|
||||
acc = 0;
|
||||
tick++;
|
||||
callback(tick);
|
||||
@@ -28,15 +70,15 @@ export function every(seconds: number, callback: (frame: number) => void) {
|
||||
|
||||
return {
|
||||
runner: everyRunner(),
|
||||
setSeconds(value: number) {
|
||||
seconds = value;
|
||||
setInterval(value) {
|
||||
interval = value;
|
||||
changed = false;
|
||||
},
|
||||
setCallback(value: (frame: number) => void) {
|
||||
setCallback(value) {
|
||||
callback = value;
|
||||
changed = false;
|
||||
},
|
||||
*sync(): ThreadGenerator {
|
||||
*sync() {
|
||||
while (!changed) {
|
||||
yield;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
/**
|
||||
* Utilities for controlling the flow and timing of an animation.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
export * from './all';
|
||||
export * from './any';
|
||||
export * from './chain';
|
||||
export * from './delay';
|
||||
export * from './every';
|
||||
export * from './loop';
|
||||
export * from './scheduling';
|
||||
export * from './sequence';
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {ThreadGenerator} from '../threading';
|
||||
|
||||
/**
|
||||
* A callback called by {@link loop} during each iteration.
|
||||
*/
|
||||
export interface LoopCallback {
|
||||
/**
|
||||
* @param i The current iteration index.
|
||||
*/
|
||||
(i: number): ThreadGenerator | void;
|
||||
}
|
||||
|
||||
decorate(loop, threadable());
|
||||
/**
|
||||
* Run the given generator N times.
|
||||
*
|
||||
* Each time iteration waits until the previous one is completed.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* const colors = [
|
||||
* '#ff6470',
|
||||
* '#ffc66d',
|
||||
* '#68abdf',
|
||||
* '#99c47a',
|
||||
* ];
|
||||
*
|
||||
* yield* loop(
|
||||
* colors.length,
|
||||
* i => rect.fill(colors[i], 2),
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param iterations Number of iterations.
|
||||
* @param factory A function creating the generator to run. Because generators
|
||||
* can't be reset, a new generator is created each iteration.
|
||||
*/
|
||||
export function* loop(
|
||||
iterations: number,
|
||||
factory: (i: number) => ThreadGenerator | void,
|
||||
) {
|
||||
factory: LoopCallback,
|
||||
): ThreadGenerator {
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const generator = factory(i);
|
||||
if (generator) {
|
||||
|
||||
@@ -3,10 +3,39 @@ import {ThreadGenerator} from '../threading';
|
||||
import {useProject, useScene} from '../utils';
|
||||
|
||||
decorate(waitUntil, threadable());
|
||||
/**
|
||||
* Wait until the given time.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* // current time: 0s
|
||||
* yield waitUntil(2);
|
||||
* // current time: 2s
|
||||
* yield waitUntil(3);
|
||||
* // current time: 3s
|
||||
* ```
|
||||
*
|
||||
* @param time Absolute time in seconds.
|
||||
* @param after
|
||||
*/
|
||||
export function waitUntil(
|
||||
time: number,
|
||||
after?: ThreadGenerator,
|
||||
): ThreadGenerator;
|
||||
/**
|
||||
* Wait until the given time event.
|
||||
*
|
||||
* Time events are displayed on the timeline and can be edited to adjust the
|
||||
* delay. By default, an event happens immediately - without any delay.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* yield waitUntil('event');
|
||||
* ```
|
||||
*
|
||||
* @param event Name of the time event.
|
||||
* @param after
|
||||
*/
|
||||
export function waitUntil(
|
||||
event: string,
|
||||
after?: ThreadGenerator,
|
||||
@@ -31,6 +60,21 @@ export function* waitUntil(
|
||||
}
|
||||
|
||||
decorate(waitFor, threadable());
|
||||
/**
|
||||
* Wait for the given amount of time.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* // current time: 0s
|
||||
* yield waitFor(2);
|
||||
* // current time: 2s
|
||||
* yield waitFor(3);
|
||||
* // current time: 5s
|
||||
* ```
|
||||
*
|
||||
* @param seconds Relative time in seconds.
|
||||
* @param after
|
||||
*/
|
||||
export function* waitFor(
|
||||
seconds = 0,
|
||||
after?: ThreadGenerator,
|
||||
@@ -3,6 +3,12 @@ import {decorate, threadable} from '../decorators';
|
||||
import {join, ThreadGenerator} from '../threading';
|
||||
|
||||
decorate(sequence, threadable());
|
||||
/**
|
||||
* Run
|
||||
*
|
||||
* @param delay
|
||||
* @param sequences
|
||||
*/
|
||||
export function* sequence(
|
||||
delay: number,
|
||||
...sequences: ThreadGenerator[]
|
||||
4
src/index.ts
Normal file
4
src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './bootstrap';
|
||||
export * from './Project';
|
||||
export * from './Scene';
|
||||
export * from './symbols';
|
||||
@@ -1,19 +1,41 @@
|
||||
import {GeneratorHelper} from '../helpers';
|
||||
import {ThreadGenerator} from './ThreadGenerator';
|
||||
|
||||
/**
|
||||
* A class representing an individual thread.
|
||||
*
|
||||
* Thread is a wrapper for a generator that can be executed concurrently.
|
||||
*
|
||||
* Aside from the main thread, all threads need to have a parent.
|
||||
* If a parent finishes execution, all of its child threads are terminated.
|
||||
*/
|
||||
export class Thread {
|
||||
public children: Thread[] = [];
|
||||
/**
|
||||
* The next value to be passed to the wrapped generator.
|
||||
*/
|
||||
public value: unknown;
|
||||
private parent: Thread = null;
|
||||
|
||||
/**
|
||||
* Check if this thread or any of its ancestors has been canceled.
|
||||
*/
|
||||
public get canceled(): boolean {
|
||||
return this._canceled || (this.parent?.canceled ?? false);
|
||||
}
|
||||
|
||||
private parent: Thread = null;
|
||||
private _canceled = false;
|
||||
|
||||
public constructor(public readonly runner: ThreadGenerator) {}
|
||||
public constructor(
|
||||
/**
|
||||
* The generator wrapped by this thread.
|
||||
*/
|
||||
public readonly runner: ThreadGenerator,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Progress the wrapped generator once.
|
||||
*/
|
||||
public next() {
|
||||
const result = this.runner.next(this.value);
|
||||
this.value = null;
|
||||
|
||||
@@ -1,13 +1,44 @@
|
||||
import type {Project} from '../Project';
|
||||
import {JoinYieldResult} from './join';
|
||||
import {CancelYieldResult} from './cancel';
|
||||
|
||||
/**
|
||||
* The main generator type produced by all generator functions in Motion Canvas.
|
||||
*
|
||||
* Yielded values can be used to control the flow of animation:
|
||||
*
|
||||
* - Progress to the next frame:
|
||||
* ```ts
|
||||
* yield;
|
||||
* ```
|
||||
*
|
||||
* - Run another generator synchronously:
|
||||
* ```ts
|
||||
* yield* generatorFunction();
|
||||
* ```
|
||||
*
|
||||
* - Run another generator concurrently:
|
||||
* ```ts
|
||||
* const task = yield generatorFunction();
|
||||
* ```
|
||||
|
||||
* - Await a [Promise][promise]:
|
||||
* ```ts
|
||||
* const result = yield asyncFunction();
|
||||
* ```
|
||||
*
|
||||
* [promise]: https://developer.mozilla.org/en-US/docs/web/javascript/reference/global_objects/promise
|
||||
*/
|
||||
export type ThreadGenerator = Generator<
|
||||
ThreadGenerator | JoinYieldResult | CancelYieldResult | Promise<any> | symbol,
|
||||
ThreadGenerator | JoinYieldResult | CancelYieldResult | Promise<any>,
|
||||
void,
|
||||
ThreadGenerator | Project | any
|
||||
ThreadGenerator | any
|
||||
>;
|
||||
|
||||
/**
|
||||
* Check if the given value is a {@link ThreadGenerator}.
|
||||
*
|
||||
* @param value A possible thread {@link ThreadGenerator}.
|
||||
*/
|
||||
export function isThreadGenerator(value: unknown): value is ThreadGenerator {
|
||||
return typeof value === 'object' && Symbol.iterator in value;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {ThreadGenerator} from './ThreadGenerator';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const THREAD_CANCEL = Symbol.for('THREAD_CANCEL');
|
||||
|
||||
/**
|
||||
* An instruction passed to the {@link threads} generator to cancel tasks.
|
||||
*/
|
||||
export interface CancelYieldResult {
|
||||
/**
|
||||
* Tasks to cancel.
|
||||
*/
|
||||
[THREAD_CANCEL]: ThreadGenerator[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given value is a {@link CancelYieldResult}.
|
||||
*
|
||||
* @param value A possible {@link CancelYieldResult}.
|
||||
*/
|
||||
export function isCancelYieldResult(
|
||||
value: unknown,
|
||||
): value is CancelYieldResult {
|
||||
@@ -14,6 +28,20 @@ export function isCancelYieldResult(
|
||||
}
|
||||
|
||||
decorate(cancel, threadable());
|
||||
/**
|
||||
* Cancel all listed tasks.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* const task = yield generatorFunction();
|
||||
*
|
||||
* // do something concurrently
|
||||
*
|
||||
* yield* cancel(task);
|
||||
* ```
|
||||
*
|
||||
* @param tasks
|
||||
*/
|
||||
export function* cancel(...tasks: ThreadGenerator[]): ThreadGenerator {
|
||||
yield {[THREAD_CANCEL]: tasks};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Thread management.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
export * from './cancel';
|
||||
export * from './join';
|
||||
export * from './Thread';
|
||||
|
||||
@@ -1,23 +1,71 @@
|
||||
import {decorate, threadable} from '../decorators';
|
||||
import {ThreadGenerator} from './ThreadGenerator';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const THREAD_JOIN = Symbol.for('THREAD_JOIN');
|
||||
|
||||
/**
|
||||
* An instruction passed to the {@link threads} generator to join tasks.
|
||||
*/
|
||||
export interface JoinYieldResult {
|
||||
/**
|
||||
* Tasks to join.
|
||||
*/
|
||||
[THREAD_JOIN]: ThreadGenerator[];
|
||||
/**
|
||||
* Whether we should wait for all tasks or for at least one.
|
||||
*/
|
||||
all: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given value is a {@link JoinYieldResult}.
|
||||
*
|
||||
* @param value A possible {@link JoinYieldResult}.
|
||||
*/
|
||||
export function isJoinYieldResult(value: unknown): value is JoinYieldResult {
|
||||
return typeof value === 'object' && THREAD_JOIN in value;
|
||||
}
|
||||
|
||||
decorate(join, threadable());
|
||||
/**
|
||||
* Pause the current generator until all listed tasks are finished.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* const task = yield generatorFunction();
|
||||
*
|
||||
* // do something concurrently
|
||||
*
|
||||
* yield* join(task);
|
||||
* ```
|
||||
*
|
||||
* @param tasks
|
||||
*/
|
||||
export function join(...tasks: ThreadGenerator[]): ThreadGenerator;
|
||||
/**
|
||||
* Pause the current generator until listed tasks are finished.
|
||||
*
|
||||
* Example
|
||||
* ```ts
|
||||
* const taskA = yield generatorFunctionA();
|
||||
* const taskB = yield generatorFunctionB();
|
||||
*
|
||||
* // do something concurrently
|
||||
*
|
||||
* // await any of the tasks
|
||||
* yield* join(false, taskA, taskB);
|
||||
* ```
|
||||
*
|
||||
* @param all Whether we should wait for all tasks or for at least one.
|
||||
* @param tasks
|
||||
*/
|
||||
export function join(
|
||||
all: boolean,
|
||||
...tasks: ThreadGenerator[]
|
||||
): ThreadGenerator;
|
||||
export function join(...tasks: ThreadGenerator[]): ThreadGenerator;
|
||||
export function* join(
|
||||
first: ThreadGenerator | boolean,
|
||||
...tasks: ThreadGenerator[]
|
||||
|
||||
@@ -5,10 +5,20 @@ import {isJoinYieldResult, THREAD_JOIN} from './join';
|
||||
import {isCancelYieldResult, THREAD_CANCEL} from './cancel';
|
||||
import {isThreadGenerator, ThreadGenerator} from './ThreadGenerator';
|
||||
|
||||
/**
|
||||
* Check if the given value is a [Promise][promise].
|
||||
*
|
||||
* @param value A possible [Promise][promise].
|
||||
*
|
||||
* [promise]: https://developer.mozilla.org/en-US/docs/web/javascript/reference/global_objects/promise
|
||||
*/
|
||||
export function isPromise(value: any): value is Promise<any> {
|
||||
return typeof value?.then === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* A generator function or a normal function that returns a generator.
|
||||
*/
|
||||
export interface ThreadsFactory {
|
||||
(): ThreadGenerator;
|
||||
}
|
||||
@@ -18,6 +28,30 @@ export interface ThreadsCallback {
|
||||
}
|
||||
|
||||
decorate(threads, threadable());
|
||||
/**
|
||||
* Create a context in which generators can be run concurrently.
|
||||
*
|
||||
* From the perspective of the external generator, `threads` is executed
|
||||
* synchronously. By default, each scene generator is wrapped in its own
|
||||
* `threads` generator.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* // first
|
||||
*
|
||||
* yield* threads(function* () {
|
||||
* const task = yield generatorFunction();
|
||||
* // second
|
||||
* }); // <- `task` will be terminated here because the scope
|
||||
* // of this `threads` generator has ended
|
||||
*
|
||||
* // third
|
||||
* ```
|
||||
*
|
||||
* @param factory
|
||||
* @param callback Called whenever threads are created, canceled or finished.
|
||||
* Used for debugging purposes.
|
||||
*/
|
||||
export function* threads(
|
||||
factory: ThreadsFactory,
|
||||
callback?: ThreadsCallback,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
vector2dTween,
|
||||
} from './index';
|
||||
import {threadable} from '../decorators';
|
||||
import {waitFor, waitUntil} from '../animations';
|
||||
import {waitFor, waitUntil} from '../flow';
|
||||
import {ThreadGenerator} from '../threading';
|
||||
import {GeneratorHelper} from '../helpers';
|
||||
|
||||
|
||||
1
src/video/index.ts
Normal file
1
src/video/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './loadVideo';
|
||||
21
typedoc.json
Normal file
21
typedoc.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"out": "api",
|
||||
"entryPoints": [
|
||||
"src",
|
||||
"src/animations",
|
||||
"src/audio",
|
||||
"src/components",
|
||||
"src/decorators",
|
||||
"src/flow",
|
||||
"src/helpers",
|
||||
"src/player",
|
||||
"src/styles",
|
||||
"src/themes",
|
||||
"src/threading",
|
||||
"src/transitions",
|
||||
"src/tweening",
|
||||
"src/types",
|
||||
"src/utils",
|
||||
"src/video"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user