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.
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.