Sep 17, 2024
Very initial draft of a notebook UI.

There are 2 kinds of lines here:

  • Regular lines are prose, but you can also insert named blanks into them using the syntax [value|name], which renders vertically across 2 lines. You can't nest blanks within blanks. These lines can wrap, but a single blank can't wrap across multiple lines on screen.
  • Indented lines (though the indentation is currently subtle) are code. They can render fractions using the syntax (a/b) as I showed before. (Not sure I'll keep this in the long term.) You can nest fractions within fractions. These lines don't support wrapping.

Planned:

  • synchronizing blanks with the same name
  • using code in indented lines to compute derived names without values inline

Inspirations:

permalink

* *
Sep 15, 2024
I've implemented the Mandelbrot set several times before, but zooming manually into the Mandelbrot set on a touch screen is surprisingly delightful.

Get it from the Lua Carousel Devlog.

It takes only 20 lines of code to implement the core of the Mandelbrot set. But then it takes 20 lines to specify the palette. And 100 lines to adjust the viewport in response to touch events. And another 10 lines to render at lower precision while touches are in progress.

This does run into the limits of floating-point precision at some high level of zoom.

permalink

* *
Sep 10, 2024
My Sokoban client now includes the Microban puzzle sets.

"Sokoban puzzles crafted with the express aim of being tiny, tight and foundational for Sokoban idiom.. Effortlessly teaching you an entire language of mechanics through focused and methodical exploration of a ruleset." — a nice 2-minute video review

Download: sokoban.love

How to switch to the Microban levels:

(Or play them in a non-mobile web browser at Sokoban Online.)

(Microban is by David W. Skinner.)

permalink

* *
Sep 8, 2024
While I recently made text2.love easy to embed in other LΓ–VE projects and documented its API, the true interface of this thing doesn't lie in how you wire it up its bog-standard handlers. No, it's what you do in a workhorse internal method called I.get_rect which gets called on every frame and for every input event.

Here's a very silly example of the sort of app that is now easier to create: 
Screenshot of the following text in an editor window -- except the fractions and nested fractions are shown vertically, with numberator and denominator separated by a horizontal line:

# Simplifying fractions

Suppose we have a fraction:

2/6

The GCD of 2 and 6 is 2.

Dividing both numerator and denominator by 2, we get:

(2/2) / (6/2) = 1/3

Repo
Compare with text2.love

permalink

* *
Sep 7, 2024
Max Bernstein and I have been playing with and putting together a rudimentary 0-dependency cross-platform platform for GUIs. Standing on the shoulders of Fenster and microUI, 2KLoC to support Linux, Windows and Mac without any SDL. (No shade on SDL, though.)

Here's a longer blog post by Max.

Repo

permalink

* *
Sep 2, 2024
Now that I have a nice design for a text editor widget, I've been putting it through its paces, particularly for ideas like syntax highlighting and something I call "syntax geometry". Here's a kinda whimsical toy where '+' flips the direction of writing from horizontal to vertical and vice versa.

I only had to put this rule in in one place, and all my support for editing, moving and clicking on the screen to position the cursor continues to work.

(I did need to generalize a couple of things to get to this point.)

permalink

* *
Aug 30, 2024
The patterns of Barricelli

I've been obsessed recently with the work of Nils Aall Barricelli who pioneered cellular automata 15 years before John Conway, artificial life 20 years before Christopher Langton and chaos theory 15 years before Benoit Mandelbrot. Barricelli called his creation "symbioorganisms", but it's interesting to try to demystify them without any analogies with living organisms.

The playing field is a finite, circular 1D space of discrete squares. Squares can be occupied by one of many different kinds of elements. Each kind of element has a propensity to move through the space with a constant step. To this space of elements striding around, Barricelli adds 3 rules. (Well, he experimented with many different tweaks in his papers, but this is one concrete, elegant formulation.)

  • Destruction: When two objects collide, delete both. (This isn't quite what Barricelli says. But it suffices!)
  • Creation: When an object A moves to where a second object B used to be, make a new copy of it, somewhere nearby that depends on B.
  • Mutation: This rule is slightly more difficult to explain, and I found the rules plenty interesting without using it in this post. In brief, empty squares sometimes create new kinds of elements.

Just adding the first 2 rules gives rise to some very interesting behavior. Here's a pretty picture:

Read more →

* *
Aug 2, 2024
Inspired by CristΓ³bal Sciutto, here's a little piece of purely client-side html for creating tables.

Save a copy for yourself. You'll need to edit the html to tailor the table dimensions for a specific context.

Saving the table downloads a new copy. (You'll probably want to rename it to the original; that bit is kinda janky.)

As I said before, just view source on this beauty! 😍

permalink

* *
Jul 22, 2024
For the past month I've been doing something very unnatural to me: throwing the first one away. Going back and rewriting some aspect of a working program just to improve one property of the code, here eliminating all derived data structures and re-deriving everything all the time in a performant way.

The problem: implementing text editor operations as lines might wrap or scroll.

e.g. clicking with the mouse to reposition the cursor, or pressing the down arrow (which might cause a scroll)

The key new solution: an API of primitives that make such operations fairly self-evident to build.

  • to_loc: (x, y) -> loc
    Identify the location at pixel coordinates (x,y) on screen.
    Returns nil if (x,y) is not on screen.
  • to_coord: loc -> x, y
    Identify the top-left coordinate on screen of location loc.
    Returns nil if loc is not on screen.
  • down: loc, dy -> loc
    Find the location at the start of a screen line dy pixels down from loc.
    Returns nil if dy is taller than the screen.
    Returns bottom of file if we hit it.
  • up: loc, dy -> loc Find the location at the start of a screen line dy pixels up from loc.
    Returns nil if dy is taller than the screen.
    Returns top of file if we hit it.
  • hor: loc, x -> loc
    Find the location at x=x0 on the same screen line as loc.

I think they might be applicable to any pixel-based editors that use proportional fonts. They seem independent of the data structure used by the editor. I use an array of lines, and so locations are defined as (line_index, pos) tuples, where pos counts in utf-8 code-points.

There's probably a few bugs but hopefully it'll stabilize quickly. I'd appreciate people trying it out.

Lessons from this experience:

  • There's a "hard part" to programming beyond the reach of tools or methods. Sometimes a problem needs the right "algebraic" abstraction, designed around an invariant and preserving it across any composition of operations.
  • Not all programs get this hard.
  • It's useful to notice when some part calls out for doing this hard, focused work.

I think I now better understand the "abyss".

permalink

* *
Jul 11, 2024
Quick and dirty prototype of the previous algo/shape/code using Vim syntax highlighting.

The code in the screenshot is a function to convert a mouse click (mx, my) into the location (line_index, pos) of the character at that point on the screen.

The problem is much of this function is boilerplate shared with several other places, such as the code to draw text on screen, compute the height of a wrapped line, etc. The boilerplate makes it difficult to see the business logic unique to this particular function, and so creates pressure to prematurely create an abstraction to "DRY things out". Highlighting the shape of the boilerplate in pink helps the eye to focus on the unique business logic in the protrusions, and so counters the pressure to hide the boilerplate before I've figured out the best way to do so.


β”Œ local y = State.top
  for line_index,line in array.each(State.lines, State.screen_top.line) do
    if line.mode == 'text' then
      local x = State.left
      local initpos = 1
      if line_index == State.screen_top.line then
        initpos = State.screen_top.pos
      end
      for pos, char in utf8chars(line.data, initpos) do
        local w = State.font:getWidth(char)  -- width of char
        if char:match('%s') then
          if line_wrap_at_word_boundary(State, x, line.data, pos) then β”˜
            if my < y+State.line_height then return line_index, pos end
         β””  x = State.left
            y = y + State.line_height
          else β”˜
            if my < y+State.line_height and mx < x+w then return line_index, pos end
          β”” x = x + w
          end
        else
          if x+w > State.right then
            x = State.left
            y = y + State.line_height β”˜
            if my < y+State.line_height then return line_index, pos end
        β”” else β”˜
            if my < y+State.line_height and mx < x+w then return line_index, pos end
        β”” end
          x = x+w
        end
      end
      y = y + State.line_height
    elseif line.mode == 'drawing' then β”˜
      if my < y+State.drawing_height then return line_index end
    β”” y = y + h
    end
  end β”˜

(As an aside, this is an example of what I think of as "programmer-configured highlighting". We've gotten used to our editors deciding what to highlight for us, and we just pick the colors. One little tool I use everyday is the ability to highlight specific identifiers which flips this around: I pick a word, and the editor picks a color at random to highlight it with. And the highlight persists across sessions. The color of the State variable in the screenshot was selected in this manner.)

permalink

* *
archive
projects
writings
videos
subscribe
Mastodon
RSS (?)
twtxt (?)
Station (?)