Programmable Ink Lab Notes
title
Inkbase demos
dated
Fri. March 8, 2024
author
Alex Warth

I prepared the following demos for a quick tour of Inkbase at our friday Show & Tell.


Hello Fishy

This one introduces ink strokes and the property editor.

Play by play:

(query/intersects me)
    
(filter (fn (x) (? x "is-fish")) (? me "touching"))
    

Costumes

This one continues to illustrate Inkbase’s reactive dataflow thing.

Play by play:

(? (? me "costume") "path/normalized")
    
(if (< 500 (? me "bbox/y")) "obj1" "obj2")
    

This Demo Kicks… Rocks

This one has a formula with side effects.

Play by play:

(query/overlaps me)
    
(each (? me "touching")
      (fn (obj)
        (! obj "bbox/x"
          (+ (? obj "bbox/x") 10))))
    

More Realistic Infections

The original infection demo in this doc didn’t have transitivity, i.e., only “patient zero” could infect other objects. This version does infections with transitivity. Also, unlike the other examples, this one is done in 3rd person (as a propety of the page, or as a field if you prefer) instead of as a property of the infected objects themselves, which I could call 1st person / object-oriented.

Play by play:

(each (? me "members")
      (fn (obj)
        (if (? obj "infected")
          (do
            (! obj "fill" "yellow")
              (each (query/intersects obj)
                (fn (otherObj) (! otherObj "infected" true)))))))
    

Logic Circuits

The Inkbase essay referenced a logic ciruits kit that I made in Etoys, a programming language for kids. They implemented something similar but with a very different feel. I wanted to try this myself, this time with wires that you can draw with the pencil and whose colors change to show whether they are carrying a value of true or false.

I’m really happy with how this one came out, but there were some hairy bits that I’ll discuss below.

Play by play:

(on "draw-started" me
      (fn (obj)
        (! obj "line-width" 4)
        (: obj "connections"
          (let (visit (fn (connections x)
                 (if (set/has? x connections)
                   connections
                   (reduce
                     visit
                     (set/add x connections)
                     (query/intersects x)))))
            (set/remove me (visit [] me))))
        (: obj "is-on"
          (reduce
            (fn (x y) (or x y))
            false
            (map (fn (x) (? x "is-true")) (? me "connections"))))
        (: obj "stroke"
          (if (? me "is-on") "yellow" "black"))))
    
    

This property uses the draw-started event, which happens every time the user starts to draw an ink stroke. You provide an event handler function whose argument is the new ink stroke object. What I’m doing here is “installing” a bunch of properties that I want every wire to have. The first one is line-width, which I set to 4 to make it easy to see the wires.

The second property is connections, whose value is the set of wires that are reachable from this wire. (me is like this in Java/C++/etc.) It’s written in a purely functional way, using a helper function called visit.

Next is the is-on property. It says "I’m on if one of the things that I am (transitively) connected to is true. Note that wires are never true (i.e., w is a wire ⇒ (not (is-true w))). It’s only the “source of truth” circle (and its clones) that have this is-true property. Wires have is-on, which is computed dynamically. (This distinction is necessary because in my model, wires can conduct value in any direction.)

Last but not least, the wire’s stroke color is yellow when it’s on, and black when it’s not.

So that’s how wires work.

And here’s how the gates work. I’ll use or as an example, but the approach is exactly the same for and and for not.

or-gate.jpg

The two inputs of the or gate shown above are the objects with ids "obj17" and "obj18". I added an is-true property to each of them that works just like a wire’s is-on property.

The output of this or gate is "obj21". It also has an is-true property whose value is true if either of its inputs is-true. The fact that the output of the logic gate is called is-true enables it to conditionally mimic a true as far as downstream wires are concerned.

There is just one last thing that was necessary to make this work: I had to create a group that included only the inputs of the logic gate. When an object is in a group, it can detect overlaps, intersections, etc. with the objects outside its group, but not the other way around. (From the outside, the queries’ result sets include the group instead of its members. This asymetry is useful here because Inkbase’s evaluator cannot handle cycles.