puts

Wednesday
06-17-2026

One possible introduction to hokusai-pocket

by zero in firehose @ 18:18 PM

Porkchop Casserole

The year is 2026. It’s Wednesday night, and I’m digesting ChatGPT porkchop casserole. Nobody is riding hoverboards, and nobody is asking about a hot new binary named hokusai-pocket

It is not exactly in my nature to give an unsolicited introductions to software that isn’t in any kind of marketable demand, but by the same coin, I perform many “unnatural” behaviors. Like working under the alienating conditions of late stage capitalism, or eating ChatGPT porkchop casserole.

By now in my life I know this for certain.

A solution born from a different intention will yield a different outcome, and be subject to different constraints.

Hokusai Pocket was made to make it easier and more enjoyable to approach complex graphical computer applications. It comes from a personal investment in an easier and more enjoyable type of future.

hokusai pocket is a tool for writing GUIs

At the core, Hokusai Pocket is a Ruby library for writing Graphical User Interfaces (GUI). A GUI is program that collects input (A mouse click here, keypress there, maybe a tap or two), and typically renders graphics in a window in response to that input.

Examples include:

  • Minesweeper
  • iTunes
  • Photoshop
  • Excel

If you are reading this, you are almost certainly using a GUI! (browser)

There are many approaches to GUI libraries. Hokusai Pocket aims to make writing GUI programs simple and fun. It provides an Ruby interface for writing stateful components that listen for input (events) and draw graphics in response.

Let’s take a look at a basic counter application.

Open a new file named counter.rb and paste the following code.

# counter.rb
class Counter < Hokusai::Block
 template <<-EOF
 [template]
   hblock
     label {
       size="190"
       :content="count.to_s"
       :color="count_color"
     }
   hblock
     vblock { @click="increment" :background="BLUE"}
       label { content="Add" }
     vblock { @click="decrement":background="RED" }
       label { content="Subtract" }
 EOF


 uses(
   vblock: Hokusai::Blocks::Vblock,
   hblock: Hokusai::Blocks::Hblock,
   label: Hokusai::Blocks::Text,
 )


 RED = [244, 0, 0]
 BLUE = [0, 0, 244]


 attr_accessor :count


 def count_positive = count > 0
 def increment(event) = self.count += 1
 def decrement(event) = self.count -= 1
 def count_color = count.negative? ? RED : BLUE


 def initialize(**args)
   @count = 0


   super
 end
end


Hokusai::Backend.run(Counter) do |config|
 config.title = "Counter"     # title
 config.width = 550
 config.height = 500


 config.after_load do
   Hokusai.fonts.register "default", Hokusai::Backend::Font.default
   Hokusai.fonts.activate "default"
 end
end

This code defines a Counter component, and passes it to the MRuby/Raylib backend.

Let’s try it out, but first we’ll need to download the hokusai-pocket binary.

The binary is available in a few ways.

Download your choice and put the program on your path somewhere.

This all brings me to my next point.

hokusai-pocket is a tool for running GUIs

Aside from being a library that provides Ruby code to author GUI applications, it is also a binary to run those same applications in a portable way.

The GUI program should look the same no matter what platform it runs on.

To run the counter.rb program we just saved, we’ll use the run command from the same directory.

hokusai-pocket run:target=counter.rb

If everything went as planned, you should see a window that looks like this.

How easy! We just passed the file containing our ruby code to hokusai-pocket and got an interactive counter application.

The Anatomy of Counter

In our Counter application, we can see that the application is simply a class that inherits from Hokusai::Block. In fact, there is no difference between a component (block) and an application in hokusai. This composability means that you could put a functioning terminal emulator inside a photoshop clone, next a spreadsheet program.

State

Since blocks in Hokusai are plain ruby objects, you can manage state with them.

For the counter, we want to keep track of the current count. An easy way to do this is to put state in the initializer.

class Counter < Hokusai::Block
 #....
 attr_accessor :count


 def initialize(**args)
   @count = 0


   super
 end

We could also manage this state in other ways as well

attr_accessor :count
def count = @count ||= 0

Or use a lifecycle hook

attr_accessor :count
def on_mounted = @count = 0

Templates

Templates are a bit more complicated. Internally, Hokusai is basically a machine that continuously generates an ordered list of things to draw in a window.

If we want to draw a red circle in the middle of blue square, the instructions might look like this.

  1. Draw a blue rectangle at the top left position (0, 0) and bottom right at (100, 100)
  2. Draw at red circle at position (50, 50) with a radius of 20

Of course, if we drew the red circle first, the blue square would be drawn on top, so we’d never see it.

A Hokusai template declares a tree of blocks. More specifically for this example, the template declares a tree of words that map to blocks. The tree is processed depth-first, and each time it is processed, it creates an ordered list of draw commands. When rendering, each block in the tree is given a recommended section of the window to draw in. The recommended sections are based on some basic layout rules.

For an idea of the rules, imagine a whitespace significant string template.

[template]
vblock
 hblock
   first
   vblock
     second
     third
 vblock
   fourth
   fifth

