feat: add CodeBlock component based on code-fns to 2D (#78)

This commit is contained in:
Ross Esmond
2022-11-16 15:53:40 -06:00
committed by GitHub
parent cdf8dc0a35
commit ad346f118d
4 changed files with 241 additions and 25 deletions

117
package-lock.json generated
View File

@@ -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",

View File

@@ -29,5 +29,8 @@
"devDependencies": {
"@motion-canvas/core": "^12.0.0-alpha.2",
"typescript": "^4.7.4"
},
"dependencies": {
"code-fns": "^0.7.0"
}
}

View 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);
}
}
}
}
}

View File

@@ -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));