Hello World [Ruby]
Introduction
The Ruby binding uses the ffi gem to call the
prebuilt libazul native library. You write idiomatic Ruby — a plain object, a
lambda per callback, and the smart WindowCreateOptions.create_with_layout(&block)
factory — and the binding wraps your object and registers the callbacks for you.
Installation
You need Ruby 2.6+ (system Ruby on macOS works), the ffi gem, and the
native libazul library.
The name azul on rubygems.org belongs to an unrelated project, so azul ships
from its own source at azul.rs. The gem bundles libazul for Linux/macOS/Windows
and pulls in ffi:
gem install azul --source https://azul.rs/ui/gems
With the gem installed you just require 'azul' (no -I.). To wire it up by
hand instead:
# --user-install avoids Gem::FilePermissionError on macOS system Ruby;
# pin 1.15.x if you are on Ruby 2.6 (ffi >= 1.16 may not build there).
gem install --user-install ffi -v 1.15.5
# download the native library:
wget -O libazul.dylib https://azul.rs/ui/release/0.2.0/libazul.dylib # macOS
wget -O libazul.so https://azul.rs/ui/release/0.2.0/libazul.so # linux
# windows: download https://azul.rs/ui/release/0.2.0/azul.dll
Then drop the generated azul.rb binding next to your script and run with -I.:
wget https://azul.rs/ui/release/0.2.0/azul.rb
Simple „Counter“ Example
require 'azul'
# Plain Ruby object - the "single source of truth" for app state.
class MyDataModel
attr_accessor :counter
def initialize(counter); @counter = counter; end
end
model = MyDataModel.new(5)
data = Azul::RefAny.wrap(model) # wrap into a handle for the framework
# Click callback: a lambda. unwrap recovers your object from the handle.
on_click = lambda do |data_ptr, _info|
m = Azul::RefAny.unwrap(data_ptr)
next Azul::Update::DoNothing if m.nil?
m.counter += 1
Azul::Update::RefreshDom
end
# Layout callback: f(data) -> Dom.
layout = lambda do |data_ptr, _info|
m = Azul::RefAny.unwrap(data_ptr)
next Azul::Dom.create_body if m.nil?
# Style a node with a plain CSS string via .with_css.
label = Azul::Dom.create_div
.with_css('font-size: 32px;')
.with_child(Azul::Dom.create_text(m.counter.to_s))
# Smart .on_click(data, &block) wraps refany + registers internally.
button = Azul::Button.create('Increase counter')
.with_button_type(Azul::ButtonType::Primary)
.on_click(m, on_click)
Azul::Dom.create_body
.with_child(label)
.with_child(button.dom)
end
# Smart factory hides the manual layout_callback splice; .with(opts) recursively
# assigns nested fields and auto-converts Ruby Strings to AzString.
window = Azul::WindowCreateOptions.create_with_layout(layout).with(
window_state: {
title: 'Hello World',
size: { dimensions: { width: 400.0, height: 300.0 } },
flags: {
decorations: Azul::WindowDecorations::NoTitleAutoInject,
background_material: Azul::WindowBackgroundMaterial::Sidebar,
},
},
)
app = Azul::App.create(data, Azul::AppConfig.create)
app.run(window)
Four things to notice.
Azul::RefAny.wrap/.unwrap— wrap any Ruby object into a handle; the same object is handed back to callbacks.unwrapreturnsnilon mismatch, so guard withnext ... if m.nil?.- Callbacks are lambdas with the signature
|data_ptr, info|. Usenext(notreturn) to yield a value out of a lambda block. - Smart builders.
WindowCreateOptions.create_with_layout(lambda)andButton.create(...).on_click(model, fn)hide the register + splice;.with(hash)drops the field-drilling boilerplate. - Style with CSS strings.
Dom#with_css('font-size: 32px;')parses a plain CSS string — no manual property construction needed (Azul::String#to_s,Option#to_opt,Result#unwrap,Vec#to_aalso exist).
Build and run
ruby -I. hello-world.rb
(-I. tells Ruby to look in the current dir for azul.rb.) You should see the window
pictured on the hello-world landing page.
Common errors
cannot load such file -- azul—azul.rbisn't on the load path. Run withruby -I. hello-world.rb.Could not open library 'libazul'— putlibazul.dylib/libazul.sonext toazul.rb, or pointAZ_LIB_DIRat its directory (AZ_LIB_DIR=. ruby -I. hello-world.rb). Note thatDYLD_LIBRARY_PATHdoes NOT work with macOS system Ruby — SIP stripsDYLD_*variables.- Counter does not advance — the lambda yielded
Azul::Update::DoNothing. Remembernextreturns from a block.
Coming Up Next
- Application Architecture — architecting a larger Azul application
- Document Object Model — the Dom tree: node types, hierarchy, and CSS
- Hello World [Node.js]