widgets

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: BSD-3-Clause Imports: 7 Imported by: 0

README

go-ruby-widgets

CI Go Reference Go Report Card

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) — shaped so that go-embedded-ruby (rbgo) can bind it as require "widgets" and build real pixel UIs.

It is a thin adapter over the go-widgets stack:

Library Role
go-widgets/toolkit The pure-Go widget set + container/layout model.
go-widgets/painter The pixel rasteriser (RGBA buffer back-end).

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 (a Hash map[string]any, an Array []any or a scalar). Nothing here imports the Ruby runtime, so it is equally usable as a standalone Go library.

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

The Ruby-facing surface

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.

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

License

BSD-3-Clause. Copyright (c) 2026, the go-ruby-widgets/widgets authors.

Documentation

Overview

Package widgets is 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 — shaped so that github.com/go-embedded-ruby/ruby (rbgo) can bind it as `require "widgets"` and build real pixel UIs.

It is a thin adapter over github.com/go-widgets/toolkit (the widget set and its container/layout model) and github.com/go-widgets/painter (the pixel rasteriser). 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: a Hash (map[string]any), an Array ([]any) or a scalar. A single dynamic entry point, Call, dispatches a Ruby-style snake_case method name to the matching Module method and coerces the arguments, which is exactly what an rbgo binding drives from method_missing. Nothing here imports the Ruby runtime, so the package is equally usable as a standalone Go library.

The object graph

  • Constructors return an opaque integer handle: Button, Label, Entry, TextView, CheckButton, DropDown, ListBox, Menu, MenuBar for leaves; Container (a config-driven fit/box/border/card layout), HBox, VBox, Grid, Frame, Dock and Border for containers.
  • Mutators address a handle: SetText/Text, SetChecked/Checked, Select, SetStyle, SetSpacing and the package-wide SetTheme.
  • Composition wires the tree: AddWidget, Add (with a flex/size/region Hash), AddFixed, AddFlex, Attach (grid), DockAt, SetRegion (border), AddMenu, SetActive (card) and SetLayout.
  • Layout + query: SetBounds, Layout (at the origin) and Bounds.
  • Render paints a tree into an RGBA pixel buffer; Dispatch routes an input event into it.

The render seam

Render(root, w, h) lays the tree out to fill a w×h surface and paints it, returning {"pixels": <RGBA bytes>, "stride": w*4, "w": w, "h": h}. The pixels are 4 bytes per pixel, row-major, top-left origin — exactly what a host (wasmbox) blits into a canvas or SharedArrayBuffer.

The event seam

Dispatch(root, {"kind" => "click", "x" => …, "y" => …}) routes the event into the tree by hit-testing container bounds, then reports {"fired": [callback ids…], "repaint": bool}. A widget is wired to a callback by passing a callback identifier to its constructor (Button, Entry, CheckButton, DropDown, ListBox and Menu items); when it fires, its identifier appears in the "fired" Array so the Ruby side can invoke the matching block.

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, "w":200, "h":80}
out, _ := m.Dispatch(ok, map[string]any{"kind": "click"})
// out["fired"] == []any{"on_ok"}

Usage from Ruby

Under rbgo, `require "widgets"` gives a Widgets module whose snake_case methods are these operations, returning Ruby Hashes, Arrays and scalars:

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, … }
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.

Example

Example builds a two-widget column, lays it out, renders it to a pixel buffer and routes a click into the button — every result a Ruby-shaped value.

package main

import (
	"fmt"

	"github.com/go-ruby-widgets/widgets"
)

func main() {
	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)
	fmt.Println("stride:", img["stride"], "w:", img["w"], "h:", img["h"])

	out, _ := m.Dispatch(ok, map[string]any{"kind": "click"})
	fmt.Println("fired:", out["fired"], "repaint:", out["repaint"])

}
Output:
stride: 800 w: 200 h: 80
fired: [on_ok] repaint: true

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Call

func Call(recv any, method string, args ...any) (any, error)

Call dispatches a Ruby-style snake_case method name to the matching exported method of the Module, coercing each Ruby-supplied argument to the Go parameter type. Trailing arguments may be omitted (they default to nil/zero). The result is the method's Ruby-shaped return value (or nil for a method that returns nothing); a trailing error return is unwrapped into Call's own error. This is the single entry point an rbgo binding drives from method_missing.

func Methods

func Methods(recv any) []string

Methods lists, sorted, the Ruby-style snake_case names Call accepts for recv.

Types

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module is the stateful Ruby receiver: the `Widgets` module under rbgo. Unlike the stateless data adapters (opentype, regexp, …) a Module owns a live widget tree — every constructed widget or container is stored under an integer handle the Ruby side references, and every mutation, layout, render and event dispatch is addressed by that handle. A Module is NOT safe for concurrent use (its handle table and pending-callback list are mutated in place).

func NewModule

func NewModule() *Module

NewModule returns a fresh Module with an empty handle table and the default light theme. Handles count up from 1, so a 0 handle always means "none" (used by Frame/Dock to mean a nil child/body).

func (*Module) Add

func (m *Module) Add(parent, child int, opts map[string]any) error

Add appends child to a Container with an explicit item config Hash: "flex" (proportional weight), "size" (fixed main-axis extent) and "region" ("north"/"south"/"east"/"west"/"center" for a border layout). A nil Hash is the zero config.

func (*Module) AddFixed

func (m *Module) AddFixed(parent, child, size int) error

AddFixed appends child to an HBox or VBox with a fixed main-axis size.

func (*Module) AddFlex

func (m *Module) AddFlex(parent, child, flex int) error

AddFlex appends child to an HBox or VBox with a proportional flex weight.

func (*Module) AddMenu

func (m *Module) AddMenu(bar int, name string, menu int) error

AddMenu appends a named menu to a MenuBar.

func (*Module) AddWidget

func (m *Module) AddWidget(parent, child int) error

AddWidget appends child to a Container, HBox or VBox with the default per-item config (an equal share in a box, the centre of a border layout).

func (*Module) Attach

func (m *Module) Attach(parent, child, col, row int) error

Attach places child at (col, row) in a Grid.

func (*Module) Border

func (m *Module) Border() int

Border constructs an edge-region container (set regions with SetRegion).

func (*Module) Bounds

func (m *Module) Bounds(id int) (map[string]any, error)

Bounds reports a widget's placement as a Hash with "x", "y", "w", "h".

func (*Module) Button

func (m *Module) Button(label, callback string) int

Button constructs a push button labelled label. When callback is non-empty it is fired (reported by Dispatch) on every click.

func (*Module) CheckButton

func (m *Module) CheckButton(label string, checked bool, callback string) int

CheckButton constructs a labelled checkbox. When callback is non-empty it fires on every toggle.

func (*Module) Checked

func (m *Module) Checked(id int) (bool, error)

Checked reads a CheckButton's state.

func (*Module) Container

func (m *Module) Container(layout string) (int, error)

Container constructs a config-driven container whose children are placed by a named layout: "fit" (fill), "box"/"hbox" (a row), "vbox" (a column), "border" (edge regions + centre) or "card" (one visible child). An unknown name is an error, reported by Call.

func (*Module) Dispatch

func (m *Module) Dispatch(id int, ev map[string]any) (map[string]any, error)

Dispatch routes an input event into a tree and reports the outcome. The ev Hash carries "kind" ("click"/"keydown"/"keyup"/"char"/"mousedrag"/"mouseup"), "x"/"y" (widget-local pixels), "code" (key/char text) and Bool "ctrl"/"shift". The result Hash is "fired" (an Array of the callback identifiers that ran, in order) and a Bool "repaint" (whether any callback ran, i.e. the tree may have changed).

func (*Module) Dock

func (m *Module) Dock(body int) (int, error)

Dock constructs an edge-docking container around body (pass 0 for a bars-only frame); attach bars with DockAt.

func (*Module) DockAt

func (m *Module) DockAt(parent, child int, side string, size int) error

DockAt attaches child to an edge ("top"/"bottom"/"left"/"right") of a Dock, reserving size pixels along that edge's axis.

func (*Module) DropDown

func (m *Module) DropDown(options []any, selected int, callback string) int

DropDown constructs a drop-down selector over options (a Ruby Array of strings) with the given initial selection. When callback is non-empty it fires on every Select.

func (*Module) Entry

func (m *Module) Entry(initial, callback string) int

Entry constructs a single-line text field seeded with initial. When callback is non-empty it fires on every value change and on submit (Enter).

func (*Module) Frame

func (m *Module) Frame(child int) (int, error)

Frame constructs a 1-pixel-bordered panel around child; pass a 0 child for an empty frame.

func (*Module) Grid

func (m *Module) Grid(cols, rows int) int

Grid constructs a cols×rows table (children placed with Attach).

func (*Module) HBox

func (m *Module) HBox() int

HBox constructs an imperative horizontal box (children added with AddWidget / AddFixed / AddFlex).

func (*Module) Label

func (m *Module) Label(text string) int

Label constructs a passive text label.

func (*Module) Layout

func (m *Module) Layout(id, w, h int) error

Layout is SetBounds at the origin — the common case for a top-level tree.

func (*Module) ListBox

func (m *Module) ListBox(items []any, callback string) int

ListBox constructs a scrollable single-column list over items (a Ruby Array of strings). When callback is non-empty it fires on every row activation.

func (*Module) Menu

func (m *Module) Menu(items []any) int

Menu constructs a vertical popover menu from items — a Ruby Array of Hashes, each with "label", optional "shortcut", a Bool "separator" and an "action" (a callback identifier fired when the row is chosen). Non-Hash elements are skipped.

func (*Module) MenuBar

func (m *Module) MenuBar() int

MenuBar constructs an empty horizontal menu bar; attach menus with AddMenu.

func (*Module) Render

func (m *Module) Render(id, w, h int) (map[string]any, error)

Render lays a tree out to fill a w×h surface and paints it, returning a Hash with the RGBA "pixels" (4 bytes per pixel, row-major, top-left origin), "stride" (the byte offset between rows, i.e. w*4) and "w"/"h". A host (wasmbox) blits pixels straight into a canvas / SharedArrayBuffer.

func (*Module) Select

func (m *Module) Select(id, idx int) error

Select changes the selection of a DropDown (which also fires its callback) or a ListBox.

func (*Module) SetActive

func (m *Module) SetActive(container, idx int) error

SetActive selects the visible child of a Container backed by a card layout.

func (*Module) SetBounds

func (m *Module) SetBounds(id, x, y, w, h int) error

SetBounds positions a widget (or the root of a tree) at (x, y) with size w×h, running the toolkit layout over any descendants.

func (*Module) SetChecked

func (m *Module) SetChecked(id int, v bool) error

SetChecked sets a CheckButton's state.

func (*Module) SetLayout

func (m *Module) SetLayout(container int, layout string) error

SetLayout swaps a Container's layout to a named one (see Container).

func (*Module) SetRegion

func (m *Module) SetRegion(parent, child int, region string, size int) error

SetRegion assigns child to a Border region ("north"/"south"/"east"/"west"/ "center"), with size the edge band's thickness (ignored for the centre).

func (*Module) SetSpacing

func (m *Module) SetSpacing(id, n int) error

SetSpacing sets the inter-child gap of an HBox, VBox or Grid.

func (*Module) SetStyle

func (m *Module) SetStyle(id int, style string) error

SetStyle sets a Button's resting appearance: "default", "prominent" or "secondary".

func (*Module) SetText

func (m *Module) SetText(id int, s string) error

SetText sets the text of a Label, Button, Entry, TextView or CheckButton.

func (*Module) SetTheme

func (m *Module) SetTheme(name string) error

SetTheme switches the render theme to "light" or "dark".

func (*Module) Text

func (m *Module) Text(id int) (string, error)

Text reads the text of a Label, Button, Entry, TextView or CheckButton.

func (*Module) TextView

func (m *Module) TextView(initial string) int

TextView constructs a multi-line editable text area seeded with initial.

func (*Module) VBox

func (m *Module) VBox() int

VBox constructs an imperative vertical box.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL