Skip to content

widgets

The pure-Go, Ruby-runtime-independent core of the Ruby widgets gem: a live widget UI toolkit — buttons, labels, text fields, lists, menus and the container/layout system that arranges them. It is a thin adapter over the pure-Go widget set go-widgets/toolkit and the pixel rasteriser go-widgets/painter.

Unlike the stateless data adapters of the go-ruby- family (opentype, regexp, erb, …), a Module here owns a live object graph: every widget and container is stored under an integer handle* the Ruby side keeps, and every operation — mutate, compose, lay out, render, dispatch an event — is addressed by that handle and returns a Ruby-shaped value.

CGO_ENABLED=0, no display, no network — cross-compiles to all six 64-bit Go architectures and js/wasm (the target wasmdesk runs).

Constructors (return an opaque integer handle)

Kind Methods
Leaves button(label, cb), label(text), entry(initial, cb), text_view(initial), check_button(label, checked, cb), drop_down(options, selected, cb), list_box(items, cb), menu(items), menu_bar
Containers container(layout) (fit/box/hbox/vbox/border/card), h_box, v_box, grid(cols, rows), frame(child), dock(body), border

Mutation

set_text / text, set_checked / checked, select(id, idx), set_style, set_spacing, and the module-wide set_theme("light"|"dark").

Composition

add_widget(parent, child), add(parent, child, {flex:, size:, region:}), add_fixed, add_flex, attach(grid, child, col, row), dock_at(dock, child, side, size), set_region(border, child, region, size), add_menu(bar, name, menu), set_active(card, idx), set_layout(container, layout).

Layout / query

set_bounds(id, x, y, w, h), layout(id, w, h) (at the origin), bounds(id).

The render seam

img = Widgets.render(root, w, h)
# => { "pixels" => <RGBA bytes>, "stride" => w*4, "w" => w, "h" => h }

pixels is 4 bytes per pixel, row-major, top-left origin — exactly what a host (wasmbox) blits into a <canvas> / SharedArrayBuffer.

The event seam

out = Widgets.dispatch(root, { "kind" => "click", "x" => 10, "y" => 4 })
# => { "fired" => ["on_ok"], "repaint" => true }

kind is one of click / keydown / keyup / char / mousedrag / mouseup. A widget is wired to a callback by passing an identifier to its constructor; when it fires, that identifier appears in fired so the Ruby side can invoke the matching block.

Dynamic dispatch

Call(recv, method, args...) (any, error) dispatches a snake_case method name to the matching Module method, coercing Ruby scalars / Arrays / Hashes to the Go parameter types (a trailing error return is unwrapped) — the single entry point an rbgo method_missing shim drives. Methods(recv) lists the accepted names.

Usage from Go

m := widgets.NewModule()
root := m.VBox()
title := m.Label("Hello")
ok := m.Button("OK", "on_ok")
_ = m.AddWidget(root, title)
_ = m.AddWidget(root, ok)
_ = m.Layout(root, 200, 80)

img, _ := m.Render(root, 200, 80)                         // {"pixels":…, "stride":800, …}
out, _ := m.Dispatch(ok, map[string]any{"kind": "click"}) // out["fired"] == []any{"on_ok"}

Usage from Ruby

require "widgets"

root  = Widgets.v_box
ok    = Widgets.button("OK", "on_ok")
Widgets.add_widget(root, ok)
Widgets.layout(root, 200, 80)
img   = Widgets.render(root, 200, 80)
fired = Widgets.dispatch(ok, { "kind" => "click" })   # => { "fired" => ["on_ok"], … }

The require "widgets" binding lives in rbgo (a thin method_missing shim over Call); it is pending in that repo — see Getting started in rbgo.