Let’s say hblock creates a container where children are positioned horizontally, and vblock creates a container where children are positioned vertically

Let’s imagine that the default behavior is to:

  • render the whole document in the window regardless if it fits.
  • clip any excess content
  • divide all space equally between children.

So for the document above, it would look something like:

|----------------|-----------------|
|                |                 |
|                |     second      |
|                |                 |
|     first      |-----------------|
|                |                 |
|                |      third      |
|                |                 |
|----------------------------------|
|                                  |
|              fourth              |
|                                  |
|----------------------------------|
|                                  |
|              fifth               |
|                                  |
|----------------------------------|

It becomes quite simple to compute the expected position of each child, as the parent already knows the verticality and dimensions of its children.

For instance, imagine adding a width to the first child, and trimming some height from the hblock

[template]
root
 hblock {height="20"}
   first {width="20"}
   vblock
     second
     third
 vblock
   fourth
   fifth

The resulting children can flex around this, resulting in something like

|------------|---------------------|
|            |     second          |
|    first   |---------------------|
|            |      third          |
|----------------------------------|
|                                  |
|                                  |
|              fourth              |
|                                  |
|                                  |
|----------------------------------|
|                                  |
|                                  |
|              fifth               |
|                                  |
|                                  |
|----------------------------------|

Earlier I mentioned the template declares a tree of words that map to blocks. In order to know which words map to which blocks in a string template, Hokusai provides a uses class method.

 # ...
 uses(
   vblock: Hokusai::Blocks::Vblock,
   hblock: Hokusai::Blocks::Hblock,
   label: Hokusai::Blocks::Text,
 )
 # ...

Reserved Template keywords

There are a couple of reserved string template keywords that should not be used with arbitrary blocks.

name purpose
virtual meant for marking a template as a no-op, useful for blocks that render themselves using the drawing api
slot meant for declaring a child as a slot. Slots allow one to compose reusable blocks that can have different children or behaviors

The Drawing API

Blocks don’t always need to use templates. Hokusai provides an API generating draw commands directly.

Let’s say we want to make a block that renders a circle without using any built-in blocks / templates.

class Circle < Hokusai::Block
 template <<~EOF
 [template]
   virtual
 EOF


 # Render takes a `Hokusai::Canvas`, processes any draw commands,
 # and yields the canvas to the next block.
 def render(canvas)
   x = canvas.x + (canvas.width / 2)
   y = canvas.y + (canvas.height / 2)


   radius = 5.0


   draw do
     circle(x, y, radius) do |command|
       command.color = Hokusai::Color.new(255, 0, 0)
     end
   end


   yield canvas
 end
end

Overloading the render method will allow the block to recieve a Hokusai::Canvas which contains the suggested coordinates where that block should draw.

Once inside, we calculate the center of the canvas using it’s coordinates.

Then we open the drawing api by calling #draw, which exposes a DSL for drawing primitives, like a circle.

Now our Circle block can be used from another blocks template. In short, this block:

  • Declares its template as virtual
  • Declares a render method
  • Calls the draw method inside render and invokes methods for drawing

A full list of commands can be found the source code.

Composability

You may have noticed from Counter that the template shows names nested inside each other.

This is because those blocks are slotted. A slot in Hokusai is placeholder for blocks provided by a parent which is not known. The blocks that fill the placeholder will be rendered as the children of the block declaring the slot, but these blocks will still receive props from and emit events to their original parent.

To illustrate, consider the template string for Hokusai::Blocks::Panel.

class Hokusai::Blocks::Panel < Hokusai::Block
template <<~EOF
   [template]
     hblock {
       :background="background"
       @wheel="wheel_handle"
     }
       clipped { :auto="autoclip" :offset="offset" }
         dynamic { @size_updated="set_size" }
           slot
       [if="scroll_active"]
         scrollbar.scroller {
           @scroll="scroll_complete"
           :top="panel_top"
           :goto="scrollbar_goto"
           :width="scroll_width"
           :background="scroll_background"
           :control_color="scroll_color"
           :control_height="scroll_control_height"
         }
 EOF


 uses(
   clipped: Hokusai::Blocks::Clipped,
   dynamic: Hokusai::Blocks::Dynamic,
   hblock: Hokusai::Blocks::Hblock,
   scrollbar: Hokusai::Blocks::Scrollbar
 )
 #...
end

The slot keyword in this template is a placeholder for any content that you want to be scrollable in a desktop app. There really isn’t any built-in magic happening here. The scrollbar, clipping region, and dynamic sizing are all just plain Hokusai::Block which each call different commands.

With virtual, slot, and the drawing API, you can invent all of your own components. You don’t really need to use the provided blocks if they don’t fit your use case.

Props

One way to make our Circle component more useful, is to allow its consumer to decide what color and radius the circle should be. We can do this with props.

Props can be either static (received as a string) or dynamic (received as a ruby object), and are declared as such.

Let’s make our Circle more extensible with props and use it in another component

