diff options
Diffstat (limited to 'src/components/function_box.tsx')
-rw-r--r-- | src/components/function_box.tsx | 290 |
1 files changed, 290 insertions, 0 deletions
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 }[]; + +/* +<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), + ); + } +} |