toolkit

package module
v0.50.0 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: BSD-3-Clause Imports: 15 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 bridge: core widgets implement an opt-in Accessible interface (A11y() A11yInfo) so a host can CollectA11y and 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 broad primitive set — buttons, inputs, containers, feedback, overlays, structural rows, semantic banners, data displays — around 8 kLoC of widget code with double that in tests.

Status

v0.9 — GTK 4 / DaisyUI parity pass. Three additive waves closed the coverage gap versus libadwaita and DaisyUI 4: v0.7 shipped 9 core widgets (Switch, Badge, Kbd, Alert, Card, Breadcrumbs, Steps, HeaderBar, Table), v0.8 shipped 12 overlays and structural rows (Avatar, Skeleton, Rating, Toast, Banner, Popover, ActionRow, ViewSwitcher, ChatBubble, SearchEntry, Diff, Pagination), v0.9 shipped 8 finishing widgets (SplitButton, IconButton, Stat, Timeline, DropZone, Chip, FormField, ProgressCircle).

62 widgets + 10 stock icons, ~8k LoC of widget code, 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 + anti-aliased NewTrueTypeFont, global SetFont + per-widget Base.Font, DrawText, TextWidth, Label
Action Button, ToggleButton, CheckButton, RadioButton + Group
Input Entry, TextView + Selection + IME preview, SpinButton, Scale, RangeSlider
Selection ListBox, TreeView, DropDown
Layout HBox, VBox, Grid, Frame, Stack, Overlay, Paned, Expander
Tabs Notebook
Scroll ScrollView
Feedback ProgressBar, LevelBar, Spinner, Image, Tooltip, Notification
Navigation Menu + MenuItem.Shortcut, MenuBar + Alt+letter, ContextMenu, Dialog, MessageDialog
Bars Toolbar, Statusbar, 10 stock DrawIcon* helpers
Composite FileChooser, ColorChooser, FontChooser, Calendar, DatePicker, MarkdownView, MarkdownEditor
Charts LineChart, BarChart, PieChart
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.9 — 8 widgets: SplitButton (button + attached dropdown arrow), IconButton (toolbar-icon variant), Stat (KPI card with trend indicator), Timeline (vertical event log), DropZone (dashed file drop target), Chip (removable tag), FormField (Label + Child + Help/Error), ProgressCircle (approximated circular progress).
  • v0.8 — 12 widgets: Avatar, Skeleton (Text/Avatar/Block kinds), Rating, Toast (transient bottom-of-screen), Banner (persistent full-width), Popover (Visible container for a Child), ActionRow (libadwaita Title/Subtitle/Prefix/Suffix), ViewSwitcher (segmented tab picker), ChatBubble (User/Other bubble), SearchEntry (Entry with prefix + clear), Diff (Context/Added/Removed colored lines), Pagination (prev/numbers/next with disabled ink).
  • v0.7 — 9 widgets: Switch (iOS-style toggle distinct from ToggleButton), Badge (auto-sizing pill), Kbd (keyboard-shortcut chip), Alert (Info/Success/Warning/Error semantic banner), Card (Title/Body/Footer three-zone), Breadcrumbs (chevron path), Steps (numbered indicator with connector), HeaderBar (Start/Title/ Subtitle/End GTK CSD), Table (Columns/Rows/Selected data grid).
  • 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.
  • 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 (v1.0 sketch)

Widget coverage is now materially complete versus GTK 4 + DaisyUI 4. The remaining pre-1.0 work is around the edges of the widget model, not the widget catalogue:

  • Font family plumbingdone (v0.20): a Font interface (Advance, Height, Draw) + a scalable built-in bitmap font. SetFont(NewBitmapFont(2)) doubles all text ("retina"), and every widget re-lays-out because metrics are now read at draw time. Breaking: GlyphHeight / GlyphAdvance and the metric-derived dimensions (CardHeaderH, DatePickerFieldH, DiffLineH, FormFieldLabelH, TimelineEventH, CardFooterH) are now functions — append () at call sites when upgrading. Extended since with anti-aliased vector text (NewTrueTypeFont(ttf, px)) and, from v0.34, a per-widget font: set widget.Font (the exported Base.Font field) to give one widget its own face/size — e.g. a 10px badge next to a 22px title — while every other widget keeps the global SetFont font. A widget with no Font set (the default, nil) renders exactly as before.
  • First-class drag-and-drop event kindsdone (v0.16): EventDragStart / EventDragMove / EventDragLeave / EventDrop, plus a DragSource / DropTarget interface pair and SplitDropPayload / JoinDropPayload for multi-item payloads. DropZone dropped its synthetic-EventChar seam and now drives its hover cue + OnDrop from the formal lifecycle as a DropTarget.
  • Context menu helperdone (v0.17): ContextMenu wraps a Menu as a right-click popup — Popup(x, y) shows it at the cursor, it auto-sizes to its items, clamps itself inside the surface, and dismisses on outside-click.
  • Overlay layout containerdone (v0.18): Overlay stacks z-ordered Layers above a primary Content child, so Popover / Toast / Notification / Tooltip / ContextMenu float without hosts arranging z-order. Events route top-down; Modal makes a miss swallow (backdrop) instead of falling through.
  • A11y bridgedone (v0.19): an opt-in Accessible interface (A11y() A11yInfo — role + name + value) implemented by the core widgets (Button/Label/Entry/CheckButton/RadioButton/ Switch/Scale), plus CollectA11y([]Widget) so a host republishes them to the platform layer (WAI-ARIA on wasm, TTY 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

Examples

Constants

View Source
const (
	// ActionRowPadX is the horizontal inset for the title / subtitle
	// text from the row's left edge (or from the prefix slot when a
	// Prefix widget is present).
	ActionRowPadX = 12
	// ActionRowPadY is the vertical inset for the title above the
	// row's top edge; the subtitle flows below the title.
	ActionRowPadY = 8
	// ActionRowSubtitleGap is the extra vertical gap between the title
	// glyph row and the subtitle glyph row.
	ActionRowSubtitleGap = 2
	// ActionRowSlotW is the fixed width of the Prefix / Suffix slots.
	ActionRowSlotW = 32
)

Sizing constants. PadX / PadY inset text from the row edges; SubtitleGap is the vertical gap between the title's baseline and the subtitle's first row; SlotW is the width reserved for the optional Prefix / Suffix child widget slots.

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 (
	BannerPadX       = 12
	BannerPadY       = 8
	BannerButtonPadX = 8
)

Banner sizing constants. BannerPadX/PadY are the internal margin between the strip edges and the text; BannerButtonPadX is the inner horizontal inset between the button label and its border box.

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
	// 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 (
	// ChatBubblePadX is the horizontal inset between the bubble edge
	// and the text.
	ChatBubblePadX = 10
	// ChatBubblePadY is the vertical inset above the first text line
	// and below the last.
	ChatBubblePadY = 6
	// ChatBubbleMaxW caps the bubble's rendered width in pixels so a
	// pathologically long line stays inside a reasonable column.
	ChatBubbleMaxW = 220
	// ChatBubbleLineSpacing is the extra vertical gap between two
	// text lines in a multi-line bubble.
	ChatBubbleLineSpacing = 2
)

Sizing constants for the bubble geometry.

View Source
const (
	ChipPadX     = 8
	ChipPadY     = 2
	ChipCloseW   = 12
	ChipCloseGap = 4
)

Chip sizing constants. PadX / PadY are the inner insets from the pill edge to the Text glyphs (kept small so a row of chips reads as compact tags); CloseW is the pixel width of the "x" click slot at the right edge; CloseGap is the pixel gap between the Text and the close slot when Closable is true.

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

Sizing.

View Source
const (
	ColorPickerSquareSize  = 120
	ColorPickerHueStripW   = 18
	ColorPickerGap         = 8
	ColorPickerAlphaH      = 16
	ColorPickerSwatchSize  = 28
	ColorPickerEyedropSize = 20

	// ColorPickerWidth and ColorPickerHeight are the picker's natural
	// (unclamped) footprint -- convenient for a host's layout pass.
	ColorPickerWidth  = ColorPickerSquareSize + ColorPickerGap + ColorPickerHueStripW
	ColorPickerHeight = ColorPickerSquareSize + ColorPickerGap + ColorPickerAlphaH + ColorPickerGap + ColorPickerSwatchSize
)

Sizing. The SV square and hue strip share a height; the alpha slider spans their combined width beneath them; the swatch + eyedropper button sit in a final row.

View Source
const (
	DropZonePadX    = 12
	DropZonePadY    = 12
	DropZoneDashLen = 4
	DropZoneDashGap = 4
	DropZoneBorderW = 2
)

DropZone sizing constants. PadX / PadY are the outer insets from the bounds to where inner content (the prompt text) sits; DashLen + DashGap describe the stripe pattern of the dashed border, and BorderW is its per-edge pixel thickness. Kept generous so the dashed rectangle reads as a container, not a thin outline.

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

Sizing constants.

View Source
const (
	// FontChooserPad is the panel's inner inset.
	FontChooserPad = 4
	// FontChooserRowPad is the vertical padding above+below each row's glyphs.
	FontChooserRowPad = 3
)

FontChooser sizing.

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: the strip's thickness (its height for a Top/Bottom strip, its width for a Left/Right strip) and each tab's extent along the strip.

View Source
const (
	NotificationPadX = 12
	NotificationPadY = 8
	NotificationLife = 180
	// NotificationMargin is the gap between a corner-anchored notification
	// and the host edges.
	NotificationMargin = 12
)

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 (
	PopoverPadX    = 8
	PopoverPadY    = 6
	PopoverBorderR = 1
)

Popover sizing constants. PopoverPadX / PopoverPadY are the inner margin between the Popover's outer edge and the child's frame; PopoverBorderR is the border stroke width, matching what strokeRect paints so a host that lays out around the popover can budget the right number of pixels for the frame.

View Source
const (
	// RatingStarW is the per-cell edge in pixels.
	RatingStarW = 14
	// RatingStarGap is the horizontal spacing between two successive
	// cells (pixels of surface visible between them).
	RatingStarGap = 2
)

Rating sizing constants. Cells are square so the strip reads as a row of tiles; the small gap keeps them visually distinct without eating layout width.

View Source
const (
	// SkeletonLineH is the pixel height of a single SkeletonText bar.
	SkeletonLineH = 10
	// SkeletonLineGap is the vertical gap between two SkeletonText bars.
	SkeletonLineGap = 6
	// SkeletonLinePad is the inset applied to SkeletonBlock so the fill
	// stops shy of the Bounds edge — matches Card's body pad.
	SkeletonLinePad = 4
)

Skeleton sizing constants. Values chosen to line up with the toolkit's GlyphHeight() so a SkeletonText row visually replaces a row of body text without shifting the surrounding layout.

View Source
const (
	// StatPadX is the horizontal inset between the border and the
	// left edge of the Title / Value / Change text.
	StatPadX = 12
	// StatPadY is the vertical inset between the top border and the
	// first row of Title text (and between the last row of Change
	// text and the bottom border).
	StatPadY = 8
	// StatTitleGap is the vertical space inserted between the Title
	// row's bottom and the Value row's top.
	StatTitleGap = 4
	// StatValueGap is the vertical space inserted between the Value
	// row's bottom and the Change row's top.
	StatValueGap = 4
)

Stat sizing constants. Padding matches Alert (12, 8) so a Stat composes cleanly next to an Alert banner; the two gap constants keep the three text rows readable at 5x7 glyphs without the tall vertical footprint of a full Card.

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 (
	// TimelineMarkerW is the reserved horizontal column width for
	// the rail + marker before the event's text begins.
	TimelineMarkerW = 12
	// TimelineMarkerSize is the pixel side of each event's filled
	// square marker painted on the rail.
	TimelineMarkerSize = 6
	// TimelineDetailGap is the vertical space inserted between an
	// event's Title row and its Detail row when Detail != "".
	TimelineDetailGap = 2
	// TimelinePadX is the horizontal inset between the widget's
	// left edge and the rail's marker column.
	TimelinePadX = 8
	// TimelinePadY is the vertical inset between the widget's top
	// edge and the first event row (and between the last event row
	// and the bottom edge).
	TimelinePadY = 8
)

Timeline sizing constants. Marker column is 12 px wide, the marker itself 6 px so it sits centred on the rail with a 3-px gutter either side; event rows are one glyph plus a 4-px vertical spacer so successive titles don't touch, and Detail rows sit 2 px below their Title with a matching glyph height.

View Source
const (
	ToastPadX = 10
	ToastPadY = 6
	// ToastMargin is the gap between a corner-anchored toast and the host
	// edges; ToastGap is the vertical space between stacked toasts.
	ToastMargin = 12
	ToastGap    = 6
)

ToastPadX / ToastPadY are the internal margin between the pill edges and the text. Slightly tighter than Notification's 12/8 so several stacked pills read as a compact column.

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 (
	// ViewSwitcherH is the default vertical extent in pixels.
	ViewSwitcherH = 32
	// ViewSwitcherPadX is the horizontal padding at the strip's left
	// and right edges. Reserved for future asymmetric layouts; the
	// current segment layout divides the full width evenly.
	ViewSwitcherPadX = 12
)

Sizing constants for the strip's default vertical extent and its horizontal end padding. ViewSwitcher's Draw does not require Bounds.H == ViewSwitcherH; the constant is exposed so callers building a HeaderBar-like layout can allocate a matching strip.

View Source
const (
	// WizardStripH is the pixel height of the top Steps strip.
	WizardStripH = 40
	// WizardButtonRowH is the pixel height of the bottom Back/Next/
	// Finish button row.
	WizardButtonRowH = 32
	// WizardButtonW is the pixel width of each Back/Next/Finish button.
	WizardButtonW = 90
	// WizardButtonGap is the horizontal gap between a button and the
	// Wizard's edge (Back hugs the left edge, Next/Finish the right).
	WizardButtonGap = 8
)

Wizard geometry constants. The strip + button row are fixed-height bands pinned to the top/bottom edges; the Body of the active step fills whatever is left between them (mirroring Notebook's strip-plus-body split in notebook.go).

View Source
const AvatarSize = 32

AvatarSize is the default square dimension in pixels when Bounds() is zero-sized. Matches the 32-px avatar most GTK / Material chat rows use so an Avatar drops naturally next to a Label without extra layout.

View Source
const BarGutter = 1

BarGutter is the horizontal gap (painter units) between adjacent bars.

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 ChartPad = 6

ChartPad is the margin (painter units) reserved for the axes on the left and bottom edges of a chart's plot area.

View Source
const ContextMenuMinW = 96

ContextMenuMinW is the floor on a context menu's width so a menu of very short labels still reads as a panel.

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 DiffPadX = 4

DiffPadX is the horizontal padding between the widget's outer border and the leading prefix glyph.

View Source
const DiffPadY = 2

DiffPadY is the vertical padding above the first line and below the last line.

View Source
const DropPayloadSep = "\n"

DropPayloadSep separates individual items within a multi-item drag payload.

View Source
const ExpanderHeaderH = 24

ExpanderHeaderH is the pixel height of the clickable header row.

View Source
const FormFieldChildGap = 4

FormFieldChildGap is the vertical gap in pixels between the bottom of the label row and the top of the composed Child widget.

View Source
const FormFieldHelpGap = 2

FormFieldHelpGap is the vertical gap in pixels between the bottom of the Child widget and the top of the help / error caption row.

View Source
const FormFieldPadX = 0

FormFieldPadX is the horizontal padding applied on both sides of the FormField body. Kept at 0 by default: a form is expected to live inside a container (VBox, Card, ...) that supplies its own margin. Callers that need extra breathing room can wrap the field in a Card.

View Source
const FormFieldPadY = 4

FormFieldPadY is the vertical padding applied at the top + bottom of the FormField body. Small: keeps a stack of fields compact without having every caller compute inter-field spacing.

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 IconButtonSize = 28

IconButtonSize is the default square dimension in pixels when Bounds() is zero-sized. Matches the 28-px toolbar icon buttons GTK / Aqua headers use so an IconButton drops naturally next to a Label or a Button without extra layout maths.

View Source
const ListRowDragPrefix = "listrow:"

ListRowDragPrefix is the payload scheme ListBox's drag-to-reorder gesture uses: DragData returns ListRowDragPrefix followed by the pressed row's decimal index, and AcceptsDrop only recognizes payloads carrying this prefix -- so a foreign payload (say, a file path offered to a DropZone) is never mistaken for a reorder drag.

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 MenuCheckGutterW = 14

MenuCheckGutterW is the pixel width of the left-hand gutter that holds a checkable/radio row's ✓ or • glyph. A Menu only reserves this gutter (shifting every row's label right) when at least one of its Items is checkable or belongs to a radio group; a Menu with no such items lays out exactly as before this feature (label starts at the plain 8px inset), so plain menus render unchanged.

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 PaginationBtnH = 24

PaginationBtnH is the pixel height of each button.

View Source
const PaginationBtnW = 28

PaginationBtnW is the pixel width of each button (prev, next, and every page number).

View Source
const PaginationGap = 2

PaginationGap is the horizontal pixel gap between successive buttons.

View Source
const PaletteMinW = 240

PaletteMinW is the floor on the panel width so a palette of short labels still reads as a dialog.

View Source
const PalettePadX = 8

PalettePadX is the horizontal padding between the panel border and its text content.

View Source
const PaletteRowH = 18

