Skip to main content

Blocks

The concept of composable components is baked into the conception of Hokusai from the start. Pulling inspiration from modern web frameworks (specifically Vue), composable components are implemented in Hokusai via Blocks.

Blocks are single file entities that consist of

  • some local state
  • a declaration of included children (which are also blocks)
  • a template for rendering it's children
  • methods for computing the properties of a child
  • methods for responding to an event emitted from a child.
  • an event emitter that publishes events to any subscribed parents.

Counter Demo

To illustrate, here is a simple counter application

class Counter < Hokusai::Block
template <<-EOF
[template]
vblock
hblock
label {
:content="count"
size="130"
:color="count_color"
}
hblock
vblock
label {
content="Add"
@click="increment"
}
vblock
label {
content="Subtract"
@click="decrement"
}
EOF

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

attr_accessor :count

def increment(event)
self.count += 1
end

def decrement(event)
self.count -= 1
end

def count_color
self.count > 0 ? [0,0,255] : [255,0,0]
end

def initialize(**args)
@count = 0

super(**args)
end
end

Hokusai::Backends::RaylibBackend.run(Counter) do |config|
config.width = 500
config.height = 500
config.title = "Counter application"
end

In this example, we see that a block is simply a class that inherits from Hokusai::Block

At a bare minimum, a block needs to declare a template to be renderable.

note

There is no difference to Hokusai between a Block and an Application.

Reactivity and Mounting

When the application starts, the mounting process...

  • instaniates the block
  • walks a block's AST (from the template) and instantiates it's children, computing the properties of each child from the local state and methods of the block.
  • adds the the block as a listener to each child's event publisher
  • passes down any provider callbacks to each child.

On each iteration of the event loop, the renderer will emit events for any base inputs.
In turn, each affected block will adjust it's local state, which may cascade new props down to it's children. Child blocks may also emit events to a parent, which may cause state changes that cascade on the next iteration of the loop.

Slots

Since many of the ideas in Hokusai are inspired by Vue, slots are included as well.
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 following.

class RectWithOther < Hokusai::Block
template <<-EOF
[template]
hblock
rect
slot
EOF

uses(
hblock: Hokusai::Blocks::Hblock,
rect: Hokusai::Blocks::Rect
)
end

We can provide a content to this slot like this

class RectWithCircle < Hokusai::Block
template <<-EOF
[template]
rect_with_other
circle
EOF

uses(
rect_with_other: RectWithOther,
circle: Hokusai::Blocks::Circle
)
end

Emits

A block instance can emit custom events to any subscribers using Hokusai::Block#emit

The first argument to emit should be the name of the event to listen for.
Any additional arguments are passed to the subscribed event handlers.

Backends

Hokusai is a backend agnostic framework, which is to say it takes responsibility for handling input and generating render commands, but not for the actual rendering.

Backend adapters can be written for Hokusai. Please see Hokusai::Backends::Raylib and Hokusai::Backends::SDL for more information.