Hello world!

Hello! This is the beginning.

Today I spent a lot of time working. I formalized algorithm steps and made the whole draft I am currently working on significantly more elegant and “well-defined”.

In my breaks, I experimented with Motion Canvas (https://github.com/motion-canvas/motion-canvas) and created a sample video (see below). It’s really cool!

I wish I had a more sophisticated use-case for Motion Canvas. I thought about creating some explanation videos for some arbitrary topic which can be intriguingly visualized. But creating videos is extremely time-consuming 😅.

Thus, I might stick to just creating some animations just to learn to use Motion Canvas more. There is probably also potential in developing some common scenes/boilerplate, either for myself or open-sourcing it; An awesome-list (awesome-motion-canvas) will definitely also exist eventually.

I also did some more (not animation-related!) programming today: I was frustrated with my spell & grammar checker in vscodium (which provides really open-source binaries for the editor; I have it installed besides vscode and configured for prose/paper writing, while I use vscode for code).

I use LTeX for spell checking (which is great in terms of functionalist and features, while also being a little finicky). LTeX uses the open-source and local version of LanguageTool (think EU/open-source-focused Grammarly) to retrieve its suggestions.

LanguageTool also has a premium plan with advanced suggestions (which are, for smaller clients, only available via their website and hosted API). LTeX luckily also supports interfacing with that API (when configured with the required credentials). But I had a lot of trouble making it work: The extension sent too much text or text too frequently, which caused all sorts of error codes on the LanguageTool API.

I wanted to use the offline version of the software for the most part (i.e., during writing) and the online version only occasionally (for looking to improve certain sections and detect mistakes the offline version does not support).

Thus, I started looking for vscode/vscodium extensions that allow to easily switch between two settings. There are a few, but they all had significant downsides. I wanted to toggle at least one setting between two (non-boolean!) states and also be shown a status icon in the bottom.

None of the existing extensions I found offered this functionality. Luckily, at least one extension was open-source under a MIT licence and had almost everything I was looking for. Quickly forking it from GitHub, updating the dependencies and applying my changes worked really well! (Although the original deprecated vscode dependency on npm sent me to a few different other deprecated projects until I finally found the maintained version.)

With some more research, I was ready to package the extension and install it in vscodium. It worked really well and included exactly the feature changes I needed for my use-case.

As I was pretty pressed for time, I did not make any more general changes which would support more use-cases yet, but now I am tempted to do so at some point. I think there are probably at least some people who would also enjoy the feature set I have in mind.

It felt great that I was able to leverage all the previous work on the extension. To me, it really neatly demonstrated the wonderful side of open-source software and tooling. ☺️👨‍💻

project.ts
import {makeProject} from '@motion-canvas/core/lib';

import main from './scenes/main?scene';
import intro from './scenes/intro?scene';
import { DARK_MODE } from './configuration';
export default makeProject({
  scenes: [intro,main],
  background: DARK_MODE ? '#141414' : '#fafafafa',
});
configuration.ts
export const DARK_MODE = false;
intro.tsx
import { Grid, Node, Text } from "@motion-canvas/2d/lib/components";
import { makeScene2D } from "@motion-canvas/2d/lib/scenes";
import { waitUntil } from "@motion-canvas/core/lib/flow";
import { createRef } from "@motion-canvas/core/lib/utils";
import { DARK_MODE } from "../configuration";
const FOREGROUND = DARK_MODE ? "#f7f2f2" : "#1e1c1c";
export default makeScene2D(function* (view) {
  const text = createRef<Text>();
  view.add(
    <Node>
      <Grid
        width={3000}
        height={1920}
        spacing={50}
        stroke={DARK_MODE ? "#444" : "lightgray"}
        lineWidth={0.5}
        lineCap="square"
        cache
      />
      <Text ref={text} text="PICK Chart" fill={FOREGROUND} scale={2} fontWeight={500} />
    </Node>
  );

  yield* text().scale(4.0, 1.0);
  yield* waitUntil("intro fadeout");
});
main.tsx
import { makeScene2D } from "@motion-canvas/2d/lib/scenes";
import { Node, Circle, Grid, Line, Text, Rect } from "@motion-canvas/2d/lib/components";
import { all, delay, waitFor, waitUntil } from "@motion-canvas/core/lib/flow";
import { Direction, Vector2 } from "@motion-canvas/core/lib/types";
import { createRef } from "@motion-canvas/core/lib/utils";
import { SignalGenerator } from "@motion-canvas/core/lib/signals";
import { slideTransition } from "@motion-canvas/core/lib/transitions";
import { ThreadGenerator } from "@motion-canvas/core/lib/threading";
import { DARK_MODE } from "../configuration";

const FOREGROUND = DARK_MODE ? "#f7f2f2" : "#1e1c1c";
const COORDINATE_SIZE = 500;

export default makeScene2D(function* (view) {
  const group = createRef<Node>();
  const innerGroup = createRef<Node>();
  const legendGroup = createRef<Node>();

  const circs = [
    { x: 100, y: -200, ref: createRef<Circle>() },
    { x: 200, y: -100, ref: createRef<Circle>() },
    { x: 400, y: -300, ref: createRef<Circle>() },
    { x: (1 / 4) * COORDINATE_SIZE, y: -430, ref: createRef<Circle>() },
  ];

  const areas = [
    {
      name: "Possible",
      x: (1 / 4) * COORDINATE_SIZE,
      y: (-3 / 4) * COORDINATE_SIZE,
      color: "#f4e19520",
      textRef: createRef<Text>(),
      rectRef: createRef<Rect>(),
    },
    {
      name: "Implement",
      x: (3 / 4) * COORDINATE_SIZE,
      y: (-3 / 4) * COORDINATE_SIZE,
      color: "#6cfc7120",
      textRef: createRef<Text>(),
      rectRef: createRef<Rect>(),
    },
    {
      name: "Challenge",
      x: (3 / 4) * COORDINATE_SIZE,
      y: (-1 / 4) * COORDINATE_SIZE,
      color: "#94cffc20",
      textRef: createRef<Text>(),
      rectRef: createRef<Rect>(),
    },
    {
      name: "Kill",
      x: (1 / 4) * COORDINATE_SIZE,
      y: (-1 / 4) * COORDINATE_SIZE,
      color: "#fc6c8220",
      textRef: createRef<Text>(),
      rectRef: createRef<Rect>(),
    },
  ];

  view.add(
    <Node ref={group} x={-250} y={250}>
      <Node ref={innerGroup}>
        <Grid
          width={3000}
          height={1920}
          spacing={50}
          stroke={DARK_MODE ? "#444" : "lightgray"}
          lineWidth={0.5}
          lineCap="square"
          cache
        />

        <Text
          text="PICK Chart"
          fill={FOREGROUND}
          fontSize={64}
          y={-1.3 * COORDINATE_SIZE}
          x={0.5 * COORDINATE_SIZE}
        />
        {circs.map((circ) => (
          <Circle
            ref={circ.ref}
            width={20}
            height={20}
            stroke={FOREGROUND}
            x={circ.x}
            y={circ.y}
            lineWidth={2}
            opacity={0}
          />
        ))}

        {areas.map((area) => (
          <Node x={area.x} y={area.y}>
            <Text ref={area.textRef} text={area.name} opacity={0} fill={FOREGROUND} fontSize={28} />
            <Rect
              ref={area.rectRef}
              height={COORDINATE_SIZE / 2}
              width={COORDINATE_SIZE / 2}
              fill={area.color}
            />
          </Node>
        ))}
        <Line
          stroke={"#47994a"}
          lineWidth={5}
          endArrow
          y={-COORDINATE_SIZE}
          arrowSize={15}
          points={[Vector2.left.scale(2.5), Vector2.right.scale(COORDINATE_SIZE + 20)]}
        />
        <Line
          stroke={"lightgrey"}
          lineWidth={2}
          arrowSize={10}
          x={COORDINATE_SIZE / 2}
          points={[Vector2.zero, Vector2.down.scale(COORDINATE_SIZE)]}
        />
        <Line
          stroke={"lightgrey"}
          lineWidth={2}
          arrowSize={10}
          y={-(COORDINATE_SIZE / 2)}
          points={[Vector2.zero, Vector2.right.scale(COORDINATE_SIZE)]}
        />
        <Line
          stroke={"#e25369"}
          lineWidth={5}
          startArrow
          arrowSize={15}
          points={[Vector2.up.scale(20), Vector2.down.scale(COORDINATE_SIZE + 2.5)]}
        />
        <Text text="Difficulty" fill={FOREGROUND} fontSize={28} y={(-1 / 2) * COORDINATE_SIZE} x={-80} />
        <Text
          text="Payoff"
          fill={FOREGROUND}
          fontSize={28}
          x={(1 / 2) * COORDINATE_SIZE}
          y={-1.07 * COORDINATE_SIZE}
        />
        <Circle x={0} y={-COORDINATE_SIZE} height={15} width={15} fill={"lightgray"} />
        <Node ref={legendGroup} opacity={0}>
          <Text
            text="high"
            fill={FOREGROUND}
            fontSize={20}
            x={COORDINATE_SIZE}
            y={-1.06 * COORDINATE_SIZE}
          />
          <Text text="high" fill={FOREGROUND} fontSize={20} x={-40} y={0} />
          <Text text="low" fill={FOREGROUND} fontSize={20} x={-20} y={-COORDINATE_SIZE - 20} />
        </Node>
      </Node>
    </Node>
  );

  // Setting up scene
  yield* slideTransition(Direction.Bottom, 0.7);
  yield* waitFor(2);
  yield* legendGroup().opacity(1.0, 0.7);

  // Add items to chart
  yield* waitUntil("add items");
  for (const circ of circs) {
    yield* circ.ref().opacity(1.0, 0.8);
  }
  

  // Add items to chart
  for (let i = 0; i < areas.length; i++) {
    const area = areas[i];
    yield* waitUntil("focus: " + area.name);
    const trans: (SignalGenerator<any, any> | ThreadGenerator)[] = [
      innerGroup().position(new Vector2(-area.x + 75, -area.y - 50), 0.8),
    ];
    if (i == 0) {
      trans.push(group().scale(3, 0.8));
    }
    yield* all(...trans);
    // Add text laabel to area
    yield* all(area.textRef().opacity(1.0, 0.6), delay(0.2, area.textRef().scale(1.2, 0.5)));
    yield* area.textRef().scale(1, 0.5);
  }

  // Zoom out again
  yield* waitUntil("zoom out");
  yield* all(group().scale(1, 0.8), innerGroup().position.y(0, 0.8), innerGroup().position.x(0, 0.8));

  // Fade out 
  yield* waitUntil("fade out");
  yield* innerGroup().opacity(0.0, 2.0);
});

Posted

in