PaletteRowH is the pixel height of every row (the query row and each result 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 ProgressCircleSize = 40

ProgressCircleSize is the default side-length in pixels of a ProgressCircle rendered with a zero-sized Bounds. Roughly matches the "large" circular-progress indicator in Material / Adwaita dashboards; small enough to sit next to a status label yet big enough for the "XX%" caption to read on the toolkit's 5x7 font.

View Source
const ProgressCircleStroke = 4

ProgressCircleStroke is the ring thickness in pixels: the offset between the outer track square and the inner "hole" that carries the percentage caption. A thicker stroke leaves less room for the text; 4px keeps a two-digit percentage centred inside the ring with pixels to spare on either side.

View Source
const SearchEntryIconW = 16

SearchEntryIconW is the pixel width reserved for the leading prefix glyph and the trailing clear affordance. Both slots share the same width so hit-testing stays symmetric.

View Source
const SearchEntryPadX = 4

SearchEntryPadX is the horizontal padding between the widget's outer border and the inner content (the search prefix, the text field, the clear affordance).

View Source
const SplitButtonArrowW = 20

SplitButtonArrowW is the pixel width of the arrow slot on the right edge when Arrow is true. Sized to comfortably fit the 5x7 arrow glyph plus symmetric padding on either side.

View Source
const SplitButtonPadX = 12

SplitButtonPadX is the horizontal padding a caller should reserve on either side of the label when positioning a sibling widget flush with the main slot's inner edge. The label itself is rendered centred; this constant is exported for external layout code that wants to align against the same inset.

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.

View Source
const TreeTableHeaderHeight = 24

TreeTableHeaderHeight is the pixel height of the header row.

View Source
const TreeTableRowHeight = 22

TreeTableRowHeight is the pixel height of one body row.

Variables

This section is empty.

Functions

func CardFooterH added in v0.7.0

func CardFooterH() int

CardFooterH is the height of the footer strip when Footer != "".

func CardHeaderH added in v0.7.0

func CardHeaderH() int

CardHeaderH is the height of the header strip when Title != "" (a function, as it derives from the active font's GlyphHeight).

func ClipboardText added in v0.42.0

func ClipboardText() string

ClipboardText returns the active clipboard's contents. Convenience shorthand for CurrentClipboard().ClipboardText().

func DatePickerFieldH added in v0.11.0

func DatePickerFieldH() int

DatePickerFieldH is the pixel height of the closed field.

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 DiffLineH added in v0.8.0

func DiffLineH() int

DiffLineH is the vertical stride between successive lines: one glyph tall plus two pixels of separation.

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, using the active font (see SetFont). It is a thin wrapper over the active Font's Draw so every widget's text rendering follows a font swap.

func EaseInCubic added in v0.35.0

func EaseInCubic(t float64) float64

EaseInCubic starts slow and accelerates towards the end (t^3), more pronounced than EaseInQuad.

func EaseInOutCubic added in v0.35.0

func EaseInOutCubic(t float64) float64

EaseInOutCubic accelerates through the first half and decelerates through the second half, using EaseInCubic then EaseOutCubic symmetrically, more pronounced than EaseInOutQuad.

func EaseInOutQuad added in v0.35.0

func EaseInOutQuad(t float64) float64

EaseInOutQuad accelerates through the first half and decelerates through the second half, using EaseInQuad then EaseOutQuad symmetrically.

func EaseInQuad added in v0.35.0

func EaseInQuad(t float64) float64

EaseInQuad starts slow and accelerates towards the end (t^2).

func EaseOutCubic added in v0.35.0

func EaseOutCubic(t float64) float64

EaseOutCubic starts fast and decelerates towards the end (1-(1-t)^3), more pronounced than EaseOutQuad.

func EaseOutQuad added in v0.35.0

func EaseOutQuad(t float64) float64

EaseOutQuad starts fast and decelerates towards the end (1-(1-t)^2).

func FormFieldLabelH added in v0.9.0

func FormFieldLabelH() int

FormFieldLabelH is the height in pixels of the label row drawn at the top of a FormField. One glyph row plus 2px of breathing space keeps the label snug against the input beneath it without touching the glyph's descender pixels.

func GlyphAdvance

func GlyphAdvance() int

GlyphAdvance is the active font's horizontal step from one glyph to the next.

func GlyphHeight

func GlyphHeight() int

GlyphHeight is the active font's glyph box height. It is a function (not a const) so widgets re-read it after SetFont; layout dimensions that derive from it are likewise functions.

func JoinDropPayload added in v0.16.0

func JoinDropPayload(items []string) string

JoinDropPayload builds a multi-item payload string from items, the inverse of SplitDropPayload — a host uses it to package several dragged paths into one Event.Code.

func Linear added in v0.35.0

func Linear(t float64) float64

Linear returns t unchanged: constant velocity from start to end.

func RenderImage added in v0.39.0

func RenderImage(w Widget, width, height int, theme *Theme) (*image.RGBA, error)

RenderImage is the toolkit's headless "screenshot" path: it renders any Widget into an in-memory *image.RGBA instead of a live pixel surface. The same Draw call a window or wasmbox surface would trigger runs here against a throwaway PixelPainter, so a widget doesn't need to know it's being captured rather than displayed. Callers use this to save a widget's appearance to disk, embed it in a generated report, or assert on pixels in a test.

width and height must both be positive; a widget with zero (or negative) extent has no pixels to capture. On success the returned image is exactly width x height, with Background painted first so any pixel the widget itself doesn't touch (e.g. outside its own drawn shape) still comes out as the theme's canvas colour rather than transparent black.

func RenderPNG added in v0.39.0

func RenderPNG(w Widget, width, height int, theme *Theme) ([]byte, error)

RenderPNG renders w exactly as RenderImage does, then encodes the result as a PNG. It is the one-call path from a widget to bytes suitable for os.WriteFile, an HTTP response body, or embedding in a document.

png.Encode into an in-memory bytes.Buffer does not fail in practice (it has no I/O to fail on), so its error is simply propagated rather than wrapped — the only error this function can realistically surface is RenderImage's dimension check.

Encode runs as its own statement (not inlined into the return) so buf is fully populated before buf.Bytes() is evaluated: Go evaluates a return statement's operands left to right, and inlining would capture buf.Bytes() while the buffer was still empty.

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 SetClipboard added in v0.42.0

func SetClipboard(c Clipboard)

SetClipboard installs c as the toolkit-wide active clipboard. Every widget's copy/cut/paste operation goes through c from this point on. Passing nil restores the default in-memory clipboard.

func SetClipboardText added in v0.42.0

func SetClipboardText(s string)

SetClipboardText replaces the active clipboard's contents. Convenience shorthand for CurrentClipboard().SetClipboardText(s).

func SetFont added in v0.20.0

func SetFont(f Font)

SetFont makes f the active font. A nil f restores the built-in default. All subsequent layout (GlyphHeight / GlyphAdvance) and DrawText use it.

Example

ExampleSetFont swaps the active font; every widget re-lays-out against the new metrics. NewBitmapFont(2) doubles the built-in bitmap ("retina" text).

package main

import (
	"fmt"

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

func main() {
	fmt.Println(toolkit.GlyphHeight()) // default 5×7 bitmap
	toolkit.SetFont(toolkit.NewBitmapFont(2))
	fmt.Println(toolkit.GlyphHeight()) // doubled
	toolkit.SetFont(nil)               // restore the default
	fmt.Println(toolkit.GlyphHeight())
}
Output:
7
14
7

func SetTextDirection added in v0.43.0

func SetTextDirection(d TextDirection)

SetTextDirection makes d the base direction used by DrawText and the font Draw paths when they reorder logical text to visual order. It affects only how mixed / right-to-left text is arranged; all-LTR text is untouched under the default DirLTR.

func SplitDropPayload added in v0.16.0

func SplitDropPayload(code string) []string

SplitDropPayload splits a drop payload (as carried in Event.Code) into its individual items, dropping empty entries so a trailing separator or an empty payload yields no phantom items.

func TextWidth

func TextWidth(text string) int

TextWidth returns the pixel width that DrawText would occupy if it rendered text in the active font. It defers to the active font's Measure so a proportional font (see NewTrueTypeFont) reports its true rendered width; the built-in bitmap font is monospace, so it still equals len(text)*GlyphAdvance.

func TimelineEventH added in v0.9.0

func TimelineEventH() int

TimelineEventH is the vertical stride from one event's Title row to the next when the event has NO Detail — one glyph row plus 4 px of inter-event spacing. A function, as it derives from the active font's GlyphHeight.

func Validate added in v0.42.0

func Validate(value string, rules ...Rule) error

Validate runs rules against value in order and returns the first non-nil error. It returns nil when every rule passes, including when rules is empty.

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 A11yInfo added in v0.19.0

type A11yInfo struct {
	Role  Role
	Name  string
	Value string
}

A11yInfo is a widget's accessibility description. Name is the accessible name (its label/caption); Value is the current value where meaningful (a textbox's text, a checkbox's "checked"/"" state, a slider's number).

func CollectA11y added in v0.19.0

func CollectA11y(widgets []Widget) []A11yInfo

CollectA11y returns the A11yInfo for every widget in the slice that implements Accessible, preserving order. A host owns its widget tree, so it passes the flat list it composed; widgets that don't implement Accessible are skipped.

Example

ExampleCollectA11y walks a host-composed widget list and returns each Accessible widget's role, name, and value for a screen-reader bridge.

package main

import (
	"fmt"

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

func main() {
	widgets := []toolkit.Widget{
		toolkit.NewButton("Save", nil),
		toolkit.NewCheckButton("Wrap", true),
	}
	for _, info := range toolkit.CollectA11y(widgets) {
		fmt.Printf("%s %q %q\n", info.Role, info.Name, info.Value)
	}
}
Output:
button "Save" ""
checkbox "Wrap" "checked"

type Accessible added in v0.19.0

type Accessible interface {
	Widget
	A11y() A11yInfo
}

Accessible is implemented by widgets that expose accessibility metadata.

type Accordion added in v0.35.0

type Accordion struct {
	Base
	Sections []AccordionSection
	Expanded int
	Multiple bool
	// contains filtered or unexported fields
}

Accordion is a vertical stack of AccordionHeaderH-tall header rows, each owning a collapsible body below it. By default the sections are mutually exclusive (exclusive-accordion behaviour): clicking a header expands it + collapses any other expanded section. Setting Multiple lets every header toggle independently instead.

Expanded (used when Multiple is false) is the index of the single expanded section, or -1 when every section is collapsed. In Multiple mode Expanded is ignored + each section's open/closed state is tracked independently.

The remaining vertical space below all headers (Bounds().H minus every header's height) is shared evenly among the currently expanded sections, mirroring how Expander gives its Content the full remaining space when there is only one section.

func NewAccordion added in v0.35.0

func NewAccordion(sections []AccordionSection) *Accordion

NewAccordion builds an Accordion with every section collapsed.

func (*Accordion) A11y added in v0.40.0

func (a *Accordion) A11y() A11yInfo

A11y reports the Accordion as a group carrying the titles of every currently-expanded section (one in single mode, zero or more in Multiple mode), joined together.

func (*Accordion) Draw added in v0.35.0

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

Draw paints every section's header (title + disclosure chevron) + the body of every currently expanded section, clipped to its share of the remaining space.

func (*Accordion) OnEvent added in v0.35.0

func (a *Accordion) OnEvent(ev Event)

OnEvent: a click on header i toggles it (see toggle); a click inside an expanded section's body forwards to that Body via translated coordinates. Anything else (non-click events, or a click that lands on neither a header nor an open body) is a no-op.

type AccordionSection added in v0.35.0

type AccordionSection struct {
	Title string
	Body  Widget
}

AccordionSection is one titled, collapsible slot in an Accordion: a header row showing Title + a body Widget shown only while the section is expanded.

type ActionRow added in v0.8.0

type ActionRow struct {
	Base
	Title    string
	Subtitle string
	Prefix   Widget // optional left slot; nil = no prefix drawn
	Suffix   Widget // optional right slot; nil = no suffix drawn
}

ActionRow is a libadwaita-style structured list row: a large Title with an optional dim Subtitle, plus optional Prefix and Suffix widget slots on the left and right edges. Composes into a settings- style list: stack several ActionRows in a VBox and each row reads as one entry.

The row paints a Theme.Surface body with a 1-pixel Theme.Border divider along its bottom edge (the classic GTK list-row separator). Prefix / Suffix widget slots are fixed-width strips at the left and right; the Title (and, when non-empty, the Subtitle) flows in the remaining central column.

ActionRow forwards EventClick events to whichever child (Prefix or Suffix) the click's X coordinate lands on. Clicks in the central text region are ignored — a caller wanting an activatable row wraps the ActionRow's Bounds in a container that intercepts clicks or overlays a button in the Suffix slot.

func NewActionRow added in v0.8.0

func NewActionRow(title string) *ActionRow

NewActionRow constructs an ActionRow with the given title. Subtitle starts empty; Prefix and Suffix start nil. The caller may assign them before the first Draw.

func (*ActionRow) A11y added in v0.40.0

func (a *ActionRow) A11y() A11yInfo

A11y reports the ActionRow as a group named by its title.

func (*ActionRow) Draw added in v0.8.0

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

Draw paints the row body + bottom divider, then positions + draws the optional Prefix / Suffix child widgets, then paints the title (and, when non-empty, the subtitle) in the remaining central column. Positioning side effect: Prefix / Suffix widgets have their Bounds updated to reflect their slot rectangle inside the row.

func (*ActionRow) OnEvent added in v0.8.0

func (a *ActionRow) OnEvent(ev Event)

OnEvent forwards EventClick to whichever Prefix / Suffix slot the click's X coordinate lands on, translating X into the child's widget-local space. Clicks in the central text region — or clicks on a slot whose child is nil — are ignored. Non-click events are dropped so keyboard input intended for a focused inner widget is not misrouted; a caller that needs richer keyboard routing wraps the ActionRow in its own dispatcher.

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) A11y added in v0.40.0

func (a *Alert) A11y() A11yInfo

A11y reports the Alert as an alert named by its message.

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 Align added in v0.26.0

type Align int

Align is a widget's horizontal text alignment within its bounds. The zero value is AlignLeft, so an unset Align keeps the original left-aligned layout.

const (
	// AlignLeft anchors text to the left edge (the default).
	AlignLeft Align = iota
	// AlignCenter centres text horizontally.
	AlignCenter
	// AlignRight anchors text to the right edge.
	AlignRight
)

type Avatar added in v0.8.0

type Avatar struct {
	Base
	Initials string
	// Color is the body fill. Leave at the zero RGBA to fall through to
	// Theme.Accent (the theme-tracking default); set to any opaque RGBA
	// to pin the avatar to a per-user tint.
	Color RGBA
}

Avatar renders a user identity chip: a rounded-square body filled in a solid colour with the user's Initials centred inside it in the accent-inverted ink. Colour resolves to Theme.Accent when the caller-supplied Color is the zero RGBA (a natural default that respects the theme); a caller wanting a per-user tint sets Color to any opaque RGBA and it will be honoured verbatim.

The rounded shape is faked by clipping the four corner pixels — the same three-band recipe Badge uses to look like a pill without touching a curve primitive. This keeps Avatar allocation-free and portable to every Painter back-end (PixelPainter, CellPainter, SvgPainter).

Auto-sizing: if Bounds().W is zero the first Draw() resizes the avatar to AvatarSize x AvatarSize (or AvatarSize x preserved-H when H is non-zero). A pre-sized Bounds is honoured verbatim so a fixed layout column doesn't shift when the widget is dropped in.

Avatar is passive: it displays and does not respond to input. The parent view is responsible for positioning it (typically top-left of a message row or the leading edge of a menu item).

func NewAvatar added in v0.8.0

func NewAvatar(initials string) *Avatar

NewAvatar constructs an Avatar carrying the given initials. Bounds default to zero so the first Draw() auto-sizes the widget to AvatarSize x AvatarSize. Color defaults to the zero RGBA so the body tracks Theme.Accent unless the caller pins it.

func (*Avatar) A11y added in v0.40.0

func (a *Avatar) A11y() A11yInfo

A11y reports the Avatar as an img named by its initials.

func (*Avatar) Draw added in v0.8.0

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

Draw paints the rounded-square body then centres Initials on top. If Bounds().W is zero the widget resizes itself to AvatarSize x AvatarSize (H preserved when already non-zero) before painting.

Body colour is Color when non-zero, otherwise Theme.Accent. Ink is accentInk(theme) so a GTK-loaded theme's OnAccent override is honoured with a fall-through to Theme.Background — the same rule Table + Button use for their accent-face branches.

type Badge added in v0.7.0

type Badge struct {
	Base
	Text string
	Fill RGBA // pill body colour; zero (A==0) => Theme.Accent
	Ink  RGBA // text colour; zero (A==0) => Theme.Background
}

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 Fill (Theme.Accent by default) with the ink in Ink (Theme.Background by default) 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.

Per-badge colour: Fill overrides the pill body colour and Ink the text colour. Both default to the zero RGBA, in which case Draw falls back to Theme.Accent / Theme.Background — so a plain NewBadge keeps the theme look, while a caller that needs a categorical colour (a per-source tag, a severity chip, ...) sets Fill/Ink without having to hand-draw its own pill. A fully-transparent colour (A==0) is treated as "unset"; callers wanting a see-through badge is not a use case the widget serves.

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) A11y added in v0.40.0

func (b *Badge) A11y() A11yInfo

A11y reports the Badge as a status region named by its 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 body is a full rounded-rect painted through the painter's FillRoundRect (radius = half the shorter side, so short pills read as a stadium and tall ones as a circle). Back-ends that cannot round (a cell grid) degrade to a square fill. Fill/Ink override the body/text colours; an unset (transparent) colour falls back to the theme.

type Banner struct {
	Base
	Text        string
	ButtonLabel string
	Revealed    bool
	OnAction    func()
	Icon        func(p painter.Painter, r Rect, ink RGBA)
}

Banner is a full-width persistent inline message strip, modelled on GTK 4's AdwBanner. Distinct from Alert (persistent, coloured by severity) in two ways:

  1. Banner is REVEAL-driven: Revealed toggles the whole strip on and off, letting the host wire dismiss and re-show without dropping the widget from the tree.
  2. Banner carries an optional right-aligned action button; a click inside the button fires OnAction. Alert has no interactive slot.

The banner paints in Theme.Accent so it reads as a system message rather than a semantic-severity Alert; the action button is drawn as a bordered box in the accent-inverted ink so it stays legible.

An optional leading Icon lets the host prefix the message with a glyph (a padlock for a sign-in prompt, a warning triangle, ...). When set, Draw reserves a GlyphHeight square at the leading edge, invokes Icon with that rect + the banner ink, and shifts the Text right past it. Icon is nil by default, leaving the text flush against BannerPadX as before, so existing callers are unaffected.

func NewBanner added in v0.8.0

func NewBanner(text string) *Banner

NewBanner constructs a Banner with the given Text. Revealed starts true so a freshly-constructed banner is visible; ButtonLabel is empty by default (no action slot rendered).

func (*Banner) A11y added in v0.40.0

func (b *Banner) A11y() A11yInfo

A11y reports the Banner as a status region named by its message.

func (*Banner) Draw added in v0.8.0

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

Draw paints the accent-filled strip + the Text ink. A non-nil Icon is drawn first as a leading square inset by BannerPadY top and bottom (so the icon scales with the banner height rather than a fixed font glyph box, keeping it legible on a high-DPI / scaled surface), and the Text is shifted right past it. When ButtonLabel is non-empty an outlined action button is drawn right-aligned inside BannerPadX of the trailing edge. Nothing drawn when !Revealed.

func (*Banner) OnEvent added in v0.8.0

func (b *Banner) OnEvent(ev Event)

OnEvent handles a click inside the action button. Events with a Kind other than EventClick are ignored; a click that falls outside the button rect is dropped; a click on a Banner without an action button (empty ButtonLabel) is dropped; a click with a nil OnAction is dropped silently -- the button is drawable but inert.

type BarChart added in v0.13.0

type BarChart struct {
	Base
	Values []float64
	Max    float64 // top of the Y axis; when <= 0, taken from the data
}

BarChart plots one series of non-negative Values as vertical bars over a left+bottom axis frame -- the categorical companion to LineChart. Bars share the plot width evenly with a 1-unit gutter between them and scale to the tallest value (or an explicit Max). Display-only.

It renders through painter.Painter, so the same chart draws as pixels (WUI/GUI) or promoted cells (TUI). An empty series draws just the axes.

Example

ExampleBarChart plots non-negative values as vertical bars.

package main

import (
	"github.com/go-widgets/painter"
	"github.com/go-widgets/toolkit"
)

// newSurface returns a PixelPainter over a fresh w×h RGBA buffer — the render
// target the examples draw into. A CellPainter would render the same widgets to
// a terminal grid instead.
func newSurface(w, h int) *painter.PixelPainter {
	return painter.NewPixelPainter(make([]byte, 4*w*h), w, h)
}

func main() {
	chart := toolkit.NewBarChart([]float64{4, 7, 2, 8, 5})
	chart.SetBounds(toolkit.Rect{X: 0, Y: 0, W: 200, H: 80})
	chart.Draw(newSurface(200, 80), toolkit.DefaultLight())
}

func NewBarChart added in v0.13.0

func NewBarChart(values []float64) *BarChart

NewBarChart builds a BarChart over the given values with an auto Y max.

func (*BarChart) A11y added in v0.40.0

func (b *BarChart) A11y() A11yInfo

A11y reports the BarChart as an img carrying its bar count.

func (*BarChart) Draw added in v0.13.0

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

Draw paints the axis frame then one Accent bar per value.

type BarSegment added in v0.36.0

type BarSegment struct {
	Value float64
	Fill  RGBA
	Label string
}

BarSegment is one band of a SegmentedBar: a non-negative Value (its share of the whole), the RGBA it paints with, and an optional Label (reserved for a future legend / tooltip — Draw does not render it today).

type Base

type Base struct {

	// Font, when non-nil, overrides the global active font for this widget
	// only. nil means "inherit the active font" (the default).
	Font Font
	// 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.

Font is an optional per-widget font override. When nil (the zero value, the default for every widget) the widget lays out + renders against the package-level active font (see SetFont / CurrentFont), so it behaves exactly as if per-widget fonts did not exist. When set — e.g.

badge.Font, _ = NewTrueTypeFont(myFontTTF, 10)   // small tag
title.Font, _ = NewTrueTypeFont(myFontTTF, 22)   // large heading

that single widget measures + paints its text with that font while every other widget keeps using the global one. Widgets consult it through the font-aware helpers (EffectiveFont / textWidth / drawText / glyphHeight / glyphAdvance) instead of the package-level TextWidth / DrawText / GlyphHeight, so a font swap is scoped to the widget that sets it.

func (*Base) Bounds

func (b *Base) Bounds() Rect

func (*Base) Draw

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

func (*Base) EffectiveFont added in v0.34.0

func (b *Base) EffectiveFont() Font

EffectiveFont is the font this widget renders with: its own Font override if one is set, otherwise the package-level active font (CurrentFont). It never returns nil, so callers can measure/draw through it unconditionally.

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)

func (*Base) SetFont added in v0.34.0

func (b *Base) SetFont(f Font) *Base

SetFont sets this widget's per-widget font override and returns the Base so the call can be chained fluently (b := (&Badge{}).SetFont(f) style). A nil f clears the override, restoring inheritance of the global active font.

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) A11y() A11yInfo

A11y reports the Breadcrumbs as navigation named by its full path.

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()
	Style   ButtonStyle // resting appearance; default is ButtonDefault
	// 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) A11y added in v0.19.0

func (b *Button) A11y() A11yInfo

A11y reports the Button as a button role named by its label.

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 ButtonStyle added in v0.10.0

type ButtonStyle int

ButtonStyle selects a button's resting fill, giving a layout visual hierarchy (macOS "prominent"/default/secondary buttons). Hover + press still override the fill on top of the style.

const (
	// ButtonDefault is a Surface-faced button (the plain look).
	ButtonDefault ButtonStyle = iota
	// ButtonProminent is filled with Accent + accent-foreground text -- the
	// primary/default action (e.g. a calculator's operator keys, "OK").
	ButtonProminent
	// ButtonSecondary is filled with SurfaceAlt -- a muted grey key that sits
	// between Default and Prominent (e.g. a calculator's C / +/- / % keys).
	ButtonSecondary
)

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) A11y added in v0.40.0

func (c *Calendar) A11y() A11yInfo

A11y reports the Calendar as a grid carrying its selected date.

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) A11y added in v0.40.0

func (c *Card) A11y() A11yInfo

A11y reports the Card as a group named by its title.

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 Carousel struct {
	Base
	Slides  []Widget
	Current int
	Wrap    bool
}

Carousel shows one child Widget (a "slide") at a time from Slides, picked by Current. A gutter on each side hosts a ◂ / ▸ arrow affordance for stepping to the previous / next slide, and a row of dot indicators below the content marks the total slide count + the active one. Navigation wraps around the ends when Wrap is set; otherwise it clamps.

Suitable for image galleries, onboarding panels, or featured-content rotators — anywhere a single "card" from a set is shown with an obvious way to step through the rest.

func NewCarousel added in v0.35.0

func NewCarousel(slides []Widget) *Carousel

NewCarousel builds a Carousel over slides, starting at Current = 0 with Wrap = false (clamp at the ends).

func (*Carousel) A11y added in v0.40.0

func (c *Carousel) A11y() A11yInfo

A11y reports the Carousel as a group carrying its "current/total" slide position.

func (*Carousel) Draw added in v0.35.0

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

Draw paints the Current slide clipped to the content rect, the left/right arrow affordances, and the dot indicators. A Carousel with no Slides paints nothing.

func (*Carousel) Next added in v0.35.0

func (c *Carousel) Next()

Next advances Current by one slide. At the last slide it wraps to the first when Wrap is set, otherwise it stays put (clamped). A no-op when Slides is empty.

func (*Carousel) OnEvent added in v0.35.0

func (c *Carousel) OnEvent(ev Event)

OnEvent: a click in the left/right gutter steps Prev/Next; a click on dot i jumps Current to i; a click inside the content rect forwards to the Current slide, translated into its local frame. Non-click events + a Carousel with no Slides are no-ops.

func (*Carousel) Prev added in v0.35.0

func (c *Carousel) Prev()

Prev retreats Current by one slide. At the first slide it wraps to the last when Wrap is set, otherwise it stays put (clamped). A no-op when Slides is empty.

type ChatBubble added in v0.8.0

type ChatBubble struct {
	Base
	Text   string
	Sender ChatSender
}

ChatBubble is a chat-transcript speech bubble: a small rounded rectangle (borrowed shape only — the toolkit's raster stays sharp- cornered) holding a short message string. Multi-line text is supported by splitting Text on '\n'; each line renders on its own glyph row.

Sizing: the bubble grows to fit the widest text line plus 2*PadX, capped at ChatBubbleMaxW so a runaway paste doesn't spill the widget's Bounds. Height is len(lines) * lineH + 2*PadY where lineH = GlyphHeight() + ChatBubbleLineSpacing.

ChatBubble is a passive display widget — it does not intercept input (HitTest / OnEvent stay as Base defaults). A caller that wants a tap-to-copy or long-press-menu bubble wraps this with an outer container that handles the gesture.

func NewChatBubble added in v0.8.0

func NewChatBubble(text string, sender ChatSender) *ChatBubble

NewChatBubble constructs a ChatBubble carrying text sent by sender.

func (*ChatBubble) A11y added in v0.40.0

func (c *ChatBubble) A11y() A11yInfo

A11y reports the ChatBubble as text carrying its message.

func (*ChatBubble) Draw added in v0.8.0

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

Draw paints the bubble: fill in Theme.Accent (user) or SurfaceAlt (other), 1-pixel Theme.Border stroke, and one DrawText per '\n'- separated line in Text. Width is derived from the widest line's TextWidth capped by ChatBubbleMaxW; height is derived from the line count. Position is right-aligned within Bounds() for ChatFromUser and left-aligned for ChatFromOther, mirroring the canonical chat-transcript convention.

type ChatSender added in v0.8.0

type ChatSender int

ChatSender enumerates which side of a chat transcript a ChatBubble belongs to. The two roles paint differently: user messages align to the right of the widget's Bounds in Theme.Accent, other-party messages align to the left in Theme.SurfaceAlt. The distinction is purely visual — the sender does not affect layout otherwise.

const (
	// ChatFromUser marks a message sent by the local user. Bubble
	// right-aligned in Theme.Accent; ink = accent-inverted colour.
	ChatFromUser ChatSender = iota
	// ChatFromOther marks a message from a remote party. Bubble
	// left-aligned in Theme.SurfaceAlt; ink = Theme.OnSurface.
	ChatFromOther
)

type CheckButton

type CheckButton struct {
	Base
	Label    string
	Checked  bool
	Size     int // box side length in px; 0 uses the 12px default
	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) A11y added in v0.19.0

func (c *CheckButton) A11y() A11yInfo

A11y reports the CheckButton as a checkbox with its 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 Chip added in v0.9.0

type Chip struct {
	Base
	Text     string
	Closable bool
	OnClose  func()
}

Chip is a small labelled pill with an optional "x" close affordance. Unlike Badge -- which is a passive counter / status indicator with no interaction surface -- Chip is a removable tag: when Closable is true the widget renders a click target at the right edge that fires OnClose when tapped. Two constructors keep the two personalities distinct at the callsite: NewChip for a passive tag, NewClosableChip for the removable variant.

Auto-sizing follows Badge's convention: if Bounds().W is zero the first Draw() sets W to the text width plus ChipPadX on each side (plus the close slot's ChipCloseGap + ChipCloseW when Closable is true), and H to GlyphHeight() + 2*ChipPadY when it is also zero. A pre-sized Bounds is honoured verbatim so a fixed-width layout row does not shift when the chip label changes.

func NewChip added in v0.9.0

func NewChip(text string) *Chip

NewChip constructs a passive (non-closable) Chip carrying the given Text. OnClose stays nil; the widget ignores clicks. Bounds default to zero so the first Draw() auto-sizes the pill.

func NewClosableChip added in v0.9.0

func NewClosableChip(text string, onClose func()) *Chip

NewClosableChip constructs a Chip whose right edge exposes an "x" close affordance. onClose may be nil (clicks on the affordance become a no-op rather than a panic) so callers can wire the callback after construction without ordering constraints.

func (*Chip) A11y added in v0.40.0

func (c *Chip) A11y() A11yInfo

A11y reports the Chip as a button when it exposes a close affordance (Closable), or as plain text otherwise.

func (*Chip) Draw added in v0.9.0

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

Draw paints the pill body + text + optional close affordance. Auto- sizes Bounds when W is zero. The pill body is a filled SurfaceAlt rectangle stroked with a Border outline; the Text is drawn left- aligned inside the pad, and (when Closable) an "x" glyph in Border colour marks the close slot at the right edge.

func (*Chip) OnEvent added in v0.9.0

func (c *Chip) OnEvent(ev Event)

OnEvent fires OnClose when an EventClick lands in the right-hand close slot and Closable is true. Non-click events, non-closable chips, and clicks outside the slot are ignored. A nil OnClose is treated as a no-op so callers can toggle Closable without wiring a callback in the same statement.

Event coordinates are widget-local (as documented on Event), so the slot's horizontal extent is measured against r.W rather than r.X; no localisation is required at the callsite.

type Clipboard added in v0.42.0

type Clipboard interface {
	// ClipboardText returns the current clipboard contents, or ""
	// when the clipboard is empty or unavailable.
	ClipboardText() string
	// SetClipboardText replaces the clipboard contents.
	SetClipboardText(s string)
}

Clipboard is a back-end-neutral text clipboard shared by every text widget in the toolkit (Entry, TextView, ...). Copy/cut write to it, paste reads from it, so text copied in one widget can be pasted into any other -- including across widget types.

The default implementation is an in-process memory buffer, which is adequate for tests and headless rendering but does not reach the real OS clipboard. A host that wants OS integration implements Clipboard itself -- e.g. the WAI/HTML5 Clipboard API on wasm, an OSC-52 escape sequence written to the TTY, NSPasteboard / the win32 clipboard via cgo -- and installs it once at startup with SetClipboard. From then on every widget's copy/cut/paste goes through the host's implementation transparently.

func CurrentClipboard added in v0.42.0

func CurrentClipboard() Clipboard

CurrentClipboard returns the toolkit-wide active Clipboard.

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) A11y added in v0.40.0

func (c *ColorChooser) A11y() A11yInfo

A11y reports the ColorChooser as a group carrying its current colour as a "#RRGGBB" hex string.

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 ColorPicker added in v0.36.0

type ColorPicker struct {
	Base

	// H is the hue in [0, 360). S and V (saturation, value) are both in
	// [0, 1].
	H, S, V float64

	// Alpha is the opacity channel, independent of the HSV triple.
	Alpha uint8

	// OnChange fires with the new RGBA whenever the SV square, hue strip,
	// or alpha slider changes the colour.
	OnChange func(c RGBA)

	// OnEyedrop fires when the eyedropper affordance is clicked. Actual
	// pixel sampling is the host's responsibility -- see the type doc.
	OnEyedrop func()
	// contains filtered or unexported fields
}

ColorPicker is a rich HSV colour picker: a saturation/value square for the current hue, a vertical hue strip, a horizontal alpha slider (checkerboard under a live transparent-to-opaque gradient of the current colour), a solid preview swatch, and an eyedropper affordance.

Unlike ColorChooser (3 independent R/G/B sliders), ColorPicker keeps its state in HSV -- the natural coordinate system for a 2D saturation/value surface -- and derives the RGBA on demand via Color().

The eyedropper button only *signals intent*: OnEyedrop fires on click, but sampling an actual screen pixel is inherently host-specific (it needs a screenshot/compositor hook the toolkit doesn't have), so that part is the host's job. A typical host response is to enter a "pick" mode, read the pixel under the next click anywhere on screen, and feed it back in via SetColor.

func NewColorPicker added in v0.36.0

func NewColorPicker(initial RGBA) *ColorPicker

NewColorPicker builds a picker seeded from initial, converting its RGB to HSV and carrying its alpha through unchanged.

func (*ColorPicker) A11y added in v0.40.0

func (c *ColorPicker) A11y() A11yInfo

A11y reports the ColorPicker as a group carrying its current colour (derived from the HSV+alpha state) as a "#RRGGBB" hex string.

func (*ColorPicker) Color added in v0.36.0

func (c *ColorPicker) Color() RGBA

Color returns the current HSV + Alpha converted back to RGBA.

func (*ColorPicker) Draw added in v0.36.0

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

Draw paints the SV square, hue strip, alpha slider, swatch + eyedropper button onto the widget's Bounds.

func (*ColorPicker) OnEvent added in v0.36.0

func (c *ColorPicker) OnEvent(ev Event)

OnEvent handles clicks + drags across the SV square, hue strip, and alpha slider (each grabs "active" on EventClick so a subsequent EventMouseDrag keeps moving the same control even after the cursor leaves its rect), and a plain click on the eyedropper button.

func (*ColorPicker) SetColor added in v0.36.0

func (c *ColorPicker) SetColor(rgba RGBA)

SetColor reseeds H/S/V/Alpha from an RGBA -- e.g. the host feeding back an eyedropper sample or a sibling hex Entry's parsed value.

type CommandPalette added in v0.35.0

type CommandPalette struct {
	Base
	Commands  []PaletteCommand
	Query     string
	Selected  int
	Visible   bool
	OnDismiss func()
}

CommandPalette is a centered overlay that combines a search-query input row with a filtered, keyboard-navigable list of commands — the "Ctrl+Shift+P" palette pattern. It layers a SearchEntry-style query field over a ListBox- style result list and, like ContextMenu, catches an outside-click anywhere on its surface to dismiss itself.

The palette's own Bounds is the whole surface it may cover (so it can catch an outside-click anywhere); the panel is measured and centered inside that frame, and incoming event coordinates are in that same surface frame.

Selected always indexes into the FILTERED list (the indices returned by filtered()), never into Commands directly, and is re-clamped after any mutation to Query so it can never point past the end of a shrinking list.

func NewCommandPalette added in v0.35.0

func NewCommandPalette(cmds []PaletteCommand) *CommandPalette

NewCommandPalette builds a hidden CommandPalette over the given commands. Query starts empty and Selected at 0; call Open to show it.

func (*CommandPalette) A11y added in v0.40.0

func (c *CommandPalette) A11y() A11yInfo

A11y reports the CommandPalette as a dialog carrying its typed query.

func (*CommandPalette) Dismiss added in v0.35.0

func (c *CommandPalette) Dismiss()

Dismiss hides the palette and resets its query + selection. It does NOT call OnDismiss itself: OnDismiss is a cancellation signal invoked only by the event handlers that dismiss on user intent (Escape / outside-click), mirroring how ContextMenu keeps activation and cancellation on separate paths.

func (*CommandPalette) Draw added in v0.35.0

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

Draw paints the centered panel when Visible: a query row (the current Query plus a trailing caret marker) followed by one row per filtered command, with the Selected filtered row highlighted in Theme.Accent. Nothing is drawn when hidden. An empty filtered list still renders the panel with just the query row.

func (*CommandPalette) OnEvent added in v0.35.0

func (c *CommandPalette) OnEvent(ev Event)

OnEvent drives the palette while Visible: EventChar appends to Query, Backspace trims it, ArrowUp/ArrowDown move Selected within the filtered list (clamped, no wraparound — matching ListBox/ContextMenu), Enter/row-click runs the selected command then dismisses, and Escape / outside-click dismisses and fires OnDismiss. Events while hidden are ignored.

func (*CommandPalette) Open added in v0.35.0

func (c *CommandPalette) Open()

Open shows the palette, clearing any prior query and selection so it always reopens in a fresh state.

type ContextMenu added in v0.17.0

type ContextMenu struct {
	Base
	Menu             *Menu
	Open             bool
	AnchorX, AnchorY int
}

ContextMenu is a right-click popup: a Menu that appears at an arbitrary point (the cursor), auto-sizes to its items, clamps itself inside the surface so it never spills off an edge, and dismisses when the user clicks outside it. It is the overlay wrapper the widget model was missing around the bare Menu, mirroring how DropDown/DatePicker own their pop-ups.

The ContextMenu's own Bounds is the whole surface it may cover (so it can catch an outside-click anywhere); AnchorX/AnchorY and incoming event coordinates are in that same frame. Call Popup(x, y) to show it at a point.

Example

ExampleContextMenu shows a right-click popup that auto-sizes, clamps inside the surface, and dismisses on an outside click.

package main

import (
	"github.com/go-widgets/painter"
	"github.com/go-widgets/toolkit"
)

// newSurface returns a PixelPainter over a fresh w×h RGBA buffer — the render
// target the examples draw into. A CellPainter would render the same widgets to
// a terminal grid instead.
func newSurface(w, h int) *painter.PixelPainter {
	return painter.NewPixelPainter(make([]byte, 4*w*h), w, h)
}

func main() {
	menu := toolkit.NewMenu([]toolkit.MenuItem{
		{Label: "Cut", Action: func() {}},
		{Label: "Copy", Action: func() {}, Shortcut: "Ctrl+C"},
	})
	cm := toolkit.NewContextMenu(menu)
	cm.SetBounds(toolkit.Rect{X: 0, Y: 0, W: 200, H: 160})
	cm.Popup(8, 8) // open at the cursor
	cm.Draw(newSurface(200, 160), toolkit.DefaultLight())
}

func NewContextMenu added in v0.17.0

func NewContextMenu(menu *Menu) *ContextMenu

NewContextMenu wraps the given Menu as a (closed) context menu.

func (*ContextMenu) A11y added in v0.40.0

func (c *ContextMenu) A11y() A11yInfo

A11y reports the ContextMenu as a menu carrying its open/closed state.

func (*ContextMenu) Close added in v0.17.0

func (c *ContextMenu) Close()

Close hides the menu.

func (*ContextMenu) Draw added in v0.17.0

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

Draw paints the Menu at its clamped bounds when open; nothing when closed.

func (*ContextMenu) MenuBounds added in v0.17.0

func (c *ContextMenu) MenuBounds() Rect

MenuBounds is the rect the Menu occupies: the measured size placed at the anchor, then shifted so it stays fully inside the surface (c.Bounds()).

func (*ContextMenu) OnEvent added in v0.17.0

func (c *ContextMenu) OnEvent(ev Event)

OnEvent routes a click inside the menu to the Menu (translated to its local frame, so the hit row's Action fires and closes the overlay via OnClose); a click anywhere outside dismisses the menu.

func (*ContextMenu) Popup added in v0.17.0

func (c *ContextMenu) Popup(x, y int)

Popup opens the menu anchored at (x, y) and wires the Menu's OnClose so that activating an item (or the menu closing itself) also closes the overlay.

type Corner added in v0.33.0

type Corner int

Corner names one of the six standard docking positions inside a host rectangle, used to anchor transient overlays (Toast, Notification) to a screen edge. TopLeft is the zero value.

The two *Center corners centre the overlay horizontally; the four true corners inset it from the nearer horizontal edge. All six inset from the nearer vertical edge, so top corners stack downward and bottom corners stack upward.

const (
	// TopLeft docks against the top + left edges.
	TopLeft Corner = iota
	// TopRight docks against the top + right edges.
	TopRight
	// BottomLeft docks against the bottom + left edges.
	BottomLeft
	// BottomRight docks against the bottom + right edges.
	BottomRight
	// TopCenter docks against the top edge, horizontally centred.
	TopCenter
	// BottomCenter docks against the bottom edge, horizontally centred.
	BottomCenter
)

type Date added in v0.35.0

type Date struct {
	Y, M, D int
}

Date is a plain (Year, Month, Day) triple — the same time-source-free representation Calendar uses (see calendar.go, which takes year/month/day ints and never touches time.Time). The zero Date{} means "unset": a real selection always has Month in 1..12, so a Month of 0 is a reliable sentinel.

type DatePicker added in v0.11.0

type DatePicker struct {
	Base
	Cal      *Calendar
	Open     bool
	OnChange func(y, m, d int)
}

DatePicker is a form input for a single calendar date: a field showing the selected date as ISO YYYY-MM-DD text with a small grid icon, and a drop-down Calendar that opens beneath it when the field is clicked. Picking a day in the calendar updates the field, closes the popup, and fires OnChange.

Where the display-only Calendar just renders a month, DatePicker is the composite entry control built around it — the pixel sibling of a native date field. It owns its Calendar (exposed as Cal) and renders the popup itself when Open, so it works standalone; a host that composites overlays on a separate surface can instead read Open + PopoverBounds and draw Cal there.

Example

ExampleDatePicker shows a date field with a drop-down calendar.

package main

import (
	"fmt"

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

func main() {
	dp := toolkit.NewDatePicker(2026, 7, 10)
	dp.OnChange = func(y, m, d int) { fmt.Printf("picked %04d-%02d-%02d\n", y, m, d) }
	dp.SetBounds(toolkit.Rect{X: 0, Y: 0, W: 170, H: toolkit.DatePickerFieldH()})
	fmt.Println(dp.Text())
}
Output:
2026-07-10

func NewDatePicker added in v0.11.0

func NewDatePicker(year, month, day int) *DatePicker

NewDatePicker builds a DatePicker initialised to (year, month, day).

func (*DatePicker) A11y added in v0.40.0

func (d *DatePicker) A11y() A11yInfo

A11y reports the DatePicker as a group carrying its selected date.

func (*DatePicker) Date added in v0.11.0

func (dp *DatePicker) Date() (y, m, d int)

Date returns the currently-selected (year, month, day).

func (*DatePicker) Draw added in v0.11.0

func (dp *DatePicker) Draw(p painter.Painter, theme *Theme)

Draw paints the field (border + date text + a grid icon) and, when Open, the Calendar popup positioned by PopoverBounds.

func (*DatePicker) OnEvent added in v0.11.0

func (dp *DatePicker) OnEvent(ev Event)

OnEvent: a click on the field toggles the popup; while open, a click inside the popup is forwarded (translated to Calendar-local coordinates) to the Calendar, whose OnSelect closes the popup and fires OnChange.

func (*DatePicker) PopoverBounds added in v0.11.0

func (dp *DatePicker) PopoverBounds() Rect

PopoverBounds is the Rect the Calendar occupies when Open: same X and full calendar width below the field. Six week-rows is the worst case.

func (*DatePicker) SetDate added in v0.11.0

func (dp *DatePicker) SetDate(year, month, day int)

SetDate moves the selection to (year, month, day) without opening the popup.

func (*DatePicker) Text added in v0.11.0

func (dp *DatePicker) Text() string

Text is the field's displayed value: ISO 8601 YYYY-MM-DD.

type DateRangePicker added in v0.35.0

type DateRangePicker struct {
	Base
	Cal        *Calendar
	Start, End Date
	OnChange   func(start, end Date)
}

DateRangePicker is a month grid on which the user clicks a start day then an end day; the inclusive range between the two is highlighted. Clicking again once a complete range exists begins a fresh selection.

It composes Calendar for all of the month-grid layout + day hit-testing math (via the embedded Cal, whose OnSelect this widget wires to its own selection logic) and adds a header with prev/next-month arrows that Calendar lacks. The range fill uses the theme's SurfaceAlt tone; the two endpoints use Accent.

func NewDateRangePicker added in v0.35.0

func NewDateRangePicker(year, month int) *DateRangePicker

NewDateRangePicker builds a picker displaying (year, month) with no initial selection. The caller positions it with SetBounds; the grid is 7 cells wide.

func (*DateRangePicker) A11y added in v0.40.0

func (d *DateRangePicker) A11y() A11yInfo

A11y reports the DateRangePicker as a group carrying its "start..end" date range.

func (*DateRangePicker) Draw added in v0.35.0

func (rp *DateRangePicker) Draw(p painter.Painter, theme *Theme)

Draw paints the header (prev arrow, month/year, next arrow), the weekday row, and the day grid with range highlighting.

func (*DateRangePicker) OnEvent added in v0.35.0

func (rp *DateRangePicker) OnEvent(ev Event)

OnEvent handles clicks (widget-local coordinates): the header arrows page the month; a day-cell click is forwarded to the embedded Calendar, whose OnSelect drives selectDay.

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) A11y added in v0.40.0

func (d *Dialog) A11y() A11yInfo

A11y reports the Dialog as a dialog named by its title. This also covers NewMessageDialog, which returns a plain *Dialog rather than a distinct type.

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 Diff added in v0.8.0

type Diff struct {
	Base
	Lines []DiffLine
}

Diff renders a coloured, line-by-line unified diff view. Each line carries a kind (context / added / removed); Draw fills the row with a light green tint for added lines, a light red tint for removed lines, and the theme's Surface for context lines. A one-character prefix (' ', '+', '-') anchors the row at the left so the widget stays legible even when its background rows are omitted (as when a caller reuses this on top of a striped background).

The widget is intentionally passive: it exposes no scroll, no selection, and no editing. Host apps that need those wrap Diff in a ScrollView + track selection externally.

func NewDiff added in v0.8.0

func NewDiff(lines []DiffLine) *Diff

NewDiff builds a Diff view over the supplied lines. A nil slice is normalised to a zero-length slice so Draw never has to nil-guard.

func (*Diff) A11y added in v0.40.0

func (d *Diff) A11y() A11yInfo

A11y reports the Diff as a group carrying its line count.

func (*Diff) Draw added in v0.8.0

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

Draw paints the widget body, each row (with its per-kind tint and prefix glyph), and the outer border.

type DiffKind added in v0.8.0

type DiffKind int

DiffKind enumerates the three per-line change categories a unified diff produces.

const (
	// DiffContext marks an unchanged, contextual line — rendered on
	// Theme.Surface with a leading space.
	DiffContext DiffKind = iota
	// DiffAdded marks a line inserted by the change — rendered on a
	// light green tint with a leading '+'.
	DiffAdded
	// DiffRemoved marks a line dropped by the change — rendered on a
	// light red tint with a leading '-'.
	DiffRemoved
)

type DiffLine added in v0.8.0

type DiffLine struct {
	Text string
	Kind DiffKind
}

DiffLine is one row in a Diff view: the raw text plus the change kind that colours it.

type DragSource added in v0.16.0

type DragSource interface {
	Widget
	DragData() string
}

DragSource is a widget a drag can originate from. DragData returns the payload string the host should carry for the drag (e.g. a file path or a row id).

type DropDown struct {
	Base
	Options  []string
	Selected int
	Open     bool
	// OpenUp makes the popover appear ABOVE the control instead of below it —
	// set it when the control sits near the bottom edge so the list has room.
	OpenUp   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) A11y() A11yInfo

A11y reports the DropDown as a combobox named by its currently-selected option.

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, height proportional to the option count (clamped to PopoverMaxRows rows). Positioned just below the widget, or — when OpenUp is set — just above it so a control near the bottom edge still has room for its list.

func (d *DropDown) Select(idx int)

Select picks idx, closes the popover + fires OnSelect.

type DropTarget added in v0.16.0

type DropTarget interface {
	Widget
	AcceptsDrop(payload string) bool
}

DropTarget is a widget that can receive a drop. AcceptsDrop reports whether the given payload is droppable here — a host consults it on EventDragStart to decide whether to show an "accepted" cursor and whether to deliver the later EventDrop.

type DropZone added in v0.9.0

type DropZone struct {
	Base
	Prompt string
	Hover  bool
	OnDrop func(paths []string)
}

DropZone is an inline "drag files here" target rendered as a bordered rectangle with a centred prompt string. It is the passive counterpart of FileChooser: FileChooser opens a modal directory browser, DropZone waits in place for the host to hand it dropped file paths (typically via a native HTML5 drag+drop listener the wasmbox compositor wires to the widget).

The Hover flag toggles the dashed-border colour + surface fill so the user sees drag-over feedback before releasing the drop. DropZone is a DropTarget: it drives Hover from the formal drag lifecycle — EventDragStart / EventDragMove raise it, EventDragLeave clears it, and EventDrop delivers the payload (multiple paths newline-separated, recovered with SplitDropPayload) to OnDrop and clears Hover. As a convenience for demos and tests, EventClick also flips Hover in place.

func NewDropZone added in v0.9.0

func NewDropZone(prompt string) *DropZone

NewDropZone constructs a DropZone with the given prompt text. An empty prompt is replaced with the default "Drop files here" so a zero-argument caller still renders a legible target. Bounds default to zero; the caller positions the DropZone via SetBounds.

func (*DropZone) A11y added in v0.40.0

func (d *DropZone) A11y() A11yInfo

A11y reports the DropZone as a group named by its drop prompt.

func (*DropZone) AcceptsDrop added in v0.16.0

func (d *DropZone) AcceptsDrop(payload string) bool

AcceptsDrop reports whether a payload is droppable here. A DropZone is a generic file target, so it accepts any non-empty payload.

func (*DropZone) Draw added in v0.9.0

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

Draw paints the surface fill, the four dashed edges + the centred prompt text. Fill + border colour swap on Hover so the drag-over state is visible without the caller having to swap in a different widget on drag-enter. Dashes are emitted as short filled rects so the toolkit stays on its two existing raster primitives (fillRect / strokeRect) rather than growing a Painter.DashedLine primitive.

func (*DropZone) OnEvent added in v0.9.0

func (d *DropZone) OnEvent(ev Event)

OnEvent implements the drag lifecycle: EventDragStart / EventDragMove raise Hover, EventDragLeave clears it, and EventDrop fires OnDrop with the payload's items (split from ev.Code) then clears Hover. EventClick flips Hover in place as a demo/test hook. All other event kinds are ignored so a keyboard event bound for a sibling widget does not accidentally trigger a drop.

type Easing added in v0.35.0

type Easing func(t float64) float64

Easing maps a normalized time t in [0, 1] to an eased progress value, typically also in [0, 1] (some easings may overshoot before settling, though none of the named easings below do). Implementations should treat t outside [0, 1] as clamped to the nearest bound.

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)

	// Composition holds the in-progress IME preview string (dead-key
	// output, CJK candidate, …). Non-empty while an IME composition is
	// active; cleared on EventCompositionEnd. Mirrors TextView's field
	// of the same name: the preview is NOT part of Text until the host
	// commits it via EventChar, so Text always reflects only committed
	// input.
	Composition 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) A11y added in v0.19.0

