Skip to content

Instantly share code, notes, and snippets.

@fami-fish
Last active February 10, 2023 19:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fami-fish/4504ccde87d1ec94da1fd86ad523493f to your computer and use it in GitHub Desktop.
Save fami-fish/4504ccde87d1ec94da1fd86ad523493f to your computer and use it in GitHub Desktop.
A Motion Canvas component for arrays of numbers and chars
import { Layout, LayoutProps, Node, Rect, Text } from "@motion-canvas/2d/lib/components";
import { initial, signal } from "@motion-canvas/2d/lib/decorators";
import { SignalValue, SimpleSignal } from "@motion-canvas/core/lib/signals";
import { ColorSignal } from "@motion-canvas/core/lib/types";
import { makeRef } from "@motion-canvas/core/lib/utils";
import { Colors, BlackLabel, WhiteLabel } from "../styles";
export enum Quote {
single = "'",
double = "\"",
tick = "`",
none = ""
}
export enum yAlign { top, bottom }
export enum xAlign { left, right }
export interface NameAlignment {
x: xAlign
y: yAlign
}
export interface LabeledArray {
[key: string]: string|number
}
export const isArrayObject = (checkObj: number[] | string[] | LabeledArray): checkObj is LabeledArray => {
if((checkObj as LabeledArray) !== undefined) {
return true;
}
return false;
}
export interface ArrayProps extends LayoutProps {
values?: SignalValue<number[] | string[] | LabeledArray>;
quoteChars?: SignalValue<string|Quote>;
invertColors?: SignalValue<boolean>;
nameAlignment?: SignalValue<NameAlignment>;
highlightIndexes?: SignalValue<number[]>;
highlightColor?: SignalValue<string>;
suffixColor?: SignalValue<string>;
suffix?: SignalValue<string>;
name?: SignalValue<string>;
}
export class Array extends Layout {
@initial([])
@signal()
public declare readonly values: SimpleSignal<number[] | string[] | {[key: string] : string|number}, this>
@initial(Quote.single)
@signal()
public declare readonly quoteChars: SimpleSignal<Quote, this>
@initial(false)
@signal()
public declare readonly invertColors: SimpleSignal<boolean, this>
@initial({x: xAlign.left, y: yAlign.top})
@signal()
public declare readonly nameAlignment: SimpleSignal<NameAlignment, this>
@initial([])
@signal()
public declare readonly highlightIndexes: SimpleSignal<number[], this>
@initial(Colors.red)
@signal()
public declare readonly suffixColor: ColorSignal<this>
@initial("[]")
@signal()
public declare readonly suffix: SimpleSignal<string, this>
@initial("array")
@signal()
public declare readonly name: SimpleSignal<string, this>
public readonly arrayContainer: Rect;
public readonly arrayName: Layout;
public readonly boxArray: Node[] = [];
public getIndexBox(index: number) {
return this.boxArray[index]
}
public constructor(props?: ArrayProps) {
super({
...props
});
let arrayObject = this.values()
if(isArrayObject(arrayObject)) {
let i = 0;
for (const [key, value] of Object.entries(arrayObject)) {
let dark = Colors.background;
let light = Colors.surfaceLight;
let whiteLabel = WhiteLabel;
let blackLabel = BlackLabel;
if(this.highlightIndexes().includes(i)) {
[whiteLabel, blackLabel] = [blackLabel, whiteLabel];
[light, dark] = [dark, light]
}
if(this.invertColors()) {
[whiteLabel, blackLabel] = [blackLabel, whiteLabel];
[light, dark] = [dark, light]
}
this.boxArray.push(
<Rect
radius={5}
width={60}
height={60}
fill={light}
gap={12}
padding={15}
justifyContent="start"
alignItems="center"
direction="column"
>
<Text
{...blackLabel}
lineHeight={blackLabel.fontSize+2}
text={`${this.quoteChars()}${value}${this.quoteChars()}`}
/>
<Text
lineHeight={WhiteLabel.fontSize}
{...WhiteLabel}
fontSize={blackLabel.fontSize / 1.5}
text={`${key.toUpperCase()}`}
/>
</Rect>
)
i++;
}
} else {
for(let i = 0; i < this.values().length; i++) {
let dark = Colors.background;
let light = Colors.surfaceLight;
let whiteLabel = WhiteLabel;
let blackLabel = BlackLabel;
if(this.highlightIndexes().includes(i)) {
[whiteLabel, blackLabel] = [blackLabel, whiteLabel];
[light, dark] = [dark, light]
}
if(this.invertColors()) {
[whiteLabel, blackLabel] = [blackLabel, whiteLabel];
[light, dark] = [dark, light]
}
this.boxArray.push(
<Rect
radius={5}
width={60}
height={60}
fill={light}
gap={12}
padding={15}
justifyContent="start"
alignItems="center"
direction="column"
>
<Text
{...blackLabel}
lineHeight={blackLabel.fontSize+2}
text={`${this.quoteChars()}${(this.values() as any[])[i]}${this.quoteChars()}`}
/>
<Text
lineHeight={WhiteLabel.fontSize}
{...WhiteLabel}
fontSize={blackLabel.fontSize / 1.5}
text={`${i}`}
/>
</Rect>
)
}
}
this.add(
<>
<Rect
ref={makeRef(this, 'arrayContainer')}
width={null}
height={100}
direction='row'
padding={20}
gap={20}
fill={Colors.surface}
radius={20}
layout
>
{this.boxArray}
</Rect>
<Layout
ref={makeRef(this, 'arrayName')}
direction='row'
gap={10}
layout
offsetX={() => this.nameAlignment().x == xAlign.left ? -1 : 1}
x={() => {
if(this.nameAlignment().x == xAlign.left) {
return -this.arrayContainer.size().x/2 +20
} else {
return this.arrayContainer.size().x/2 - 20
}
}}
y={() => {
if(this.nameAlignment().y == yAlign.top)
return this.arrayContainer.position().y - this.arrayContainer.size().y + 25
else {
return this.arrayContainer.position().y + this.arrayContainer.size().y - 25
}
}}
>
<Text
{...WhiteLabel}
text={this.name()}
/>
<Text
{...WhiteLabel}
fill={this.suffixColor()}
text={this.suffix()}
/>
</Layout>
</>
)
}
}
@fami-fish
Copy link
Author

