add function box initial component

This commit is contained in:
Elizabeth Hunt 2024-02-08 15:32:45 -07:00
parent 6a3ce850c5
commit 93dd6c53f1
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
3 changed files with 312 additions and 134 deletions

View File

@ -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 }[];
/*
<Node ref={this.node} opacity={0}>
<CodeBlock
fontFamily={theme.font}
language="typescript"
ref={this.block}
fontSize={1}
code={this.source}
></CodeBlock>
</Node>
*/
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<CodeBlock>();
private readonly node = createRef<Node>();
private readonly rect = createRef<Rect>();
private readonly boxMoji = createRef<Txt>();
private readonly inputSegments: Line[] = [];
private readonly inputs: Rect[] = [];
private readonly outputSegment = createRef<Line>();
private readonly output = createRef<Node>();
private readonly child = createRef<FunctionBox>();
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(
<Rect
opacity={this.opacity}
direction={"row"}
alignItems={"center"}
layout
>
<Rect direction={"column"} alignItems={"end"}>
{range(this.arity).map((i) => (
<Rect direction={"row"} alignItems={"center"} gap={10}>
<Rect
direction={"row"}
ref={makeRef(this.inputs, i)}
justifyContent={"end"}
opacity={1}
></Rect>
<Line
points={[]}
stroke={theme.green.hex}
ref={makeRef(this.inputSegments, i)}
lineWidth={5}
arrowSize={10}
endArrow
></Line>
</Rect>
))}
</Rect>
<Rect
ref={this.rect}
radius={4}
stroke={theme.overlay0.hex}
fill={theme.crust.hex}
lineWidth={4}
padding={60}
direction={"row"}
height={"100%"}
gap={40}
>
<Txt fontFamily={theme.font} fill={theme.text.hex} ref={this.boxMoji}>
{this.idlingText}
</Txt>
</Rect>
<Rect direction={"column"} height={"100%"} alignItems={"end"}>
<Rect direction={"row"} alignItems={"center"} gap={10}>
<Line
points={[]}
stroke={theme.red.hex}
lineWidth={5}
arrowSize={10}
ref={this.outputSegment}
endArrow
></Line>
<Rect
direction={"row"}
ref={this.output}
justifyContent={"end"}
opacity={1}
></Rect>
</Rect>
</Rect>
</Rect>,
);
}
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 ?? (
<Txt fontFamily={theme.font} fontSize={30} fill={theme.text.hex}>
{args[i].val.toString()}
</Txt>
),
);
});
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(
<FunctionBox
opacity={0}
isChild={true}
ref={this.child}
fn={output}
></FunctionBox>,
);
} else {
yield this.output().add(
<Txt fontFamily={theme.font} fontSize={30} fill={theme.text.hex}>
{output.toString()}
</Txt>,
);
}
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),
);
}
}

View File

@ -1,10 +1,9 @@
import { Txt, makeScene2D } from "@motion-canvas/2d"; import { makeScene2D } from "@motion-canvas/2d";
import { import {
Direction, Direction,
beginSlide, beginSlide,
createRef, createRef,
slideTransition, slideTransition,
waitFor,
} from "@motion-canvas/core"; } from "@motion-canvas/core";
import { CodeBlock, insert } from "@motion-canvas/2d/lib/components/CodeBlock"; import { CodeBlock, insert } from "@motion-canvas/2d/lib/components/CodeBlock";
import { theme } from "../theme"; import { theme } from "../theme";

View File

@ -1,156 +1,45 @@
import { import { makeScene2D } from "@motion-canvas/2d";
Img,
Rect,
Node,
Video,
makeScene2D,
Txt,
Line,
LineSegment,
} from "@motion-canvas/2d";
import { import {
Direction, Direction,
beginSlide, beginSlide,
createRef, createRef,
map,
slideTransition, slideTransition,
tween,
all, all,
waitFor, waitFor,
} from "@motion-canvas/core"; } from "@motion-canvas/core";
import { CodeBlock } from "@motion-canvas/2d/lib/components/CodeBlock"; import { FunctionBox } from "../components/function_box";
import { theme } from "../theme";
const fibonacciFn = (n: number): number => { const add = `(a: number, b: number) => {
if (n <= 2) { return a + b;
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");
export default makeScene2D(function* (view) { export default makeScene2D(function* (view) {
const block = createRef<CodeBlock>(); const functionBox = createRef<FunctionBox>();
const node = createRef<Node>();
const rect = createRef<Rect>();
const boxMoji = createRef<Txt>(); view.add(<FunctionBox arity={2} source={add} ref={functionBox} />);
const inSegment = createRef<Line>();
const outSegment = createRef<Line>();
const input = createRef<Txt>();
const output = createRef<Txt>();
yield* view.add(
<>
<Rect
ref={rect}
radius={4}
stroke={theme.overlay0.hex}
fill={theme.crust.hex}
lineWidth={4}
padding={60}
layout
>
<Node ref={node} opacity={0}>
<CodeBlock
fontFamily={theme.font}
language="typescript"
ref={block}
fontSize={1}
code={fibonacci}
></CodeBlock>
</Node>
</Rect>
<Txt fontFamily={theme.font} fill={theme.text.hex} ref={boxMoji}>
😴
</Txt>
<Line
points={[]}
ref={inSegment}
stroke={theme.green.hex}
lineWidth={8}
radius={40}
endArrow
></Line>
<Line
points={[]}
ref={outSegment}
stroke={theme.red.hex}
lineWidth={8}
radius={40}
endArrow
></Line>
<Txt
opacity={0}
fontFamily={theme.font}
fill={theme.text.hex}
ref={input}
></Txt>
<Txt
opacity={0}
fontFamily={theme.font}
fill={theme.text.hex}
ref={output}
></Txt>
</>,
);
yield boxMoji().position(rect().middle());
yield* slideTransition(Direction.Left); yield* slideTransition(Direction.Left);
yield* beginSlide("Black Box"); yield* beginSlide("Black Box");
const padding = 100; for (const [a, b] of [
const left = rect().left(); [-1, 2],
const right = rect().right(); [3, 4],
yield* all( [5, 6],
inSegment().points([left.addX(-padding), left], 0.5), ] as [number, number][]) {
outSegment().points([right, right.addX(padding)], 0.5), const inputId = "(" + [a, b].join(",") + ")";
);
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),
);
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* beginSlide("Add Inputs " + inputId);
yield* all(input().opacity(0, 0.2), boxMoji().text("👷‍♀️⚙️", 0.2));
yield* waitFor(0.5);
const result = fibonacciFn(i); yield* functionBox().propogateInput(0.5);
yield* all( yield* waitFor(0.3);
output().opacity(1, 0.5), yield* functionBox().propogateOutput(0.5);
output().text(result.toString(), 0.5),
output().position(right, 0.5),
);
yield* all(boxMoji().text("😴", 0.2)); yield* beginSlide("Propogate Outputs of " + inputId);
yield* beginSlide("Propogate Outputs of 1" + inputId);
yield* beginSlide("Output " + i);
} }
yield* all(
boxMoji().opacity(0, 0.2),
block().fontSize(30, 1),
node().opacity(1, 1),
);
yield* beginSlide("Revealing");
}); });