func (e *Entry) A11y() A11yInfo

A11y reports the Entry as a textbox carrying its current text.

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.

func (*Entry) Value added in v0.42.0

func (e *Entry) Value() string

Value returns the entry's current text. It is the accessor FormField.Value uses (via the unexported valueGetter interface) to pull an Entry's contents without depending on the Text field name directly, so a FormField wrapping an Entry can be validated.

type Event

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

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.

Ctrl and Shift report whether those modifier keys were held when the event was produced. They are meaningful on any event kind but most useful on EventClick, where they drive multi-selection (Ctrl-click toggles a row, Shift-click extends a range from the anchor). Hosts that don't track modifiers simply leave them false, which preserves the original single-selection behaviour everywhere.

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
	// EventMouseDrag fires when the mouse moves while a button is
	// still pressed. X/Y carry the current widget-local position.
	// The initial button press was already dispatched as EventClick,
	// so a widget that wants drag semantics remembers "am I being
	// dragged" from the EventClick and consults it on drag ticks.
	EventMouseDrag
	// EventMouseUp fires when the button is released. Widgets that
	// track drag state clear it here. X/Y carry the release
	// position (widget-local).
	EventMouseUp
	// EventDragStart fires on a DropTarget when a drag first enters it
	// (drag-enter). The target typically raises a hover cue. Code
	// carries the drag payload the host is offering, so the target can
	// decide via AcceptsDrop whether to signal acceptance.
	EventDragStart
	// EventDragMove fires as the drag pointer moves while still over the
	// same DropTarget. X/Y carry the widget-local position (for an
	// insertion indicator); Code still carries the payload.
	EventDragMove
	// EventDragLeave fires when the drag pointer exits a DropTarget
	// without dropping. The target clears its hover cue. It completes
	// the enter/move/leave/drop lifecycle so a target never stays stuck
	// in the hover state.
	EventDragLeave
	// EventDrop fires when the drag is released over a DropTarget. Code
	// carries the payload (multiple items newline-separated — see
	// SplitDropPayload); the target consumes it and clears its hover cue.
	EventDrop

	// EventTouchStart fires when a touch point first lands inside the
	// widget. X/Y carry the widget-local touch position. Code carries
	// the touch/pointer id (a host-assigned string, stable for the
	// lifetime of that contact) so a widget — or a GestureRecognizer —
	// can distinguish concurrent contacts in a multi-touch stream even
	// though the toolkit's own GestureRecognizer only tracks one active
	// pointer at a time.
	EventTouchStart
	// EventTouchMove fires as an already-started touch point moves.
	// X/Y carry the current widget-local position; Code carries the
	// same touch/pointer id as the EventTouchStart that began it.
	EventTouchMove
	// EventTouchEnd fires when a touch point is lifted. X/Y carry the
	// widget-local release position; Code carries the same touch/pointer
	// id as the EventTouchStart that began it, so a listener can match
	// the end to its start even if other contacts are interleaved.
	EventTouchEnd
)

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) A11y added in v0.40.0

func (e *Expander) A11y() A11yInfo

A11y reports the Expander as a group named by its label, carrying its expanded/collapsed state.

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) A11y added in v0.40.0

func (f *FileChooser) A11y() A11yInfo

A11y reports the FileChooser as a group named by its root directory, carrying the currently-selected file path as its Value.

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 FocusRing added in v0.35.0

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

FocusRing gives a set of Focusables a single, shared keyboard focus: Next and Prev move it forward/backward through the members, wrapping at both ends; Focus jumps straight to a member (e.g. in response to a click hit- test performed by the caller); HandleKey maps the ring's Tab/Shift+Tab key convention onto Next/Prev for callers that dispatch toolkit.Event directly.

A FocusRing does not implement toolkit.Widget: it neither draws nor receives events on its own, it only supervises which member is focused. The zero value is not usable; construct one with NewFocusRing.

func NewFocusRing added in v0.35.0

func NewFocusRing(items ...Focusable) *FocusRing

NewFocusRing builds a ring over items, focusing the first one (if any).

func (*FocusRing) Add added in v0.35.0

func (r *FocusRing) Add(f Focusable)

Add appends f to the ring. If the ring was empty, f is focused immediately (mirroring NewFocusRing's treatment of the first item).

func (*FocusRing) Clear added in v0.35.0

func (r *FocusRing) Clear()

Clear removes every item from the ring, first defocusing whichever item currently holds focus, and resets the ring to its empty state.

func (*FocusRing) Current added in v0.35.0

func (r *FocusRing) Current() int

Current returns the index of the focused item, or -1 when the ring is empty.

func (*FocusRing) Focus added in v0.35.0

func (r *FocusRing) Focus(i int)

Focus moves focus to item i. Out-of-range i (including any index on an empty ring) is ignored. SetFocused(false) is called on the previously focused item and SetFocused(true) on item i, matching tui.FocusRing.Focus.

func (*FocusRing) Focused added in v0.35.0

func (r *FocusRing) Focused() Focusable

Focused returns the focused item, or nil when the ring is empty.

func (*FocusRing) HandleKey added in v0.35.0

func (r *FocusRing) HandleKey(code string) bool

HandleKey maps a toolkit.EventKeyDown Code — "Tab" or "Shift+Tab" — onto Next/Prev, matching the key convention tui.FocusRing.OnEvent dispatches on. It reports whether the key was consumed so a caller forwards everything else (e.g. Enter, arrow keys) to the focused member itself.

func (*FocusRing) Next added in v0.35.0

func (r *FocusRing) Next()

Next moves focus to the following item, wrapping from the last item to the first. No-op on an empty ring.

func (*FocusRing) Prev added in v0.35.0

func (r *FocusRing) Prev()

Prev moves focus to the preceding item, wrapping from the first item to the last. No-op on an empty ring.

type Focusable added in v0.35.0

type Focusable interface {
	// SetFocused is called by the ring when focus enters (true) or leaves
	// (false) this item.
	SetFocused(focused bool)
}

Focusable is a widget that can hold keyboard focus within a FocusRing.

This is intentionally a minimal surface — narrower than the terminal sibling's tui.Focusable, which embeds toolkit.Widget (Bounds/SetBounds/ Draw/HitTest/OnEvent) because tui's FocusRing also lays out, draws, and routes events to its members on their behalf. This FocusRing does none of that: it only tracks which member holds focus and toggles SetFocused as focus moves. A concrete toolkit widget (Button, Entry, ...) can satisfy Focusable with nothing more than a SetFocused method; its own Draw/OnEvent stay untouched, and wiring a widget's rendering/dispatch into a ring is a later task.

type Font added in v0.20.0

type Font interface {
	Advance() int
	Height() int
	Measure(text string) int
	Draw(p painter.Painter, x, y int, text string, ink RGBA)
}

Font is the toolkit's text metrics + rendering abstraction. Widgets lay themselves out against the ACTIVE font's metrics (via GlyphHeight / GlyphAdvance) and paint text through DrawText, so swapping the active font with SetFont rescales the whole UI's typography without touching any widget.

  • Advance is the horizontal step from one glyph origin to the next.
  • Height is the glyph box height.
  • Measure is the total width text occupies when drawn (proportional fonts sum per-glyph advances; a monospace font returns len*Advance).
  • Draw paints text left-to-right at (x, y) in the given ink.

The built-in bitmap font is monospace (Measure == len*Advance), which keeps grid-aligned layout math trivial. A proportional font (see NewTrueTypeFont) still lays out correctly because widgets size text through Measure/TextWidth rather than assuming a fixed advance.

func CurrentFont added in v0.20.0

func CurrentFont() Font

CurrentFont returns the active font.

func NewBitmapFont added in v0.20.0

func NewBitmapFont(scale int) Font

NewBitmapFont returns the built-in 5x7 font scaled by the given integer factor (clamped to at least 1). SetFont(NewBitmapFont(2)) doubles all text.

func NewTrueTypeFont added in v0.31.0

func NewTrueTypeFont(ttf []byte, sizePx int) (Font, error)

NewTrueTypeFont parses ttf (a TrueType byte blob) and returns a Font that renders it anti-aliased at sizePx pixels. Parse failures are wrapped and returned; on success the face and its metrics are cached for the font's life.

Typical use pairs it with an embedded face, e.g.:

f, err := NewTrueTypeFont(myFontTTF, 16)
if err != nil { /* handle */ }
SetFont(f)

type FontChooser added in v0.21.0

type FontChooser struct {
	Base
	Options  []FontOption
	Selected int
	OnChoose func(idx int, f Font)
}

FontChooser is a vertical picker of fonts: each option's name is drawn in that very font, so the list doubles as a live size/style preview. Clicking a row selects it, applies it as the active font via SetFont, and fires OnChoose. It is the picker the Font interface (v0.20) unblocked — the long-deferred sibling of ColorChooser / FileChooser.

With no options supplied it defaults to three scales of the built-in bitmap font (Regular / Large / Extra Large), so an app gets a working font size picker for free.

func NewFontChooser added in v0.21.0

func NewFontChooser(options []FontOption) *FontChooser

NewFontChooser builds a FontChooser over the given options (defaulting to the built-in scale ladder when none are supplied).

func (*FontChooser) A11y added in v0.40.0

func (f *FontChooser) A11y() A11yInfo

A11y reports the FontChooser as a combobox named by its currently-selected font option.

func (*FontChooser) Draw added in v0.21.0

func (fc *FontChooser) Draw(p painter.Painter, theme *Theme)

Draw paints the panel and each option's name rendered in its own font, the Selected row on an Accent band.

func (*FontChooser) OnEvent added in v0.21.0

func (fc *FontChooser) OnEvent(ev Event)

OnEvent: a click on a row selects it, applies it as the active font (SetFont), and fires OnChoose.

type FontOption added in v0.21.0

type FontOption struct {
	Name string
	Font Font
}

FontOption is one named font in a FontChooser.

type FormField added in v0.9.0

type FormField struct {
	Base
	Label string
	Help  string // optional dim caption below the child
	Error string // optional error caption below the child (takes precedence)
	Child Widget // the actual input; may be nil
	Rules []Rule // optional validation rules run by Validate
}

FormField is a labelled input row: a Label above (in theme.OnBack- ground), an optional Child input widget below, and an optional caption row underneath the Child that shows either an Error (in fixed red) or Help text (in theme.Border for a muted look). Error takes precedence over Help when both are set.

FormField sits directly on theme.Background (it is a form container, not a card) and does not fill its own body — the label glyphs and the composed Child provide their own inks. Callers wanting a filled body can wrap the FormField in a Card.

Child composition: SetBounds on the Child is called during Draw so callers only have to position the FormField itself. OnEvent forwards clicks (and other event kinds' point events) to the Child when (X, Y) falls inside the Child rect, translating coordinates into Child-local space. Non-point events (keyboard) are forwarded unconditionally so the Child can react to focus-driven input.

func NewFormField added in v0.9.0

func NewFormField(label string, child Widget) *FormField

NewFormField constructs a FormField wrapping child with a label above. Help + Error remain empty; the caller assigns them as the field's state changes.

func (*FormField) A11y added in v0.40.0

func (f *FormField) A11y() A11yInfo

A11y reports the FormField as a group named by its label, carrying its error text (if any) as Value.

func (*FormField) Draw added in v0.9.0

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

Draw paints the label row, positions + draws the Child (when non- nil), and paints the caption row (Error > Help > nothing).

func (*FormField) OnEvent added in v0.9.0

func (f *FormField) OnEvent(ev Event)

OnEvent forwards the event to Child when Child is non-nil. Point events (EventClick) are gated on the Child rect so a click outside the input body is dropped; non-point events (keyboard/composition) are forwarded unconditionally so a focused Child sees them. Nil Child is a no-op.

func (*FormField) Validate added in v0.42.0

func (f *FormField) Validate() bool

Validate runs Rules against the field's current Value, in order, stopping at the first failure -- the same short-circuit semantics as the package-level Validate. On failure, Error is set to the failing rule's message and Validate returns false. On success (or when Rules is empty), Error is cleared and Validate returns true.

Validate only ever touches Error; it does not repaint -- callers invoke it (typically from a submit handler or an OnChange callback on Child) and then trigger their own redraw so the caption row picks up the new Error.

func (*FormField) Value added in v0.42.0

func (f *FormField) Value() string

Value returns the current text of the wrapped Child, or "" when Child is nil or does not implement valueGetter.

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) A11y added in v0.40.0

func (f *Frame) A11y() A11yInfo

A11y reports the Frame as a plain grouping container. Frame carries no title text of its own (see the type doc), so Name is always empty -- unlike the other "group" widgets above that surface a label.

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 GestureRecognizer added in v0.39.0

type GestureRecognizer struct {
	// TapSlop is the largest movement (in pixels, on whichever axis moved
	// most) still considered "held still" for tap and long-press purposes.
	TapSlop int
	// LongPressTicks is the number of Tick() calls a touch must be held
	// (without moving past TapSlop) before OnLongPress fires.
	LongPressTicks int
	// SwipeMinDist is the minimum net displacement (in pixels, on the
	// dominant axis) for a release to be recognized as a swipe.
	SwipeMinDist int

	// OnTap fires when a touch starts and ends within TapSlop pixels and
	// wasn't already resolved as a long press. x, y are the release
	// position (widget-local).
	OnTap func(x, y int)
	// OnLongPress fires once a held touch reaches LongPressTicks without
	// moving past TapSlop. x, y are the current (held) position.
	OnLongPress func(x, y int)
	// OnSwipe fires on release when the net displacement reaches
	// SwipeMinDist on its dominant axis.
	OnSwipe func(dir SwipeDir)
	// contains filtered or unexported fields
}

GestureRecognizer turns a stream of EventTouchStart / EventTouchMove / EventTouchEnd events (see widget.go) into higher-level tap, long-press and swipe callbacks. It is pure logic — it does not draw or hold a Widget reference — so any widget (or a host, ahead of dispatch) can embed one.

State machine:

EventTouchStart always (re)arms the recognizer: it becomes "active", remembers the touch/pointer id (Event.Code) and the start position, and resets the hold-tick counter and the long-press-fired flag. Only one touch is tracked at a time — a recognizer is meant to sit behind a single widget's single active contact; multi-touch gestures (pinch, ...) are out of scope and left as future work.

EventTouchMove updates the current position, but only when it carries the id of the active touch; anything else (no active touch, or a different id — e.g. a second finger) is ignored.

Event has no timestamp, so long-press timing is driven by the caller calling Tick() on its own clock (e.g. once per animation frame or per timer tick) instead of wall-clock time. While a touch is active and has not yet moved past TapSlop, each Tick() increments a hold counter; once it reaches LongPressTicks, OnLongPress fires exactly once for that touch and a flag suppresses the Tap that would otherwise fire when the touch is released. Once movement exceeds TapSlop, Tick() stops counting (a long press requires holding still).

EventTouchEnd resolves the gesture from the net displacement between the start and end positions (ignored if the id doesn't match the active touch):

  • if the displacement's largest-axis magnitude is >= SwipeMinDist, a swipe fired along whichever axis moved further, in the direction of travel;
  • otherwise, if the magnitude is <= TapSlop and no long-press already fired for this touch, OnTap fires;
  • anything in between (moved more than a tap, but not far enough for a swipe) resolves to nothing.

func NewGestureRecognizer added in v0.39.0

func NewGestureRecognizer() *GestureRecognizer

NewGestureRecognizer returns a GestureRecognizer with sensible default thresholds (TapSlop=8px, LongPressTicks=30, SwipeMinDist=24px). Callers wanting different behaviour can override any field, or build a GestureRecognizer{} literal directly with their own thresholds — the callbacks and Feed/Tick logic don't depend on how the struct was built.

func (*GestureRecognizer) Feed added in v0.39.0

func (g *GestureRecognizer) Feed(ev Event)

Feed consumes one input event. Only EventTouchStart, EventTouchMove and EventTouchEnd are meaningful to a GestureRecognizer; every other kind is ignored so a host can feed it its full event stream unfiltered.

func (*GestureRecognizer) Tick added in v0.39.0

func (g *GestureRecognizer) Tick()

Tick advances the long-press timer by one caller-driven step. It is a no-op unless a touch is currently active and held within TapSlop of its start position; once the hold reaches LongPressTicks, OnLongPress fires exactly once for that touch (subsequent ticks, and the eventual EventTouchEnd's Tap, are then suppressed for it).

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 (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; each takes a flex share of the width or a fixed width (see boxChild), with Spacing gaps between them. 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.

func NewHBox

func NewHBox() *HBox

NewHBox constructs an empty HBox. Add children via Append/AddFlex/AddFixed.

func (*HBox) AddFixed added in v0.50.0

func (h *HBox) AddFixed(w Widget, size int)

AddFixed adds w with a fixed width in pixels (clamped to ≥0).

func (*HBox) AddFlex added in v0.50.0

func (h *HBox) AddFlex(w Widget, flex int)

AddFlex adds w with an explicit flex weight (clamped to ≥1).

func (*HBox) Append

func (h *HBox) Append(w Widget)

Append adds w with flex weight 1 (an equal share of the width).

func (*HBox) Draw

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

Draw paints every child in append order (the box itself draws nothing).

func (*HBox) OnEvent

func (h *HBox) OnEvent(ev Event)

OnEvent forwards to the first child whose Bounds contains the event point, translated into that child's local space.

func (*HBox) SetBounds

func (h *HBox) SetBounds(r Rect)

SetBounds positions the HBox + lays out its children across the width.

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) A11y added in v0.40.0

func (h *HeaderBar) A11y() A11yInfo

A11y reports the HeaderBar as a banner named by its title.

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 IconButton added in v0.9.0

type IconButton struct {
	Base
	Icon    string
	OnClick func()
}

IconButton is a compact toolbar button whose entire face is one short glyph string ("+", "OK", "v", ...). Distinct from Button (which carries a text label with hover/press states) and ToggleButton (which carries toggle state) — IconButton is a passive Surface-faced tile meant for dense toolbars where the glyph itself is the semantic content.

The face is theme.Surface with a 1-px theme.Border stroke; the glyph renders in theme.OnSurface. No accent fill by default — this keeps the button reading as a subtle toolbar affordance rather than a primary action.

Auto-sizing: if Bounds().W is zero the first Draw() resizes the button to IconButtonSize x IconButtonSize (H preserved when non-zero). A pre-sized Bounds is honoured verbatim so a fixed toolbar column doesn't shift when the widget is dropped in.

func NewIconButton added in v0.9.0

func NewIconButton(icon string, onClick func()) *IconButton

NewIconButton constructs an IconButton carrying the given glyph + click handler. onClick may be nil (a no-op button is still rendered). Bounds default to zero so the first Draw() auto-sizes the widget to IconButtonSize x IconButtonSize.

func (*IconButton) A11y added in v0.40.0

func (b *IconButton) A11y() A11yInfo

A11y reports the IconButton as a button named by its icon identifier (it has no separate text label).

func (*IconButton) Draw added in v0.9.0

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

Draw paints the surface + border and centres Icon inside using the toolkit's 5x7 bitmap font. If Bounds().W is zero the widget resizes itself to IconButtonSize x IconButtonSize (H preserved when already non-zero) before painting.

func (*IconButton) OnEvent added in v0.9.0

func (i *IconButton) OnEvent(ev Event)

OnEvent fires OnClick on EventClick; other event kinds are ignored. OnClick is nil-safe.

type Image

type Image struct {
	Base
	Pixels []byte    // RGBA bytes, W*H*4 in length
	W, H   int       // source dimensions
	Scale  ScaleMode // how the source maps onto the bounds (default ScaleStretch)
}

Image paints a caller-supplied RGBA byte buffer into its bounds. Scaling is nearest-neighbour; Scale selects stretch-to-fill (default) or aspect-preserving fit-and-centre.

func NewImage

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

NewImage wraps pixels (length must equal w*h*4) + the source dimensions in a stretch-to-fill image. Caller owns the pixels; the toolkit just reads them.

func NewImageFit added in v0.45.0

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

NewImageFit is NewImage with ScaleFit: the image preserves its aspect ratio and is centred within its bounds.

func (*Image) A11y added in v0.40.0

func (i *Image) A11y() A11yInfo

A11y reports the Image as an img. Image carries no alt-text field of its own, so Name is left for the host to fill in from context.

func (*Image) Draw

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

Draw paints the image into bounds (or, for ScaleFit, into the aspect-preserving centred sub-rect of 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) A11y added in v0.40.0

func (k *Kbd) A11y() A11yInfo

A11y reports the Kbd as text naming the key combination it renders.

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
	Align Align
}

Label is a passive widget that displays Text in the theme's OnSurface colour, drawn with the toolkit's 5x7 bitmap font. Horizontally aligned per Align (left by default), 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) A11y added in v0.19.0

func (l *Label) A11y() A11yInfo

A11y reports the Label as static 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
	Orientation Orientation
}

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 / VU-meter style indicators. Orientation Horizontal (default) fills left→right; Vertical fills bottom→top.

func NewLevelBar

func NewLevelBar(max int) *LevelBar

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

func (*LevelBar) A11y added in v0.40.0

func (l *LevelBar) A11y() A11yInfo

A11y reports the LevelBar as a meter carrying its "value/max" reading.

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 LineChart added in v0.12.0

type LineChart struct {
	Base
	Series   []float64
	Min, Max float64 // Y bounds; when equal, taken from the data
}

LineChart plots one series of Y values as a polyline over a left+bottom axis frame -- the full-size sibling of the inline Sparkline. Values are spread evenly across the plot width and scaled vertically between Min and Max (auto- derived from the data when Min == Max). Display-only.

It renders through painter.Painter, so the same chart draws as anti-aliased pixels (WUI/GUI) or promoted cells (TUI). A single point renders as a dot; an empty series draws just the axes.

Example

ExampleLineChart plots a series as a polyline over an axis frame.

package main

import (
	"github.com/go-widgets/painter"
	"github.com/go-widgets/toolkit"
)

// newSurface returns a PixelPainter over a fresh w×h RGBA buffer — the render
// target the examples draw into. A CellPainter would render the same widgets to
// a terminal grid instead.
func newSurface(w, h int) *painter.PixelPainter {
	return painter.NewPixelPainter(make([]byte, 4*w*h), w, h)
}

func main() {
	chart := toolkit.NewLineChart([]float64{3, 7, 2, 8, 5, 9, 4})
	chart.SetBounds(toolkit.Rect{X: 0, Y: 0, W: 220, H: 80})
	chart.Draw(newSurface(220, 80), toolkit.DefaultLight())
}

func NewLineChart added in v0.12.0

func NewLineChart(series []float64) *LineChart

NewLineChart builds a LineChart over the given series with auto Y bounds.

func (*LineChart) A11y added in v0.40.0

func (l *LineChart) A11y() A11yInfo

A11y reports the LineChart as an img carrying its data-point count.

func (*LineChart) Draw added in v0.12.0

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

Draw paints the axis frame then the polyline (or a dot for a lone point).

type ListBox

type ListBox struct {
	Base
	Items       []string
	Selected    int // -1 = no selection; anchor/cursor row
	RowHeight   int // pixels per row; default 18 via NewListBox
	OnActivate  func(idx int)
	MultiSelect bool // enable Ctrl/Shift multi-row selection

	// Reorderable enables drag-to-reorder (see the type doc). Default
	// false leaves the ListBox exactly as it behaved before this feature
	// existed.
	Reorderable bool

	// OnReorder fires after a successful drag-reorder with the row's
	// original index (from) and its final index after the move (to).
	// Nil-guarded; never called while Reorderable is false.
	OnReorder func(from, to int)

	// ScrollRow is the index of the row painted at the very top of the
	// widget's bounds. Reads through Draw/OnEvent are clamped to
	// [0, maxScrollRow()] on the fly (see clampedScrollRow), so setting
	// this directly to an out-of-range value is safe -- it just behaves
	// as whichever in-range value it clamps to. Prefer ScrollTo/ScrollBy,
	// which clamp + write back immediately.
	ScrollRow int
	// contains filtered or unexported fields
}

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.

Multi-selection: setting MultiSelect enables Ctrl/Shift-modified clicks that build a set of selected rows (see IsSelected / SelectedIndices / SetSelection / ClearSelection / ToggleSelect / SelectRange). Selected remains the anchor/cursor row -- the point a Shift-click range is measured from, and the row most recently clicked (plain or Ctrl). When MultiSelect is false (the default) none of this is reachable: Ctrl/Shift are ignored and only Selected is ever highlighted, exactly as before this feature existed.

Virtual scrolling: ListBox is self-contained -- it never relies on an outer ScrollView. ScrollRow is the index of the top visible row; Draw paints only the rows that fit in Bounds().H (windowed rendering, so a list with thousands of rows costs the same per frame as one with a handful), and OnEvent maps click coordinates back through ScrollRow so hit-testing stays correct while scrolled. See ScrollTo / ScrollBy. When every row already fits in the viewport (len(Items) <= the number of visible rows) rendering is byte-identical to a ListBox with no scrolling at all -- no scrollbar is drawn and the windowing has no visible effect.

Drag-to-reorder: setting Reorderable turns the ListBox into both a DragSource and a DropTarget for its own private "listrow:" payload scheme (see ListRowDragPrefix / DragData / AcceptsDrop) -- a host wires its native drag gestures to the widget exactly as it would for any other DragSource/DropTarget pair (see dnd.go), and the ListBox handles tracking the pressed row, painting an insertion-line indicator on EventDragMove, and reordering Items in place on EventDrop, firing OnReorder. When Reorderable is false (the default) none of this is reachable: DragData always returns "", AcceptsDrop always returns false, EventDragMove/EventDragLeave/EventDrop are no-ops, and Draw never paints an indicator -- rendering + behavior are byte-identical to a ListBox with no drag-to-reorder support at all.

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) A11y added in v0.40.0

func (l *ListBox) A11y() A11yInfo

A11y reports the ListBox as a listbox. Value is the selected item's text in single-select mode, or a "N selected" count while MultiSelect is on.

func (*ListBox) AcceptsDrop added in v0.37.0

func (l *ListBox) AcceptsDrop(payload string) bool

AcceptsDrop reports whether payload is a reorder drag carrying ListBox's own "listrow:" scheme. It is always false when Reorderable is false, and false for any payload that doesn't carry the scheme (e.g. a different DragSource's payload).

func (*ListBox) ClearSelection added in v0.37.0

func (l *ListBox) ClearSelection()

ClearSelection empties the selection set. Selected (the anchor/cursor row) is left untouched.

func (*ListBox) DragData added in v0.37.0

func (l *ListBox) DragData() string

DragData returns the drag-to-reorder payload for the row hit by the most recent EventClick (see pressedRow), or "" when Reorderable is false or no row has been pressed yet.

func (*ListBox) Draw

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

Draw paints only the rows currently within the scroll window -- [ScrollRow, ScrollRow+visibleRows) -- positioning row i at top + (i-ScrollRow)*RowHeight. When every row already fits (len(Items) <= visibleRows() and ScrollRow clamps to 0), that window covers the whole list and rendering is byte-identical to a non-scrolling ListBox: no scrollbar, no clipping, full-width rows.

When the list overflows the viewport, rows are clipped to the content area (via painter.Clipper, if the backend supports it) so a partially-visible trailing row never bleeds past Bounds().H, and a thin scrollbar track+thumb is painted on the right edge.

func (*ListBox) IsSelected added in v0.37.0

func (l *ListBox) IsSelected(i int) bool

IsSelected reports whether row i is a member of the multi-selection set. It is independent of MultiSelect + Selected, so it can be queried (and pre-seeded via SetSelection/ToggleSelect/SelectRange) even before multi-selection is switched on.

func (*ListBox) OnEvent

func (l *ListBox) OnEvent(ev Event)

OnEvent dispatches: EventClick to onClick (selection, unchanged from before this feature); EventDragMove/EventDragLeave/EventDrop to the drag-to-reorder handlers below, which are all no-ops while Reorderable is false, so behavior is byte-identical to a ListBox with no drag-to-reorder support when the feature isn't opted into. Every other event kind is ignored.

func (*ListBox) ScrollBy added in v0.37.0

func (l *ListBox) ScrollBy(delta int)

ScrollBy shifts ScrollRow by delta rows (negative scrolls up), clamped exactly like ScrollTo.

func (*ListBox) ScrollTo added in v0.37.0

func (l *ListBox) ScrollTo(row int)

ScrollTo moves the top visible row to row, clamped to [0, maxScrollRow()], and writes the clamped value back to ScrollRow.

func (*ListBox) SelectRange added in v0.37.0

func (l *ListBox) SelectRange(a, b int)

SelectRange selects the inclusive range of rows between a and b (either order accepted), replacing the current selection set. The range is clamped to [0, len(Items)); if the list is empty, or the clamped range is inverted, the resulting selection is empty.

func (*ListBox) SelectedIndices added in v0.37.0

func (l *ListBox) SelectedIndices() []int

SelectedIndices returns the selected rows in ascending order. The returned slice is a fresh copy the caller may mutate freely.

func (*ListBox) SetSelection added in v0.37.0

func (l *ListBox) SetSelection(indices ...int)

SetSelection replaces the selection set with exactly the given indices. Indices outside [0, len(Items)) are silently dropped.

func (*ListBox) ToggleSelect added in v0.37.0

func (l *ListBox) ToggleSelect(i int)

ToggleSelect flips row i's membership in the selection set. Out-of-range indices are a no-op.

type MarkdownEditor added in v0.38.0

type MarkdownEditor struct {
	Base

	// Source is the editable Markdown-source pane.
	Source *TextView
	// Preview is the read-only rendered pane, kept in sync with Source.
	Preview *MarkdownView

	// Split is the fraction (0..1, exclusive) of Bounds given to Source
	// along the split axis; the remainder (minus the divider) goes to
	// Preview. A value outside (0, 1) -- including the zero value, so a
	// struct literal built without NewMarkdownEditor behaves sanely --
	// falls back to 0.5.
	Split float64

	// SideBySide selects the split axis: true lays Source left / Preview
	// right; false stacks Source above Preview.
	SideBySide bool
}

MarkdownEditor is a split source/preview editor: a TextView holding editable Markdown source on one side, a MarkdownView rendering that source live on the other. It composes the two existing widgets purely through their public API (TextView.Text/SetText, MarkdownView.Source) -- it does not modify either.

Only the Source pane is interactive; a click landing in the Preview pane is a no-op (the preview is display-only, per MarkdownView's own docs). Every event routed to Source is followed by a re-sync of Preview.Source so the rendered pane never drifts from the edited buffer.

func NewMarkdownEditor added in v0.38.0

func NewMarkdownEditor(initial string) *MarkdownEditor

NewMarkdownEditor builds a MarkdownEditor seeded with initial Markdown source: Source gets a TextView pre-loaded with initial (an empty string still yields a valid single-line buffer, per NewTextView), Preview gets a MarkdownView already rendering that same text. Split defaults to 0.5 and SideBySide defaults to true (left/right).

func (*MarkdownEditor) Draw added in v0.38.0

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

Draw lays out + paints Source and Preview side by side (or stacked), split per Split/SideBySide, with a markdownEditorDividerW-px divider between them.

func (*MarkdownEditor) OnEvent added in v0.38.0

func (m *MarkdownEditor) OnEvent(ev Event)

OnEvent routes every event to Source, translated into its local frame, then resyncs Preview. Only EventClick is hit-tested against the pane split first: a click landing in the Preview pane (or on the divider) is a no-op, since Preview is display-only and nothing there should steal focus from Source. Non-click events (keyboard, IME, drag) always go to Source, matching the "only Source is interactive" model -- there is no competing widget that could plausibly want them instead.

func (*MarkdownEditor) SetText added in v0.38.0

func (m *MarkdownEditor) SetText(s string)

SetText replaces the source-pane text + resyncs Preview.

func (*MarkdownEditor) Text added in v0.38.0

func (m *MarkdownEditor) Text() string

Text returns the current source-pane text, or "" when Source is nil.

type MarkdownView added in v0.15.0

type MarkdownView struct {
	Base
	Source string
}

MarkdownView renders a subset of Markdown as laid-out text -- the read-only document widget the toolkit lacked. It handles the block structure that matters on a fixed-width bitmap font: ATX headings (`#`..`######`), bullet lists (`-`/`*`/`+`), fenced code blocks (```), and blank-line-separated paragraphs, word-wrapped to the widget width.

The 5x7 font has a single weight and size, so hierarchy is shown through layout rather than type scale: headings get an Accent underline (levels 1-2) and the Accent ink, bullets get a "• " marker with a hanging indent, and code blocks sit on a SurfaceAlt band. Inline emphasis (*bold*, _italic_) is not styled -- the bitmap font has no variants -- but its text still renders. Display-only; wrap in a ScrollView for long documents.

Example

ExampleMarkdownView renders a Markdown subset (headings, lists, code, paragraphs) laid out for the bitmap font.

package main

import (
	"github.com/go-widgets/painter"
	"github.com/go-widgets/toolkit"
)

// newSurface returns a PixelPainter over a fresh w×h RGBA buffer — the render
// target the examples draw into. A CellPainter would render the same widgets to
// a terminal grid instead.
func newSurface(w, h int) *painter.PixelPainter {
	return painter.NewPixelPainter(make([]byte, 4*w*h), w, h)
}

func main() {
	md := toolkit.NewMarkdownView("# Title\n\nA paragraph.\n\n- one\n- two")
	md.SetBounds(toolkit.Rect{X: 0, Y: 0, W: 260, H: 120})
	md.Draw(newSurface(260, 120), toolkit.DefaultLight())
}

func NewMarkdownView added in v0.15.0

func NewMarkdownView(source string) *MarkdownView

NewMarkdownView builds a MarkdownView over the given Markdown source.

func (*MarkdownView) A11y added in v0.40.0

func (m *MarkdownView) A11y() A11yInfo

A11y reports the MarkdownView as a document. Source is typically a full document body, too long to usefully surface as an accessible Name.

func (*MarkdownView) Draw added in v0.15.0

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

Draw lays the parsed blocks top-to-bottom within Bounds.

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) A11y() A11yInfo

A11y reports the Menu as a menu carrying the hovered row's label, if any.

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). Before Action runs, a checkable row toggles its own Checked; a radio-grouped row instead selects itself exclusively within its RadioGroup (see selectRadio).

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 (m *MenuBar) A11y() A11yInfo

A11y reports the MenuBar as a menubar carrying the currently-open top-level menu's name, if any.

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
	Checkable  bool
	Checked    bool
	RadioGroup int
}

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

Checkable marks the row as a toggle: activating it flips Checked (in addition to still calling Action, if any) + the row renders a ✓ glyph in a left-hand gutter when Checked.

RadioGroup, when non-zero, makes the row a member of a mutually exclusive set: every item in the same Menu sharing the same RadioGroup value is a sibling. Activating one sets its Checked to true + clears Checked on every sibling; the row renders a • (bullet) glyph instead of a check mark. RadioGroup implies checkable behaviour — Checkable does not need to also be set. RadioGroup == 0 means "not part of any radio group".

type Notebook

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

Notebook is a tabbed container. A NotebookTabStripH-thick strip on the side chosen by TabSide (Top by default) hosts the tabs; the rest is the active page's body. For Top/Bottom the tabs run horizontally (each NotebookTabWidth wide); for Left/Right they stack vertically (each NotebookTabStripH tall). Clicking a tab swaps Active + fires OnTabChanged.

func NewNotebook

func NewNotebook() *Notebook

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

func (*Notebook) A11y added in v0.40.0

func (n *Notebook) A11y() A11yInfo

A11y reports the Notebook as a tablist named by its active tab.

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 (on the chosen side) + the active page.

func (*Notebook) OnEvent

func (n *Notebook) OnEvent(ev Event)

OnEvent: a click on a tab (any side) selects it; a click in the body — or any non-click event — routes to the active page, translated into its local frame.

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) A11y added in v0.40.0

func (n *Notification) A11y() A11yInfo

A11y reports the Notification as a status region named by its message.

func (*Notification) AnchorIn added in v0.33.0

func (n *Notification) AnchorIn(host Rect, corner Corner)

AnchorIn sizes the notification to its Text + positions it at corner of host, inset by NotificationMargin. A convenience over the host computing SetBounds by hand for the common "top-right"/"bottom-centre" placements the type is designed for.

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 Orientation added in v0.25.2

type Orientation int

Orientation selects whether a linear widget (Scale, RangeSlider, ProgressBar, LevelBar, …) runs across its width or up its height. Horizontal is the zero value, so an unset Orientation keeps the original left-to-right layout.

A vertical widget fills / travels from the BOTTOM up, matching how a physical meter, fader, or level indicator reads.

const (
	// Horizontal runs left-to-right (the default).
	Horizontal Orientation = iota
	// Vertical runs bottom-to-top.
	Vertical
)

type Overlay added in v0.18.0

type Overlay struct {
	Base
	Content Widget
	Layers  []Widget
	Modal   bool
}

Overlay is a z-ordered stacking container: a primary Content child that fills the bounds, plus a stack of Layers painted on top of it in order (the last Layer is topmost). It is the piece the widget model was missing so transient widgets -- Popover, Toast, Notification, Tooltip, ContextMenu -- can float above the main UI without the host arranging screen positions or z-order.

Unlike Stack (which shows exactly one page at a time), an Overlay draws every layer, and events route top-down: the topmost Layer whose HitTest covers the point handles the event; if none do, the event falls through to Content -- unless Modal is set, in which case a miss while any Layer is up is swallowed (a modal backdrop). Layers self-position via their own Bounds; only Content is resized to fill the Overlay.

Example

ExampleOverlay stacks z-ordered layers above a primary child. Events route top-down; a Modal overlay swallows clicks that miss every layer.

package main

import (
	"github.com/go-widgets/painter"
	"github.com/go-widgets/toolkit"
)

// newSurface returns a PixelPainter over a fresh w×h RGBA buffer — the render
// target the examples draw into. A CellPainter would render the same widgets to
// a terminal grid instead.
func newSurface(w, h int) *painter.PixelPainter {
	return painter.NewPixelPainter(make([]byte, 4*w*h), w, h)
}

func main() {
	base := toolkit.NewLabel("main content")
	ov := toolkit.NewOverlay(base)
	ov.Push(toolkit.NewTooltip("floating layer"))
	ov.SetBounds(toolkit.Rect{X: 0, Y: 0, W: 200, H: 120})
	ov.Draw(newSurface(200, 120), toolkit.DefaultLight())
}

func NewOverlay added in v0.18.0

func NewOverlay(content Widget) *Overlay

NewOverlay builds an Overlay around the given primary child (which may be nil and set later).

func (*Overlay) A11y added in v0.40.0

func (o *Overlay) A11y() A11yInfo

A11y reports the Overlay as a group carrying its modal state.

func (*Overlay) Clear added in v0.18.0

func (o *Overlay) Clear()

Clear removes every layer, leaving just the Content.

func (*Overlay) Draw added in v0.18.0

func (o *Overlay) Draw(p painter.Painter, theme *Theme)

Draw paints Content first, then each layer bottom-to-top.

func (*Overlay) OnEvent added in v0.18.0

func (o *Overlay) OnEvent(ev Event)

OnEvent routes to the topmost layer whose HitTest covers the point; failing that, to Content -- or, when Modal and a layer is present, nowhere (the backdrop swallows the click). Coordinates are passed through unchanged (an Overlay is a surface-frame container, like Paned).

func (*Overlay) Pop added in v0.18.0

func (o *Overlay) Pop() Widget

Pop removes and returns the topmost layer, or nil when there are none.

func (*Overlay) Push added in v0.18.0

func (o *Overlay) Push(w Widget)

Push adds w as the new topmost layer.

func (*Overlay) SetBounds added in v0.18.0

func (o *Overlay) SetBounds(r Rect)

SetBounds resizes Content to fill the Overlay; layers keep their own bounds (they self-position at a point).

func (*Overlay) Top added in v0.18.0

func (o *Overlay) Top() Widget

Top returns the topmost layer without removing it, or nil when there are none.

type Pagination added in v0.8.0

type Pagination struct {
	Base
	Current  int
	Total    int
	OnChange func(page int)
}

Pagination is a page-navigator strip: a "<" prev button, a series of page-number buttons, and a ">" next button. Clicking a page number jumps Current to that page and fires OnChange; clicking prev or next steps by one (clamped). When Current is at either extreme the corresponding step button renders in a disabled tone and swallows clicks.

When Total exceeds paginationMaxButtons the middle of the range collapses into a "1 ... k-1 k k+1 ... Total" window so the widget's footprint stays bounded. Non-numeric window slots ("...") are drawn but not clickable — the hit-test skips them.

func NewPagination added in v0.8.0

func NewPagination(current, total int) *Pagination

NewPagination builds a Pagination with the given current and total page counts. Current is clamped to [1, Total] when Total > 0, and to 1 when Total <= 0 (the widget then renders empty and swallows events).

func (*Pagination) A11y added in v0.40.0

func (p *Pagination) A11y() A11yInfo

A11y reports the Pagination as navigation carrying its "current/total" page position.

func (*Pagination) Draw added in v0.8.0

func (pg *Pagination) Draw(p painter.Painter, theme *Theme)

Draw paints the widget body, each button in its correct tint, and the button labels. Total <= 0 paints only the body — no buttons. Bounds that cannot accommodate a single button are treated the same as Total <= 0 so a mis-sized Pagination degrades gracefully.

func (*Pagination) OnEvent added in v0.8.0

func (pg *Pagination) OnEvent(ev Event)

OnEvent routes an EventClick to whichever button contains (X, Y). Prev/next step Current by one when enabled; a numeric slot sets Current to its page. Ellipsis slots and out-of-band clicks are no-ops.

type PaletteCommand added in v0.35.0

type PaletteCommand struct {
	Label  string
	Action func()
}

PaletteCommand is one entry in a CommandPalette: a human-readable Label the user searches for and an Action to run when it is chosen. Action may be nil (e.g. a placeholder or disabled entry); activating such a command simply dismisses the palette without running anything.

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) A11y added in v0.40.0

func (p *Paned) A11y() A11yInfo

A11y reports the Paned as a plain grouping container for its two panes.

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 PieChart added in v0.14.0

type PieChart struct {
	Base
	Values []float64
	Colors []RGBA // optional per-slice palette override; cycles by index
}

PieChart plots proportional Values as wedges of a filled disc -- the part-of- whole complement to LineChart/BarChart. Wedges start at 12 o'clock and run clockwise, sized by each value's share of the total. Colours cycle through a built-in categorical palette unless Colors is set. Display-only.

It fills each wedge per-pixel over painter.Painter's putPixel (no arc primitive needed), so it renders as pixels (WUI/GUI) or promoted cells (TUI). A zero or empty total draws nothing.

Example

ExamplePieChart fills a disc with one proportional wedge per value.

package main

import (
	"github.com/go-widgets/painter"
	"github.com/go-widgets/toolkit"
)

// newSurface returns a PixelPainter over a fresh w×h RGBA buffer — the render
// target the examples draw into. A CellPainter would render the same widgets to
// a terminal grid instead.
func newSurface(w, h int) *painter.PixelPainter {
	return painter.NewPixelPainter(make([]byte, 4*w*h), w, h)
}

func main() {
	chart := toolkit.NewPieChart([]float64{3, 5, 2, 4})
	chart.SetBounds(toolkit.Rect{X: 0, Y: 0, W: 120, H: 120})
	chart.Draw(newSurface(120, 120), toolkit.DefaultLight())
}

func NewPieChart added in v0.14.0

func NewPieChart(values []float64) *PieChart

NewPieChart builds a PieChart over the given values with the default palette.

func (*PieChart) A11y added in v0.40.0

func (p *PieChart) A11y() A11yInfo

A11y reports the PieChart as an img carrying its slice count.

func (*PieChart) Draw added in v0.14.0

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

Draw fills the disc, colouring each pixel by the wedge its angle falls in.

type Popover added in v0.8.0

type Popover struct {
	Base
	Visible bool
	Child   Widget
	Title   string
}

Popover is a Visible floating container for a single child widget, modelled on GTK 4's Popover -- a rectangular panel with a border stroke and an optional Title header. Popover is the natural home for dropdown menus, ephemeral pickers and detail overlays that the host wants to show and hide without tearing down and rebuilding the underlying child.

Distinct from Card (a passive display container) in two ways:

  1. Popover has a Visible toggle: the whole widget short-circuits Draw + OnEvent when hidden so the host does not have to unlink the child from the tree between showings.
  2. Popover forwards input events to its child with coordinates translated for the pad + optional title header, so the child sees widget-local coords in its own frame.

func NewPopover added in v0.8.0

func NewPopover(child Widget) *Popover

NewPopover constructs a hidden Popover wrapping child. child may be nil, in which case the Popover renders as an empty framed panel.

func (*Popover) A11y added in v0.40.0

func (p *Popover) A11y() A11yInfo

A11y reports the Popover as a dialog named by its title.

func (*Popover) Draw added in v0.8.0

func (p *Popover) Draw(pnt painter.Painter, theme *Theme)

Draw paints the surface fill + border, optionally draws the Title at the top-left inside PopoverPad, then draws Child (if non-nil) into the inset child rect. Nothing drawn when !Visible.

func (*Popover) OnEvent added in v0.8.0

func (p *Popover) OnEvent(ev Event)

OnEvent forwards the event to Child with coordinates translated into the child's local frame. No-op when !Visible or Child is nil. Mirrors the translateEvent pattern used by HBox / VBox / Grid.

type ProgressBar

type ProgressBar struct {
	Base
	Fraction    float64
	Label       string
	Orientation Orientation
}

ProgressBar is a bar with a filled portion proportional to Fraction in [0,1]. Orientation picks the fill direction: Horizontal (default) fills left→right, Vertical fills bottom→top. An optional Label is centred over the bar in Theme.OnSurface ink (drawn for the horizontal orientation, where it fits).

func NewProgressBar

func NewProgressBar() *ProgressBar

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

func (*ProgressBar) A11y added in v0.40.0

func (p *ProgressBar) A11y() A11yInfo

A11y reports the ProgressBar as a progressbar carrying its fraction as a whole-number percentage.

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 ProgressCircle added in v0.9.0

type ProgressCircle struct {
	Base
	Fraction float64 // 0..1; clamped by Draw
}

ProgressCircle is a fake-circular progress indicator: a rounded square track with a "cup-filling" band that rises from the bottom as Fraction grows from 0 to 1. Not a true arc — the pixel-blitting toolkit does not carry a curve rasteriser — but it conveys the same "circular progress" intent at the same abstraction level as Spinner (a rotating radial line) and Avatar (a rounded square).

Layout: an outer square filled in theme.SurfaceAlt (the "track"), an inner square inset by ProgressCircleStroke on all sides filled in theme.Surface (the "hole" that the caption sits in), and a horizontal Accent band inside the ring whose height is proportional to Fraction. The band grows from the bottom edge upward for the familiar "filling up" visual. The percentage caption ("XX%") is drawn in theme.OnSurface centred inside the inner square.

func NewProgressCircle added in v0.9.0

func NewProgressCircle() *ProgressCircle

NewProgressCircle constructs a ProgressCircle at Fraction=0.

func (*ProgressCircle) A11y added in v0.40.0

func (p *ProgressCircle) A11y() A11yInfo

A11y reports the ProgressCircle as a progressbar carrying its fraction as a whole-number percentage.

func (*ProgressCircle) Draw added in v0.9.0

func (pc *ProgressCircle) Draw(p painter.Painter, theme *Theme)

Draw paints the track, the hole, the fill band, and the centred percentage caption. Draw clamps Fraction defensively so callers bypassing SetFraction still render a valid frame.

func (*ProgressCircle) SetFraction added in v0.9.0

func (pc *ProgressCircle) SetFraction(f float64)

SetFraction clamps + assigns Fraction. 0 = empty, 1 = full. Kept as a symmetrical helper to ProgressBar.SetFraction so both widgets present the same knob to callers.

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) A11y added in v0.19.0

func (r *RadioButton) A11y() A11yInfo

A11y reports the RadioButton as a radio with its checked state.

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 RangeSlider added in v0.10.0

type RangeSlider struct {
	Base
	Min, Max    float64
	Low, High   float64
	Orientation Orientation
	OnChange    func(low, high float64)
	// contains filtered or unexported fields
}

RangeSlider is a two-handle slider selecting a sub-interval [Low, High] within a continuous Min..Max range -- a price band, a date window, a volume gate. It is the two-thumb sibling of Scale: the same rounded track and circular white thumbs, but the Accent fill spans the selected band between the handles rather than from the left edge.

A click grabs whichever handle is nearest the cursor and jumps it there; a subsequent drag moves that same handle, clamped so Low never crosses High.

Example

ExampleRangeSlider selects a sub-interval; SetRange orders + clamps its ends.

package main

import (
	"fmt"

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

func main() {
	rs := toolkit.NewRangeSlider(0, 100, 20, 80)
	rs.SetRange(90, 10) // passed out of order → normalised to Low <= High
	fmt.Printf("%.0f-%.0f\n", rs.Low, rs.High)
}
Output:
10-90

func NewRangeSlider added in v0.10.0

func NewRangeSlider(min, max, low, high float64) *RangeSlider

NewRangeSlider builds a RangeSlider spanning [min, max] with the given initial band. The band is clamped and ordered so Low <= High.

func (*RangeSlider) A11y added in v0.40.0

func (s *RangeSlider) A11y() A11yInfo

A11y reports the RangeSlider as a group carrying its "low..high" band -- two cooperating handles read more naturally as one control's range value than as two independent sliders.

func (*RangeSlider) Draw added in v0.10.0

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

Draw paints the rounded track, the Accent band between the two handles, and a circular white thumb at each handle -- matching Scale's macOS styling.

func (*RangeSlider) OnEvent added in v0.10.0

func (s *RangeSlider) OnEvent(ev Event)

OnEvent: a click grabs the nearer handle and jumps it to the cursor; a drag moves the grabbed handle; a mouse-up releases it. Each move re-clamps so the handles never cross, and fires OnChange.

func (*RangeSlider) SetRange added in v0.10.0

func (s *RangeSlider) SetRange(low, high float64)

SetRange clamps both bounds to [Min, Max] and swaps them if low > high, so the invariant Low <= High always holds.

type Rating added in v0.8.0

type Rating struct {
	Base
	Value    int
	Max      int
	OnChange func(v int)
}

Rating is a horizontal star-rating strip: Max square cells drawn left-to-right, each carrying an ASCII asterisk overlay. Cells with index < Value fill in Theme.Accent (the "filled" state); cells with index >= Value fill in Theme.SurfaceAlt (the "empty" state). The star glyph itself is drawn as the ASCII "*" character because the toolkit's 5x7 bitmap font only covers ASCII — a Unicode "★" would render blank via DrawText's font5x7 lookup fall-through.

A click on cell index i sets Value = i+1 (so the leftmost cell yields 1, the rightmost Max) and fires OnChange when non-nil. Clicks outside the strip (Y outside the cell row, X to the right of the last cell) are ignored — the parent container already routes only hits inside Bounds() but a stray x >= Max*(RatingStarW+RatingStarGap) would otherwise resolve to an out-of-range index.

func NewRating added in v0.8.0

func NewRating(value, max int) *Rating

NewRating constructs a Rating with the given value and max. Max defaults to 5 when non-positive; Value is clamped to the [0, Max] interval so a bogus caller input can never render more filled cells than Max.

func (*Rating) A11y added in v0.40.0

func (r *Rating) A11y() A11yInfo

A11y reports the Rating as a slider carrying its "value/max" score.

func (*Rating) Draw added in v0.8.0

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

Draw paints Max cells left-to-right. Filled cells use Theme.Accent + the accent-inverted ink; empty cells use Theme.SurfaceAlt + Theme.OnSurface. Every cell carries an ASCII "*" overlay so the row reads as stars even when the palette is monochrome.

func (*Rating) OnEvent added in v0.8.0

func (r *Rating) OnEvent(ev Event)

OnEvent handles a click by resolving the star index from ev.X and setting Value = index+1. Non-click events are ignored (matches Switch / ToggleButton). Clicks with X to the right of the last cell (index >= Max) are ignored so a spurious hit doesn't push Value past Max.

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.

func FitBounds added in v0.46.0

func FitBounds(srcW, srcH int, bounds Rect) Rect

FitBounds returns the largest rect of source aspect (srcW:srcH) that fits entirely within bounds, centred in it — the geometry ScaleFit paints into. Consumers can call it to size/lay out an image area (e.g. grow a box to the image's fitted height) before drawing. When srcW or srcH is non-positive the aspect is unknown and bounds is returned unchanged.

type Role added in v0.19.0

type Role string

Role is a widget's accessibility role, named after the WAI-ARIA roles a host maps them onto.

const (
	RoleButton   Role = "button"
	RoleText     Role = "text"
	RoleTextbox  Role = "textbox"
	RoleCheckbox Role = "checkbox"
	RoleRadio    Role = "radio"
	RoleSwitch   Role = "switch"
	RoleSlider   Role = "slider"
)

The roles the built-in widgets report.

const (
	RoleSearchbox    Role = "searchbox"
	RoleSpinbutton   Role = "spinbutton"
	RoleCombobox     Role = "combobox"
	RoleListbox      Role = "listbox"
	RoleGrid         Role = "grid"
	RoleTree         Role = "tree"
	RoleTablist      Role = "tablist"
	RoleNavigation   Role = "navigation"
	RoleMenu         Role = "menu"
	RoleMenuBar      Role = "menubar"
	RoleAlert        Role = "alert"
	RoleStatus       Role = "status"
	RoleProgressbar  Role = "progressbar"
	RoleMeter        Role = "meter"
	RoleImg          Role = "img"
	RoleGroup        Role = "group"
	RoleDialog       Role = "dialog"
	RoleTooltip      Role = "tooltip"
	RoleBanner       Role = "banner"
	RoleList         Role = "list"
	RoleDocument     Role = "document"
	RoleToolbar      Role = "toolbar"
	RolePresentation Role = "presentation"
)

Additional Role constants for the widgets implemented in this file. See a11y.go for the original seven roles (button/text/textbox/checkbox/ radio/switch/slider) and the Accessible/A11yInfo/CollectA11y machinery they share with everything below.

type Rule added in v0.42.0

type Rule func(value string) error

Rule validates a single string value. It returns a non-nil error -- whose Error() text is the message shown to the user (e.g. via FormField.Error) -- when value fails the rule's check, or nil when value passes.

func All added in v0.42.0

func All(rules ...Rule) Rule

All combines rules into a single Rule that runs them in order and fails on the first one that fails, discarding the rest -- the same short-circuit semantics as Validate. Useful for grouping a related set of rules (e.g. a password policy) behind one Rule value.

func Email added in v0.42.0

func Email(msg string) Rule

Email rejects a value that does not look like an email address.

func MaxLen added in v0.42.0

func MaxLen(n int, msg string) Rule

MaxLen rejects a value with more than n runes.

func MinLen added in v0.42.0

func MinLen(n int, msg string) Rule

MinLen rejects a value with fewer than n runes.

func Pattern added in v0.42.0

func Pattern(re *regexp.Regexp, msg string) Rule

Pattern rejects a value that does not match re.

func Required added in v0.42.0

func Required(msg string) Rule

Required rejects an empty value.

type Scale

type Scale struct {
	Base
	Min, Max    float64
	Value       float64
	Orientation Orientation
	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) A11y added in v0.19.0

func (s *Scale) A11y() A11yInfo

A11y reports the Scale as a slider carrying its current value.

func (*Scale) Draw

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

Draw paints a macOS-style slider: a rounded track whose filled portion (up to the thumb) is Accent and whose remainder is SurfaceAlt, with a circular white thumb -- matching the Switch's pill track + circular knob.

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 ScaleMode added in v0.45.0

type ScaleMode int

ScaleMode selects how an Image's source pixels map onto its bounds.

const (
	// ScaleStretch fills the whole bounds, ignoring the source aspect ratio
	// (nearest-neighbour). It is the zero value, so existing callers are
	// unchanged.
	ScaleStretch ScaleMode = iota
	// ScaleFit ("contain") preserves the source aspect ratio, scaling the image
	// to the largest size that fits entirely within the bounds and centring it;
	// the margin around it is left untouched.
	ScaleFit
)

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) is painted on the right edge, and — when the content is wider than the viewport — along the bottom edge too, each in Theme.SurfaceAlt with a Theme.Accent thumb sized proportionally to the viewport/content ratio. Scroll(dx, dy) moves on both axes.

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) A11y added in v0.40.0

func (s *ScrollView) A11y() A11yInfo

A11y reports the ScrollView as a plain grouping container for its child.

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 SearchEntry added in v0.8.0

type SearchEntry struct {
	Base
	Text     string
	Focused  bool // when true, a text caret is drawn at the end of Text
	OnChange func(s string)
	Icon     func(p painter.Painter, r Rect, ink RGBA)
}

SearchEntry is a single-line text input decorated with a leading search-prefix glyph and, when Text is non-empty, a trailing "clear" affordance on the right. Think GTK's SearchEntry: an Entry whose visual chrome hints at its role and offers a one-click reset. The widget appends printable characters, deletes on Backspace, and clears on a click in the right-side X slot. It draws a simple end-of-text caret when Focused (set by the host), measured with its own font so it always aligns; it has no cursor navigation or IME — callers needing those should reach for Entry / TextView instead.

An optional leading Icon lets the host paint a real magnifier (or any glyph) in the left prefix slot instead of the "?" text stand-in. When set, Draw invokes Icon with the prefix slot's rect + the OnSurface ink and skips the "?" text; when nil, the classic "?" stand-in is drawn, so existing callers are unaffected. This mirrors Banner.Icon.

func NewSearchEntry added in v0.8.0

func NewSearchEntry(text string) *SearchEntry

NewSearchEntry builds a SearchEntry pre-loaded with initial text. The constructor does not run OnChange for the initial value so callers can wire the callback after construction without a spurious notification.

func (*SearchEntry) A11y added in v0.40.0

func (s *SearchEntry) A11y() A11yInfo

A11y reports the SearchEntry as a searchbox carrying its current text.

func (*SearchEntry) Draw added in v0.8.0

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

Draw paints the entry body, the leading prefix glyph, the current Text, and (when Text is non-empty) the trailing clear affordance.

func (*SearchEntry) OnEvent added in v0.8.0

func (s *SearchEntry) OnEvent(ev Event)

OnEvent handles character insertion (EventChar), Backspace deletion (EventKeyDown / "Backspace"), and click-to-clear in the right icon slot (EventClick, when Text is non-empty). Other events are ignored.

type SegmentedBar added in v0.36.0

type SegmentedBar struct {
	Base
	Segments    []BarSegment
	Orientation Orientation
}

SegmentedBar is a single bar split into proportional colored bands laid end to end -- e.g. a disk-usage meter showing used/free/reserved as one stacked strip. Orientation picks the layout axis: Horizontal (default) lays segments left→right, Vertical lays them bottom→top (the first segment sits at the bottom), matching ProgressBar/LevelBar's vertical convention.

func NewSegmentedBar added in v0.36.0

func NewSegmentedBar(segs []BarSegment) *SegmentedBar

NewSegmentedBar builds a SegmentedBar with the given segments.

func (*SegmentedBar) A11y added in v0.40.0

func (s *SegmentedBar) A11y() A11yInfo

A11y reports the SegmentedBar as a group. The individual segments carry no independent accessible identity (BarSegment is a plain data struct, not a Widget), so the bar as a whole is the accessible unit.

func (*SegmentedBar) Draw added in v0.36.0

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

Draw paints a 1-px border around the whole bar + each segment's proportional share filled with its own color, separated by a 1-px Theme.Border line. A zero (or empty) total draws a bare Theme.SurfaceAlt track. Integer-rounding leftover is pushed onto the last segment so the bands always sum to exactly the bar's length, mirroring Table.columnWidths's remainder handling.

func (*SegmentedBar) Total added in v0.36.0

func (s *SegmentedBar) Total() float64

Total sums every segment's Value.

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 Skeleton added in v0.8.0

type Skeleton struct {
	Base
	Kind  SkeletonKind
	Lines int
}

Skeleton is a placeholder rendered while real content is loading. Every Skeleton fills in Theme.SurfaceAlt so the shape reads as "content coming" without demanding attention; there is no text or animation — the visual weight alone signals the pending state.

A caller typically swaps a Skeleton for the real widget once data arrives; there is no Visible flag because dropping the widget from the tree is cheaper than gating every Draw on a bool.

Skeleton is passive: it displays and does not respond to input.

func NewSkeleton added in v0.8.0

func NewSkeleton(kind SkeletonKind, lines int) *Skeleton

NewSkeleton constructs a Skeleton of the given kind + line count. The lines argument is honoured only when kind == SkeletonText; if it is non-positive in that case it defaults to 3 (a natural stand-in for a paragraph). For SkeletonAvatar / SkeletonBlock the value is stored verbatim but ignored by Draw.

func (*Skeleton) A11y added in v0.40.0

func (s *Skeleton) A11y() A11yInfo

A11y reports the Skeleton as a decorative presentation element -- a screen reader should not announce a loading placeholder as content.

func (*Skeleton) Draw added in v0.8.0

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

Draw paints the placeholder appropriate for Kind. Nothing else about the widget is theme-aware: every fill lands in Theme.SurfaceAlt so the placeholder recedes into the panel without demanding attention.

type SkeletonKind added in v0.8.0

type SkeletonKind int

SkeletonKind selects the placeholder shape. Three kinds cover the dominant loading-state patterns: SkeletonText for a paragraph or a list row, SkeletonAvatar for an identity chip (paired with SkeletonText in a message row), SkeletonBlock for a media thumbnail or a card body.

const (
	// SkeletonText draws N horizontal bars stacked vertically. The last
	// bar is 60% width so the shape reads as "wrapped text" rather than
	// a solid block.
	SkeletonText SkeletonKind = iota
	// SkeletonAvatar draws a rounded square in SurfaceAlt matching the
	// Avatar widget's shape — so a Skeleton row lines up pixel-for-pixel
	// with the real Avatar it will be swapped for once the data loads.
	SkeletonAvatar
	// SkeletonBlock draws one filled rectangle covering Bounds() inset
	// by SkeletonLinePad — the "media thumbnail loading" affordance.
	SkeletonBlock
)

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) A11y added in v0.40.0

func (s *SpinButton) A11y() A11yInfo

A11y reports the SpinButton as a spinbutton carrying its numeric value.

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) A11y added in v0.40.0

func (s *Spinner) A11y() A11yInfo

A11y reports the Spinner as a status region carrying "busy" while active.

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 SplitButton added in v0.9.0

type SplitButton struct {
	Base
	Label   string
	Arrow   bool
	OnClick func()
	OnArrow func()
}

SplitButton is a two-part button: a primary action face on the left plus an attached narrow arrow face on the right that opens a secondary action (typically a menu). Mirrors GTK's SplitButton and GtkMenuButton — one click target for the default action, a separate click target for "show the alternatives".

When Arrow is false the arrow slot is not drawn and OnArrow is ignored — the widget degrades to a solid Accent-face action button, so a caller can toggle the split visual at runtime without swapping widgets.

The two faces share theme.Accent as their fill; the label + arrow glyph render in accentInk(theme) — theme.Extra["OnAccent"] with a fall-through to theme.Background, matching Button + Table + Avatar.

func NewSplitButton added in v0.9.0

func NewSplitButton(label string, onClick func()) *SplitButton

NewSplitButton constructs a SplitButton with Arrow enabled by default and OnArrow left nil. onClick may be nil (a no-op primary action is still rendered).

func (*SplitButton) A11y added in v0.40.0

func (b *SplitButton) A11y() A11yInfo

A11y reports the SplitButton as a button named by its label.

func (*SplitButton) Draw added in v0.9.0

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

Draw paints the two-slot face. Both slots fill in theme.Accent; when Arrow is true a 1-px theme.Border separator is drawn between them and a small "v" glyph is centred in the arrow slot. Ink for both the label and the arrow glyph is accentInk(theme) so the text stays legible against the Accent face and honours any theme.Extra["OnAccent"] override.

func (*SplitButton) OnEvent added in v0.9.0

func (s *SplitButton) OnEvent(ev Event)

OnEvent routes clicks to OnClick or OnArrow depending on where the click landed. ev.X is widget-local; when Arrow is true a click in the right SplitButtonArrowW pixels fires OnArrow, otherwise OnClick. Both callbacks are nil-safe. Non-click event kinds are ignored.

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 Stat added in v0.9.0

type Stat struct {
	Base
	Title  string
	Value  string
	Change string
	Trend  StatTrend
}

Stat is a compact KPI card — a small dim Title on top, a large Value in the middle drawn with a one-pixel horizontal thickening pass to fake a bold weight, and an optional Change indicator at the bottom coloured by Trend. Modelled on DaisyUI's `<div class="stat">` block: three vertically stacked text rows on a bordered Surface panel.

Stat is passive display only — the parent view supplies any interaction (a click-through link, a tooltip) as a separate widget on top. HitTest / OnEvent stay as Base defaults.

func NewStat added in v0.9.0

func NewStat(title, value string) *Stat

NewStat constructs a Stat with the given title + value. Change defaults to "" (no change row painted) and Trend defaults to StatFlat; the caller assigns those fields directly to enable the bottom row.

func (*Stat) A11y added in v0.40.0

func (s *Stat) A11y() A11yInfo

A11y reports the Stat as a group named by its title, carrying its headline value.

func (*Stat) Draw added in v0.9.0

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

Draw paints the surface fill, the three text rows and finally the outer border stroke. Draw order matches Card (fill, decorations, border last) so the 1-px border always sits on top and clips overlapping ink.

The Value row is drawn TWICE — once at (x, y) and again at (x+1, y) — to fake a bold weight. Since the 5x7 bitmap font ships one stroke width, the double-draw thickens each column by one pixel so the Value visually outweighs the surrounding Title + Change rows without a second glyph table.

type StatTrend added in v0.9.0

type StatTrend int

StatTrend selects the semantic direction of a Stat's optional Change indicator. StatFlat renders the change text in the theme's dim border ink (the same "muted-ink" convention HeaderBar uses for its subtitle); StatUp and StatDown paint fixed green and red shades so the direction reads at a glance regardless of the app's accent choice. Widgets that want a neutral or accent-tinted change value use StatFlat and let the theme drive the tone.

const (
	// StatFlat is the neutral "no direction" trend. Change ink comes
	// from Theme.Border so the value blends with the surrounding
	// muted labels.
	StatFlat StatTrend = iota
	// StatUp signals a positive change ("+12%", "revenue up"). Ink is
	// a fixed sea-green so up-trends read the same across every theme.
	StatUp
	// StatDown signals a negative change ("-4%", "errors up"). Ink is
	// a fixed brick-red so down-trends read the same across every theme.
	StatDown
)

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) A11y added in v0.40.0

func (s *Statusbar) A11y() A11yInfo

A11y reports the Statusbar as a status region carrying its segments joined into one string.

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
	// Orientation lays the badges out left-to-right (Horizontal, the zero
	// value — a wizard strip) or top-to-bottom (Vertical — a side
	// checklist). Vertical draws its connectors as vertical lines and
	// renders each caption to the right of its badge instead of below it.
	Orientation Orientation
}

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) A11y added in v0.40.0

func (s *Steps) A11y() A11yInfo

A11y reports the Steps strip as a group carrying its current step's label.

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 SwipeDir added in v0.39.0

type SwipeDir int

SwipeDir is the dominant-axis direction of a recognized swipe gesture.

const (
	// SwipeLeft is a horizontal swipe whose net motion is negative in X.
	SwipeLeft SwipeDir = iota
	// SwipeRight is a horizontal swipe whose net motion is positive in X.
	SwipeRight
	// SwipeUp is a vertical swipe whose net motion is negative in Y.
	SwipeUp
	// SwipeDown is a vertical swipe whose net motion is positive in Y.
	SwipeDown
)

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) A11y added in v0.19.0

func (s *Switch) A11y() A11yInfo

A11y reports the Switch as a switch with an on/off value.

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 TabSide added in v0.24.1

type TabSide int

TabSide selects which edge of the Notebook hosts the tab strip.

const (
	// TabTop places the strip along the top edge (the default).
	TabTop TabSide = iota
	// TabBottom places the strip along the bottom edge.
	TabBottom
	// TabLeft places the strip down the left edge (tabs stacked vertically).
	TabLeft
	// TabRight places the strip down the right edge (tabs stacked vertically).
	TabRight
)

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. While MultiSelect is
	// true, Selected doubles as the anchor a Shift-click ranges from;
	// it is still the ONLY row painted while MultiSelect is false.
	Selected int

	// MultiSelect switches body-row clicks (handled by OnEvent) from
	// inert to selection-driving: a plain click selects only that row
	// (clearing any other selection, moving the Selected anchor to
	// it); a Ctrl-click toggles that row's membership without
	// disturbing the anchor; a Shift-click selects the inclusive
	// range between the anchor (Selected) and the clicked row,
	// likewise leaving the anchor in place so repeated Shift-clicks
	// keep ranging from the same origin. Header-row clicks (sort) and
	// separator drags (resize) are unaffected either way.
	//
	// The zero value (false) is the original passive-viewer
	// behaviour: OnEvent never touches Selected or any selection
	// state for a body-row click, and Draw highlights only Selected --
	// byte-for-byte the same as before this field existed.
	MultiSelect bool

	// ScrollRow is the 0-indexed body row currently painted at the top
	// of the body (the header itself never scrolls). Draw + rowAt both
	// read it through clampScrollRow, so an out-of-range value set
	// directly (or left stale after Rows shrinks) never windows past
	// [0, maxScrollRow()] -- the same defensive-collapse idiom Selected
	// and SortColumn already use. The zero value (0) is the original,
	// pre-feature behaviour: the body starts at row 0, and if every row
	// fits within Bounds().H, Draw renders byte-identically to before
	// this field existed (no scrollbar, no windowing). Use ScrollTo /
	// ScrollBy / scrollToSelected to move it -- they keep the field
	// itself clamped, unlike a raw assignment.
	ScrollRow int

	// SortColumn is the 0-indexed column currently sorted, or -1 (or
	// any out-of-range value) for "no sort" -- Draw skips the ▲/▼
	// indicator and OnEvent treats every header click as a fresh sort.
	// The Table never reorders Rows itself; SortColumn/SortAsc only
	// drive the indicator glyph, matching how Selected only drives the
	// accent highlight.
	SortColumn int
	// SortAsc is the direction of SortColumn: true draws ▲ (ascending),
	// false draws ▼ (descending). Meaningless while SortColumn is out
	// of range.
	SortAsc bool
	// OnSort fires when a Sortable header cell is clicked. col is the
	// clicked column; ascending is the NEW direction after the click
	// (clicking the already-active column toggles it, clicking a new
	// column resets to ascending). The Table updates SortColumn/SortAsc
	// itself before firing so the very next Draw shows the indicator;
	// the host is responsible for re-sorting Rows and handing them back.
	OnSort func(col int, ascending bool)

	// OnColumnResize fires whenever a separator drag (or a direct
	// SetColumnWidth call) changes a column's width. newWidth is the
	// clamped pixel width now in effect.
	OnColumnResize func(col, newWidth int)

	// Reorderable opts the Table into drag-to-reorder BODY rows: it makes
	// the Table both a DragSource (a press on a body row becomes a
	// draggable "tablerow:<index>" payload -- see DragData) and a
	// DropTarget for that same payload (see AcceptsDrop). The zero value
	// (false) is the original, pre-feature behaviour: DragData always
	// returns "", AcceptsDrop always returns false, and every drag event
	// (EventDragMove / EventDragLeave / EventDrop) is a no-op -- Draw and
	// OnEvent render/behave byte-identically to before this field
	// existed. Header-cell sort clicks and separator-drag resizes never
	// start a row drag regardless of this flag -- only a press that lands
	// on a BODY row does.
	Reorderable bool

	// OnReorder fires after a successful drop reorders Rows in place:
	// from is the row's index BEFORE the move, to is where it now sits.
	// Nil-guarded -- a host that doesn't care to be notified simply
	// leaves it unset.
	OnReorder func(from, to int)
	// contains filtered or unexported fields
}

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). When MultiSelect is true every row in the multi-row selection set paints the same way, not just Selected (which keeps acting as the anchor for Shift-range clicks) -- see MultiSelect + SelectedRows.

The widget is content-only: it never reorders Rows itself. Header clicks + separator drags are surfaced through OnSort/OnColumnResize so the host (which owns the data model) can re-sort Rows or persist a new column width, then hand the Table back its updated state.

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) A11y added in v0.40.0

func (t *Table) A11y() A11yInfo

A11y reports the Table as a grid. Value names the selected row in single-select mode, or a "N rows selected" count while MultiSelect is on.

func (*Table) AcceptsDrop added in v0.37.0

func (t *Table) AcceptsDrop(payload string) bool

AcceptsDrop reports whether payload is one of this Table's own "tablerow:" drags -- true only while Reorderable is true AND the payload parses as a well-formed tablerow payload. A foreign payload (a different scheme, or garbage) is always rejected, including while Reorderable is false.

func (*Table) ClearRowSelection added in v0.37.0

func (t *Table) ClearRowSelection()

ClearRowSelection empties the multi-row selection set.

func (*Table) ColumnSeparatorAt added in v0.36.0

func (t *Table) ColumnSeparatorAt(localX int) int

ColumnSeparatorAt returns the 0-based index of the separator under localX (a Table-local x coordinate) -- the separator between column i and column i+1 -- within tableSeparatorHitTolerance pixels, or -1 if localX is not near any separator. A single-column (or empty) Table has no separators and always returns -1.

func (*Table) DragData added in v0.37.0

func (t *Table) DragData() string

DragData reports the drag payload for the body row currently pressed (see dragRow) -- "tablerow:<index>" -- while Reorderable is true and a body row was actually the most recent press; otherwise "". A stale dragRow left over after Rows shrinks collapses to "" the same defensive way Draw collapses an out-of-range Selected.

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.

func (*Table) IsRowSelected added in v0.37.0

func (t *Table) IsRowSelected(i int) bool

IsRowSelected reports whether row i is a member of the multi-row selection set. A negative i is always false -- mirrors how every other row/column index in this file collapses an invalid value instead of indexing into (or panicking on) the underlying map/slice. It answers from the raw set regardless of MultiSelect; only Draw and OnEvent gate their use of it on MultiSelect being true.

func (*Table) OnEvent added in v0.36.0

func (t *Table) OnEvent(ev Event)

OnEvent implements header-click sorting, separator drag-resize, and (while MultiSelect is true) body-row multi-selection. The toolkit's event model is click-only (see Paned): a resize drag begins on an EventClick that lands on a separator (ColumnSeparatorAt), is driven tick-by-tick by EventMouseDrag while the button stays down, and ends on EventMouseUp -- the same grab/move/release state machine RangeSlider uses for its thumbs. A click that lands on a header cell instead of a separator sorts that column (if Sortable).

A click below the header row is a header/sort/resize no-op -- it falls through to the body-row branch instead. With MultiSelect false that branch is itself a no-op (the original, selection-free behaviour); with MultiSelect true a plain click selects only that row and moves the Selected anchor to it, Ctrl toggles the row without moving the anchor, and Shift selects the inclusive range between the anchor and the clicked row (also without moving the anchor, so repeated Shift-clicks keep ranging from the same origin). A click past the last row (rowAt returns -1) is ignored.

func (*Table) ScrollBy added in v0.37.0

func (t *Table) ScrollBy(delta int)

ScrollBy adjusts ScrollRow by delta rows (positive scrolls down, negative scrolls up), clamped the same way as ScrollTo. A mouse wheel or arrow-key handler calls this directly.

func (*Table) ScrollTo added in v0.37.0

func (t *Table) ScrollTo(row int)

ScrollTo sets ScrollRow to row, clamped into [0, maxScrollRow()] -- the direct, host-callable entry point a scrollbar drag or a PageUp/PageDown key handler drives, mirroring how SetColumnWidth is the direct entry point a separator drag drives.

func (*Table) SelectRowRange added in v0.37.0

func (t *Table) SelectRowRange(a, b int)

SelectRowRange replaces the selection with the inclusive range between a and b -- callers may pass either endpoint first, matching how a Shift-click can land above OR below the anchor. A negative endpoint clamps to 0 (so an anchor of -1, "nothing selected yet", still yields a sane from-the-top range instead of an empty one); if both endpoints are negative the resulting selection is empty.

func (*Table) SelectedRows added in v0.37.0

func (t *Table) SelectedRows() []int

SelectedRows returns every selected row index in ascending order, or nil if nothing is selected. The slice is a fresh copy -- mutating it has no effect on the Table's selection state.

func (*Table) SetColumnWidth added in v0.36.0

func (t *Table) SetColumnWidth(col, w int)

SetColumnWidth pins column col to a fixed pixel width w (clamped to tableMinColumnWidth), then fires OnColumnResize with the clamped value. Like a Paned's MoveHandle, this is the direct, host-callable entry point a drag handler (internal or external) drives; an out-of-range col is a no-op. Setting a width converts an "auto" column into a fixed one, exactly as dragging a Paned's handle turns its 50/50 default into an explicit Position.

func (*Table) SetRowSelection added in v0.37.0

func (t *Table) SetRowSelection(rows ...int)

SetRowSelection replaces the current selection with exactly rows. Negative entries are dropped; calling it with no arguments (or with only negative ones) clears the selection, same end state as ClearRowSelection.

func (*Table) ToggleRowSelect added in v0.37.0

func (t *Table) ToggleRowSelect(i int)

ToggleRowSelect flips row i's membership in the selection set -- selecting it if absent, deselecting it if present. A negative i is a no-op.

type TableColumn added in v0.7.0

type TableColumn struct {
	Title string
	Width int // pixels; 0 = auto (equal share of remaining space)
	// Align controls horizontal placement of BOTH the header title and
	// every body cell in this column. The zero value (AlignLeft) keeps
	// the original left-justified behaviour; AlignRight is the natural
	// choice for numeric columns, AlignCenter for short status flags.
	Align Align
	// Sortable opts this column into header-click sorting. The zero
	// value (false) makes a header click a no-op, so existing callers
	// that never set it keep the original passive-viewer behaviour.
	Sortable bool
}

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 TextDirection added in v0.43.0

type TextDirection int

TextDirection selects the base paragraph direction the toolkit uses when it reorders logical text into visual order before laying glyphs strictly left-to-right (see visualText). The zero value, DirLTR, is the default, and under it pure left-to-right text (Latin, digits, CJK) is reordered as a no-op — the visual order equals the logical order byte-for-byte, so existing rendering is unchanged.

const (
	// DirLTR forces a left-to-right base level. All-LTR text is unchanged.
	DirLTR TextDirection = iota
	// DirRTL forces a right-to-left base level, so neutral runs and trailing
	// whitespace resolve towards the right.
	DirRTL
	// DirAuto derives the base level from the first strong character of the
	// text (Unicode rules P2/P3), defaulting to left-to-right.
	DirAuto
)

func CurrentTextDirection added in v0.43.0

func CurrentTextDirection() TextDirection

CurrentTextDirection returns the active base text direction.

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
	// contains filtered or unexported fields
}

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) A11y added in v0.40.0

func (t *TextView) A11y() A11yInfo

A11y reports the TextView as a textbox carrying its full buffer text.

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 and, when non-empty, writes it to the toolkit's global Clipboard (see clipboard.go) so it can be pasted into any other text widget. Leaves the buffer untouched. An empty selection is a no-op on the clipboard (mirrors a Ctrl+C-with-nothing-selected not clobbering whatever was copied before).

func (*TextView) CutSelection

func (t *TextView) CutSelection() string

CutSelection returns the selected text, writes it to the global Clipboard (when non-empty) + 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. The whole operation -- selection removal + insertion -- is a single undo step. Callers that want to paste the toolkit's global Clipboard contents pass ClipboardText() (this is what the Ctrl+V key path does); Paste itself stays a plain "insert this text" primitive so callers can also use it to insert arbitrary programmatic text.

func (*TextView) Redo added in v0.38.0

func (t *TextView) Redo()

Redo re-applies the most recently undone mutation, pushing the current state back onto the undo stack. No-op when there is nothing to redo.

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").

func (*TextView) Undo added in v0.38.0

func (t *TextView) Undo()

Undo restores the buffer + cursor + selection to the state before the most recent mutation, pushing the current state onto the redo stack. No-op when there is nothing to undo.

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.

func WhiteSurDark added in v0.9.3

func WhiteSurDark() *Theme

WhiteSurDark returns the WhiteSur dark palette as a Theme, the drop-in dark sibling of WhiteSurLight.

func WhiteSurLight added in v0.9.3

func WhiteSurLight() *Theme

WhiteSurLight returns the WhiteSur light palette as a Theme. It never fails (the embedded CSS is non-empty and well-formed), so unlike LoadGTKTheme it has no error return -- callers use it as a drop-in for DefaultLight.

type Timeline added in v0.9.0

type Timeline struct {
	Base
	Events []TimelineEvent
	// Horizontal runs the rail left-to-right (a process ribbon) instead of
	// top-to-bottom. The zero value (false) keeps the original vertical
	// activity-stream layout. A bool rather than the shared Orientation enum
	// because Timeline's natural default is vertical, whereas that enum's
	// zero value is Horizontal — a plain flag keeps the non-breaking default
	// unambiguous.
	Horizontal bool
}

Timeline is a vertical event log — think a GitHub PR activity stream or a Discord message list. The widget draws a 1-px vertical rail on the left, one filled square marker per event on that rail, and the event's Title (+ optional Detail) rendered to the right of the marker.

Timeline is a passive display container — hit-testing / event routing are not implemented. A caller who wants click-to-focus walks the same layout math externally.

func NewTimeline added in v0.9.0

func NewTimeline(events []TimelineEvent) *Timeline

NewTimeline constructs a Timeline carrying the given events. A nil events slice is normalised to a non-nil empty slice so downstream code (range loops, len() checks) never has to guard for nil separately.

func (*Timeline) A11y added in v0.40.0

func (t *Timeline) A11y() A11yInfo

A11y reports the Timeline as a list carrying its event count.

func (*Timeline) Draw added in v0.9.0

func (tl *Timeline) Draw(p painter.Painter, theme *Theme)

Draw paints the surface fill, the vertical rail line, one marker per event and each event's Title (+ optional Detail). The rail is painted BEFORE the markers so a marker overwrites the rail pixel where they intersect, giving the marker its full square silhouette without a separate clipping pass.

type TimelineEvent added in v0.9.0

type TimelineEvent struct {
	Title  string
	Detail string
	Kind   TimelineKind
}

TimelineEvent is one row in a Timeline's Events slice. Title is the always-visible headline; Detail is an optional second line rendered underneath in the dim Border ink (matching HeaderBar's subtitle convention). Kind drives the marker colour.

type TimelineKind added in v0.9.0

type TimelineKind int

TimelineKind selects the semantic colour of a timeline event's marker square. TimelineDefault reuses the theme's Accent so a neutral event matches the app's palette; the other three carry fixed shades — green for success, amber for warning, red for error — reusing the exact RGB tuples Alert already ships, so an Alert banner and a Timeline row read as the same colour language.

const (
	// TimelineDefault is a neutral event. Marker fill = Theme.Accent.
	TimelineDefault TimelineKind = iota
	// TimelineSuccess flags a completed step ("Deploy OK"). Green.
	TimelineSuccess
	// TimelineWarning flags a non-fatal event ("High latency"). Amber.
	TimelineWarning
	// TimelineError flags a failure ("Build failed"). Red.
	TimelineError
)

type Toast added in v0.8.0

type Toast struct {
	Base
	Text    string
	Kind    ToastKind
	Visible bool

	// Life is the number of Tick() calls remaining before the toast
	// auto-hides. The zero value is a sentinel meaning "sticky": Tick
	// is a no-op until the host assigns a positive Life. When Life is
	// positive, each Tick decrements it; when the countdown reaches
	// zero Visible is cleared.
	Life int

	// ActionLabel, when non-empty, arms a small action button rendered
	// right-aligned inside the pill (e.g. "Undo") and makes OnEvent
	// route clicks landing in that button to Action. Empty (the zero
	// value) means "no action" -- Draw + AnchorIn behave exactly as a
	// pre-action Toast.
	ActionLabel string
	// Action is invoked when the action button is clicked. Nil-safe:
	// clicking the button still dismisses the toast when Action is nil.
	Action func()
}

Toast is a short-lived, self-dismissing pill that slides in over the app's normal frame, holds for a few ticks, then hides itself. Distinct from Notification in three ways:

  1. Toast carries a Kind (like Alert) so the pill's fill colour conveys severity at a glance; Notification is always Accent.
  2. Toast's Life = 0 sentinel means "sticky" (do not auto-hide), letting a host post a persistent pill without a matching Life-budget assignment.
  3. Toast is designed to STACK: several Toast values can share the same host, each Bounds()'d to its own row; the host mutates Visible + Life directly and iterates Tick over the collection.

The host drives Life via Tick() from its own animation loop (typically a rAF tick).

A Toast may also carry a single action ("Copied — Undo"): set ActionLabel + Action to render a small button inside the pill's right edge. Leaving ActionLabel empty (the zero value) opts out -- the pill renders + sizes exactly as a plain message toast.

func NewToast added in v0.8.0

func NewToast(text string, kind ToastKind) *Toast

NewToast builds a hidden Toast with the given text + kind. The host sets Visible=true (typically via a Show helper it wraps around the widget) + assigns Life to arm the auto-dismiss countdown.

func (*Toast) A11y added in v0.40.0

func (t *Toast) A11y() A11yInfo

A11y reports the Toast as a status region named by its message.

func (*Toast) AnchorIn added in v0.33.0

func (t *Toast) AnchorIn(host Rect, corner Corner, index int)

AnchorIn sizes the toast to its Text (+ action button, when present) and positions it at corner of host, stacked at row index (0 = the row nearest the docked edge). Top corners stack downward, bottom corners upward, so a host can lay out a column of toasts by calling AnchorIn once per visible toast with an increasing index.

func (*Toast) Draw added in v0.8.0

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

Draw paints the pill when Visible. Filled Kind-coloured panel with a 1-px Border stroke; Text in the accent-inverted ink so it stays legible against every Kind's face. When ActionLabel is set, a 1-px Border divider + the action label (same accent-inverted ink) are painted right-aligned inside the pill. Nothing drawn when hidden.

func (*Toast) OnEvent added in v0.36.0

func (t *Toast) OnEvent(ev Event)

OnEvent runs Action + hides the toast when a click lands inside the action button; a click anywhere else in the pill (or when ActionLabel is empty) is a no-op. ev.X is widget-local, matching SplitButton's arrow-slot convention. Action is nil-checked, so an action-less callback still dismisses the toast on click.

func (*Toast) Tick added in v0.8.0

func (t *Toast) Tick()

Tick decrements Life by 1 when Life is positive. When the countdown reaches 0 the toast auto-hides. Life == 0 is a sticky sentinel and leaves Visible untouched, so a host may post a persistent toast by leaving Life at its zero value.

type ToastKind added in v0.8.0

type ToastKind int

ToastKind selects the semantic colour of a Toast pill. ToastInfo reuses the theme's Accent (the same tint used by focus rings + the Notification banner); the other three carry hard-coded shades tuned for meaning -- green for success, amber for warning, red for error -- mirroring AlertKind so a Toast and an Alert with the same kind read as visual siblings.

const (
	// ToastInfo is a neutral heads-up ("Copied to clipboard"). Rendered
	// in Theme.Accent so it matches the app's own accent colour.
	ToastInfo ToastKind = iota
	// ToastSuccess signals a completed operation ("File uploaded"). Green.
	ToastSuccess
	// ToastWarning flags a non-fatal issue ("Battery low"). Amber.
	ToastWarning
	// ToastError signals a failure the user must address ("Network
	// unreachable"). Red.
	ToastError
)

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) A11y added in v0.40.0

func (t *ToggleButton) A11y() A11yInfo

A11y reports the ToggleButton as a button carrying its pressed 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
	// Orientation lays the buttons out left-to-right (Horizontal, the zero
	// value) or top-to-bottom (Vertical). A vertical toolbar draws its
	// separators as horizontal dividers, so the same Items slice works as a
	// side rail without change.
	Orientation Orientation
	// 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) A11y added in v0.40.0

func (t *Toolbar) A11y() A11yInfo

A11y reports the Toolbar as a toolbar carrying its item count.

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
	Placement TooltipPlacement
	Anchor    Rect // widget the tooltip belongs to
}

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 + padding; positioned on the side of the anchor chosen by Placement (below by default).

func NewTooltip

func NewTooltip(text string) *Tooltip

NewTooltip builds a hidden tooltip with the given text.

func (*Tooltip) A11y added in v0.40.0

func (t *Tooltip) A11y() A11yInfo

A11y reports the Tooltip as a tooltip carrying its 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 TooltipPlacement added in v0.27.0

type TooltipPlacement int

TooltipPlacement selects which side of the anchor the bubble sits on. Below is the zero value (the original behaviour).

const (
	// PlaceBelow puts the bubble under the anchor (the default).
	PlaceBelow TooltipPlacement = iota
	// PlaceAbove puts the bubble over the anchor.
	PlaceAbove
	// PlaceLeft puts the bubble to the anchor's left.
	PlaceLeft
	// PlaceRight puts the bubble to the anchor's right.
	PlaceRight
)

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 TreeTable added in v0.42.0

type TreeTable struct {
	Base
	// Columns are the header cells. A zero Width means "auto" — the
	// column claims an equal share of whatever pixel budget is left
	// after the fixed-Width columns, same rule as Table.Columns.
	Columns []TreeTableColumn
	// Root holds the top-level nodes (a forest, not a single root, so a
	// host can list multiple top-level entries without a synthetic
	// invisible parent).
	Root []*TreeTableNode
	// Selected is the node highlighted with Theme.Accent, or nil for no
	// selection.
	Selected *TreeTableNode

	// ScrollRow is the index, into the current visible-flattened node
	// list, of the top row Draw paints. It's clamped on every Draw /
	// OnEvent to [0, max(0, visibleCount-windowRows)], so it's always
	// safe to set directly; prefer ScrollTo/ScrollBy for arithmetic on
	// it. When the whole tree fits in Bounds().H, ScrollRow==0 paints
	// every row.
	ScrollRow int
	// contains filtered or unexported fields
}

TreeTable renders a Table-shaped grid whose body rows form a TREE: a fixed header row of column titles sits above body rows built from the visible (expand-aware) flattening of Root, exactly like TreeView flattens its single Root node. The first column carries the tree structure (indentation + a ▸/▾ disclosure glyph); the rest are plain cells.

Rendering is windowed (virtualized) the same way TreeView is: only the rows that fit inside Bounds().H (below the header) are ever painted, no matter how many nodes are visible in the flattened order. See ScrollRow.

Use for file managers, outline-grids, or anything that's "a Table, but the rows nest".

func NewTreeTable added in v0.42.0

func NewTreeTable(cols []TreeTableColumn, root []*TreeTableNode) *TreeTable

NewTreeTable builds a TreeTable with the given columns + forest of root nodes.

func (*TreeTable) Draw added in v0.42.0

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

Draw paints the header, then the rows in the current scroll window: flattened nodes [ScrollRow, ScrollRow+bodyVisibleRows()). The first column is indented by depth + prefixed with a ▸/▾ disclosure glyph when the node has Children (identical shape to TreeView's chevron); the rest are plain cells, aligned per column exactly like Table. A right-edge scrollbar is painted only when the flattened list overflows the window.

func (*TreeTable) OnEvent added in v0.42.0

func (t *TreeTable) OnEvent(ev Event)

OnEvent: a click on the first column's disclosure glyph toggles that node's Expanded (re-clamping ScrollRow, since toggling can shrink or grow the visible row count out from under it); a click anywhere else on a row selects the node. Y is mapped through ScrollRow back to the flattened index it targets, exactly like TreeView.OnEvent.

func (*TreeTable) ScrollBy added in v0.42.0

func (t *TreeTable) ScrollBy(delta int)

ScrollBy adjusts ScrollRow by delta, with the same clamping as ScrollTo. Negative delta scrolls up.

func (*TreeTable) ScrollTo added in v0.42.0

func (t *TreeTable) ScrollTo(row int)

ScrollTo sets ScrollRow to row, clamped against the tree's current flattened shape + the widget's bounds.

type TreeTableColumn added in v0.42.0

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

TreeTableColumn is one column definition for a TreeTable header — the same shape as TableColumn (title + optional fixed pixel Width + Align), reused here so a host that already knows Table's column model doesn't have to learn a second one.

type TreeTableNode added in v0.42.0

type TreeTableNode struct {
	Cells    []string
	Children []*TreeTableNode
	Expanded bool
}

TreeTableNode is one row of a TreeTable. Cells[0] is rendered in the first (tree) column, indented by the node's depth and prefixed with a disclosure glyph when it has Children; Cells[1:] render as plain, column-aligned text in the remaining columns exactly like a Table row. A node shorter than len(Columns) renders blank trailing cells, mirroring Table.Rows' own "short row" tolerance.

type TreeView

type TreeView struct {
	Base
	Root       *TreeNode
	Selected   *TreeNode
	OnActivate func(node *TreeNode)
	RowHeight  int // default 18

	// ScrollRow is the index, into the current visible-flattened node
	// list, of the top row Draw paints. It's clamped on every Draw /
	// OnEvent to [0, max(0, visibleCount-windowRows)], so it's always
	// safe to set directly; prefer ScrollTo/ScrollBy for arithmetic on
	// it. When the whole tree fits in Bounds().H, ScrollRow==0 paints
	// byte-identically to a TreeView with no virtualization.
	ScrollRow int

	// MultiSelect enables a multi-node selection set on top of the
	// single-node Selected anchor. When false (the default), TreeView
	// behaves exactly as before: only Selected is tracked/painted.
	MultiSelect bool
	// 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.

Rendering is windowed (virtualized): only the rows that fit inside Bounds().H are ever painted, no matter how many nodes are visible in the flattened (expand-aware) order. See ScrollRow.

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) A11y added in v0.40.0

func (t *TreeView) A11y() A11yInfo

A11y reports the TreeView as a tree. Value is the selected node's label in single-select mode, or a "N selected" count while MultiSelect is on.

func (*TreeView) ClearSelection added in v0.37.0

func (t *TreeView) ClearSelection()

ClearSelection empties the multi-select set. Selected (the anchor) is left untouched.

func (*TreeView) Draw

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

Draw paints the rows in the current scroll window: flattened nodes [ScrollRow, ScrollRow+windowRows). When the whole tree fits inside Bounds().H, that window covers every row + ScrollRow clamps to 0, so painting is byte-identical to an unvirtualized TreeView. When it doesn't fit, a right-edge scrollbar track+thumb is painted too.

func (*TreeView) IsSelected added in v0.37.0

func (t *TreeView) IsSelected(n *TreeNode) bool

IsSelected reports whether n is part of the multi-select set. It only reflects MultiSelect state; when MultiSelect is false it always returns false (single-select uses Selected directly).

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. Y is mapped through ScrollRow back to the flattened index it targets.

func (*TreeView) ScrollBy added in v0.37.0

func (t *TreeView) ScrollBy(delta int)

ScrollBy adjusts ScrollRow by delta, with the same clamping as ScrollTo. Negative delta scrolls up.

func (*TreeView) ScrollTo added in v0.37.0

func (t *TreeView) ScrollTo(row int)

ScrollTo sets ScrollRow to row, clamped against the tree's current flattened shape + the widget's bounds.

func (*TreeView) SelectRange added in v0.37.0

func (t *TreeView) SelectRange(a, b *TreeNode)

SelectRange selects every node between a + b (inclusive) over the currently-visible flattened node order (collapsed subtrees are excluded, matching what the user can actually see). If either node isn't currently visible, SelectRange is a no-op.

func (*TreeView) SelectedNodes added in v0.37.0

func (t *TreeView) SelectedNodes() []*TreeNode

SelectedNodes returns the multi-selected nodes in visible (pre-order, expanded-aware) traversal order. Empty when MultiSelect is false or nothing is selected.

func (*TreeView) SetSelection added in v0.37.0

func (t *TreeView) SetSelection(nodes ...*TreeNode)

SetSelection replaces the multi-select set with nodes. The last node (if any) becomes the anchor (Selected).

func (*TreeView) ToggleSelect added in v0.37.0

func (t *TreeView) ToggleSelect(n *TreeNode)

ToggleSelect flips n's membership in the multi-select set.

type Tween added in v0.35.0

type Tween struct {
	// From is the starting value.
	From float64
	// To is the ending value.
	To float64
	// Duration is the total number of ticks the tween takes to complete.
	// A Duration <= 0 makes the tween immediately Done, at To.
	Duration int
	// Ease shapes the progress curve. A nil Ease behaves as Linear.
	Ease Easing
	// contains filtered or unexported fields
}

Tween animates a scalar value from From to To over Duration ticks, using Ease to shape the progress curve. Advance it once per frame/tick via Tick, or read the current value without advancing via Value.

func NewTween added in v0.35.0

func NewTween(from, to float64, duration int, ease Easing) *Tween

NewTween creates a Tween animating from from to to over duration ticks using ease. A nil ease defaults to Linear. A duration <= 0 produces a Tween that is immediately Done, reporting to as its Value.

func (*Tween) Done added in v0.35.0

func (tw *Tween) Done() bool

Done reports whether the tween has reached its Duration.

func (*Tween) Reset added in v0.35.0

func (tw *Tween) Reset()

Reset restarts the tween from its beginning (elapsed = 0).

func (*Tween) Tick added in v0.35.0

func (tw *Tween) Tick() float64

Tick advances the tween by one tick, clamping elapsed progress at Duration, and returns the current eased value between From and To.

func (*Tween) Value added in v0.35.0

func (tw *Tween) Value() float64

Value returns the current eased value between From and To without advancing the tween.

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, each a flex share of the height or a fixed height, filling the box's width.

func NewVBox

func NewVBox() *VBox

NewVBox constructs an empty VBox.

func (*VBox) AddFixed added in v0.50.0

func (v *VBox) AddFixed(w Widget, size int)

AddFixed adds w with a fixed height in pixels (clamped to ≥0).

func (*VBox) AddFlex added in v0.50.0

func (v *VBox) AddFlex(w Widget, flex int)

AddFlex adds w with an explicit flex weight (clamped to ≥1).

func (*VBox) Append

func (v *VBox) Append(w Widget)

Append adds w with flex weight 1 (an equal share of the height).

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 forwards to the first child containing the event point.

func (*VBox) SetBounds

func (v *VBox) SetBounds(r Rect)

SetBounds positions the VBox + stacks its children down the height.

type ViewSwitcher added in v0.8.0

type ViewSwitcher struct {
	Base
	Views    []string
	Current  int
	OnChange func(i int)
}

ViewSwitcher is a libadwaita/GTK-style horizontal segmented tab picker: an evenly-divided strip of same-width segments where exactly one is highlighted as the active view. Clicking a segment swaps Current and fires OnChange with the new index.

The strip's background is Theme.SurfaceAlt; the active segment paints in Theme.Accent with the accent-inverted ink (theme.Extra["OnAccent"] with a Theme.Background fallback, matching what Button, ListBox, TreeView, and Table already do). A 1-pixel Theme.Border line sits along the strip's bottom edge so the switcher reads as a discrete band above the switched content.

A ViewSwitcher with no Views paints only the background + bottom border; clicks are ignored. This lets a caller assemble the widget before it knows which views the app will surface without tripping a nil-Views guard downstream.

func NewViewSwitcher added in v0.8.0

func NewViewSwitcher(views []string, current int) *ViewSwitcher

NewViewSwitcher constructs a ViewSwitcher over views with the initial highlighted segment at current. current is clamped into the [0, len(views)-1] range, or forced to 0 when views is empty, so the widget is never in a hard-to-reason "index out of range" state.

func (*ViewSwitcher) A11y added in v0.40.0

func (v *ViewSwitcher) A11y() A11yInfo

A11y reports the ViewSwitcher as a tablist named by its current view.

func (*ViewSwitcher) Draw added in v0.8.0

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

Draw paints the strip background, then each segment with the active one highlighted in Theme.Accent, then the 1-pixel bottom border. Segments share the same width via integer division of Bounds.W by len(Views); any left-over pixel column on the right remains SurfaceAlt (this matches how HeaderBar's title strip tolerates non-integer central strips).

func (*ViewSwitcher) OnEvent added in v0.8.0

func (v *ViewSwitcher) OnEvent(ev Event)

OnEvent handles a click by locating which segment the X coordinate lands on and updating Current + firing OnChange. Non-click events, clicks with an empty Views slice, clicks on a zero-width strip and clicks that fall outside every segment are all no-ops.

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.

type Wizard added in v0.35.0

type Wizard struct {
	Base
	Steps    []WizardStep
	Current  int
	OnFinish func()
}

Wizard is a multi-step "Assistant" flow: a Steps strip across the top tracks progress through Steps, the current step's Body fills the middle, and a Back / Next-or-Finish button row sits at the bottom. Next is disabled (a no-op) whenever the current step's CanAdvance reports false; Back is disabled on the first step. Advancing past the last step swaps the Next label to "Finish" and invokes OnFinish instead of moving further.

func NewWizard added in v0.35.0

func NewWizard(steps []WizardStep) *Wizard

NewWizard constructs a Wizard over the given steps, starting on the first one (Current == 0).

func (*Wizard) A11y added in v0.40.0

func (w *Wizard) A11y() A11yInfo

A11y reports the Wizard as a group carrying its current step's title.

func (*Wizard) Back added in v0.35.0

func (w *Wizard) Back()

Back moves to the previous step, clamped at 0 (a no-op on the first step).

func (*Wizard) Draw added in v0.35.0

func (w *Wizard) Draw(p painter.Painter, theme *Theme)

Draw paints the Steps strip, the active step's Body, and the Back/Next-or-Finish button row. A Wizard with no Steps paints nothing (there is nothing to show progress through). Back renders in ButtonSecondary tone (dimmed) on the first step; Next/Finish renders dimmed whenever the active step's CanAdvance forbids moving on.

func (*Wizard) Next added in v0.35.0

func (w *Wizard) Next()

Next advances to the following step when the current one's CanAdvance allows it. On the last step it instead invokes OnFinish (if set) and leaves Current unchanged — Next() is the "Finish" action once there is nowhere further to advance to.

func (*Wizard) OnEvent added in v0.35.0

func (w *Wizard) OnEvent(ev Event)

OnEvent routes a click on the Back button to Back(), a click on the Next/Finish button to Next() (both no-ops when disabled — Current == 0 for Back, a failing CanAdvance for Next/Finish), and any other click that lands in the body area — or any non-click event — to the active step's Body, translated into its local coordinate space (the same pattern Notebook.OnEvent uses in notebook.go). A click that lands in neither the buttons nor the body (e.g. the empty strip band) is ignored.

type WizardStep added in v0.35.0

type WizardStep struct {
	Title      string
	Body       Widget
	CanAdvance func() bool // nil = always allowed
}

WizardStep is one page of a Wizard: a Title shown in the top Steps strip, a Body widget shown in the content area while the step is active, and an optional CanAdvance gate. CanAdvance is consulted before the Wizard lets the user move past this step; a nil CanAdvance means "always allowed" (the common case — a step with no validation).

Directories

Path Synopsis
rougelex module

Jump to

Keyboard shortcuts

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