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.
Recommended: RubyGems
gem install azul --source https://azul.rs/gems
Manual
gem install ffi # the FFI runtime
# download the native library from /releases:
wget -O libazul.dylib https://azul.rs/release/0.2.0/libazul.dylib # macOS
Then drop the generated azul.rb next to your script (it ships in the
examples archive under ruby/) and run with -I..
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?
# CssProperty is a tagged-union without a Ruby wrapper class yet; use the
# Native form directly.
font_size_px = Azul::Native.az_style_font_size_px(32.0)
font_size_prop = Azul::Native.az_css_property_font_size(font_size_px)
label = Azul::Dom.create_div
.with_css_property(Azul::CssPropertyWithConditions.simple(font_size_prop))
.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(Azul::Dom.new(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.ptr)
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. CssPropertyhas no wrapper class yet — build it viaAzul::Native.az_css_property_*for now (Azul::String#to_s,Option#to_opt,Result#unwrap,Vec#to_ado 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'— the native library isn't onDYLD_LIBRARY_PATH/LD_LIBRARY_PATH, or not in the working directory.- 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]