mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-13 07:48:03 -05:00
feat: add CodeBlock component based on code-fns to 2D (#78)
This commit is contained in:
117
package-lock.json
generated
117
package-lock.json
generated
@@ -8,9 +8,6 @@
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@motion-canvas/2d": "^12.0.0-alpha.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.0.3",
|
||||
"@commitlint/config-conventional": "^17.0.3",
|
||||
@@ -6641,6 +6638,26 @@
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@wooorm/starry-night": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@wooorm/starry-night/-/starry-night-1.3.1.tgz",
|
||||
"integrity": "sha512-GeGmK+cXEJsTkE3iQBIQbU9Z6OBj22hBG/rNHaaYs8X1ySZQI4y4Os4OGCkyjwZuAClajNRP7lekrLia195BBQ==",
|
||||
"dependencies": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"import-meta-resolve": "^2.0.0",
|
||||
"vscode-oniguruma": "^1.0.0",
|
||||
"vscode-textmate": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/@wooorm/starry-night/node_modules/vscode-textmate": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-7.0.3.tgz",
|
||||
"integrity": "sha512-OkE/mYm1h5ZX9IEKeKR/2zKDt2SzYyIfTEOVFX4QhA+B3BPROvNEmDDXvBThz3qknKO3Cy/VVb8/sx1UlqP/Xw=="
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
@@ -8505,6 +8522,14 @@
|
||||
"node": ">= 0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/code-fns": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/code-fns/-/code-fns-0.7.0.tgz",
|
||||
"integrity": "sha512-Okxa0zo6xG/XzHOpGQNfJwqZ4khJmvT8TY01LvVIFuoe+jAWcSl3xLmmE1We3Hcf8d3vFG1HNSL/hQbsVxdDAA==",
|
||||
"dependencies": {
|
||||
"@wooorm/starry-night": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/collapse-white-space": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
|
||||
@@ -12806,6 +12831,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/import-meta-resolve": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.1.0.tgz",
|
||||
"integrity": "sha512-yG9pxkWJVTy4cmRsNWE3ztFdtFuYIV8G4N+cbCkO8b+qngkLyIUhxQFuZ0qJm67+0nUOxjMPT7nfksPKza1v2g==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/imurmurhash": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
@@ -21568,8 +21602,7 @@
|
||||
"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
|
||||
"integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA=="
|
||||
},
|
||||
"node_modules/vscode-textmate": {
|
||||
"version": "5.2.0",
|
||||
@@ -22430,10 +22463,13 @@
|
||||
},
|
||||
"packages/2d": {
|
||||
"name": "@motion-canvas/2d",
|
||||
"version": "12.0.0-alpha.1",
|
||||
"version": "12.0.0-alpha.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"code-fns": "^0.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -22442,7 +22478,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@motion-canvas/core",
|
||||
"version": "12.0.0-alpha.0",
|
||||
"version": "12.0.0-alpha.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"colorjs.io": "^0.3.0",
|
||||
@@ -22466,7 +22502,7 @@
|
||||
},
|
||||
"packages/create": {
|
||||
"name": "@motion-canvas/create",
|
||||
"version": "12.0.0-alpha.0",
|
||||
"version": "12.0.0-alpha.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prompts": "^2.4.2"
|
||||
@@ -22475,8 +22511,8 @@
|
||||
"create-motion-canvas": "index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/ui": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"@motion-canvas/ui": "^12.0.0-alpha.2",
|
||||
"@motion-canvas/vite-plugin": "^12.0.0-alpha.0"
|
||||
}
|
||||
},
|
||||
@@ -22507,7 +22543,7 @@
|
||||
},
|
||||
"packages/legacy": {
|
||||
"name": "@motion-canvas/legacy",
|
||||
"version": "12.0.0-alpha.1",
|
||||
"version": "12.0.0-alpha.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.0",
|
||||
@@ -22518,7 +22554,7 @@
|
||||
"three": "^0.141.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.9"
|
||||
},
|
||||
@@ -22532,8 +22568,8 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@motion-canvas/2d": "*",
|
||||
"@motion-canvas/core": "*"
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/legacy": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
@@ -22543,10 +22579,10 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@motion-canvas/ui",
|
||||
"version": "12.0.0-alpha.0",
|
||||
"version": "12.0.0-alpha.2",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"@preact/preset-vite": "^2.3.0",
|
||||
"preact": "10.7.3",
|
||||
"typescript": "^4.6.4",
|
||||
@@ -26310,7 +26346,8 @@
|
||||
"@motion-canvas/2d": {
|
||||
"version": "file:packages/2d",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"code-fns": "^0.7.0",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
},
|
||||
@@ -26334,8 +26371,8 @@
|
||||
"@motion-canvas/create": {
|
||||
"version": "file:packages/create",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/ui": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"@motion-canvas/ui": "^12.0.0-alpha.2",
|
||||
"@motion-canvas/vite-plugin": "^12.0.0-alpha.0",
|
||||
"prompts": "^2.4.2"
|
||||
}
|
||||
@@ -26362,7 +26399,7 @@
|
||||
"@motion-canvas/legacy": {
|
||||
"version": "file:packages/legacy",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/three": "^0.141.0",
|
||||
"colorjs.io": "^0.3.0",
|
||||
@@ -26376,8 +26413,8 @@
|
||||
"@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"
|
||||
@@ -26386,7 +26423,7 @@
|
||||
"@motion-canvas/ui": {
|
||||
"version": "file:packages/ui",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.0",
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"@preact/preset-vite": "^2.3.0",
|
||||
"preact": "10.7.3",
|
||||
"typescript": "^4.6.4",
|
||||
@@ -27760,6 +27797,24 @@
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@wooorm/starry-night": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@wooorm/starry-night/-/starry-night-1.3.1.tgz",
|
||||
"integrity": "sha512-GeGmK+cXEJsTkE3iQBIQbU9Z6OBj22hBG/rNHaaYs8X1ySZQI4y4Os4OGCkyjwZuAClajNRP7lekrLia195BBQ==",
|
||||
"requires": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"import-meta-resolve": "^2.0.0",
|
||||
"vscode-oniguruma": "^1.0.0",
|
||||
"vscode-textmate": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-textmate": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-7.0.3.tgz",
|
||||
"integrity": "sha512-OkE/mYm1h5ZX9IEKeKR/2zKDt2SzYyIfTEOVFX4QhA+B3BPROvNEmDDXvBThz3qknKO3Cy/VVb8/sx1UlqP/Xw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
@@ -29105,6 +29160,14 @@
|
||||
"integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
|
||||
"dev": true
|
||||
},
|
||||
"code-fns": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/code-fns/-/code-fns-0.7.0.tgz",
|
||||
"integrity": "sha512-Okxa0zo6xG/XzHOpGQNfJwqZ4khJmvT8TY01LvVIFuoe+jAWcSl3xLmmE1We3Hcf8d3vFG1HNSL/hQbsVxdDAA==",
|
||||
"requires": {
|
||||
"@wooorm/starry-night": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"collapse-white-space": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
|
||||
@@ -32220,6 +32283,11 @@
|
||||
"resolve-cwd": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"import-meta-resolve": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.1.0.tgz",
|
||||
"integrity": "sha512-yG9pxkWJVTy4cmRsNWE3ztFdtFuYIV8G4N+cbCkO8b+qngkLyIUhxQFuZ0qJm67+0nUOxjMPT7nfksPKza1v2g=="
|
||||
},
|
||||
"imurmurhash": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
@@ -38650,8 +38718,7 @@
|
||||
"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
|
||||
"integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA=="
|
||||
},
|
||||
"vscode-textmate": {
|
||||
"version": "5.2.0",
|
||||
|
||||
@@ -29,5 +29,8 @@
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^12.0.0-alpha.2",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"code-fns": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
131
packages/2d/src/components/CodeBlock.ts
Normal file
131
packages/2d/src/components/CodeBlock.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import {computed, property} from '../decorators';
|
||||
import {Signal, SignalValue} from '@motion-canvas/core/lib/utils';
|
||||
import {Shape, ShapeProps} from './Shape';
|
||||
import {CodeTree, parse, diff, ready, MorphToken} from 'code-fns';
|
||||
import {
|
||||
clampRemap,
|
||||
easeInOutSine,
|
||||
TimingFunction,
|
||||
tween,
|
||||
} from '@motion-canvas/core/lib/tweening';
|
||||
import {threadable} from '@motion-canvas/core/lib/decorators';
|
||||
|
||||
export interface CodeProps extends ShapeProps {
|
||||
children?: CodeTree;
|
||||
code?: CodeTree;
|
||||
}
|
||||
|
||||
export class CodeBlock extends Shape {
|
||||
@property('')
|
||||
public declare readonly code: Signal<CodeTree, this>;
|
||||
|
||||
private progress: number | null = null;
|
||||
private diffed: MorphToken[] | null = null;
|
||||
|
||||
@computed()
|
||||
protected parsed() {
|
||||
return parse(this.code());
|
||||
}
|
||||
|
||||
public constructor({children, ...rest}: CodeProps) {
|
||||
super(rest);
|
||||
if (children) {
|
||||
this.code(children);
|
||||
}
|
||||
}
|
||||
|
||||
protected override collectAsyncResources(resources: Promise<any>[]): void {
|
||||
super.collectAsyncResources(resources);
|
||||
resources.push(ready());
|
||||
}
|
||||
|
||||
@threadable()
|
||||
public *tweenCode(
|
||||
code: SignalValue<CodeTree>,
|
||||
time: number,
|
||||
timingFunction: TimingFunction,
|
||||
) {
|
||||
if (typeof code === 'function') throw new Error();
|
||||
this.progress = 0;
|
||||
this.diffed = diff(this.code(), code);
|
||||
yield* tween(
|
||||
time,
|
||||
value => (this.progress = timingFunction(value)),
|
||||
() => {
|
||||
this.progress = null;
|
||||
this.diffed = null;
|
||||
this.code(code);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
protected override draw(context: CanvasRenderingContext2D) {
|
||||
this.requestFontUpdate();
|
||||
this.applyStyle(context);
|
||||
context.font = this.styles.font;
|
||||
context.textBaseline = 'top';
|
||||
const lh = parseInt(this.styles.fontSize) * 1.5;
|
||||
|
||||
const w = context.measureText('X').width;
|
||||
if (this.progress == null) {
|
||||
const parsed = this.parsed();
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
for (const token of parsed) {
|
||||
context.fillStyle = token.color as string;
|
||||
const [first, ...rest] = token.code.split('\n');
|
||||
context.fillText(first, x, y);
|
||||
x += w * first.length;
|
||||
for (const text of rest) {
|
||||
x = 0;
|
||||
y += lh;
|
||||
context.fillText(text, x, y);
|
||||
x = w * text.length;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const diffed = this.diffed!;
|
||||
|
||||
const beginning = 0.2;
|
||||
const ending = 0.8;
|
||||
const overlap = 0.15;
|
||||
for (const token of diffed) {
|
||||
context.fillStyle = token.color as string;
|
||||
if (token.morph === 'delete') {
|
||||
context.save();
|
||||
const opacity = clampRemap(
|
||||
0,
|
||||
beginning + overlap,
|
||||
1,
|
||||
0,
|
||||
this.progress,
|
||||
);
|
||||
context.globalAlpha = opacity;
|
||||
context.fillText(token.code, token.from![0] * w, token.from![1] * lh);
|
||||
context.restore();
|
||||
} else if (token.morph === 'create') {
|
||||
context.save();
|
||||
const opacity = clampRemap(ending - overlap, 1, 0, 1, this.progress);
|
||||
context.globalAlpha = opacity;
|
||||
context.fillText(token.code, token.to![0] * w, token.to![1] * lh);
|
||||
context.restore();
|
||||
} else if (token.morph === 'retain') {
|
||||
const progress = clampRemap(beginning, ending, 0, 1, this.progress);
|
||||
const x = easeInOutSine(
|
||||
progress,
|
||||
token.from![0] * w,
|
||||
token.to![0] * w,
|
||||
);
|
||||
const y = easeInOutSine(
|
||||
progress,
|
||||
token.from![1] * lh,
|
||||
token.to![1] * lh,
|
||||
);
|
||||
context.fillText(token.code, x, y);
|
||||
} else {
|
||||
console.error(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,21 @@ export function easeInOutQuint(value: number, from = 0, to = 1) {
|
||||
return map(from, to, value);
|
||||
}
|
||||
|
||||
export function easeInOutSine(value: number, from: number, to: number): number {
|
||||
value = -(Math.cos(Math.PI * value) - 1) / 2;
|
||||
return map(from, to, value);
|
||||
}
|
||||
|
||||
export function easeInSine(value: number, from: number, to: number): number {
|
||||
value = 1 - Math.cos((value * Math.PI) / 2);
|
||||
return map(from, to, value);
|
||||
}
|
||||
|
||||
export function easeOutSine(value: number, from: number, to: number): number {
|
||||
value = Math.sin((value * Math.PI) / 2);
|
||||
return map(from, to, value);
|
||||
}
|
||||
|
||||
export function createEaseInBack(s = 1.70158): TimingFunction {
|
||||
return (value: number, from = 0, to = 1) => {
|
||||
return map(from, to, value * value * ((s + 1) * value - s));
|
||||
|
||||
Reference in New Issue
Block a user