fami-fish commented Feb 6, 2023

Array Component

The array component is heavily based off of aarthifical's style of videos. To keep faithful to his style, I have included his styles.ts in my project and used colours from it.

Changes

  • highlighted boxes are now animatable.

Feel free to use any part of this code, adapt, modify and re-distribute to your hearts content ❤️

Example usage

    <Array
        ref={strArray}
        name="hello"
        suffix="&str"
        suffixColor={Colors.FUNCTION}
        opacity={() => strOpacity()}
        y={() => 200 * stringOpacity()}
        x={80}
        values={() => stringLiteral()}
        nameAlignment={{ x: xAlign.left, y: yAlign.bottom }}
        invertColors={true}
        highlightIndexes={[1]}
    />

Implementation example

import { makeScene2D } from "@motion-canvas/2d";
import { Line, Rect, Node } from "@motion-canvas/2d/lib/components";
import { CodeBlock, insert, lines, range } from "@motion-canvas/2d/lib/components/CodeBlock";
import { all, waitFor, waitUntil } from "@motion-canvas/core/lib/flow";
import { createSignal } from "@motion-canvas/core/lib/signals";
import { slideTransition } from "@motion-canvas/core/lib/transitions";
import { Direction } from "@motion-canvas/core/lib/types";
import { createRef, useLogger, useScene } from "@motion-canvas/core/lib/utils";
import { Array, Quote, xAlign, yAlign } from "../components/array";
import { Colors } from '../styles'

