go-ruby-widgets

The Ruby face of go-widgets โ€” pixel widgets, terminal-cell widgets and data-binding, each a pure-Go gem core that rbgo binds as require "widgets" / "tui" / "mvvm".

pure Go ยท zero cgo require "widgets" / "tui" / "mvvm" method_missing dispatch Hash / Array results handle-addressed object graph RGBA + ANSI render seams id + drain callback seam no Ruby-runtime dep 100% coverage 6+ arches
Documentation GitHub
Documentation (MkDocs Material + mike) License: BSD-3-Clause Go 1.26.4+ Backed by go-widgets

go-ruby-widgets is three pure-Go, Ruby-runtime-independent adapters โ€” widgets, tui and mvvm โ€” shaped so that go-embedded-ruby (rbgo) can bind each as require "widgets", require "tui" and require "mvvm". Each is a thin adapter over its go-widgets counterpart, exposed through Ruby-facing handles whose methods return Ruby-shaped values โ€” a Hash (map[string]any), an Array ([]any) or a scalar. A single dynamic entry point, Call, dispatches a snake_case method name to the matching handle method, which is exactly what an rbgo binding drives from method_missing. No cgo, no Ruby runtime dependency anywhere in the stack โ€” CI-green across amd64, arm64, riscv64, loong64, ppc64le, s390x and (for widgets) js/wasm.

widgets ready

A live pixel widget UI toolkit over go-widgets/toolkit + go-widgets/painter: buttons, labels, entries, lists, menus and the container/layout system (box/border/card/grid/dock) that arranges them. Every widget and container is an integer handle; Render paints a tree to an RGBA buffer, Dispatch routes input events by hit-testing bounds.

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)   # => { "pixels"=>, "stride"=>800, "w"=>200, "h"=>80 }
fired = Widgets.dispatch(ok, { "kind" => "click" })   # => { "fired" => ["on_ok"], ... }

tui ready

A terminal-cell UI toolkit over go-widgets/tui: label/button/entry/check_button/list_box/progress_bar widgets, a border layout container (header/footer bands + overlays), draggable h_split/v_split panes and a card layout notebook (tab strip). render emits a self-contained ANSI stream; render_cells/decode_cells return the decoded grid at cell precision for tests.

require "tui"

button = Tui.button("OK")
button.on_click("ok")

root = Tui.container
root.set_header_height(1)
root.set_header(Tui.label("Title"))
root.set_body(button)

Tui.set_size(root, 40, 10)
puts Tui.render(root, 40, 10)                      # => ANSI String
fired = Tui.dispatch(button, {"kind" => "click"}) # => {"fired"=>["ok"], "repaint"=>true}

mvvm ready

The data-binding layer over go-widgets/mvvm: a bindable Observable property, a Command action and an ObservableList collection. A hosted Go library can’t call a Ruby block synchronously, so every change queues a callback-id-tagged event Hash; the rbgo binding polls it once per tick with drain_events โ€” the id + drain seam.

require "mvvm"

name = Mvvm.observable("")
name.subscribe(:on_name)
name.set("Ada")                    # queues {callback_id: :on_name, kind: "changed", value: "Ada"}

Mvvm.drain_events.each do |ev|     # => Array<Hash>, once per UI tick
  dispatch(ev[:callback_id], ev)   # Ruby runs the actual block
end

rbgo binding planned

require "widgets" / "tui" / "mvvm" โ€” the thin method_missing shim over each adapter’s Call that exposes it inside github.com/go-embedded-ruby/ruby (rbgo). Pending in that repo; nothing in the three adapters above depends on the Ruby runtime.

Three siblings, one shape: an object graph addressed by integer handles, a uniform Call/Methods dynamic-dispatch surface driven by reflection, and Ruby-shaped results throughout. widgets owns a live pixel object graph and renders to an RGBA buffer; tui owns a live terminal-cell object graph and renders to ANSI (decodable at cell precision via DecodeANSI); mvvm has no live UI tree at all โ€” just an Observable property, a Command action and an ObservableList collection, notifying Ruby through a queued, callback-id-tagged event Hash that drain_events pulls once per tick. All three sit over the go-widgets toolkits (toolkit, tui, mvvm) and are bound into rbgo by a thin method_missing shim โ€” a sibling pattern to go-ruby-opentype, go-ruby-regexp and go-ruby-erb in the go-ruby-* family.