Open Source Adventures: Episode 21: Imba 2 Matrix Rain

taw

Tomasz Wegrzanowski

Posted on March 24, 2022

Open Source Adventures: Episode 21: Imba 2 Matrix Rain

Let's port another Imba 1 app to Imba 2, this time Matrix Rain, a screensaver similar to the Matrix.

You can see it in action here, and the source code of Imba 1 version here.

Imba 1 app.imba

This app uses two components - App (top level) and Stream (a single stream of falling characters), and a few lifecycle hooks, with setInterval, and setup.

There's also a few loose functions with unusually looking let def.

let def random_int(min, max)
  min + Math.floor( Math.random() * (max - min + 1) )

let def random_katakana
  String.fromCharCode(random_int(0x30A0, 0x30FF))

let def random_symbols
  for i in [0..random_int(5, 30)]
    { v: random_katakana() }

tag Stream
  def render
    <self css:top=data:y css:left=data:x>
      for symbol, index in data:symbols
        <div.symbol .first=(index==0)>
          symbol:v

tag App
  def setup
    @streams = []
    let x = 10
    while x + 30 < window:inner-width
      @streams.push({
        x: x,
        y: Math.random() * window:inner-height,
        speed: 10 + Math.random() * 20,
        symbols: random_symbols()
      })
      x += 30

  def mount
    setInterval(&,10) do
      for stream in @streams
        stream:y += stream:speed
        if stream:y > window:inner-height + stream:symbols:length * 20
          stream:symbols = random_symbols()
          stream:speed = 10 + Math.random() * 20
          stream:y = - stream:symbols:length * 20
        for symbol in stream:symbols
          if Math.random() < 0.01
            symbol:v = random_katakana()
      Imba.commit

  def render
    <self>
      for stream in @streams
        <Stream[stream]>

Imba.mount <App>
Enter fullscreen mode Exit fullscreen mode

Imba 1 app.scss

There's a bit of unusual styling here, featuring rarely used flex-direction: column-reverse. As usual, SCSS used only for nesting, and not for anything fancy.

@import 'normalize-scss';
@include normalize();

body {
  overflow: hidden;
}

.App {
  background-color: black;
  height: 100vh;
  width: 100vw;
  overflow: hidden;

  .Stream {
    position: absolute;
    display: flex;
    flex-direction: column-reverse;

    .symbol {
      height: 20px;
      width: 20px;
      line-height: 20px;
      position: relative;
      font-size: 16px;
      text-align: center;
      color: #8f4;

      &.first {
        color: #dfa;
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Imba 2 app.imba

Here's the app ported to Imba 2:

global css body
  background-color: black
  overflow: hidden
  margin: 0
  height: 100vh
  width: 100vw

def random_int(min, max)
  min + Math.floor( Math.random() * (max - min + 1) )

def random_katakana
  String.fromCharCode(random_int(0x30A0, 0x30FF))

def random_symbols
  for i in [0 .. random_int(5, 30)]
    { v: random_katakana() }

tag stream
  prop x
  prop y
  prop symbols

  <self[top:{y}px left:{x}px]>
    for symbol, index in symbols
      <div.symbol>
        symbol.v

  css
    position: absolute
    display: flex
    flex-direction: column-reverse

    .symbol
      height: 20px
      width: 20px
      line-height: 20px
      position: relative
      font-size: 16px
      text-align: center
      color: #8f4

      &:first-child
        color: #dfa

tag app
  def setup
    streams = []
    let x = 10
    while x + 30 < window.innerWidth
      streams.push({
        x: x
        y: Math.random() * window.innerHeight
        speed: 3 + Math.random() * 5
        symbols: random_symbols()
      })
      x += 30

  def mount
    imba.setInterval(&, 10) do
      for stream in streams
        stream.y += stream.speed
        if stream.y > window.innerHeight
          stream.symbols = random_symbols()
          stream.speed = 3 + Math.random() * 5
          stream.y = - stream.symbols.length * 20
        for symbol in stream.symbols
          if Math.random() < 0.01
            symbol.v = random_katakana()

  <self>
    for stream in streams
      <stream x=stream.x y=stream.y symbols=stream.symbols>

  css
    height: 100vh
    width: 100vw
    overflow: hidden

imba.mount <app>
Enter fullscreen mode Exit fullscreen mode

Some notes:

  • syntax is a bit closer to standard JavaScript
  • imba.setInterval is like regular setInterval, except it also tells Imba that properties might have changed. It's a different model of reactivity than Svelte's.
  • each symbol is wrapped with {v: ...} to make update in a loop easier
  • one baffling thing was <self[top:{y}px left:{x}px]> - I first tried <self[top:{y} left:{x}]> expecting pixels to be the default units, as that's how everything about DOM works, but somehow Imba converted 10 to 40px. Like what an actual fuck? It makes no sense whatsoever. It's some weird Tailwind nonsense, where h-1 means height: 0.25rem, and that's 4px. That's just totally baffling at framework level.
  • Imba's CSS has enough SCSS features (like &:first-child) that there's no need to use SCSS

Source code

Source code is in imba2-matrix-rain repository. At some point I'll try to add GitHub Pages support for it.

Coming next

In the next few episodes I'll try to port a few more Imba 1 apps to Imba 2, and then I guess I'll summarize my thoughts about Imba situation.

💖 💪 🙅 🚩
taw
Tomasz Wegrzanowski

Posted on March 24, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related