export default makeScene2D(function* (view) {
    const scale = 75;
    const code = createRef<CodeBlock>();
    const preview = createRef<Node>();

    const stringLiteral = createSignal(["H", "e", "l", "l", "o"])
    const strArray = createRef<Array>();
    const stringArray = createRef<Array>();


    const previewOpacity = createSignal(0);

    const strOpacity = createSignal(0);
    const stringOpacity = createSignal(0);
    const lineOpacity = createSignal(0);

    const index = createSignal(1);
    view.add(
        <Node x={400} ref={preview} opacity={() => previewOpacity()}>
            <Line
                points={() => {
                    if(index() == -1) return [[0, 0], [0, 0]];
                    let x = stringArray().getIndexBox(0).position().x;
                    let i = Math.round(index());
                    useLogger().info(i+" " + index().toFixed(2))
                    let newPos = strArray().getIndexBox(i+1);

                    return [
                        [x, stringArray().getIndexBox(i).position().y + 60],
                        [newPos.position().x, strArray().position().y - 60],
                    ]
                }}
                opacity={() => lineOpacity()}
                lineWidth={8}
                arrowSize={20}
                stroke={Colors.surfaceLight}
            />
            <Array
                ref={stringArray}
                opacity={() => stringOpacity()}
                name="hello"
                suffix="String"
                suffixColor={Colors.red}
                values={() => {
                    return { ptr: `*${index() == -1 ? "?" : Math.round(index())}`, len: `${stringLiteral.length}`, cap: '5' }
                }}
                highlightIndexes={[0]}
                quoteChars={Quote.none}
                invertColors={true}
            />

            <Array
                ref={strArray}
                name="hello"
                suffix="&str"
                suffixColor={Colors.FUNCTION}
                opacity={() => strOpacity()}
                y={() => 200 * stringOpacity()}
                x={80}
                values={() => stringLiteral()}
                nameAlignment={{ x: xAlign.left, y: yAlign.bottom }}
                invertColors={true}
                highlightIndexes={[1]}
            />
        </Node>
    );

    yield view.add(
        <>
            <Rect
                // layout
                offset={-1}
                x={-960 + 80}
                y={-540 + 80}
                height={1080 - 160}
                width={960}
                clip
            >
                <CodeBlock
                    language='rust'
                    selection={[
                        [
                            [0, 0],
                            [8, 100],
                        ],
                    ]}
                    ref={code}
                    fontSize={24}
                    lineHeight={36}
                    offsetX={-1}
                    fontFamily={'JetBrains Mono'}

                    x={-960 / 2}
                    code={`fn main() {\n\n};`}
                />
            </Rect>
        </>,
    );

    yield* slideTransition(Direction.Bottom, 1);
    yield* waitUntil('define_str');
    yield* code().selection(lines(1), 0.3);
    yield* code().edit(0.8)`fn main() {
    ${insert(`let hello = "Hello";`)}
};`;
    
    yield* waitUntil('type_str');
    yield* code().selection(lines(-1), 0.3)
    yield* code().edit(1.2, true)`fn main() {
    let hello${insert(`: &Str`)} = "Hello";
};`;

    yield* waitUntil('memory_str')
    yield* all(previewOpacity(1, 0.5), strOpacity(1, 0.5))

    yield* waitUntil('define_string')
    yield* code().edit(1.2)`fn main() {
    let hello: &Str = "Hello";${insert(`
    let greeting = String::from(hello);`)}
};`;

    yield* waitUntil('memory_representation')
    yield* stringOpacity(1, 0.5)
    yield* waitFor(0.5)
    yield* all(code().selection(range(1, 24, 1, 25), 0.3), lineOpacity(1, 0.5))

    yield* waitUntil("end")
    useScene().enterCanTransitionOut();
});

@Starstalker-awe
Copy link

Criminey... And here I am trying to make some circles grow at the same rate 😅

@fami-fish
Copy link
Author

Criminey... And here I am trying to make some circles grow at the same rate 😅
so uhh this isnt designed for that. I made this only for Number and Char arrays.
sorry!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment