toolkit

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-widgets/toolkit

CI release pkg.go.dev coverage go license

Pure-Go widget toolkit that renders into an RGBA byte buffer. Zero JS / DOM / canvas dependency — every widget composes pixels into a caller-supplied []byte, so the toolkit runs identically in GOOS=js GOARCH=wasm (browser SharedArrayBuffer clients), on any native target Go ships (native canvas backends, image files), or in headless tests (screenshot-hash regressions).

Goals

  • One toolkit per app: consumers stop reinventing buttons, scrollbars, text fields. They import github.com/go-widgets/toolkit and compose.
  • Coherent theming: a single Theme value cascades through every widget — change a colour at the top, see it everywhere. LoadGTKTheme(css) parses libadwaita / GTK3 @define-color declarations into a Theme, so any GTK-desktop palette (Adwaita, Juno, WhiteSur, Solarized …) drives the widget ink colours.
  • Pure Go + CGO=0: no C toolchain, no shared libraries. Builds for every Go target including GOOS=js GOARCH=wasm.
  • A11y-ready scaffolding: widgets carry a Role + Label so a future a11y bridge can publish them to screen readers without rewriting every widget.

Non-goals

  • Not a CodeMirror replacement. Complex web-grade editors are best embedded via an iframe overlay at the host level.
  • Not GTK. No CSS engine, no SVG renderer, no full BiDi/IME. A light primitive set: Button, Label, TextInput, ListBox, ScrollView, HBox, VBox, Splitter, TabBar, MenuBar. Around 5-10k LoC.

Status

v0.6 — Painter back-end abstraction. Every widget's Draw now takes a painter.Painter instead of a fixed []byte + stride pair, so the same widget code renders into a pixel buffer (WUI browser canvas, GUI native window, image file), a terminal cell grid (TUI), or an SVG stream. See "v0.6 breaking change" below for the migration path.

35 widgets + 10 stock icons, ~8k LoC, 100% statement coverage. Pure Go, no CGO, stdlib only. Builds for GOOS=js GOARCH=wasm and every native target Go ships.

Family Widgets
Base Widget, Base, Rect, Event (+ IME composition), Theme, RGBA
Text bitmap 5x7 font (60+ glyphs), DrawText, TextWidth, Label
Action Button, ToggleButton, CheckButton, RadioButton + Group
Input Entry, TextView + Selection + IME preview, SpinButton, Scale
Selection ListBox, TreeView, DropDown
Layout HBox, VBox, Grid, Frame, Stack, Paned, Expander
Tabs Notebook
Scroll ScrollView
Feedback ProgressBar, LevelBar, Spinner, Image, Tooltip, Notification
Navigation Menu + MenuItem.Shortcut, MenuBar + Alt+letter, Dialog, MessageDialog
Bars Toolbar, Statusbar, 10 stock DrawIcon* helpers
Composite FileChooser, ColorChooser, Calendar
Theming LoadGTKTheme(css) (GTK3 + libadwaita @define-color → Theme)
v0.6 breaking change

Widget.Draw signature moved from

Draw(surface []byte, surfaceW int, theme *Theme)

to

Draw(p painter.Painter, theme *Theme)

where painter.Painter is a 5-primitive interface (FillRect, StrokeRect, PutPixel, Text, Size). Existing callers that had a []byte + width migrate one line:

// before v0.6:
wg.Draw(surface, w, theme)

// v0.6+:
p := painter.NewPixelPainter(surface, w, h)
wg.Draw(p, theme)

The []byte is still writable + owned by the caller — the PixelPainter just wraps it so the primitives translate to writes. All other widget APIs (Bounds, SetBounds, HitTest, OnEvent, NewButton, …) are unchanged.

toolkit.Rect + toolkit.RGBA became type aliases of painter.Rect + painter.RGBA, so cross-package assignments work transparently.

The 10 DrawIcon* helpers also lost their (surface, surfaceW) prefix; new signature is DrawIconX(p painter.Painter, r Rect, ink RGBA).

Earlier releases
  • v0.5 — Toolbar / Statusbar / FileChooser / ColorChooser / Calendar, Selection on TextView, LoadGTKTheme(css), 10 stock icon helpers.
  • v0.4 — 34 widgets. Toolbar / Statusbar, FileChooser / ColorChooser / Calendar, Selection model on TextView, LoadGTKTheme(css).
  • v0.3 — 28 widgets. TextView (multi-line editor), Menu/MenuBar, Dialog/MessageDialog, Tooltip, DropDown, TreeView.
  • v0.2 — 22 widgets. Layout containers (HBox/VBox/Grid/Frame), scroll (ScrollView/ListBox), input (Entry/Check/Radio/Toggle), structural (Stack/Notebook/Paned/Expander), feedback (ProgressBar/LevelBar/Scale/SpinButton/Image/Spinner), bitmap font.
  • v0.1 — scaffolding. Widget interface, Theme value, Button + Label, primitive event dispatch.
Next (v0.6 sketch)
  • Font family plumbing — the toolkit ships one 5×7 bitmap font today. v0.6 introduces Font (an interface with GlyphAdvance, GlyphHeight, Draw) so a caller can plug a larger bitmap or a hand-rasterised TrueType at boot. Unblocks FontChooser (v0.5 deferral) + retina-size Label rendering.
  • Drag-and-drop event kindsEventDragStart / EventDragMove / EventDrop, plus a DragSource / DropTarget interface pair. Landing drivers: TreeView + ListBox row re-ordering.
  • Context menu helper — one-line ShowContextMenu(x, y, *Menu, *Popover) that spawns a Menu at worker-relative coords + auto- dismisses on outside-click. Every real app needs this; hosts currently reinvent it.
  • Popover widget — first-class overlay type (the MenuBar's own popover is currently host-side; formalising it unlocks Tooltip-as-popover, DatePicker overlays, autocomplete lists).
  • Overlay layout container — z-ordered stacking above a primary child, so a widget can layer feedback (Progress overlay, Notification stack) without the host arranging screen positions.
  • A11y bridgeRole + Label fields on widgets already; wire a A11yPublisher interface a host can plug (WAI-ARIA wrapping on wasm, TTY-cell metadata on tui, ...).

Architecture

+----------------------+
| Theme                |  Palette + metrics + font ref
+----------------------+
           |
+----------------------+    +-------------------+
| Widget interface     |    | Event             |
|   Draw(rgba, theme)  |<---|   Kind: click/key |
|   HitTest(x, y)      |    |   X,Y / Code      |
|   OnEvent(ev)        |    +-------------------+
+----------------------+
           |
   +-------+-------+-------+-------+
   |               |       |       |
+--+---+        +--+--+  +-+--+  +-+--+
|Button|        |Label|  |HBox|  | ...|
+------+        +-----+  +----+  +----+

License

BSD 3-Clause. See LICENSE.

Documentation

Overview

Package toolkit provides a pure-Go widget set for wasmdesk native apps. Widgets render per-pixel into an RGBA byte buffer (the SAB backed framebuffer wasmbox clients write to) and dispatch input events received from the wasmbox compositor.

Design notes:

  • Every widget exposes the same three-method interface, so a container (HBox, VBox, ScrollView, ...) can hold any leaf.
  • Drawing is allocation-free in the steady state: the widget writes into a caller-owned RGBA slice + reads its theme by reference. Per-frame work is bounded by the widget's bbox.
  • Coordinates are integer pixels in the caller's surface space; the widget's Rect is its placement within that surface.
  • Events are pre-translated into widget-local (X, Y) before dispatch by the parent container (HBox/VBox/ScrollView do the hit-testing + offset adjustment).

Index

Constants

View Source
const (
	AlertPadX = 12
	AlertPadY = 8
)

AlertPadX / AlertPadY set the internal margin between the banner edges and the text. Matches Notification's PadX/PadY so the two widgets read as siblings when they're used side-by-side.

View Source
const (
	BadgePadX = 4
	BadgePadY = 1
)

BadgePadX / BadgePadY are the horizontal and vertical insets between the pill body and the text glyphs. Small: a badge should read as a compact tag, not a button. Vertical padding is intentionally 1 so the pill stays short next to same-line body text.

View Source
const (
	CalendarHeaderH = 22
	CalendarCellW   = 24
	CalendarCellH   = 18
)

Sizing.

View Source
const (
	// CardPadX is the horizontal inset for header / body / footer text.
	CardPadX = 8
	// CardPadY is the vertical inset for the body text above the first
	// line + between the footer text and its strip border.
	CardPadY = 6
	// CardHeaderH is the height of the header strip when Title != "".
	CardHeaderH = GlyphHeight + 2*CardPadY
	// CardFooterH is the height of the footer strip when Footer != "".
	CardFooterH = GlyphHeight + 2*CardPadY
	// CardLineSpacing is the extra vertical gap inserted between two
	// body lines so successive glyph rows don't touch.
	CardLineSpacing = 2
)

Card sizing constants. Header + Footer strips are the same size so the card reads as a symmetric frame; the body gets a matching inner pad on the left and top.

View Source
const (
	ColorChooserChannelH    = 22
	ColorChooserPreviewH    = 36
	ColorChooserPadX        = 8
	ColorChooserChannelPadY = 4
)

Sizing.

View Source
const (
	FileChooserTreeRatio    = 35 // % of width for the tree pane
	FileChooserButtonStripH = 32
	FileChooserPathH        = 24
)

Sizing constants.

View Source
const (
	KbdPadX = 4
	KbdPadY = 2
)

KbdPadX / KbdPadY are the internal margin between the border box and the key text glyphs. Small values keep the chip compact enough to nest inside a menu row.

View Source
const (
	NotebookTabStripH = 24
	NotebookTabWidth  = 80
)

Geometry constants for the tab strip.

View Source
const (
	NotificationPadX = 12
	NotificationPadY = 8
	NotificationLife = 180
)

NotificationPadX / NotificationPadY / NotificationLife are the visual + timing defaults; a caller wanting shorter or louder toasts overrides them per-instance (via SetBounds + direct Life assignment).

View Source
const (
	PanedHorizontal = 0 // First left, Second right
	PanedVertical   = 1 // First top, Second bottom
)

Paned orientations.

View Source
const (
	StatusbarH           = 18
	StatusbarSegmentMinW = 80
	StatusbarPadX        = 6
)

Sizing constants.

View Source
const (
	// StepBoxW is the pixel width of each badge.
	StepBoxW = 16
	// StepBoxH is the pixel height of each badge.
	StepBoxH = 16
	// StepConnectorW is the horizontal length of the connector line
	// between two badges.
	StepConnectorW = 20
	// StepLabelGap is the vertical gap between a badge's bottom edge
	// and the caption text below it.
	StepLabelGap = 3
)

Steps sizing constants. Chosen so the badges + connectors fit inside a 40-px-tall bar (a common toolbar strip height).

View Source
const (
	ToolbarButtonW = 24
	ToolbarButtonH = 24
	ToolbarSepW    = 8
)

Sizing constants. Square buttons read as a true icon-toolbar (vs the MenuBar's wider text cells).

View Source
const (
	TooltipPadX = 8
	TooltipPadY = 4
)

TooltipPadX / TooltipPadY are the inner text-padding constants.

View Source
const BreadcrumbGap = 4

BreadcrumbGap is the horizontal pixel gap inserted on either side of the separator glyph so the chevron doesn't touch the segment ink.

View Source
const BreadcrumbSep = ">"

BreadcrumbSep is the character(s) drawn between two segments. Kept as a package constant so a caller who wants "/" or "»" replaces one symbol without touching Draw.

View Source
const DialogButtonStripH = 32

DialogButtonStripH is the pixel height of the bottom action strip.

View Source
const DialogButtonW = 90

DialogButtonW is the width allocated per action button.

View Source
const DialogTitleH = 28

DialogTitleH is the pixel height of the title bar.

View Source
const ExpanderHeaderH = 24

ExpanderHeaderH is the pixel height of the clickable header row.

View Source
const GlyphAdvance = 6

GlyphAdvance is the horizontal step from one glyph's origin to the next (5px glyph body + 1px inter-glyph spacing). All glyphs share the same advance so layout stays grid-aligned even with proportional shapes.

View Source
const GlyphHeight = 7

GlyphHeight is the per-glyph vertical extent in pixels.

View Source
const HeaderBarHeight = 40

HeaderBarHeight is the default vertical extent of a HeaderBar in pixels. HeaderBar's Draw code assumes Bounds.H == HeaderBarHeight but scales cleanly for taller / shorter bars: children are inset by HeaderBarPad/2 top+bottom and the title / subtitle are centred in whatever remains.

View Source
const HeaderBarPad = 8

HeaderBarPad is the horizontal padding at the bar's left + right edges (space between the bar's edge and the first Start / End child). Also drives the vertical inset around child widgets: children get Bounds.Y = bar.Y + HeaderBarPad/2 and Bounds.H = bar.H - HeaderBarPad, matching GTK's typical inner spacing.

View Source
const HeaderBarSubtitleGap = 2

HeaderBarSubtitleGap is the vertical gap (in pixels) between the title's last row and the subtitle's first row in the two-line layout. Kept as a package constant so the two-line block height stays predictable across themes + font sizes.

View Source
const MenuBarH = 22

MenuBarH is the pixel height of the bar strip.

View Source
const MenuBarItemPadX = 8

MenuBarItemPadX is the horizontal padding around a top-level name when its natural width exceeds MenuBarItemW — i.e. the extra breathing room beyond the raw glyph run.

View Source
const MenuBarItemW = 60

MenuBarItemW is the DEFAULT (minimum) pixel width allocated per top-level name. Names whose TextWidth exceeds this bound scale up (with 2×MenuBarItemPadX horizontal padding on each side); shorter names take exactly this width so the bar looks stable across varying label lengths.

View Source
const MenuRowH = 22

MenuRowH is the pixel height of a menu row.

View Source
const MenuSeparatorH = 6

MenuSeparatorH is the height of a separator row.

View Source
const PanedHandleW = 6

PanedHandleW is the pixel thickness of the splitter handle.

View Source
const PopoverMaxRows = 12

PopoverMaxRows caps the dropdown popover height; longer option lists can wrap in a ScrollView the caller supplies.

View Source
const TableCellPadX = 4

TableCellPadX is the left/right pixel padding applied inside every header + body cell before its text lands.

View Source
const TableHeaderHeight = 24

TableHeaderHeight is the pixel height of the header row.

View Source
const TableRowHeight = 22

TableRowHeight is the pixel height of one body row.

View Source
const TreeChevronW = 14

TreeChevronW is the pixel column the chevron lives in.

View Source
const TreeIndentW = 16

TreeIndentW is the per-depth pixel indent.

Variables

This section is empty.

Functions

func DaysInMonth

func DaysInMonth(year, month int) int

DaysInMonth returns the day count for (year, month).

func DeleteSelection

func DeleteSelection(lines []string, sel Selection) []string

DeleteSelection removes the selected range from lines + returns the new lines slice. The result always has at least one line (an empty line at minimum).

func DrawIconCopy

func DrawIconCopy(p painter.Painter, r Rect, ink RGBA)

DrawIconCopy paints two overlapping document outlines.

func DrawIconCut

func DrawIconCut(p painter.Painter, r Rect, ink RGBA)

DrawIconCut paints a pair-of-scissors icon (two open-circle handles + crossed blades).

func DrawIconNew

func DrawIconNew(p painter.Painter, r Rect, ink RGBA)

DrawIconNew paints a document-outline icon (rectangle with a folded top-right corner).

func DrawIconOpen

func DrawIconOpen(p painter.Painter, r Rect, ink RGBA)

DrawIconOpen paints a folder-outline icon (rectangle with a small tab on the top-left).

func DrawIconPaste

func DrawIconPaste(p painter.Painter, r Rect, ink RGBA)

DrawIconPaste paints a clipboard outline with a clip on top.

func DrawIconRedo

func DrawIconRedo(p painter.Painter, r Rect, ink RGBA)

DrawIconRedo paints a curved arrow pointing right (mirror of Undo).

func DrawIconSave

func DrawIconSave(p painter.Painter, r Rect, ink RGBA)

DrawIconSave paints a floppy-disk-outline icon (outer square with a small label rectangle on top).

func DrawIconSearch

func DrawIconSearch(p painter.Painter, r Rect, ink RGBA)

DrawIconSearch paints a magnifying-glass icon (a circle + a diagonal handle).

func DrawIconSettings

func DrawIconSettings(p painter.Painter, r Rect, ink RGBA)

DrawIconSettings paints a gear-outline icon (approximated as a square with corner "teeth").

func DrawIconUndo

func DrawIconUndo(p painter.Painter, r Rect, ink RGBA)

DrawIconUndo paints a curved arrow pointing left (approximated as a horizontal stroke + a triangular head).

func DrawText

func DrawText(p painter.Painter, x, y int, text string, ink RGBA)

DrawText paints text left-to-right starting at (x, y) in widget- local coordinates. Each glyph is rendered into a 5x7 cell whose origin is (x + k*GlyphAdvance, y) for the k-th rune. Unknown characters render as a blank but still advance the cursor so column alignment is preserved.

On a *painter.PixelPainter (the WUI + GUI back-end path) DrawText uses toolkit's own 60+ glyph 5x7 bitmap font by writing one pixel per lit bit. On any other painter (a *painter.CellPainter for a TUI, an SvgPainter for vector output) DrawText delegates to the painter's own Text primitive — a CellPainter maps one rune per cell + gets terminal-native text, an SvgPainter emits <text>.

Pixels are written as opaque ink (alpha forced to 0xFF unless the caller's ink already carries an alpha) with per-pixel clipping so glyphs that overflow the painter degrade gracefully.

func SelectionText

func SelectionText(lines []string, sel Selection) string

SelectionText returns the substring covered by sel in lines (a TextView's Lines slice). Empty selection returns "".

func TextWidth

func TextWidth(text string) int

TextWidth returns the pixel width that DrawText would occupy if it rendered text. Every character (known or unknown) consumes one GlyphAdvance slot so callers can pre-size text containers from len(text) alone -- this matches the dock's textWidth helper.

func WeekdayOfFirst

func WeekdayOfFirst(year, month int) int

WeekdayOfFirst returns the weekday-index (0=Mon..6=Sun) of the first day of (year, month). Uses Zeller-ish congruence so we don't depend on time.Time.

Types

type Alert added in v0.7.0

type Alert struct {
	Base
	Text string
	Kind AlertKind
}

Alert is a persistent banner sitting at the top or bottom of a view, carrying a Text message coloured by Kind. Shares Notification's filled-panel-with-border shape but differs in three ways:

  1. No Life field: an Alert stays on screen until the host removes it (the "you are offline" banner). No Tick(), no auto-hide.
  2. No Visible toggle: an Alert that exists is drawn. To hide an alert the host stops rendering it (or drops it from the tree).
  3. Coloured by Kind: Notification is always Accent; Alert varies colour by severity so success + error read differently at a glance.

The banner is not interactive; the parent view supplies a dismiss button as a separate Button if the design calls for one.

func NewAlert added in v0.7.0

func NewAlert(text string, kind AlertKind) *Alert

NewAlert constructs an Alert with the given Text + Kind. Bounds are zero-initialised; the host is responsible for positioning + sizing the banner (typically full-width across the top of the parent view).

func (*Alert) Draw added in v0.7.0

func (a *Alert) Draw(p painter.Painter, theme *Theme)

Draw paints the filled panel + border + text. The ink is Theme.Background so it stays legible against every Kind's face — the same inversion trick Notification uses against its own Accent panel.

type AlertKind added in v0.7.0

type AlertKind int

AlertKind selects the semantic colour of an Alert banner. Info reuses the theme's Accent (the same blue used by focus rings + link text); the other three carry hard-coded shades tuned for meaning — green for success, amber for warning, red for error — because the theme palette doesn't carry semantic slots and adding them would blow up the Theme surface for every app.

const (
	// AlertInfo is a neutral heads-up ("Backup started"). Rendered in
	// Theme.Accent so it matches the app's own accent colour.
	AlertInfo AlertKind = iota
	// AlertSuccess signals a completed operation ("Saved!"). Green.
	AlertSuccess
	// AlertWarning flags a non-fatal issue ("Battery low"). Amber.
	AlertWarning
	// AlertError signals a failure the user must address ("Sync
	// failed"). Red.
	AlertError
)

type Badge added in v0.7.0

type Badge struct {
	Base
	Text string
}

Badge is a small pill-shaped counter or indicator — the "12" that hangs off an inbox icon, the "NEW" beside a menu item. Renders Text inside a rounded-pill body filled in Theme.Accent with the ink in Theme.Background for contrast.

A Badge is passive: it displays a value + does not respond to input. The parent widget (button, menu item, ...) is responsible for positioning it in the top-right corner or wherever the design puts it.

Auto-sizing: if the caller sets Bounds().W to 0, the first Draw() resizes the Bounds to the text width plus BadgePadX on each side (plus GlyphHeight + BadgePadY on each side vertically if H is also 0). This spares the caller from having to compute glyph widths just to paint a two-digit counter. A pre-sized Bounds is honoured verbatim so a fixed-width layout column doesn't shift when the digit count changes.

func NewBadge added in v0.7.0

func NewBadge(text string) *Badge

NewBadge constructs a Badge with the given text. Bounds default to zero so the first Draw() auto-sizes the pill to the text.

func (*Badge) Draw added in v0.7.0

func (b *Badge) Draw(p painter.Painter, theme *Theme)

Draw paints the pill body + centred text. If Bounds().W is zero the widget resizes itself to fit its Text (and Bounds().H is filled in too if it was zero) before painting; a pre-sized Bounds is preserved.

The pill shape is approximated by clipping the four corner pixels: the body fills the full rectangle minus a one-pixel bite off each corner, which reads as "rounded" against the low-resolution 5x7 glyph aesthetic without touching the painter's curve primitives.

type Base

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

Base provides default Bounds/SetBounds/HitTest impls so a widget embedding it only has to implement Draw + OnEvent. Embedding is optional but convenient.

func (*Base) Bounds

func (b *Base) Bounds() Rect

func (*Base) Draw

func (b *Base) Draw(p painter.Painter, theme *Theme)

func (*Base) HitTest

func (b *Base) HitTest(px, py int) bool

func (*Base) OnEvent

func (b *Base) OnEvent(ev Event)

func (*Base) SetBounds

func (b *Base) SetBounds(r Rect)
type Breadcrumbs struct {
	Base
	Segments []string
}

Breadcrumbs is a horizontal navigation path — "Home > Docs > Reference" — rendered as a sequence of Segments separated by a chevron character. Segment text uses Theme.OnBackground; each chevron uses Theme.Border so it reads as a subtle divider rather than another clickable label.

The widget is passive display only: it computes per-segment X positions from TextWidth so a caller wanting hit-testing (e.g. clicking "Docs" to navigate up two levels) can walk the same offset table externally. HitTest / OnEvent stay as Base defaults.

func NewBreadcrumbs added in v0.7.0

func NewBreadcrumbs(segments []string) *Breadcrumbs

NewBreadcrumbs constructs a Breadcrumbs with the given segments. A nil or empty Segments slice renders as a no-op — Draw exits without painting anything.

func (b *Breadcrumbs) Draw(p painter.Painter, theme *Theme)

Draw paints each segment followed by a separator (except after the last one). Segments are vertically centred inside Bounds when Bounds.H exceeds GlyphHeight, otherwise they anchor at Bounds.Y.

type Button

type Button struct {
	Base
	Label   string
	OnClick func()
	// contains filtered or unexported fields
}

Button is a clickable rectangle with a centred label. Paints a 1-pixel border in Theme.Border on a Theme.Surface body; hovered / pressed states cycle through SurfaceAlt + Accent so the user sees click feedback before the callback fires.

Wire a handler via OnClick; the button calls it from OnEvent when it receives an EventClick. Callers re-paint via Draw after any state mutation (the toolkit doesn't drive its own frame loop -- the wasmbox compositor's tick is the redraw trigger).

func NewButton

func NewButton(label string, onClick func()) *Button

NewButton constructs a Button with the given label + click handler. Handler may be nil (a no-op button is still rendered).

func (*Button) Draw

func (b *Button) Draw(p painter.Painter, theme *Theme)

Draw paints the button through p using theme's palette. Face cycles through Surface / SurfaceAlt (hovered) / Accent (pressed); the Label is centred in the body using the toolkit's 5x7 bitmap font. When the button is pressed the ink swaps to the theme's Background so the label stays legible against the Accent face.

func (*Button) OnEvent

func (b *Button) OnEvent(ev Event)

OnEvent dispatches click events to the OnClick callback. Other event kinds are ignored at this level (the parent container may have already pre-filtered).

func (*Button) SetHovered

func (b *Button) SetHovered(v bool)

SetHovered/SetPressed are wired by the parent container's mouse dispatcher so the button can render its hover/press visual states. Direct setters (vs deducing from OnEvent kinds) keep the parent in control of state propagation -- enter/leave events would duplicate the same logic in every leaf widget.

func (*Button) SetPressed

func (b *Button) SetPressed(v bool)

type Calendar

type Calendar struct {
	Base
	Year     int
	Month    int // 1..12
	Day      int // selected day in [1, daysInMonth]
	TodayY   int
	TodayM   int
	TodayD   int
	OnSelect func(y, m, d int)
}

Calendar renders a month grid (Mon..Sun columns, up to 6 rows) for a given (Year, Month). The currently-selected day is highlighted; click on a day-cell selects it + fires OnSelect with the absolute (Y, M, D) triple.

Calendar takes no time-source dep; the host must pass it the current year/month/day. A "today" pill can be drawn by setting Today (year/month/day); set to (0, 0, 0) to disable it.

func NewCalendar

func NewCalendar(year, month, day int) *Calendar

NewCalendar builds a Calendar for the given (year, month, day).

func (*Calendar) Draw

func (c *Calendar) Draw(p painter.Painter, theme *Theme)

Draw paints header (Y M) + weekday row + day grid.

func (*Calendar) OnEvent

func (c *Calendar) OnEvent(ev Event)

OnEvent dispatches a click on a day cell to OnSelect.

func (*Calendar) SetDate

func (c *Calendar) SetDate(year, month, day int)

SetDate moves the calendar to (year, month, day).

func (*Calendar) SetToday

func (c *Calendar) SetToday(y, m, d int)

SetToday records the "today" pill the calendar should highlight regardless of which (Y/M) is being viewed.

type Card added in v0.7.0

type Card struct {
	Base
	Title  string
	Body   string
	Footer string
}

Card is a bordered container laid out as three optional zones: a header strip at the top (title text on a SurfaceAlt background), a body area with multi-line text (each '\n'-separated line rendered on its own row) and a footer strip at the bottom (SurfaceAlt like the header). The whole card sits on a Theme.Surface fill and is framed by a 1-px Theme.Border stroke — the same visual grammar Button and Menu use so a Card composes cleanly next to them.

Any zone may be empty:

  • Title == "" -> the header strip is skipped, the body starts at r.Y.
  • Body == "" -> no text lines are drawn (the surface fill still shows).
  • Footer== "" -> the footer strip is skipped, the body flows to r.Y+r.H.

Card is a passive display container — it does not intercept input (HitTest / OnEvent stay as Base defaults) so a caller that needs an interactive Card wraps it with an outer container or overlays a Button on top.

func NewCard added in v0.7.0

func NewCard(title, body, footer string) *Card

NewCard constructs a Card with the given title, body + footer. Any of the three may be "" to skip that zone.

func (*Card) Draw added in v0.7.0

func (c *Card) Draw(p painter.Painter, theme *Theme)

Draw paints the surface fill, the optional header and footer strips, each body line and finally the outer border stroke. Draw order is bottom-to-top (fill, then decorations, then border) so the 1-px border always sits on top and clips overlapping strips.

type CheckButton

type CheckButton struct {
	Base
	Label    string
	Checked  bool
	OnToggle func(checked bool)
}

CheckButton is a square checkbox + a label. Click toggles Checked + fires OnToggle. Visual: 12 x 12 px box (left-aligned), Theme.Border outline, Theme.Surface fill, Theme.Accent fill + two diagonal "checkmark" strokes in Theme.Background when Checked. Label rendered in Theme.OnBackground to the right of the box.

func NewCheckButton

func NewCheckButton(label string, checked bool) *CheckButton

NewCheckButton constructs a CheckButton with the given label + initial Checked state.

func (*CheckButton) Draw

func (c *CheckButton) Draw(p painter.Painter, theme *Theme)

Draw paints the box + checkmark + label.

func (*CheckButton) OnEvent

func (c *CheckButton) OnEvent(ev Event)

OnEvent flips Checked + fires OnToggle on click.

type ColorChooser

type ColorChooser struct {
	Base
	Color    RGBA
	OnChange func(c RGBA)
}

ColorChooser is a 3-channel R/G/B picker with a live preview. Each channel is rendered as a horizontal track with a 1-pixel knob the user drags to change the value. The OnChange callback fires with the new RGBA whenever any channel moves.

The widget owns the RGBA value; the host reads .Color() to get the current pick + may also stash a hex string via SetHex if there is a sibling Entry the user can type into.

func NewColorChooser

func NewColorChooser(initial RGBA) *ColorChooser

NewColorChooser builds a chooser starting at initial. Alpha is forced to 0xFF so a freshly-constructed chooser always reads as fully-opaque.

func (*ColorChooser) Draw

func (c *ColorChooser) Draw(p painter.Painter, theme *Theme)

Draw paints the 3 sliders + preview swatch + hex label.

func (*ColorChooser) Hex

func (c *ColorChooser) Hex() string

Hex returns the color as "#RRGGBB".

func (*ColorChooser) OnEvent

func (c *ColorChooser) OnEvent(ev Event)

OnEvent handles clicks on the 3 tracks to move the channel knob.

func (*ColorChooser) SetHex

func (c *ColorChooser) SetHex(s string)

SetHex parses "#RRGGBB" or "RRGGBB" into the chooser's color. Bad input is silently ignored so a malformed Entry payload can't break the picker state.

type Dialog

type Dialog struct {
	Base
	Title   string
	Content Widget
	Buttons []*Button
	OnClose func()
}

Dialog is a modal overlay: a centred Surface card with an optional Title bar, a Content widget filling the body, and an action-button strip at the bottom. The compositor draws a semi-darkened backdrop over the rest of the surface so the user's attention focuses on the dialog.

v0.3 ships the structure; the host app is responsible for routing input events only to the dialog while it's open (existing wasmbox modal-grab behaviour).

func NewDialog

func NewDialog(title string, content Widget, buttons ...*Button) *Dialog

NewDialog builds a Dialog with the given title, content + action buttons. Buttons are laid out right-aligned in the bottom strip.

func NewMessageDialog

func NewMessageDialog(title, message string, onOK func()) *Dialog

NewMessageDialog is a convenience constructor for the most common dialog: a title, a Label as content, and an OK button that calls onOK + closes the dialog via the caller's OnClose hook.

func (*Dialog) Draw

func (d *Dialog) Draw(p painter.Painter, theme *Theme)

Draw paints card + title + content + buttons.

func (*Dialog) OnEvent

func (d *Dialog) OnEvent(ev Event)

OnEvent forwards to content + buttons. A click that doesn't land on any button or the content falls through silently (the app keeps the dialog open).

func (*Dialog) SetBounds

func (d *Dialog) SetBounds(r Rect)

SetBounds also lays out the content + button positions.

type DropDown struct {
	Base
	Options  []string
	Selected int
	Open     bool
	OnSelect func(idx int)
}

DropDown is a one-of-N selector that shows the current choice in a button-like rectangle. Clicking opens a popover ListBox of all Options just below the widget; selecting one closes the popover + fires OnSelect.

Like Dialog, the popover's rendering surface is owned by the host app; the toolkit exposes Open + Selected so the host knows what to draw. This keeps DropDown independent of how the compositor handles overlay surfaces (some apps use a separate canvas, some draw the popover directly into the same buffer).

func NewDropDown

func NewDropDown(options []string, selected int) *DropDown

NewDropDown builds a DropDown with the given options + an initial selection (clamped to a valid index, or 0 when options is empty).

func (d *DropDown) Current() string

Current returns the currently-selected option's string, or "" when Options is empty.

func (d *DropDown) Draw(p painter.Painter, theme *Theme)

Draw paints the closed widget. The popover, when Open, is the host app's responsibility (host can render a ListBox on top using PopoverBounds).

func (d *DropDown) OnEvent(ev Event)

OnEvent toggles Open on click. Selection happens via Select() which the host wires to its popover ListBox's OnActivate.

func (d *DropDown) PopoverBounds() Rect

PopoverBounds returns the Rect the host should give to its popover ListBox: same X+W as the widget, positioned just below, height proportional to the option count (clamped to PopoverMaxRows rows).

func (d *DropDown) Select(idx int)

Select picks idx, closes the popover + fires OnSelect.

type Entry

type Entry struct {
	Base
	Text     string
	Cursor   int // rune index in [0, len(runes)]
	Focused  bool
	OnChange func(text string)
	OnSubmit func(text string)
}

Entry is a single-line text input. Receives focus on click, edits Text via EventKeyDown (Backspace, ArrowLeft/Right, Home, End, Enter) + EventChar (printable runes). A 1-pixel vertical cursor renders at the cursor offset when Focused.

The widget treats Text as a rune index space so multi-byte UTF-8 characters move the cursor by one position even when they take several bytes on the wire.

func NewEntry

func NewEntry(initial string) *Entry

NewEntry builds an Entry with initial text + cursor parked at end.

func (*Entry) Draw

func (e *Entry) Draw(p painter.Painter, theme *Theme)

Draw paints the border, fill, text + (when Focused) a 1-px cursor stroke at the cursor's pixel position.

func (*Entry) OnEvent

func (e *Entry) OnEvent(ev Event)

OnEvent handles focus, keyboard navigation, character insertion + delete.

type Event

type Event struct {
	Kind EventKind
	X, Y int
	Code string
}

Event is one input event delivered to a widget. The parent container translated mouse coordinates into widget-local pixels; Code is the key/char text for keyboard events.

type EventKind

type EventKind int

EventKind enumerates the input event types a widget can receive. The wasmbox compositor routes DOM events through this enum so widgets don't depend on the browser's exact event names.

const (
	// EventClick fires on a mousedown+mouseup pair inside the widget.
	// X/Y carry widget-local coordinates.
	EventClick EventKind = iota
	// EventKeyDown fires when a key is pressed while the widget has
	// focus. Code carries the key name (e.g. "Enter", "ArrowLeft").
	EventKeyDown
	// EventKeyUp is the symmetric release event.
	EventKeyUp
	// EventChar fires for printable character input (post-IME).
	// Code carries the character as a one-rune string.
	EventChar
	// EventCompositionStart fires when an IME composition begins
	// (typically a dead-key press or a CJK IME popup opening). Widgets
	// that echo text (Entry / TextView) should render Code as the
	// "in-progress" preview string, underlined or ghosted, WITHOUT
	// committing it to their buffer. The host is responsible for
	// resolving the composition via EventCompositionUpdate ticks and
	// finally an EventChar (post-commit).
	EventCompositionStart
	// EventCompositionUpdate refreshes the preview string mid-flow.
	// Code carries the current, un-committed composed text.
	EventCompositionUpdate
	// EventCompositionEnd fires when the composition is either
	// committed (host follows up with EventChar carrying the same
	// text) or cancelled (host does NOT send an EventChar and the
	// widget discards the preview).
	EventCompositionEnd
)

type Expander

type Expander struct {
	Base
	Label    string
	Expanded bool
	Content  Widget
	OnExpand func(expanded bool)
}

Expander is a header row that toggles a content area's visibility. The header is ExpanderHeaderH px tall, shows a chevron + label; clicking the header flips Expanded + fires OnExpand.

When Expanded, Content occupies the remaining bounds below the header. When collapsed, only the header is drawn.

func NewExpander

func NewExpander(label string, content Widget) *Expander

NewExpander builds an Expander with a label + initial content widget (may be nil to render header-only).

func (*Expander) Draw

func (e *Expander) Draw(p painter.Painter, theme *Theme)

Draw paints the header (chevron + label) + the content widget when Expanded.

func (*Expander) OnEvent

func (e *Expander) OnEvent(ev Event)

OnEvent: click on the header toggles Expanded + fires OnExpand; clicks below the header forward to Content (when expanded).

type FileChooser

type FileChooser struct {
	Base
	Root      *TreeNode
	ListFiles func(dir *TreeNode) []string
	OnAccept  func(path string)
	OnCancel  func()
	// contains filtered or unexported fields
}

FileChooser is a directory-tree + file-list + path-entry composite. It does NO I/O — the host hands it a virtual root (a TreeNode tree representing directories) + a func that lists files in a given directory. Selection is reported via OnAccept.

FileChooser is the canonical use-case for TreeView + ListBox + Entry composed together. It is what an "Open File…" dialog renders inside a wasmbox app that has no JS file picker access.

func NewFileChooser

func NewFileChooser(root *TreeNode, listFiles func(dir *TreeNode) []string) *FileChooser

NewFileChooser builds a FileChooser rooted at root with the given directory-listing func.

func (*FileChooser) Draw

func (f *FileChooser) Draw(p painter.Painter, theme *Theme)

Draw paints the composite.

func (*FileChooser) OnEvent

func (f *FileChooser) OnEvent(ev Event)

OnEvent dispatches to the child widgets based on which one the event falls inside.

func (*FileChooser) Path

func (f *FileChooser) Path() string

Path returns the entry text — the current effective selection.

func (*FileChooser) SetBounds

func (f *FileChooser) SetBounds(r Rect)

SetBounds positions the child widgets at the chosen split.

type Frame

type Frame struct {
	Base
	// Padding is the inset (in pixels) between Frame's border + its
	// child. Defaults to 4 when left at zero; negative values are
	// clamped to zero at layout time.
	Padding int
	// contains filtered or unexported fields
}

Frame draws a 1-pixel border around a single child widget + inset the child by Padding pixels inside that border. Useful as a group- box / panel separator when an app wants to visually fence off a region of widgets.

Frame is a Widget: Draw paints the border + delegates to the child; OnEvent forwards to the child with translated coordinates.

func NewFrame

func NewFrame(child Widget) *Frame

NewFrame wraps child in a Frame. child may be nil (the Frame then just draws its border + accepts no events).

func (*Frame) Draw

func (f *Frame) Draw(p painter.Painter, theme *Theme)

Draw paints the 1-pixel border then the child (if any).

func (*Frame) OnEvent

func (f *Frame) OnEvent(ev Event)

OnEvent forwards to the child if the event lands inside its Bounds.

func (*Frame) SetBounds

func (f *Frame) SetBounds(r Rect)

SetBounds positions the Frame + resizes its child to fit inside the border + padding.

type Grid

type Grid struct {
	Base
	// contains filtered or unexported fields
}

Grid lays children out in a fixed cols x rows table. Each cell is the same size (container W/cols, H/rows). Children are placed via Attach(child, col, row); a cell with no attached child stays empty.

Grid is a Widget: Draw fans out to every attached child + OnEvent hit-tests then forwards.

func NewGrid

func NewGrid(cols, rows int) *Grid

NewGrid constructs an empty cols x rows grid. cols + rows must be positive; the constructor clamps non-positive inputs to 1 to keep the divide-by-zero out of SetBounds.

func (*Grid) Attach

func (g *Grid) Attach(w Widget, col, row int)

Attach places w at (col, row). Out-of-range coordinates are clamped into the grid so a typo doesn't silently vanish + the child still ends up somewhere visible. Re-runs layout immediately.

func (*Grid) Draw

func (g *Grid) Draw(p painter.Painter, theme *Theme)

Draw paints every attached child in attach order.

func (*Grid) OnEvent

func (g *Grid) OnEvent(ev Event)

OnEvent hit-tests attached children + forwards with translated coordinates.

func (*Grid) SetBounds

func (g *Grid) SetBounds(r Rect)

SetBounds positions the Grid + sizes every attached child to its (col, row) cell.

type HBox

type HBox struct {
	Base
	// Spacing is the gap in pixels between adjacent children. Defaults
	// to 4 when left at zero (set to a negative value to truly disable
	// the gap; negative values are clamped to zero at layout time).
	Spacing int
	// contains filtered or unexported fields
}

HBox is a horizontal flow container. Children are laid out left-to- right + share the container's width equally (minus Spacing gaps between adjacent children). Children's Y + height fill the box's vertical extent.

HBox is a Widget itself: Draw fans out to every child + OnEvent hit-tests by child Bounds, translating coordinates into the matched child's local space before forwarding.

func NewHBox

func NewHBox() *HBox

NewHBox constructs an empty HBox. Callers add children via Append + then call SetBounds to trigger layout.

func (*HBox) Append

func (h *HBox) Append(w Widget)

Append adds w to the right of any existing children. Re-runs layout so the new child is positioned immediately + the caller doesn't have to remember to re-call SetBounds.

func (*HBox) Draw

func (h *HBox) Draw(p painter.Painter, theme *Theme)

Draw paints every child in append order. Children render directly into the surface using their own Bounds; the HBox itself draws no background or border (it's a pure layout container).

func (*HBox) OnEvent

func (h *HBox) OnEvent(ev Event)

OnEvent hit-tests by child Bounds + forwards the event with coordinates translated into the child's local space. The first child whose Bounds contains the event point wins (children should not overlap inside an HBox, but a stable order is still useful).

func (*HBox) SetBounds

func (h *HBox) SetBounds(r Rect)

SetBounds positions the HBox + lays out its children. Width is divided equally among children; Spacing pixels separate adjacent cells. Children's Y matches the box's Y; height matches H.

type HeaderBar added in v0.7.0

type HeaderBar struct {
	Base
	Title    string
	Subtitle string
	Start    []Widget // rendered left-to-right along the left edge
	End      []Widget // rendered right-to-left along the right edge
}

HeaderBar is the GTK "client-side decorations" bar: an optional row of Start widgets (usually navigation — back, menu), a centred Title (+ optional Subtitle) and an optional row of End widgets (usually actions — search, close). Composes cleanly above a Notebook + Statusbar so an app can assemble a stock GNOME window out of just three toolkit widgets.

Start widgets paint left-to-right from the bar's left edge; End widgets paint right-to-left from the bar's right edge. The title (and subtitle, when non-empty) are centred horizontally in whatever space remains between the two child regions.

HeaderBar does not intercept events itself; children receive events via the parent container's usual dispatch after HeaderBar has positioned them (Draw does the layout side-effect).

func NewHeaderBar added in v0.7.0

func NewHeaderBar(title string) *HeaderBar

NewHeaderBar constructs a HeaderBar carrying title. Subtitle, Start and End remain zero-valued; the caller populates them before the first Draw.

func (*HeaderBar) Draw added in v0.7.0

func (h *HeaderBar) Draw(p painter.Painter, theme *Theme)

Draw paints the bar body, positions + draws every Start / End child, then paints Title (+ Subtitle when non-empty) centred in whatever horizontal space is left between the two child regions.

Positioning side effect: every Start / End widget's Bounds is updated to reflect its position inside the bar. The widget's original Bounds.W is preserved; Bounds.H is fitted to the bar's inner height (bar.H - HeaderBarPad). This mirrors GTK's HdyHeaderBar pattern — children carry their own preferred width but let the bar decide vertical placement.

type Image

type Image struct {
	Base
	Pixels []byte // RGBA bytes, W*H*4 in length
	W, H   int    // source dimensions
}

Image paints a caller-supplied RGBA byte buffer into its bounds. If source dims == bounds, the blit is 1:1; otherwise the image is nearest-neighbour scaled to fit the bounds (no aspect-ratio preservation in v0.2).

func NewImage

func NewImage(pixels []byte, w, h int) *Image

NewImage wraps pixels (length must equal w*h*4) + the source dimensions. Caller owns the pixels; the toolkit just reads them.

func (*Image) Draw

func (i *Image) Draw(p painter.Painter, theme *Theme)

Draw paints the image into bounds. Scaling is nearest-neighbour.

type Kbd added in v0.7.0

type Kbd struct {
	Base
	Keys string
}

Kbd renders a keyboard-shortcut hint like the "⌘K" chip beside a menu item: a small bordered box with the key text centred inside. Uses Theme.Surface for the face + Theme.Border for the stroke so the chip reads as a raised inlay against the parent panel.

Kbd is passive (no OnEvent handling); the caller sets Bounds to position it — a Kbd typically lives to the right of a menu label, vertically centred with the menu row. Nothing about the widget depends on the input actually being pressed; it's a purely visual mnemonic.

func NewKbd added in v0.7.0

func NewKbd(keys string) *Kbd

NewKbd constructs a Kbd carrying the given key text. Callers set Bounds via SetBounds before Draw; a natural fit is {W: TextWidth(keys) + 2*KbdPadX, H: GlyphHeight + 2*KbdPadY}.

func (*Kbd) Draw added in v0.7.0

func (k *Kbd) Draw(p painter.Painter, theme *Theme)

Draw paints the chip: filled Surface body, 1-px Border stroke, Keys text centred in OnSurface. Zero-size Bounds degrade to a no-op via fillRect/strokeRect's own dimension guards.

type Label

type Label struct {
	Base
	Text string
}

Label is a passive widget that displays Text in the theme's OnSurface colour, drawn with the toolkit's 5x7 bitmap font. Left- aligned inside Bounds, vertically centred if Bounds.H exceeds GlyphHeight.

Label is non-interactive: HitTest returns false so clicks pass through to the widget beneath. Apps that want a clickable label should compose a Button with the text instead.

func NewLabel

func NewLabel(text string) *Label

NewLabel constructs a Label carrying text.

func (*Label) Draw

func (l *Label) Draw(p painter.Painter, theme *Theme)

Draw paints the Label's text with the toolkit's bitmap font. If Bounds.H > GlyphHeight the text is vertically centred; otherwise it lands at Bounds.Y.

func (*Label) HitTest

func (l *Label) HitTest(_, _ int) bool

HitTest returns false unconditionally: a Label is decorative, not interactive. Override (or compose with a Button) to make a label receive events.

type LevelBar

type LevelBar struct {
	Base
	Value, Max int
}

LevelBar is the discrete cousin of ProgressBar: Max equal cells, the first Value cells filled in Accent + the rest in SurfaceAlt. Useful for battery / signal-strength style indicators.

func NewLevelBar

func NewLevelBar(max int) *LevelBar

NewLevelBar builds a LevelBar with the given Max (Value defaults to 0).

func (*LevelBar) Draw

func (l *LevelBar) Draw(p painter.Painter, theme *Theme)

Draw paints Max cells with a 1-px gap; the first Value cells use Theme.Accent, the rest Theme.SurfaceAlt.

type ListBox

type ListBox struct {
	Base
	Items      []string
	Selected   int // -1 = no selection
	RowHeight  int // pixels per row; default 18 via NewListBox
	OnActivate func(idx int)
}

ListBox is a vertical list of selectable string rows. Click on a row selects it + fires OnActivate.

Visual: each row is RowHeight pixels tall. The selected row uses Theme.Accent as background + Theme.Background as ink; unselected rows use Theme.Surface + Theme.OnSurface. Rows are rendered via font.DrawText with a 4 px left margin.

func NewListBox

func NewListBox(items []string) *ListBox

NewListBox builds a ListBox containing items. Selected starts at -1 (no row selected) and RowHeight defaults to 18 (a comfortable 7-px font + 11 px vertical padding).

func (*ListBox) Draw

func (l *ListBox) Draw(p painter.Painter, theme *Theme)

Draw paints every row inside the widget's bounds. Rows that fall outside the bounds (because the list is longer than the viewport) are still drawn but clipped per-pixel by the raster helpers; wrap a ScrollView around the ListBox for proper scrollable behaviour.

func (*ListBox) OnEvent

func (l *ListBox) OnEvent(ev Event)

OnEvent dispatches click events: a click at (X, Y) selects the row idx = Y / RowHeight (clamped to the list length); OnActivate fires with that idx.

type Menu struct {
	Base
	Items   []MenuItem
	Hover   int // index of hovered row, -1 if none
	OnClose func()
}

Menu is a vertical popover-style list of MenuItems. Used by the compositor's right-click root menu, by MenuBar drop-downs and by any widget that needs an Openbox-style picker.

func NewMenu

func NewMenu(items []MenuItem) *Menu

NewMenu builds a Menu with the given items + Hover at -1.

func (m *Menu) Draw(p painter.Painter, theme *Theme)

Draw paints the menu's body + every row + a hover highlight on the currently-hovered row.

func (m *Menu) OnEvent(ev Event)

OnEvent: a click on an enabled row fires its Action + closes the menu via OnClose (if wired).

func (m *Menu) SetHover(y int)

SetHover updates Hover based on a mouse-Y coordinate (widget-local). Useful for keyboard / mouse-move handlers that want to highlight the row the user is pointing at.

type MenuBar struct {
	Base
	Names  []string
	Menus  []*Menu
	Active int // -1 if none open
}

MenuBar is a horizontal strip of top-level menu names (File, Edit, View, ...). Clicking a name opens its associated Menu as a popover just below the strip.

The MenuBar itself doesn't own the open Menu's drawing (the containing app composes it with whatever overlay surface it has); MenuBar just exposes Active so the app knows which menu to render.

func NewMenuBar

func NewMenuBar() *MenuBar

NewMenuBar builds a MenuBar (Active = -1).

func (b *MenuBar) AddMenu(name string, m *Menu)

AddMenu appends (name, menu) to the bar.

func (b *MenuBar) Draw(p painter.Painter, theme *Theme)

Draw paints the bar + every name + a highlight on the Active name.

func (b *MenuBar) HandleShortcut(code string) bool

HandleShortcut walks every menu's items and fires the Action of the first item whose Shortcut equals code (case-sensitive; the host is expected to normalise Ctrl+N vs Cmd+N before calling). Returns true if an item fired, false if no match. Menu ordering + item ordering give a deterministic priority — first match wins.

Skipped: separators, disabled items (nil Action). A matching item with a submenu still fires its Action (if any); the submenu is not opened by a shortcut.

Typical usage from a wasmbox client's Go main:

case "keydown":
    code := formatShortcut(ev)   // host builds "Ctrl+N" etc.
    if state.menuBar.HandleShortcut(code) { render(); return }
    state.editor.OnEvent(...)    // fallthrough: forward to focus
func (b *MenuBar) Mnemonic(i int) byte

Mnemonic returns the first letter of the i-th menu name (upper-case, or 0 if the index is out of range / the name is empty). Useful for a host that wants to draw "_F_ile"-style underlines under the mnemonic character.

func (b *MenuBar) NameOriginX(i int) int

NameOriginX returns the X offset of the i-th top-level name within the bar (cumulative sum of NameWidth up to i, exclusive). Same motivation as NameWidth: a host that positions a popover under a clicked name reads this to align on the correct column.

func (b *MenuBar) NameWidth(i int) int

NameWidth returns the pixel width of the i-th top-level name after the auto-size rule: max(MenuBarItemW, TextWidth(name) + 2*pad). Exposed so hosts that render their own popover under a clicked name know how wide the "click zone" was.

func (b *MenuBar) OnEvent(ev Event)

OnEvent: a click on a name toggles its menu (Active = idx or -1). Also honours mnemonic keyboard shortcuts on EventKeyDown when the Code carries an "Alt+X" hint (X = one of the top-level names' first letter, case-insensitive) — matches the GNOME/Windows menu-bar Alt+letter convention. The host is responsible for formatting the key event's Code as "Alt+F" etc. before forwarding.

type MenuItem struct {
	Label     string
	Action    func()
	Submenu   *Menu
	Separator bool
	Shortcut  string
}

MenuItem is one row in a Menu. Label is the human text; Action is the callback fired on click. A nil Action turns the row into a disabled (greyed-out) entry; a non-empty Submenu lets it open a nested Menu (popover) on hover or click.

Separator items render as a thin SurfaceAlt line + are not clickable. They have empty Label + nil Action.

Shortcut is a hint string ("Ctrl+N", "Cmd+O", …) drawn right-aligned on the row in the muted SurfaceAlt tone. Purely visual: the host app is responsible for actually wiring the key combo to the item's Action (there is no cross-platform "Ctrl vs Cmd" logic in the toolkit — different apps route keys through different SDKs).

type Notebook

type Notebook struct {
	Base
	Tabs         []NotebookTab
	Active       int
	OnTabChanged func(idx int)
}

Notebook is a tabbed container. The top NotebookTabStripH pixels host the tab strip; the rest is the active page's body. Clicking a tab swaps Active + fires OnTabChanged.

func NewNotebook

func NewNotebook() *Notebook

NewNotebook returns an empty Notebook with no tabs + Active = 0.

func (*Notebook) AddTab

func (n *Notebook) AddTab(label string, page Widget)

AddTab appends a tab to the strip with label + the page widget shown when that tab is active.

func (*Notebook) Draw

func (n *Notebook) Draw(p painter.Painter, theme *Theme)

Draw paints the strip + the active page.

func (*Notebook) OnEvent

func (n *Notebook) OnEvent(ev Event)

OnEvent: click on the strip selects a tab; click in the body routes to the active page.

type NotebookTab

type NotebookTab struct {
	Label string
	Page  Widget
}

NotebookTab is one entry in a Notebook. Label is the human title painted on the tab; Page is the widget shown when the tab is active.

type Notification

type Notification struct {
	Base
	Text    string
	Visible bool

	// Life is the number of Tick() calls remaining before the
	// notification auto-hides. NotificationLife (~180 ≈ 3 s at 60 Hz)
	// is a reasonable default; Show() re-arms it. Set directly for
	// long-lived notifications (e.g. Life = 3600 for a persistent
	// "network offline" banner the host manually Hide()s later).
	Life int
}

Notification is a transient toast — an auto-dismissing banner that slides in over the app's normal frame, holds for a few ticks, then hides itself. Cousin of Tooltip (both are informational overlays) but with three key differences:

  1. Notification is time-bounded (Tick decrements Life; hides at 0).
  2. Notification is positioned by the host (typically top-right or bottom-centre), NOT anchored to a source widget.
  3. Notification stays up while the user is doing something else — Tooltip requires the mouse to hover over its anchor.

The host drives Life via Tick() from its own animation loop (typically a rAF tick). One Notification instance can be reused — call Show(text) to re-arm it with a fresh Life budget.

func NewNotification

func NewNotification(text string) *Notification

NewNotification builds a hidden notification with the given text + the default Life budget pre-armed (so a caller who forgets to call Show still gets a sensible time-out on the first Tick loop).

func (*Notification) Draw

func (n *Notification) Draw(p painter.Painter, theme *Theme)

Draw paints the toast when Visible. Filled Accent panel with a 1-px Border stroke, Text in the Background ink (inverted for contrast). Nothing drawn when hidden.

func (*Notification) Hide

func (n *Notification) Hide()

Hide dismisses the notification immediately (independent of Life).

func (*Notification) Show

func (n *Notification) Show(text string)

Show makes the notification visible + resets Life to NotificationLife. Bounds are auto-sized to the text width + the standard padding; the host is responsible for positioning (SetBounds) BEFORE calling Show — Show only refreshes the width to match the current Text.

func (*Notification) Tick

func (n *Notification) Tick()

Tick decrements Life by 1. When Life reaches 0, the notification auto-hides. The host calls this from its animation loop; a rAF-driven caller ticks 60 Hz so NotificationLife = 180 ≈ 3 s. Callers wanting a paused notification (freeze on user hover) just skip the Tick during the pause.

type Paned

type Paned struct {
	Base
	First, Second     Widget
	Orientation       int
	Position          int
	OnPositionChanged func(pos int)
}

Paned splits its bounds into two child regions separated by a PanedHandleW-px draggable handle. Position is the handle's offset (in pixels) from the leading edge of First; orientation chooses whether that's measured along X (PanedHorizontal) or Y (PanedVertical).

The toolkit's full event model is click-only in v0.2, so drag is exposed via direct OnDragHandle helpers callers wire to their own mouse-tracking state.

func NewHPaned

func NewHPaned(first, second Widget) *Paned

NewHPaned builds a horizontal Paned with a sensible default Position (mid-bounds, applied at first SetBounds).

func NewVPaned

func NewVPaned(first, second Widget) *Paned

NewVPaned builds a vertical Paned with the same defaults as NewHPaned.

func (*Paned) Draw

func (pd *Paned) Draw(p painter.Painter, theme *Theme)

Draw paints both children + the handle.

func (*Paned) MoveHandle

func (p *Paned) MoveHandle(pos int)

MoveHandle slides the splitter to pos (clamped) and re-lays out children. Fires OnPositionChanged with the new value.

func (*Paned) OnEvent

func (p *Paned) OnEvent(ev Event)

OnEvent forwards to the appropriate child based on click position.

func (*Paned) SetBounds

func (p *Paned) SetBounds(r Rect)

SetBounds lays out First/Second around the handle.

type ProgressBar

type ProgressBar struct {
	Base
	Fraction float64
	Label    string
}

ProgressBar is a horizontal bar with a filled portion proportional to Fraction in [0,1]. An optional Label is centred over the bar in Theme.OnSurface ink.

func NewProgressBar

func NewProgressBar() *ProgressBar

NewProgressBar builds an empty (Fraction=0) ProgressBar with no label.

func (*ProgressBar) Draw

func (pb *ProgressBar) Draw(p painter.Painter, theme *Theme)

Draw paints border + track + fill + optional centered label.

func (*ProgressBar) SetFraction

func (p *ProgressBar) SetFraction(f float64)

SetFraction clamps + assigns Fraction. 0 = empty, 1 = full.

type RGBA

type RGBA = painter.RGBA

RGBA is a 32-bit colour value packed as bytes (Red, Green, Blue, Alpha). Alias of painter.RGBA so widgets emit values that flow unchanged through any Painter back-end (pixel buffer, cell grid, SVG stream). Alpha is honoured by the pixel rasteriser; the stock widgets all paint opaque pixels (A=0xFF).

func RGB

func RGB(r, g, b uint8) RGBA

RGB constructs an opaque colour with A=0xFF. Kept as a package- level helper so theme literals don't have to import painter.

type RadioButton

type RadioButton struct {
	Base
	Label    string
	Checked  bool
	OnToggle func(checked bool)
	// contains filtered or unexported fields
}

RadioButton is a circular toggle paired with a label. RadioButtons are typically grouped via RadioGroup so exactly one in the group is Checked at any time. A standalone RadioButton (not added to a group) behaves like a CheckButton (toggleable on click).

func NewRadioButton

func NewRadioButton(label string) *RadioButton

NewRadioButton constructs a standalone RadioButton with the given label. Add it to a RadioGroup with group.Add(r) for mutual-exclusion behaviour.

func (*RadioButton) Draw

func (r *RadioButton) Draw(p painter.Painter, theme *Theme)

Draw paints the circular mark + label. The "circle" is a 12 x 12 box with a 1-pixel inset on every side, painted as a stroked rectangle (approximate to avoid bringing in trig). When Checked, a smaller Accent-filled rect sits inside as the radio dot.

func (*RadioButton) OnEvent

func (r *RadioButton) OnEvent(ev Event)

OnEvent: on click, route through the group (if any) so siblings clear; otherwise toggle Checked locally.

type RadioGroup

type RadioGroup struct {
	Members []*RadioButton
	Active  int
}

RadioGroup makes a set of RadioButtons mutually exclusive. Active is the index of the currently-checked member, or -1 when none has been clicked yet.

func NewRadioGroup

func NewRadioGroup() *RadioGroup

NewRadioGroup builds an empty group with Active = -1.

func (*RadioGroup) Add

func (g *RadioGroup) Add(r *RadioButton)

Add appends r to the group + remembers its membership so a click on any member can clear the others.

type Rect

type Rect = painter.Rect

Rect is an axis-aligned rectangle in pixel coordinates. X/Y is the top-left corner; W/H are width/height. Aliased to painter.Rect so widgets can render on any painter.Painter (PixelPainter for a pixel buffer, CellPainter for a terminal grid) without a type conversion. Contains() is inherited from painter.Rect.

type Scale

type Scale struct {
	Base
	Min, Max float64
	Value    float64
	OnChange func(v float64)
}

Scale is a horizontal slider over a continuous Min..Max range. Click on the track jumps the thumb to that x-position + fires OnChange. The 4-px track sits across the vertical midpoint in Theme.SurfaceAlt; the 10-px square thumb sits at the value's position in Theme.Accent.

func NewScale

func NewScale(min, max, initial float64) *Scale

NewScale builds a Scale spanning [min, max] with the given initial value. Min == Max is allowed but renders a non-interactive track.

func (*Scale) Draw

func (s *Scale) Draw(p painter.Painter, theme *Theme)

Draw paints the track + the thumb.

func (*Scale) OnEvent

func (s *Scale) OnEvent(ev Event)

OnEvent: click jumps the thumb to the clicked x-position + fires OnChange.

func (*Scale) SetValue

func (s *Scale) SetValue(v float64)

SetValue clamps to [Min, Max] before assigning.

type ScrollView

type ScrollView struct {
	Base
	Child            Widget
	OffsetX, OffsetY int
	// contains filtered or unexported fields
}

ScrollView is a viewport over a child widget whose content may be larger than the visible area. The child's own Bounds is logical (= content size); ScrollView paints the child clipped to its own Bounds, with origin shifted by -OffsetX/-OffsetY.

A thin scrollbar track (8 px wide) is painted on the right edge in Theme.SurfaceAlt; a Theme.Accent thumb sized proportionally to the viewport/content ratio shows the current scroll position.

Wheel events scroll vertically; horizontal scrolling is supported via direct Scroll(dx, dy) calls (no horizontal scrollbar drawn in v0.2).

func NewScrollView

func NewScrollView(child Widget) *ScrollView

NewScrollView builds a ScrollView around child. Call SetContentSize after construction to declare the child's logical extent so the thumb is sized correctly + scrolling is clamped.

func (*ScrollView) Draw

func (s *ScrollView) Draw(p painter.Painter, theme *Theme)

Draw paints the child clipped to the viewport, then the scrollbar track + thumb on the right edge.

func (*ScrollView) HitTest

func (s *ScrollView) HitTest(px, py int) bool

HitTest covers the full bounds (the scrollbar is interactive too).

func (*ScrollView) Scroll

func (s *ScrollView) Scroll(dx, dy int)

Scroll mutates the offsets by (dx, dy) and clamps to [0, contentSize - viewportSize] so the thumb never falls off the track. Negative offsets are clamped to 0.

func (*ScrollView) SetContentSize

func (s *ScrollView) SetContentSize(w, h int)

SetContentSize tells the ScrollView how big the child's logical drawing area is. Used by Scroll() to clamp + by Draw() to size the thumb. Caller is responsible for invoking this when the child grows / shrinks.

type Selection

type Selection struct {
	StartLine, StartCol int
	EndLine, EndCol     int
}

Selection is a (start, end) range of TextView positions. Positions are (line, col) pairs in rune coordinates -- same model the TextView cursor uses. Start <= End in canonical order; SelectionRange normalises any (anchor, cursor) pair the caller hands it.

Selection is pure data; the TextView holds one + uses it to drive painting + range-delete + clipboard ops.

func SelectionRange

func SelectionRange(anchorLine, anchorCol, cursorLine, cursorCol int) Selection

SelectionRange returns a canonical Selection from an anchor + a cursor: whichever pair is "earlier" in document order becomes the start.

func (Selection) IsEmpty

func (s Selection) IsEmpty() bool

IsEmpty reports whether the selection covers zero characters.

type SpinButton

type SpinButton struct {
	Base
	Min, Max int
	Value    int
	Step     int
	OnChange func(v int)
}

SpinButton is an integer input with `+` and `−` buttons on the right. Click `+` adds Step, click `−` subtracts Step (clamped to [Min, Max]). The value is rendered as a decimal string in the left portion of the body.

func NewSpinButton

func NewSpinButton(min, max, initial, step int) *SpinButton

NewSpinButton builds a SpinButton spanning [min, max] with the given initial + step. Step <= 0 is clamped to 1 so clicks never no-op silently.

func (*SpinButton) Draw

func (s *SpinButton) Draw(p painter.Painter, theme *Theme)

Draw paints the body (with the value text) + the two stacked buttons on the right.

func (*SpinButton) OnEvent

func (s *SpinButton) OnEvent(ev Event)

OnEvent: click on the upper-right button increments; click on the lower-right button decrements.

func (*SpinButton) SetValue

func (s *SpinButton) SetValue(v int)

SetValue clamps + assigns.

type Spinner

type Spinner struct {
	Base
	Active bool
	Phase  float64 // 0..1, full cycle
}

Spinner is an indeterminate loading indicator. When Active, Draw paints a "clock hand" from the centre of bounds rotated by Phase * 2π in Theme.Accent. Caller drives Phase via Tick(dt) so the animation cadence stays tied to the host's frame loop (no goroutine, no timer).

func NewSpinner

func NewSpinner() *Spinner

NewSpinner builds a Spinner stopped at Phase=0.

func (*Spinner) Draw

func (s *Spinner) Draw(p painter.Painter, theme *Theme)

Draw paints the rotating hand when Active. Hand length = 40% of the smaller of (W, H); painted as a thin radial line of single pixels from centre outward.

func (*Spinner) Tick

func (s *Spinner) Tick(deltaSeconds float64)

Tick advances Phase by deltaSeconds, wrapping modulo 1 so the value stays bounded.

type Stack

type Stack struct {
	Base
	Pages   map[string]Widget
	Visible string
}

Stack holds N named pages (Widgets) but shows only ONE at a time -- the page named by Visible. Use AddPage / SetVisible to navigate. Events route to the visible page only.

Suitable for application "screens" (settings vs main vs about), wizard steps, or anywhere the user expects a CLEAN swap with no transition.

func NewStack

func NewStack() *Stack

NewStack builds an empty Stack with no pages + no visible name.

func (*Stack) AddPage

func (s *Stack) AddPage(name string, w Widget)

AddPage registers a page under name. If this is the first page, it auto-becomes Visible so an unconfigured Stack still draws something.

func (*Stack) Draw

func (s *Stack) Draw(p painter.Painter, theme *Theme)

Draw paints only the visible page.

func (*Stack) OnEvent

func (s *Stack) OnEvent(ev Event)

OnEvent routes to the visible page.

func (*Stack) SetBounds

func (s *Stack) SetBounds(r Rect)

SetBounds also propagates to the visible page so it fills the Stack's rect. Other pages have stale bounds until SetVisible brings them forward -- they re-bound at draw time.

func (*Stack) SetVisible

func (s *Stack) SetVisible(name string)

SetVisible swaps the showing page. Names not in Pages are silently ignored so the caller can SetVisible blind.

type Statusbar

type Statusbar struct {
	Base
	Segments []string

	// SegmentMinW is the minimum width any non-last segment takes. The
	// last segment ALWAYS fills the rest of the bar.
	SegmentMinW int // default StatusbarSegmentMinW
}

Statusbar is a thin horizontal strip at the bottom of a window that shows N text segments (e.g. "Line 12, Col 4" + "UTF-8" + "Plain text" in an editor). Segments paint left-to-right with a 1-pixel divider between them; the LAST segment expands to fill any remaining width so an empty Statusbar still looks deliberate.

Statusbar is the natural pairing for MenuBar + Toolbar above and a document area in the middle — together they assemble the "stock GTK" window frame.

func NewStatusbar

func NewStatusbar(segs []string) *Statusbar

NewStatusbar builds a Statusbar with the given segments.

func (*Statusbar) Draw

func (s *Statusbar) Draw(p painter.Painter, theme *Theme)

Draw paints the strip + every segment.

func (*Statusbar) SetSegment

func (s *Statusbar) SetSegment(i int, text string)

SetSegment replaces the i-th segment in place. Indexes out of range are appended (filling intermediate slots with "") so callers can grow the bar lazily.

type Steps added in v0.7.0

type Steps struct {
	Base
	Labels  []string
	Current int
}

Steps is a horizontal step indicator — [1]—[2]—[3]—[4] — for multi-step flows (a wizard, an on-boarding tour, a checkout page). Each entry is drawn as a small square badge carrying its 1-based index number, with a 1-px connector line between successive badges. A Labels entry that is not "" renders below its badge as caption text in Theme.OnBackground.

Current is the 0-indexed cursor into Labels; badges up to AND including Current fill with Theme.Accent (the "done / active" colour), later badges fill with Theme.SurfaceAlt (the "pending" colour). A Current outside [0, len(Labels)) means either "no step active yet" (Current < 0 -> every badge is pending) or "all done" (Current >= len -> every badge is filled).

Steps is a passive display container: hit-testing / event routing are not implemented — a caller who needs a click-to-jump interaction walks the same layout math externally.

func NewSteps added in v0.7.0

func NewSteps(labels []string, current int) *Steps

NewSteps constructs a Steps indicator with the given labels + the initial current-step cursor.

func (*Steps) Draw added in v0.7.0

func (s *Steps) Draw(p painter.Painter, theme *Theme)

Draw paints each badge, its connector to the previous badge (if any) and the optional caption below it. The badge fill switches from Accent (index <= Current) to SurfaceAlt (index > Current); the number ink inverts accordingly so it stays legible.

type Switch added in v0.7.0

type Switch struct {
	Base
	On       bool
	OnToggle func(on bool)
}

Switch is a compact iOS-style toggle: a wide horizontal track with a small square knob that sits on the left when Off and on the right when On. Distinct from ToggleButton in shape + intent: ToggleButton is a full-face button whose entire body flips colour with state, so it reads as "an action that stays pressed"; Switch is decorative chrome — a settings-row indicator whose knob position is the entire affordance ("is this feature on?").

Track fill flips between SurfaceAlt (Off) and Accent (On) so the on-state stands out at a glance; the knob is drawn in Surface with a Border stroke so it stays visible against either track colour.

Click flips On + fires OnToggle. Non-click events are ignored.

func NewSwitch added in v0.7.0

func NewSwitch(on bool) *Switch

NewSwitch constructs a Switch with the given initial state. The OnToggle callback is nil by default; assign it after construction if the caller wants a click hook.

func (*Switch) Draw added in v0.7.0

func (s *Switch) Draw(p painter.Painter, theme *Theme)

Draw paints the track + knob. Track colour is picked by On; the knob slides between left + right edges by rewriting knobX in the On branch. Zero-height or extremely narrow Bounds degrade to a no-op via fillRect's own dimension guard.

func (*Switch) OnEvent added in v0.7.0

func (s *Switch) OnEvent(ev Event)

OnEvent flips On + fires OnToggle on click. All other event kinds pass through without effect (matches ToggleButton / CheckButton).

type Table added in v0.7.0

type Table struct {
	Base
	// Columns are the header cells (title + optional pixel width).
	// A zero Width means "auto" -- the column claims an equal share of
	// whatever pixel budget is left after the fixed-Width columns.
	Columns []TableColumn
	// Rows is the body content. Each inner slice SHOULD have
	// len == len(Columns); rows shorter than that render only the
	// cells they carry (missing trailing cells are drawn as blank
	// space, the row background still paints edge-to-edge).
	Rows [][]string
	// Selected is the 0-indexed row highlighted with Theme.Accent;
	// -1 (or any out-of-range value) means "no selection" and the
	// zebra stripe pattern paints unmodified.
	Selected int
}

Table renders a structured data grid: a fixed header row of column titles above a body of text rows. The widget is the missing piece vs GTK's ColumnView + DaisyUI's Table -- the toolkit's ListBox + TreeView give a single column of items, whereas Table lays cells out horizontally under labelled columns.

Visual (per row):

+----------------+--------+-------------+
| Header A       | Hdr B  | Header C    |  <- TableHeaderHeight, SurfaceAlt
+----------------+--------+-------------+
| row 0 cell 0   | 0.1    | 0.2         |  <- TableRowHeight, Surface
| row 1 cell 0   | 1.1    | 1.2         |  <- TableRowHeight, Background
| ...
+----------------+--------+-------------+

Selected row (if 0 <= Selected < len(Rows)) paints in Theme.Accent with the accent-inverted ink -- theme.Extra["OnAccent"] when the GTK loader supplied one, otherwise theme.Background (the same fallback the Button + ListBox + TreeView selected states already use, so the visual reads consistent across widgets).

The widget is content-only: no per-cell events, no header sorting, no column drag-resize. A future revision layered on top may add them; the MVP is a passive spreadsheet-shaped viewer.

func NewTable added in v0.7.0

func NewTable(cols []TableColumn, rows [][]string) *Table

NewTable builds a Table with the given columns + rows. Selected starts at -1 (no row selected) so a freshly constructed Table renders with plain zebra striping.

func (*Table) Draw added in v0.7.0

func (t *Table) Draw(p painter.Painter, theme *Theme)

Draw paints the header + body + column separators through p using theme's palette. Widths for auto columns are computed here, so resizing the widget's Bounds() between frames re-flows the columns automatically.

type TableColumn added in v0.7.0

type TableColumn struct {
	Title string
	Width int // pixels; 0 = auto (equal share of remaining space)
}

TableColumn is one column definition: a header title + an optional fixed pixel Width. A Width of 0 marks the column as "auto" -- its width is computed at Draw time by evenly dividing the remaining pixel budget among all auto columns.

type TextView

type TextView struct {
	Base
	Lines      []string
	CursorLine int
	CursorCol  int
	Focused    bool
	OnChange   func()

	// Selection is the (start, end) range the host paints highlighted
	// + range-deletes via DeleteSelection / cut+paste via
	// CopySelection / CutSelection / Paste. An empty selection (Start
	// == End) means "no selection"; HasSelection() is the convenience
	// predicate.
	Selection Selection

	// Composition holds the in-progress IME preview string (dead-key
	// output, CJK candidate, …). Non-empty while an IME composition
	// is active; cleared on EventCompositionEnd. The Draw method
	// paints it in a muted colour at the cursor position — the
	// preview is NOT part of the buffer until the host commits via
	// EventChar. Widgets that read Lines/Text() see only committed
	// text, so downstream logic (search, syntax, autosave) never
	// operates on half-formed input.
	Composition string
}

TextView is the multi-line cousin of Entry. Lines are stored as a []string (one element per visible line); Cursor is a (line, col) position in rune coordinates. Wraps Entry's keyboard model with an added vertical axis (ArrowUp / ArrowDown / PageUp / PageDown).

This is the foundation a native wasmdesk editor builds on top of: syntax highlighting, search/replace and find can live above TextView without it growing those concerns. v0.3 ships the raw buffer; v0.4 will add a SelectionStart/End pair for range ops.

func NewTextView

func NewTextView(initial string) *TextView

NewTextView builds a TextView pre-loaded with initial text (split on "\n"). Empty initial text creates a single empty line so the cursor always has a row to live on.

func (*TextView) ClearSelection

func (t *TextView) ClearSelection()

ClearSelection collapses the selection to (CursorLine, CursorCol).

func (*TextView) CopySelection

func (t *TextView) CopySelection() string

CopySelection returns the selected text + leaves the buffer untouched. Wired to a host clipboard via the host (the toolkit has no global clipboard).

func (*TextView) CutSelection

func (t *TextView) CutSelection() string

CutSelection returns the selected text + removes it from the buffer.

func (*TextView) DeleteSelection

func (t *TextView) DeleteSelection()

DeleteSelection removes the selected text + parks the cursor at the deletion point. No-op when the selection is empty.

func (*TextView) Draw

func (t *TextView) Draw(p painter.Painter, theme *Theme)

Draw paints border + fill + every visible line + (when Focused) a 1-px vertical cursor stroke at the cursor's screen position.

Lines that would render past the bottom of the bounds are painted-but-clipped by the raster helpers; wrap in a ScrollView for proper scrollable behaviour.

func (*TextView) HasSelection

func (t *TextView) HasSelection() bool

HasSelection reports whether the TextView's selection covers > 0 characters.

func (*TextView) OnEvent

func (t *TextView) OnEvent(ev Event)

OnEvent dispatches the editing operations.

func (*TextView) Paste

func (t *TextView) Paste(text string)

Paste inserts text at the cursor (after first deleting the selection if any). "\n" splits lines.

func (*TextView) SelectAll

func (t *TextView) SelectAll()

SelectAll selects the entire buffer + parks the cursor at its end.

func (*TextView) SelectionText

func (t *TextView) SelectionText() string

SelectionText returns the selected substring, or "".

func (*TextView) SetSelection

func (t *TextView) SetSelection(sel Selection)

SetSelection records a new (start, end) selection without moving the cursor.

func (*TextView) SetText

func (t *TextView) SetText(s string)

SetText replaces the entire buffer + parks the cursor at (0,0).

func (*TextView) Text

func (t *TextView) Text() string

Text returns the buffer's concatenated content with "\n" line terminators. Mirrors strings.Join(Lines, "\n").

type Theme

type Theme struct {
	Background   RGBA
	Surface      RGBA
	SurfaceAlt   RGBA
	OnBackground RGBA
	OnSurface    RGBA
	Accent       RGBA
	Border       RGBA

	// Extra holds @define-color entries from GTK-source themes that don't
	// map to one of the canonical fields above (headerbar_bg_color,
	// success_color, ...). Populated by LoadGTKTheme; nil for code-built
	// themes. A host that needs a custom colour (e.g. for its window-
	// decoration painter) looks it up here without growing this struct
	// for every GTK colour name in the wild.
	Extra map[string]RGBA
}

Theme bundles every visual constant a widget needs to render itself. One Theme value cascades through every widget in an app, so swapping to a dark / Aqua / Fluxbox theme is a single assignment.

Field naming follows Material/Fluxbox conventions:

  • Background = the surface a widget sits on (panel/window body)
  • Surface = the widget's own filled body (button face, ...)
  • SurfaceAlt = a contrasting tone (hovered button, alternating row)
  • OnBackground / OnSurface = ink/text on those grounds
  • Accent = focus rings, the active-tab underline, the link colour
  • Border = a thin separator line drawn around or between surface regions

func DefaultDark

func DefaultDark() *Theme

DefaultDark is a low-contrast dark theme. Same shape as DefaultLight; used by themed wasmaqua apps + test coverage.

func DefaultLight

func DefaultLight() *Theme

DefaultLight is a low-stakes light theme used by tests + as the fall-through when an app doesn't supply its own. Numbers are the Fluxbox Light palette wasmbox's dock already uses, so a widget dropped into the dock without an explicit theme renders cleanly.

func LoadGTKTheme

func LoadGTKTheme(css string) (*Theme, error)

LoadGTKTheme parses a GTK theme source (the gtk.css or gtk-3.0/gtk.css or gtk-4.0/gtk.css that ships with a libadwaita / GTK3 theme) and returns a Theme that mirrors the theme's palette.

We recognise BOTH the GTK3 names (theme_bg_color / theme_fg_color / …) AND the libadwaita / GTK4 names (window_bg_color / accent_bg_color / …); when both are present the GTK4 name wins because it is the newer convention and a theme that defines both intends the GTK4 name as canonical. Unknown @define-color declarations are kept in the returned Theme's Extra map so themes that ship custom color names (e.g. "headerbar_bg_color" for a window-decoration painter) can still be looked up by a host without growing the canonical Theme struct.

Anything beyond @define-color (selectors, properties, gradients, image references) is ignored — the toolkit is a flat-paint compositor that only consumes solid RGBA values. We do not implement a full CSS parser for the same reason.

The mapping from GTK names to toolkit Theme fields:

GTK4 (preferred)         | GTK3 (fallback)          | Theme field
-------------------------|--------------------------|--------------
window_bg_color          | theme_bg_color           | Background
window_fg_color          | theme_fg_color           | OnBackground
view_bg_color            | theme_base_color         | Surface
view_fg_color            | theme_text_color         | OnSurface
card_bg_color            | insensitive_bg_color     | SurfaceAlt
accent_bg_color          | theme_selected_bg_color  | Accent
borders                  | borders                  | Border

Returns an error only if the input is empty (defensively) — malformed declarations are skipped, not fatal, so a real-world gtk.css with a stray syntax error still yields the rest of its palette.

type ToggleButton

type ToggleButton struct {
	Base
	Label    string
	Pressed  bool
	OnToggle func(pressed bool)
}

ToggleButton is a Button with a sticky on/off state. Click flips Pressed + fires OnToggle. Pressed = Theme.Accent face, unpressed = Theme.Surface; the label is rendered centered in the button.

func NewToggleButton

func NewToggleButton(label string, pressed bool) *ToggleButton

NewToggleButton constructs a ToggleButton with the given label + initial state.

func (*ToggleButton) Draw

func (t *ToggleButton) Draw(p painter.Painter, theme *Theme)

Draw paints the face + border + centred label.

func (*ToggleButton) OnEvent

func (t *ToggleButton) OnEvent(ev Event)

OnEvent: click flips Pressed + fires OnToggle.

type Toolbar

type Toolbar struct {
	Base
	Items   []ToolbarItem
	ButtonW int // default ToolbarButtonW
	ButtonH int // default ToolbarButtonH
	// contains filtered or unexported fields
}

Toolbar is a horizontal strip of square icon-buttons + optional separators. Each entry has a Label (used as the fallback glyph character), an optional Icon (drawn as an RGBA blit when non-empty), an OnClick callback + a Disabled flag.

Toolbar is the icon-strip that sits below a MenuBar; it composes cleanly with both Notebook + Statusbar so a "stock GTK" window can be assembled out of MenuBar + Toolbar + Notebook + Statusbar.

func NewToolbar

func NewToolbar(items []ToolbarItem) *Toolbar

NewToolbar builds a Toolbar with the given items.

func (*Toolbar) Draw

func (t *Toolbar) Draw(p painter.Painter, theme *Theme)

Draw paints the toolbar strip.

func (*Toolbar) OnEvent

func (t *Toolbar) OnEvent(ev Event)

OnEvent dispatches click events to the matching item.

type ToolbarItem

type ToolbarItem struct {
	Label    string
	Icon     []byte // optional ButtonW x ButtonH RGBA; nil = draw Label initial
	OnClick  func()
	Disabled bool

	// Separator, when true, draws a 1-pixel vertical divider instead of
	// a button. Label/Icon/OnClick are ignored.
	Separator bool
}

ToolbarItem is one cell in a Toolbar.

type Tooltip

type Tooltip struct {
	Base
	Text    string
	Visible bool
	Anchor  Rect // widget the tooltip belongs to; positions below it
}

Tooltip is a small text bubble shown near the cursor when the user hovers over a target widget. The host app drives Visible + Anchor (typically toggled by a mouse-enter/leave handler with a 500 ms delay); the toolkit's role is the rendering geometry.

Auto-sized to the Text width + 8 px horizontal padding + 6 px vertical padding; appears just below/right of (Anchor.X, Anchor.Y).

func NewTooltip

func NewTooltip(text string) *Tooltip

NewTooltip builds a hidden tooltip with the given text.

func (*Tooltip) Draw

func (t *Tooltip) Draw(p painter.Painter, theme *Theme)

Draw paints the bubble when Visible.

func (*Tooltip) Hide

func (t *Tooltip) Hide()

Hide removes the tooltip from view.

func (*Tooltip) Show

func (t *Tooltip) Show(anchor Rect)

Show makes the tooltip visible, anchored to the given widget rect.

type TreeNode

type TreeNode struct {
	Label    string
	Expanded bool
	Children []*TreeNode

	// Anything the host wants to associate with this node (typically a
	// path, an id, or the model object). The toolkit doesn't read it.
	Data any
}

TreeNode is one entry in a TreeView. Children are nested arbitrarily deep; Expanded controls whether the children are rendered.

type TreeView

type TreeView struct {
	Base
	Root       *TreeNode
	Selected   *TreeNode
	OnActivate func(node *TreeNode)
	RowHeight  int // default 18
	// contains filtered or unexported fields
}

TreeView renders a hierarchical TreeNode set as indented rows. Click on a row's ▶/▼ chevron toggles Expanded; click anywhere else on the row selects it + fires OnActivate with the clicked node.

Use for file browsers, settings hierarchies, JSON inspectors, outline views.

func NewTreeView

func NewTreeView(root *TreeNode) *TreeView

NewTreeView builds a TreeView rooted at root (which may be nil for an empty initial view).

func (*TreeView) Draw

func (t *TreeView) Draw(p painter.Painter, theme *Theme)

Draw paints every visible row.

func (*TreeView) OnEvent

func (t *TreeView) OnEvent(ev Event)

OnEvent: a click on the chevron toggles Expanded; a click anywhere else on the row selects the node + fires OnActivate.

type VBox

type VBox struct {
	Base
	// Spacing is the gap in pixels between adjacent children; same
	// semantics as HBox.Spacing.
	Spacing int
	// contains filtered or unexported fields
}

VBox is the vertical analogue of HBox: children stack top-to-bottom, sharing the container's height + filling its width.

func NewVBox

func NewVBox() *VBox

NewVBox constructs an empty VBox.

func (*VBox) Append

func (v *VBox) Append(w Widget)

Append adds w below any existing children + re-runs layout.

func (*VBox) Draw

func (v *VBox) Draw(p painter.Painter, theme *Theme)

Draw paints every child in append order.

func (*VBox) OnEvent

func (v *VBox) OnEvent(ev Event)

OnEvent hit-tests + forwards just like HBox.

func (*VBox) SetBounds

func (v *VBox) SetBounds(r Rect)

SetBounds positions the VBox + stacks its children vertically.

type Widget

type Widget interface {
	// Bounds returns the widget's placement within its parent surface.
	// Used by containers for hit-testing + relative-coordinate translation.
	Bounds() Rect

	// SetBounds updates the placement. Containers call this during
	// layout to position children.
	SetBounds(r Rect)

	// Draw paints the widget onto the Painter using the supplied
	// theme. The Painter's back-end decides whether the primitives
	// land as pixels (browser canvas, native window, image file) or
	// cells (terminal grid). Widgets MUST NOT draw outside their
	// Bounds() rectangle.
	Draw(p painter.Painter, theme *Theme)

	// HitTest reports whether (px, py) (in surface coordinates) falls
	// on a sensitive part of the widget. Most widgets just return
	// Bounds().Contains(px, py); transparent or overlapping widgets
	// may return false even within their bounds.
	HitTest(px, py int) bool

	// OnEvent delivers an input event whose X/Y are WIDGET-LOCAL.
	// The widget mutates its internal state + may schedule a redraw
	// (the caller is responsible for invoking Draw again).
	OnEvent(ev Event)
}

Widget is the toolkit's single core abstraction. Every widget -- Button, Label, TextInput, HBox, ScrollView, ... -- implements it. Containers themselves are widgets too: a VBox passes Draw / OnEvent to its children after offsetting coordinates by the child's Rect.

Jump to

Keyboard shortcuts

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