diff --git a/src/components/function_box.tsx b/src/components/function_box.tsx new file mode 100644 index 0000000..3c65062 --- /dev/null +++ b/src/components/function_box.tsx @@ -0,0 +1,290 @@ +import { + Img, + Rect, + Node, + Video, + makeScene2D, + Txt, + Line, + LineSegment, + NodeProps, +} from "@motion-canvas/2d"; +import { + Direction, + beginSlide, + createRef, + map, + slideTransition, + tween, + all, + waitFor, + range, + makeRef, +} from "@motion-canvas/core"; + +import { CodeBlock } from "@motion-canvas/2d/lib/components/CodeBlock"; +import { theme } from "../theme"; + +import * as ts from "typescript"; + +export interface FunctionBoxProps extends NodeProps { + source?: string; + fn?: Function; + padding?: number; + delta?: number; + + workingText?: string; + idlingText?: string; + + arity?: number; + + isChild?: boolean; +} + +type FunctionArgs = { node?: Node; val: any }[]; + +/* + + + + */ + +export class FunctionBox extends Node { + private readonly source: string; + private readonly workingText: string; + private readonly idlingText: string; + private readonly function: any; + private readonly delta: number; + private readonly arity: number; + private readonly padding: number; + + private readonly block = createRef(); + private readonly node = createRef(); + private readonly rect = createRef(); + + private readonly boxMoji = createRef(); + private readonly inputSegments: Line[] = []; + private readonly inputs: Rect[] = []; + private readonly outputSegment = createRef(); + private readonly output = createRef(); + private readonly child = createRef(); + + private readonly isChild: boolean; + + private currentArgs: FunctionArgs = []; + + public constructor(props?: FunctionBoxProps) { + super({ + ...props, + }); + + this.arity = props?.arity ?? 1; + if (props.fn) { + this.source = props.fn.toString(); + this.function = props.fn; + } else { + this.source = props?.source ?? `(x: number): number => x + 2`; + + const functionCode = ts.transpile(this.source); + this.function = eval(functionCode); + } + + this.delta = props?.delta ?? 20; + this.padding = props?.padding ?? 100; + + this.workingText = props?.workingText ?? "👷‍♀️⚙️"; + this.idlingText = props?.idlingText ?? "😴"; + + this.isChild = props?.isChild ?? false; + + this.add( + + + {range(this.arity).map((i) => ( + + + + + + ))} + + + + + {this.idlingText} + + + + + + + + + + , + ); + } + + public *resetInput(duration: number) { + yield* all( + ...this.inputs.map((x) => + all( + x.opacity(0, duration), + x.height(0, duration), + x.width(0, duration), + ), + ), + ...this.inputSegments.map((segment) => segment.points([], duration)), + ); + } + + public *resetOutput(duration: number) { + yield* all( + this.output().opacity(0, duration), + this.outputSegment().points([], duration), + ); + yield this.output().removeChildren(); + } + + public *reset(duration: number) { + yield* all(this.resetInput(duration), this.resetOutput(duration)); + } + + public *setInputs(args: FunctionArgs, duration: number) { + if (args.length != this.arity) + throw new Error("input length must equal function arity"); + this.currentArgs = args; + + this.inputs.forEach((input, i) => { + input.removeChildren(); + input.add( + args[i].node ?? ( + + {args[i].val.toString()} + + ), + ); + }); + yield* all( + ...this.inputSegments.map((segment) => + segment.points( + [ + { x: -this.padding, y: 0 }, + { x: -this.delta, y: 0 }, + ], + duration, + ), + ), + ...this.inputs.map((input) => + all( + input.height(40, duration), + input.width(40, duration), + input.opacity(1, duration), + ), + ), + ); + } + + public *propogateInput(duration: number) { + const opacityChangeDuration = 0.1; + + yield* all( + ...this.inputSegments.map((segment) => + segment.opacity(0.2, opacityChangeDuration), + ), + ); + + yield* all( + ...this.inputSegments.map((segment) => segment.points([], duration)), + ); + + yield* all( + ...this.inputSegments.map((segment) => + segment.opacity(1, opacityChangeDuration), + ), + ...this.inputs.map((input) => input.opacity(0, opacityChangeDuration)), + this.boxMoji().text(this.workingText, duration), + ); + } + + public *propogateOutput(duration: number) { + const opacityChangeDuration = 0.1; + + const output = this.function(...this.currentArgs.map((input) => input.val)); + if (typeof output === "function") { + yield this.output().add( + , + ); + } else { + yield this.output().add( + + {output.toString()} + , + ); + } + + yield* all( + this.boxMoji().text(this.idlingText, duration), + this.outputSegment().points( + [ + { x: -this.delta, y: 0 }, + { x: this.padding, y: 0 }, + ], + duration, + ), + this.child()?.opacity(1, duration), + this.output().opacity(1, duration), + this.outputSegment().opacity(1, duration), + ); + } +} diff --git a/src/scenes/doctor.tsx b/src/scenes/doctor.tsx index 642b83e..4b6c483 100644 --- a/src/scenes/doctor.tsx +++ b/src/scenes/doctor.tsx @@ -1,10 +1,9 @@ -import { Txt, makeScene2D } from "@motion-canvas/2d"; +import { makeScene2D } from "@motion-canvas/2d"; import { Direction, beginSlide, createRef, slideTransition, - waitFor, } from "@motion-canvas/core"; import { CodeBlock, insert } from "@motion-canvas/2d/lib/components/CodeBlock"; import { theme } from "../theme"; diff --git a/src/scenes/first_box.tsx b/src/scenes/first_box.tsx index 4927157..5508a41 100644 --- a/src/scenes/first_box.tsx +++ b/src/scenes/first_box.tsx @@ -1,156 +1,45 @@ -import { - Img, - Rect, - Node, - Video, - makeScene2D, - Txt, - Line, - LineSegment, -} from "@motion-canvas/2d"; +import { makeScene2D } from "@motion-canvas/2d"; import { Direction, beginSlide, createRef, - map, slideTransition, - tween, all, waitFor, } from "@motion-canvas/core"; -import { CodeBlock } from "@motion-canvas/2d/lib/components/CodeBlock"; -import { theme } from "../theme"; +import { FunctionBox } from "../components/function_box"; -const fibonacciFn = (n: number): number => { - if (n <= 2) { - return 1; - } - return fibonacciFn(n - 1) + fibonacciFn(n - 2); -}; - -const fibonacci = ` -const fibonacci = (n: number): number => { - if (n <= 2) { - return 1; - } - return fibonacci(n - 1) + fibonacci(n - 2); -}; -` - .split("\n") - .filter((x) => x.trim()) - .join("\n"); +const add = `(a: number, b: number) => { + return a + b; +}`; export default makeScene2D(function* (view) { - const block = createRef(); - const node = createRef(); - const rect = createRef(); + const functionBox = createRef(); - const boxMoji = createRef(); - const inSegment = createRef(); - const outSegment = createRef(); - const input = createRef(); - const output = createRef(); + view.add(); - yield* view.add( - <> - - - - - - - 😴 - - - - - - , - ); - - yield boxMoji().position(rect().middle()); yield* slideTransition(Direction.Left); yield* beginSlide("Black Box"); - const padding = 100; - const left = rect().left(); - const right = rect().right(); - yield* all( - inSegment().points([left.addX(-padding), left], 0.5), - outSegment().points([right, right.addX(padding)], 0.5), - ); - yield input().position(left.addX(-padding)); - yield output().position(right); - for (const i of [1, 2, 3]) { - yield* all( - input().opacity(1, 0.5), - input().text(i.toString(), 0.5), - input().position(left.addX(-padding - 20), 0.5), - ); + for (const [a, b] of [ + [-1, 2], + [3, 4], + [5, 6], + ] as [number, number][]) { + const inputId = "(" + [a, b].join(",") + ")"; - yield* beginSlide("Input " + i); + yield* all(functionBox().reset(0.25)); + yield* functionBox().setInputs([{ val: a }, { val: b }], 0.5); - yield* input().position(left, 0.5); - yield* all(input().opacity(0, 0.2), boxMoji().text("👷‍♀️⚙️", 0.2)); - yield* waitFor(0.5); + yield* beginSlide("Add Inputs " + inputId); - const result = fibonacciFn(i); - yield* all( - output().opacity(1, 0.5), - output().text(result.toString(), 0.5), - output().position(right, 0.5), - ); + yield* functionBox().propogateInput(0.5); + yield* waitFor(0.3); + yield* functionBox().propogateOutput(0.5); - yield* all(boxMoji().text("😴", 0.2)); - - yield* beginSlide("Output " + i); + yield* beginSlide("Propogate Outputs of " + inputId); + yield* beginSlide("Propogate Outputs of 1" + inputId); } - - yield* all( - boxMoji().opacity(0, 0.2), - block().fontSize(30, 1), - node().opacity(1, 1), - ); - - yield* beginSlide("Revealing"); });