# circle.rb
class Circle < Hokusai::Block
 template <<~EOF
 [template]
   virtual
 EOF


 # the computed class method tells us that we expect a prop
 # and auto-generates an instance method for it.
 computed :color, default: [255, 0, 0], convert: Hokusai::Color
 computed :radius, default: 10.0, convert: proc(&:to_f)


 def render(canvas)
   x = canvas.x + (canvas.width / 2)
   y = canvas.y + (canvas.height / 2)


   draw do
     # we can call `color` and `radius` directly now
     circle(x, y, radius) do |command|
       command.color = color
     end
   end


   yield canvas
 end
end

The Hokusai::Block::computed method allows us to declare expected props as well as change their type. These props can then be used directly in any of the block’s instance methods.

require_relative "./circle"


class ColoredCircles < Hokusai::Block
 template <<~EOF
 [template]
   blue_circle { color="0,0,255" }
   red_circle { :color="get_red_color" }
 EOF


 uses(
   blue_circle: Circle,
   red_circle: Circle
 )


 def get_red_color
   [255, 0, 0]
 end
end

The above component draws a blue circle above a red circle.

  • static prop content (without a colon) are passed to the child as a string,
  • dynamic prop content (starting with a colon) is evaluated in the context of the instance.

Events

Hokusai supports both builtin and custom events.

Event attributes are similar to prop declarations except begin with an @ symbol. The value of the event attribute should be the name of the method that will receive the event.

Hokusai supports basic input events out of the box. the handlers attached to these events will receive the following objects as their parameter.

Event Object
@click Hokusai::ClickEvent
@hover Hokusai::HoverEvent
@mousemove Hokusai::MouseMoveEvent
@mouseup Hokusai::MouseUpEvent
@mousedown Hokusai::MouseDownEvent
@mouseout Hokusai::MouseOutEvent
@wheel Hokusai::WheelEvent
@keypress Hokusai::KeyPressEvent
@keyup Hokusai::KeyUpEvent

When the backend is configured to use touch, these events will also be exposed the application.

Touch Event Object
@tap Hokusai::TapEvent
@doubletap Hokusai::DoubleTapEvent
@taprelease Hokusai::TapReleaseEvent
@drag Hokusasi::DragEvent
@taphold Hokusai::TapHoldEvent
@pinchin Hokusai::PinchInEvent
@pinchout Hokusai::PinchOutEvent
@swipe Hokusai::SwipeEvent

Now that our Circle block uses props, we can make it change colors on each click.

class ColorChangingCircle < Hokusai::Block
 template <<~EOF
 [template]
   circle { :color="circle_color" @click="change_color" }
 EOF


 uses(circle: Circle)


 def circle_color = @circle_color || [255,255,255]
 def change_color(event) = @circle_color = [[255,0,0], [0,255,0], [0,0,255]].sample
end

In the above component, we pass a dynamic color prop based on the value of @circle_color In the @click handler, and we set @circle_color to a random value of red, blue, or green.

This new value will be passed to the child on the next render.

Emits

Blocks can also emit their own events with Hokusai::Block#emit, the first argument to emit is the name of the event, and the rest of the arguments are passed to any subscribed event handlers.

We can use it from our block like this.

class ColorChangingCircleWithCustomEmit < Hokusai::Block
 template <<~EOF
 [template]
   circle { :color="circle_color" @click="change_color" }
 EOF


 uses(circle: Circle)


 def circle_color = @circle_color || [255,255,255]


 def change_color(event)
   @circle_color = [[255,0,0], [0,255,0], [0,0,255]].sample
   emit("color_changed", @circle_color)
 end
end


Provisions

Provisions are probably the most useful idea in Hokusai Pocket.

A block can declare a provided method that can be injected into any descendant. Like a scoped global, a provision is useful for providing state to groups of related components and avoid drilling props.

The first argument to Hokusai::Block::provide is the name of the provision, the second points to an instance method on the block that returns the provided state. In child components, call Hokusai::Block::inject with the provision name to make it available to that block.

A common idiom I use in more complex apps is to provide a control object at the root which maintains mutable state for the whole application, and inject it to components as needed.

Conclusion

There’s a lot of stuff I haven’t covered, but also, I am not perfect, and this work isn’t perfect either. There are quirks and annoyances (as with any library), but for a category of intention, I hope that it operates in an immensely fun and productive way.

But I do want to discuss a few footguns

  • Sometimes the build breaks for windows, and the binary is missing winpthreads. It should be statically linked.
  • Releases are somewhat sporadic. If there are specific needs then they will become more predictable. I use long running feature branches, so it’s worth checking out the CI builds over the releases.
  • There are directives for conditionals and looping. Please ask me about them or look at projects that use them.
  • You can create templates using a Ruby DSL instead of a string, but they don’t have feature parity. Sometimes it is more advantageous to use one or the other.
  • For things that require a blocks height to be figured out at runtime, (such as wrapping text), the parent currently has no mechanic to figure this out. The height must be set on the node or emitted to the parent.

Projects that are currently using this library

Please enjoy, and if you can’t - I’d love to know.

— z

Comments (0)