toolkit

package module
v0.183.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: BSD-3-Clause Imports: 20 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, dashboard/data widgets, charts and desktop-shell pieces — around 30 kLoC of widget code with roughly 40 kLoC of tests.

Status

v0.109.0 — broad GTK 4 / DaisyUI / desktop-shell coverage. The widget set has grown well past the early parity passes into a full application toolkit: ~150 widget types across inputs, containers, overlays, structural rows, semantic banners, a dashboard/data suite, a chart family, desktop-shell pieces (status area, wallpaper, command palette), and a loading-screen Skeleton family. A declarative Container + swappable Layout model (FitLayout, BoxLayout, BorderLayout, CardLayout, FlowLayout) sits under the convenience containers (HBox/VBox/Grid/Frame/Dock/ Border).

~150 widget types + 10 stock icons, ~30 kLoC 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, Accessible/A11yInfo, Focusable, Measurer
Text bitmap 5x7 + anti-aliased NewTrueTypeFont, one-call AA default UseOpenTypeText, global SetFont + per-widget Base.Font, DrawText, TextWidth, Label
Action Button, ToggleButton, CheckButton, RadioButton + RadioGroup, Switch, SplitButton, IconButton, CycleButton, Chip, SegmentedBar
Input Entry, TextView + Selection + IME preview + optional Highlighter (syntax spans) + ShowLineNumbers gutter, SpinButton, Scale, RangeSlider, SearchEntry, TagField, ComboBox, FormField, Validate/Rule
Terminal TerminalView (Cols×Rows TermCell grid, block cursor, wrap/scroll, per-cell FG/BG)
Selection ListBox, TreeView, DropDown
Containers Container + swappable Layout (FitLayout, BoxLayout, BorderLayout, CardLayout, FlowLayout); convenience wrappers HBox, VBox, Grid, Frame, Dock, Border, Stack, Overlay, Paned, Expander, Accordion; declarative Node builder + ViewController (LookupAs)
Tabs Notebook, ViewSwitcher, Carousel
Scroll ScrollView, Scrollbar
Feedback ProgressBar, ProgressCircle, LevelBar, Spinner, Image, Tooltip, Notification, Toast, Banner, Alert, Badge, LoadMask, Backdrop, FocusRing
Loading Skeleton (Text/Rect/Circle/Avatar/Block kinds, shimmer via SetPhase), SkeletonGroup, NewSkeletonCard, NewPageSkeleton
Structure Card, HeaderBar, ActionRow, Breadcrumbs, Steps, Avatar, Rating, Stat, Kbd, ChatBubble, Diff, Pagination, DropZone
Navigation Menu + MenuItem.Shortcut, MenuBar + Alt+letter, ContextMenu, Popover, CommandPalette, Dialog, MessageDialog, Wizard
Window Window, WindowDecoration, DecoButton (client-side decorations)
Bars Toolbar, PagingToolbar, Statusbar, 10 stock DrawIcon* helpers
Composite FileChooser, ColorChooser, ColorPicker, FontChooser, Calendar, DatePicker, DateRangePicker, TimePicker, MarkdownView, MarkdownEditor
Data suite Table (cell edit, frozen columns, group rows, aggregates, row-expanders), TreeTable, PropertyGrid, Kanban (drag cards), Gantt (drag/resize bars), Agenda/AgendaCalendar event calendar (week/month/quarter/year, colour-coded calendars, sidebar, inline editor)
Charts LineChart, BarChart, PieChart, AreaChart, ScatterChart, RadarChart, Gauge, Sparkline
Shell StatusIcon, StatusArea, Wallpaper, Thumbnail
Motion Easing/Tween (Linear, EaseIn*/Out*/InOut*), GestureRecognizer
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).
  • Skeleton (loading placeholders) — shape kinds Text (rounded bars, tunable LineH/LineGap/LastFrac/Radius), Rect (rounded media block), Circle (avatar), plus the original Avatar/Block swap-parity kinds. Optional diagonal shimmer band (a lighter tint over Theme.SurfaceAlt, working in light + dark) driven by SetPhase(t) advanced per frame. SkeletonGroup composes primitives; presets NewSkeletonCard(bounds) (avatar + 2-line header + media block) and NewPageSkeleton(bounds) (top bar + paragraph groups + image blocks, the webengine browser client's loading screen).
  • 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. Since v0.77, UseOpenTypeText() turns on anti-aliased, shaped text for the whole UI in one call — it installs the bundled Atkinson Hyperlegible face (from github.com/go-opentype/fonts, no extra imports, js/wasm-safe) as the active font, so consumers get crisp AA/multi-script glyphs without sourcing a face of their own. Use UseOpenTypeTextSize(px) for a specific size, or DefaultOpenTypeFont(px) to compose the default face into a NewFallbackFont chain (add a CJK/Arabic face) before SetFont. The 5x7 bitmap stays the compiled-in default (so existing pixel/metric tests keep their geometry); AA is an explicit opt-in, and SetFont(nil) restores the bitmap.
  • 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 (
	// AgendaHeaderH is the pixel height of the day-name header row (also the
	// weekday header in the month view).
	AgendaHeaderH = 24
	// AgendaHourH is the pixel height of one hour row in the grid.
	AgendaHourH = 32
	// AgendaGutterW is the pixel width of the left hour-label gutter.
	AgendaGutterW = 48
	// AgendaDayCellH is the pixel height of one day cell in the month view.
	AgendaDayCellH = 56
	// AgendaMiniMonthGap is the pixel gap between mini month grids in the
	// quarter and year views.
	AgendaMiniMonthGap = 12
)

Agenda sizing constants, exported like TableRowHeight / GanttHeaderH so a host can measure the widget before it has a surface: scaled(AgendaHeaderH) + hours* AgendaHourH gives the natural height and scaled(AgendaGutterW) is the fixed hour-label gutter width.

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 (
	// AppDockItemW / AppDockItemH are one item's resting width and height.
	AppDockItemW = 120
	AppDockItemH = 28
	// AppDockGap is the spacing between adjacent items (and the end padding).
	AppDockGap = 4
	// AppDockGlyphPx is the icon's side length inside a resting item.
	AppDockGlyphPx = 18
	// AppDockPadX is the inset from an item's left edge to its glyph.
	AppDockPadX = 8
	// AppDockLabelGap is the gap between the glyph and the label.
	AppDockLabelGap = 8
)

AppDock geometry, in logical pixels (each routed through the metric scale so the bar tracks the display like every other widget).

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 (
	// BrowserTabStripH is the tab-strip row height (shown only in MultiTab with
	// at least two tabs).
	BrowserTabStripH = 24
	// BrowserToolbarH is the Back/Forward/Reload/address toolbar row height. It
	// is deliberately tall enough that a button box (BrowserToolbarH - 2*PadY)
	// is a comfortable ~34px square at scale 1 — a real, clickable icon button
	// rather than a cramped text chip. Multiply by Browser.Scale on HiDPI hosts.
	BrowserToolbarH = 44
	// BrowserProgressH is the loading bar height across the content top.
	BrowserProgressH = 3
	// BrowserPadX / BrowserPadY are the toolbar's inner insets. PadY is generous
	// so the buttons sit centred in the taller row with breathing room above and
	// below.
	BrowserPadX = 4
	BrowserPadY = 5
	// BrowserBtnGap is the gap between toolbar buttons.
	BrowserBtnGap = 4
	// BrowserBtnPad is the horizontal text inset inside a toolbar button / the
	// address field.
	BrowserBtnPad = 6
	// BrowserMaxTabs caps how many tabs MultiTab keeps; opening past it evicts
	// the oldest.
	BrowserMaxTabs = 12
	// BrowserScrollStep is the content pixels scrolled per wheel row.
	BrowserScrollStep = 24
	// BrowserTabGap is the gap between tab pills.
	BrowserTabGap = 2
	// BrowserTabCloseW is the width of a tab pill's × close box.
	BrowserTabCloseW = 14
)

Chrome sizing constants.

View Source
const (
	// BrowserMinZoom / BrowserMaxZoom clamp the page-zoom factor.
	BrowserMinZoom = 0.5
	BrowserMaxZoom = 3.0
)

Page-zoom bounds. Zoom scales the already-delivered page bitmap for display only (it never re-fetches or re-renders): the content is drawn magnified or shrunk by the zoom factor, the scroll extent grows/shrinks with it, and link hit-testing maps back through the zoom.

View Source
const (
	CalendarHeaderH = 22
	CalendarCellW   = 24
	CalendarCellH   = 18
	// CalendarNavW is the width of each header prev/next arrow hit-zone, at the
	// left ("<") and right (">") ends of the header row.
	CalendarNavW = 20
)

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 (
	// CardCornerRadius is the corner rounding of a content card's frame, in
	// pixels. Matched to Badge / GalleryView so a card sits next to them cleanly;
	// a cell back-end that cannot round degrades to square corners.
	CardCornerRadius = 6
	// CardGapX is the horizontal gap between a leading glyph column (a favicon,
	// say) and the text beside it.
	CardGapX = 6
	// CardGapY is the vertical gap inserted between two stacked content blocks
	// (thumbnail / title / body / meta). Distinct from CardLineSpacing, which is
	// the tighter gap between successive lines WITHIN one wrapped text block.
	CardGapY = 4
)
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
	// ChipDotD is the diameter in pixels of the optional leading swatch
	// circle (kept modest to match the chip's compact scale); ChipDotGap
	// is the pixel gap between that swatch and the start of the Text.
	ChipDotD   = 6
	ChipDotGap = 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) LOGICAL footprint. Use [ColorPickerNaturalSize] for the
	// footprint to lay out with: at a metric scale above 1 it is larger, like
	// every other metric.
	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 (
	// GanttRowH is the pixel height of one task row.
	GanttRowH = 24
	// GanttHeaderH is the pixel height of the tick-header band.
	GanttHeaderH = 20
	// GanttLabelW is the pixel width of the left label gutter.
	GanttLabelW = 96
)

Gantt sizing constants, exported like TableRowHeight / TableHeaderHeight so a host can measure a chart before it has a surface (rows*scaled(GanttRowH) + GanttHeaderH gives the natural height; scaled(GanttLabelW) is the fixed gutter width).

View Source
const (
	// GroupChevronW is the logical width of the disclosure-chevron column.
	GroupChevronW = 16
	// GroupMemberH is the logical height of one expanded member row.
	GroupMemberH = 20
	// GroupCheckSize is the logical side length of the download checkbox.
	GroupCheckSize = 18
)
View Source
const (
	// KanbanHeaderH is the pixel height of a column's header band.
	KanbanHeaderH = 28
	// KanbanCardH is the pixel height of one card.
	KanbanCardH = 46
	// KanbanColGap is the horizontal gap between adjacent columns.
	KanbanColGap = 8
	// KanbanCardGap is the vertical gap between stacked cards (and the
	// horizontal inset of a card from its column's edges).
	KanbanCardGap = 6
	// KanbanCardPadX is the inner horizontal text inset inside a card /
	// column header.
	KanbanCardPadX = 8
	// KanbanStripeW is the pixel width of a card's left accent stripe.
	KanbanStripeW = 4
	// KanbanCardRadius is the corner radius of a card's rounded body.
	KanbanCardRadius = 6
)

Kanban layout constants, exported so a host can measure a board the same way it reads TableRowHeight / CardHeaderH.

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 (
	// DefaultPostCardThumbW / DefaultPostCardThumbH are the thumbnail column's
	// pixel size when ThumbW / ThumbH are left unset.
	DefaultPostCardThumbW = 72
	DefaultPostCardThumbH = 72
	// DefaultPostCardTitleLines caps the wrapped title when MaxTitleLines is
	// unset: a long headline shows at most this many lines before it is cut.
	DefaultPostCardTitleLines = 3
)
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 default pixel height of a SkeletonText bar.
	SkeletonLineH = 10
	// SkeletonLineGap is the default vertical gap between two 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
	// SkeletonLastFrac is the default width fraction of the last text
	// bar (60%), so the paragraph terminates naturally.
	SkeletonLastFrac = 0.6
	// SkeletonRectRadius is the default corner radius for SkeletonRect.
	SkeletonRectRadius = 6
)

Skeleton sizing + shimmer constants. Line values 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 (
	// SpreadsheetColWidth is the uniform width of a data column.
	SpreadsheetColWidth = 64
	// SpreadsheetRowHeight is the uniform height of a data row.
	SpreadsheetRowHeight = 20
	// SpreadsheetHeaderHeight is the height of the column-letter header band.
	SpreadsheetHeaderHeight = 20
	// SpreadsheetRowHeaderWidth is the width of the row-number header band.
	SpreadsheetRowHeaderWidth = 36
)

Spreadsheet cell + band metrics, in LOGICAL pixels (scaled to device pixels through scaled() at use, exactly like Table's TableRowHeight etc.).

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
	// ToastLineGap is the vertical space between stacked message lines in a
	// multi-line (Lines) toast. Irrelevant to a single-line toast.
	ToastLineGap = 2
)

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 AgendaSidebarDoubleClick = "double"

AgendaSidebarDoubleClick is the Event.Code a host tags a double-click EventClick with, mirroring StatusIconSecondary for a right-click: a click carrying this Code opens the inline rename editor on the row under it, while an ordinary click (empty Code) toggles the row's visibility. A host that does not distinguish double-clicks simply never sets it, and rows only ever toggle — the rename editor stays fully opt-in.

View Source
const AgendaSidebarRowH = 24

AgendaSidebarRowH is the pixel height of one calendar row.

View Source
const AreaFillAlpha = 90

AreaFillAlpha is the opacity (0..255) of the shaded band under each series, so overlapping bands stay legible against one another and the ground.

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

BorderSplitW is the pixel thickness of a Border splitter handle (matches PanedHandleW).

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 DefaultArticleBodyLines = 3

DefaultArticleBodyLines is the body clamp used when BodyLines is unset (zero or negative): a summary shows at most this many lines before it is cut.

View Source
const DefaultBoxSpacing = 4

DefaultBoxSpacing is the inter-child gap (in pixels) the box constructors NewHBox/NewVBox/NewBoxLayout seed into their Spacing field. Picked to match the 4-pixel rhythm the rest of the toolkit uses (Frame.Padding, the Button border inset, ...). Because the default lives in the constructor rather than the layout math, Spacing is honoured LITERALLY: a caller who wants a flush, zero-gap box sets Spacing = 0 explicitly; only negative values are clamped (to 0). Containers expose Spacing as a public field so apps can override it before the first SetBounds call.

View Source
const DefaultOpenTypeSizePx = 16

DefaultOpenTypeSizePx is the pixel size UseOpenTypeText renders the bundled default face at. It is chosen for comfortable on-screen UI text — clearly larger and more legible than the 7px bitmap default — while staying compact enough for dense chrome (window titles, dock labels, menus). Use UseOpenTypeTextSize (or DefaultOpenTypeFont) for a different size.

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 LOGICAL height of the clickable header row. Use ExpanderHeaderHeight for the height to lay out with: at a metric scale above 1 the header is taller, like every other metric.

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 FrameTitleH = 22

FrameTitleH is the pixel height of a Frame's optional title bar.

View Source
const GaugeThickness = 6

GaugeThickness is the default arc stroke width in LOGICAL pixels, used when Gauge.Thickness is left at its zero value. It leaves room inside the ring for the centred Caption on the toolkit's 5x7 font while keeping the track visually prominent.

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 LoadMaskSpinnerSize = 32

LoadMaskSpinnerSize is the pixel side of a LoadMask's centred spinner.

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 MenuMinW = 96

MenuMinW is the floor width a submenu popover sizes to (see preferredSize) so a child of very short labels still reads as a panel.

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

PagingBtnH is the pixel height of the toolbar (and each button).

View Source
const PagingBtnW = 26

PagingBtnW is the pixel width of each toolbar button.

View Source
const PagingGap = 2

PagingGap is the horizontal pixel gap between successive buttons.

View Source
const PaletteMaxRows = 12

PaletteMaxRows caps how many result rows the panel shows at once; a broader query's remaining matches are reachable by scrolling (see scroll), so the panel never grows taller than one query row plus PaletteMaxRows results.

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 are reachable by scrolling the popover (see popScroll).

View Source
const PopoverRowH = 18

PopoverRowH is the pixel height of one option row in the popover.

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

RadarRings is the number of concentric grid rings drawn between the centre and the outer edge.

View Source
const ScatterDot = 2

ScatterDot is the side (painter units) of the square marker drawn per point.

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 SourceRowDragPrefix = "sourcerow:"

SourceRowDragPrefix is the payload scheme a SourceList reorder drag carries: DragData returns this prefix followed by "<section>:<row>", and AcceptsDrop recognizes only payloads bearing it, so a foreign drag is never mistaken for a row reorder.

View Source
const SparkPad = 1

SparkPad is the inset (painter units) reserved on every edge so the line's endpoints and the tallest bar never bleed against the widget's border.

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

StatusAreaGap is the default horizontal spacing in pixels between the icons in a StatusArea when Gap is left at its zero value.

View Source
const StatusIconSecondary = "right"

StatusIconSecondary is the Event.Code a host tags a secondary (right / menu) click with so a StatusIcon can route it to OnRightClick. A primary click carries any other Code (typically ""). This mirrors how other widgets read Event.Code to distinguish an activation variant without a dedicated EventKind — the compositor sets it when the secondary mouse button (or a long-press) produced the click.

View Source
const StatusIconSize = 18

StatusIconSize is the default square dimension in pixels when Bounds() is zero-sized. 18 px sits in the 16-22 px band a desktop status area (the GNOME top-bar tray, the macOS menu-bar extras) gives each indicator, so a StatusIcon drops next to a clock or a Label without extra layout maths.

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 TableDoubleClick = "double"

TableDoubleClick is the Event.Code a host tags onto an EventClick to mark it a double-click, so a Table in EditOnDoubleClick mode can tell the second click of a double-click from a fresh single click without the toolkit growing a dedicated double-click EventKind.

View Source
const TableHeaderHeight = 24

TableHeaderHeight is the pixel height of the header row.

View Source
const TableIconSize = 16

TableIconSize is the pixel width+height of the square rect a per-row leading icon (see Table.RowIcon) is painted into -- sized to sit comfortably inside a TableRowHeight-tall body row with a little vertical breathing room.

View Source
const TableRowHeight = 22

TableRowHeight is the pixel height of one body row.

View Source
const ThumbnailLabelPad = 2

ThumbnailLabelPad is the vertical padding above + below the caption text in the label strip.

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.

View Source
const WindowTitleH = 24

WindowTitleH is the pixel height of the title-bar band.

Variables

View Source
var ErrConflict = errors.New("toolkit: chord already bound in this scope")

ErrConflict is returned by Keymap.Bind / Keymap.Rebind when the target chord is already bound to a different action in the same scope.

View Source
var ErrEmptyAction = errors.New("toolkit: empty action id")

ErrEmptyAction is returned when a bind is attempted with an empty action id.

View Source
var ErrEmptyChord = errors.New("toolkit: empty chord")

ErrEmptyChord is returned when a bind is attempted with a zero-length chord.

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 and the metric-scaled pad).

func ClipboardText added in v0.42.0

func ClipboardText() string

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

func ColorPickerNaturalSize added in v0.166.0

func ColorPickerNaturalSize() (w, h int)

ColorPickerNaturalSize is the picker's footprint in device pixels at the current MetricScale.

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 ExpanderHeaderHeight added in v0.162.0

func ExpanderHeaderHeight() int

ExpanderHeaderHeight is the header height in device pixels at the current MetricScale.

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 LookupAs added in v0.62.0

func LookupAs[T Widget](vc *ViewController, name string) (val T, ok bool)

LookupAs returns the widget tagged with Ref(name) as type T. ok is false when the name is absent or the widget is not a T — the typed lookup by name.

func MetricScale added in v0.159.0

func MetricScale() float64

MetricScale returns the current global metric scale (1.0 by default).

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 Scaled added in v0.159.0

func Scaled(v int) int

Scaled is the exported form of [scaled]: it rounds a base (logical-pixel) metric to device pixels at the current MetricScale. Sibling packages that compose these widgets (e.g. the virtual list feed) route their own fixed metrics through it so they scale in lockstep with the core widgets instead of re-deriving the rounding.

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 gives the built-in bitmap back, at whatever the current MetricScale is. 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 SetMetricScale added in v0.159.0

func SetMetricScale(f float64)

SetMetricScale sets the global metric scale. A non-positive value is ignored, so the scale never collapses metrics to zero.

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 TickTree added in v0.155.0

func TickTree(root Widget, dt float64)

TickTree advances every Animator in the tree rooted at root by dt seconds.

It descends into any widget exposing its children via [childContainer] — the same walk WalkA11y and CollectRuns use — so one call drives a whole composed UI. The root itself is ticked when it is an Animator, and the walk still descends into it (a widget can be both an Animator and a container). A nil root (or a nil child a container might yield) is skipped, so callers need not guard the tree they hand in.

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 TreeAnimating added in v0.155.0

func TreeAnimating(root Widget) bool

TreeAnimating reports whether at least one Animator in the tree rooted at root still needs frames (its Animating returns true).

It descends through [childContainer] exactly like TickTree, and short-circuits: the first still-animating widget found ends the walk, because a host only needs to know that *something* wants another frame, not how many. A nil root (or a nil child) contributes nothing, so an empty or partly-built tree simply reports false.

func UseOpenTypeText added in v0.77.0

func UseOpenTypeText() error

UseOpenTypeText switches the toolkit's active font from the built-in 5x7 bitmap to anti-aliased, shaped OpenType text — the bundled Atkinson Hyperlegible face at DefaultOpenTypeSizePx — in a single call. After it, every widget (window titles, dock, menus, HUD, …) re-lays-out and repaints against the vector face without any further per-widget wiring.

Call it once at start-up. It returns any parse error (the bundled face never produces one) and leaves the active font unchanged in that case, so a failure degrades to the still-working bitmap default rather than to no text. Restore the bitmap default at any time with SetFont(nil).

func UseOpenTypeTextSize added in v0.77.0

func UseOpenTypeTextSize(sizePx int) error

UseOpenTypeTextSize is UseOpenTypeText at an explicit pixel size — for apps (or high-DPI surfaces) that want AA text larger or smaller than DefaultOpenTypeSizePx. The active font is only swapped on success; on a parse error it is left untouched and the error is returned.

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

	// HasRange reports whether Min, Max and Now carry a meaningful reading.
	HasRange bool
	// Min, Max and Now are the numeric range and current position of a
	// range-valued control, valid only when HasRange is true.
	Min, Max, Now float64
}

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

Range-valued controls (sliders, spin buttons, ratings, progress/level bars, gauges) additionally expose a machine-readable numeric reading via Min, Max and Now — the WAI-ARIA aria-valuemin / aria-valuemax / aria-valuenow triple. HasRange gates them: it is true only when the widget populated all three, so a consumer can tell a genuine 0 (Now on an empty slider) from an unset field. A single bool rather than three *float64 pointers keeps A11yInfo a comparable value type — == / != still work, as the table-driven tests rely on — and no existing caller is affected, since every widget that omits a range leaves HasRange false and the three floats at their zero value.

func CollectA11y added in v0.19.0

func CollectA11y(widgets []Widget) []A11yInfo

CollectA11y returns the A11yInfo for every MEANINGFUL widget in the slice, preserving order. A host owns its widget tree, so it passes the flat list it composed.

Two kinds of widget are skipped, for two different reasons. One that does not implement Accessible has nothing to say. One that reports RolePresentation has deliberately said it is layout or decoration — a box, a scrim, a scrollbar — and ARIA's role="presentation" means exactly "look through me to the content inside". Either way the result is the list a reader should announce, with no structural furniture in it.

The distinction matters to the toolkit rather than to callers: every widget now answers A11y(), so "described as presentational" and "never described" are no longer the same silence, even though both are filtered out here.

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 A11yNode added in v0.135.0

type A11yNode struct {
	A11yInfo

	// Rect is the element's placement in SURFACE coordinates.
	//
	// It is [Widget.Bounds] verbatim, because bounds in this toolkit are
	// already absolute rather than parent-relative — [translateEvent] converts
	// a parent-local event to child-local by `ev.X + parentRect.X -
	// childRect.X`, which only holds when both rectangles share the surface's
	// origin. Accumulating offsets during the walk, the obvious reading of
	// "placement within its parent surface", would double every position.
	Rect Rect
}

A11yNode is one accessible element together with WHERE it is.

CollectA11y answers what a tree contains; this answers where each thing is, which is the other half every platform accessibility API asks for. A screen reader draws the focus ring, routes a touch or moves the pointer from this rectangle, so an element described without one can be read but not pointed at.

func WalkA11y added in v0.135.0

func WalkA11y(w Widget) []A11yNode

WalkA11y returns every meaningful element of the tree rooted at w, each with its bounds, in visual order.

It descends through any widget that exposes its children via [childContainer] — the same convention CollectRuns uses — so a host does not have to keep a flat list of everything it composed, which is what CollectA11y requires and what a deeply nested layout makes impractical.

Widgets reporting RolePresentation are skipped exactly as CollectA11y skips them: ARIA's role="presentation" means "look through me to the content inside", so a box, a scrim or a scrollbar contributes nothing to announce — but the walk still descends INTO it, because its children usually do.

Nothing else is filtered here. Whether an unnamed or zero-area element is worth publishing is a decision for the platform bridge consuming this, which knows what its own screen reader does with one.

type Accelerator added in v0.151.0

type Accelerator struct {
	Key   string
	Ctrl  bool
	Shift bool
	Alt   bool
	Meta  bool
}

Accelerator is a single key combination: a base Key plus the four desktop modifier flags. It is the atom a Keymap binds and an Event is matched against.

Key is a canonical key name — a single upper-case letter ("P"), a digit ("1"), a punctuation rune ("/"), or a named key ("Enter", "ArrowLeft", "F1", "Escape"). The modifier flags mirror Event: Ctrl/Shift are the two common modifiers, Alt is the Option (⌥) / Alt key, and Meta is the Command (⌘) / Super (Windows/logo) key. Two accelerators are equal only when the Key and all four flags match, so Ctrl+P, Shift+Ctrl+P and plain P are three distinct accelerators.

func AcceleratorFromEvent added in v0.151.0

func AcceleratorFromEvent(ev Event) (Accelerator, bool)

AcceleratorFromEvent derives the Accelerator a key event represents, reporting ok=false for any non-keyboard event or a keyboard event with an empty Code. It reads EventKeyDown and EventChar, canonicalising Code the same way ParseAccelerator canonicalises a key token, so a binding parsed from a string matches an event delivered by a host.

func MustParseAccelerator added in v0.151.0

func MustParseAccelerator(s string) Accelerator

MustParseAccelerator is ParseAccelerator that panics on error, for package-level accelerator literals known to be valid at author time.

func ParseAccelerator added in v0.151.0

func ParseAccelerator(s string) (Accelerator, error)

ParseAccelerator parses a human accelerator string ("Ctrl+Shift+P", "Meta+K", "Alt+Left", "Ctrl++") into an Accelerator. Segments are split on '+'; every segment but the last is a modifier (ctrl/control, shift, alt/opt/option, meta/cmd/command/super/win) and the last is the key. A trailing '+' denotes the '+' key itself ("Ctrl++" = Ctrl and the plus key). Modifier and key spelling are case-insensitive. An empty string, an unknown modifier, or a missing key returns an error.

func (Accelerator) String added in v0.151.0

func (a Accelerator) String() string

String renders the accelerator in canonical Ctrl+Shift+Alt+Meta+Key order, the inverse of ParseAccelerator (aliases resolved), suitable as a menu or tooltip hint.

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

	// OnToggle fires whenever a section's expanded state flips through a user
	// interaction -- a header click, or Enter/Space on the focused header. i is
	// the section index and expanded is its NEW state (true = just opened). In
	// exclusive mode, opening section i also collapses whichever section was
	// open, but OnToggle reports only the section the user acted on. Nil is safe.
	OnToggle func(i int, expanded 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) Children added in v0.137.0

func (a *Accordion) Children() []Widget

Children yields the section bodies, in section order.

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) Focused added in v0.102.0

func (f *Accordion) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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. While focused, Enter/Space toggles the focused header and Up/Down move the header focus between sections. Anything else (other non-click events, or a click that lands on neither a header nor an open body) is a no-op.

Scroll model: a section body is a host-supplied Widget occupying its allotted rect, so the Accordion does not itself scroll a body's contents. Instead a tall body is clipped cleanly to its rect (see Draw) and the mouse wheel (EventScroll) over an expanded body is forwarded to that body -- translated into its local frame -- so a scrollable child (a ListBox, a ScrollView, ...) scrolls itself. A wheel event over a header, a collapsed section, or dead space is ignored.

func (*Accordion) SetFocused added in v0.102.0

func (f *Accordion) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 Action added in v0.151.0

type Action struct {
	// ID is the stable identifier a [Keymap] binds and an [ActionRegistry]
	// keys on. It must be non-empty to register.
	ID string
	// Label is the human-readable text shown in menus, buttons and the
	// palette.
	Label string
	// Icon, when set, draws the action's glyph into rect r in colour ink —
	// the same callback shape as [Button.Icon]. Optional (nil draws no
	// glyph).
	Icon func(p painter.Painter, r Rect, ink RGBA)
	// Shortcut is the action's declared default accelerator (single- or
	// multi-stroke). Register it into a [Keymap] with
	// [ActionRegistry.BindDefaults] or [Keymap.Bind]; the Keymap, not this
	// field, is the live source of truth once rebinding is allowed.
	Shortcut Chord
	// Run is the command body, invoked by [Action.Execute] when the action
	// is enabled. Nil makes the action a non-running placeholder.
	Run func()

	// Enabled gates whether the action runs and reads as active; Visible
	// gates whether surfaces show it at all. Both are observable.
	Enabled *mvvm.Observable[bool]
	Visible *mvvm.Observable[bool]
}

Action is a single named command — the one source of truth that a menu item, a toolbar button, a command-palette entry and a keyboard shortcut all point at. Define it once, then surface it in every affordance via the converters (Action.MenuItem, Action.ToolbarButton) and register it with an ActionRegistry (which feeds a CommandPalette) and a Keymap (which binds its accelerator); flipping Action.Enabled or Action.Visible then updates every surface at once.

Enabled and Visible are mvvm.Observable values so app state can drive them through the go-widgets MVVM layer — an Observable[bool] on a view model binds straight onto them — and any surface observing them repaints on change. NewAction initialises both to true; the fields also default to a usable nil (treated as "enabled"/"visible") so a zero-value literal still runs.

func NewAction added in v0.151.0

func NewAction(id, label string, run func()) *Action

NewAction builds an enabled, visible action with the given id, label and command body.

func (*Action) CanRun added in v0.151.0

func (a *Action) CanRun() bool

CanRun reports whether Execute would run: the action has a body and is enabled.

func (*Action) Execute added in v0.151.0

func (a *Action) Execute() bool

Execute runs the action's body if Action.CanRun, returning whether it ran. A disabled or bodyless action is a safe no-op — every surface routes through this guard, so a stale menu click or palette pick on a just-disabled action does nothing.

func (*Action) IsEnabled added in v0.151.0

func (a *Action) IsEnabled() bool

IsEnabled reports the action's enabled state, treating a nil observable as enabled.

func (*Action) IsVisible added in v0.151.0

func (a *Action) IsVisible() bool

IsVisible reports the action's visible state, treating a nil observable as visible.

func (*Action) MenuItem added in v0.151.0

func (a *Action) MenuItem(keymap *Keymap) MenuItem

MenuItem projects the action onto a MenuItem, filling Label, an Execute handler and — when keymap is non-nil and the action is bound — the shortcut hint from the keymap's most specific binding, so the menu always shows the live accelerator.

func (*Action) SetEnabled added in v0.151.0

func (a *Action) SetEnabled(b bool)

SetEnabled sets the enabled state, allocating the observable if needed.

func (*Action) SetVisible added in v0.151.0

func (a *Action) SetVisible(b bool)

SetVisible sets the visible state, allocating the observable if needed.

func (*Action) ToolbarButton added in v0.151.0

func (a *Action) ToolbarButton() ToolbarItem

ToolbarButton projects the action onto a ToolbarItem, filling Label, an OnClick handler and Disabled from the action's enabled state. (ToolbarItem takes a raster Icon, not a draw callback, so Action.Icon is not copied here; a host that wants the glyph renders it into an Icon buffer itself.)

type ActionRegistry added in v0.151.0

type ActionRegistry struct {

	// OnChange, when set, fires when the action set changes or any
	// registered action's Enabled/Visible flips.
	OnChange func()
	// contains filtered or unexported fields
}

ActionRegistry is the lookup + iteration index for [Action]s: one place to register a command, find it by id, toggle it, run it, and enumerate the set (in registration order) to build a menu, a toolbar or a CommandPalette.

It observes each registered action's Enabled/Visible observables and fires OnChange when any of them changes (or when an action is added/removed), so a palette rebuilt from ActionRegistry.PaletteCommands stays current without the app polling.

func NewActionRegistry added in v0.151.0

func NewActionRegistry() *ActionRegistry

NewActionRegistry returns an empty registry.

func (*ActionRegistry) Action added in v0.151.0

func (r *ActionRegistry) Action(id string) *Action

Action returns the action for id, or nil if it is not registered.

func (*ActionRegistry) Actions added in v0.151.0

func (r *ActionRegistry) Actions() []*Action

Actions returns the registered actions in registration order.

func (*ActionRegistry) Add added in v0.151.0

func (r *ActionRegistry) Add(id, label string, run func()) *Action

Add is a convenience that builds an action via NewAction and registers it, returning the action so the caller can set Icon/Shortcut.

func (*ActionRegistry) BindDefaults added in v0.151.0

func (r *ActionRegistry) BindDefaults(keymap *Keymap, scope Scope) error

BindDefaults binds every registered action that declares a non-empty Action.Shortcut into keymap at the given scope, so a single pass wires the declared accelerators. It stops and returns the first conflict error (ErrConflict); on success it returns nil.

func (*ActionRegistry) Disable added in v0.151.0

func (r *ActionRegistry) Disable(id string) bool

Disable disables action id, returning whether it was found.

func (*ActionRegistry) Enable added in v0.151.0

func (r *ActionRegistry) Enable(id string) bool

Enable enables action id, returning whether it was found.

func (*ActionRegistry) Len added in v0.151.0

func (r *ActionRegistry) Len() int

Len reports how many actions are registered.

func (*ActionRegistry) Lookup added in v0.151.0

func (r *ActionRegistry) Lookup(id string) (*Action, bool)

Lookup returns the action for id and whether it was found.

func (*ActionRegistry) PaletteCommands added in v0.151.0

func (r *ActionRegistry) PaletteCommands() []PaletteCommand

PaletteCommands projects the visible actions (in registration order) onto [PaletteCommand]s for a CommandPalette. Each command's handler runs the action through its enabled guard, so a disabled-but-visible action shows in the palette yet does nothing when chosen. Pair with CommandPalette.SetActions and the registry's OnChange to keep the palette live.

func (*ActionRegistry) Register added in v0.151.0

func (r *ActionRegistry) Register(a *Action) *Action

Register adds (or replaces, by id) an action and returns it for chaining. Registering a new id appends it to the iteration order; re-registering an existing id keeps its position and swaps the action. The registry subscribes to the action's Enabled/Visible observables so later toggles fan out through OnChange. Panics on a nil action or an empty ID (programmer errors).

func (*ActionRegistry) Run added in v0.151.0

func (r *ActionRegistry) Run(id string) bool

Run executes action id (through its enabled guard), returning whether it both existed and ran.

func (*ActionRegistry) SetEnabled added in v0.151.0

func (r *ActionRegistry) SetEnabled(id string, enabled bool) bool

SetEnabled toggles the enabled state of action id, returning whether it was found.

func (*ActionRegistry) Unregister added in v0.151.0

func (r *ActionRegistry) Unregister(id string) bool

Unregister removes the action with id (unsubscribing its observers), returning whether one was present.

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) Children added in v0.137.0

func (a *ActionRow) Children() []Widget

Children yields the row's leading then trailing widget.

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 Agenda added in v0.82.0

type Agenda struct {
	Base
	Events             []AgendaEvent
	DayNames           []string
	StartHour, EndHour int
	OnSelect           func(i int)
	Selected           int
	// OnDayActivate fires when a month-view click lands on an in-month day cell
	// that is not an event chip, carrying that cell's (year, month, day). A
	// host uses it to add an event on the clicked day. Nil is safe.
	OnDayActivate func(year, month, day int)

	// OnEventEdited fires after the inline editor (see EditEvent) commits a
	// change to event i — a title edit or a calendar reassignment. Nil is safe.
	OnEventEdited func(i int)

	// Calendars are the named, colour-coded event sources (see
	// AgendaCalendar). An event's colour is resolved from its Calendar index
	// into this slice, and an event whose calendar is Hidden is not drawn or
	// hit. Empty (the default) keeps the original behaviour: every event uses
	// its own Fill (or the theme Accent) and none are ever hidden. Share this
	// exact slice with an AgendaSidebar so toggling a row's visibility is seen
	// here without any extra wiring.
	Calendars []AgendaCalendar

	// View selects the layout (week/month/quarter/year); zero = AgendaWeek.
	View AgendaView
	// Year and Month are the focused period for the calendar views. When a
	// needed field is zero it is derived from the first dated event (see
	// focusYM/focusYear); if none is available the calendar grid stays empty.
	Year  int
	Month int // 1..12
	// contains filtered or unexported fields
}

Agenda is a week view of events: a top header row of day names, a left gutter of hour labels, and a day-column × hour-row grid on which each AgendaEvent paints as a rounded block positioned at its Day column and spanning its [StartMin, EndMin) range clamped to the visible hours. StartHour and EndHour bound that visible range; when they are unset (or EndHour <= StartHour) they fall back to a 08:00..18:00 working day so a caller can leave them zero. Selected (when it indexes an event) tints that block and draws an accent border, and a click inside a block fires OnSelect with its index.

Agenda renders through painter.Painter, so the same week draws as pixels (WUI/GUI) or promoted cells (TUI). It is distinct from Calendar, which is a month date-picker; Agenda plots events on a day/time grid. An empty event slice draws just the header, gutter and grid.

func NewAgenda added in v0.82.0

func NewAgenda(events []AgendaEvent) *Agenda

NewAgenda builds an Agenda over the given events, seeded with a Monday-first week (Mon..Sun), an 08:00..18:00 visible day and no selection (Selected = -1). A nil slice is normalised to a non-nil empty slice so range loops and len() checks never special-case nil.

func (*Agenda) A11y added in v0.105.0

func (a *Agenda) A11y() A11yInfo

A11y reports the Agenda as a grid carrying its focused period as a "YYYY-MM" string (the month the calendar views centre on), or "" when no period can be resolved from the fields or events.

func (*Agenda) DayAt added in v0.84.0

func (a *Agenda) DayAt(x, y int) (year, month, day int, ok bool)

DayAt maps widget-local (x, y) to the in-month day cell under it in the month view, returning the focused (year, month, day) and ok=true; ok=false for the header, outside the grid, or a spill cell. Exposed so a host can hit-test a right-click and offer an "add event here" menu. Only meaningful when View == AgendaMonth.

func (*Agenda) Draw added in v0.82.0

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

Draw dispatches to the active View's painter. AgendaWeek (the zero value) draws the original week grid unchanged; the calendar views draw a month, quarter or year of dated events.

func (*Agenda) DrawEditor added in v0.97.0

func (a *Agenda) DrawEditor(p painter.Painter, theme *Theme)

DrawEditor paints the open editor overlay (a no-op when none is open or the editing index has fallen out of range). The panel is clipped to itself so no control ever bleeds past its rounded border. The swatch of the event's current calendar carries an accent ring so the active calendar is obvious.

func (*Agenda) EditEvent added in v0.97.0

func (a *Agenda) EditEvent(i int)

EditEvent opens the inline editor for event i, seeding the title Entry with its current Title. Out-of-range i is a no-op (no editor opens). Opening a new editor replaces any editor already open.

func (*Agenda) Editing added in v0.97.0

func (a *Agenda) Editing() int

Editing returns the index of the event whose editor is open, or -1 when no editor is open. A host checks this to decide whether to route keystrokes to EditorChar/EditorKey and to draw the overlay.

func (*Agenda) EditorChar added in v0.97.0

func (a *Agenda) EditorChar(code string)

EditorChar feeds a printable character (post-IME) to the open title Entry. A no-op when no editor is open.

func (*Agenda) EditorClick added in v0.97.0

func (a *Agenda) EditorClick(x, y int) bool

EditorClick routes a click at (x, y) — in the SAME (absolute) coordinate space as the widget's Bounds, like DropDown.PopoverClick — while the editor is open, and reports whether it consumed the event (always true while open, so the host doesn't also treat the click as a grid selection). A click on a calendar swatch reassigns the event (fires OnEventEdited); a click in the title Entry focuses it; a click anywhere outside the panel commits the edit and closes. When no editor is open it returns false and does nothing.

func (*Agenda) EditorKey added in v0.97.0

func (a *Agenda) EditorKey(code string)

EditorKey feeds a key press to the open editor: "Enter" commits + closes, "Escape" cancels + closes (discarding the title edit), and every other key (Backspace, arrows, Home/End, clipboard shortcuts) is forwarded to the title Entry. A no-op when no editor is open.

func (*Agenda) OnEvent added in v0.82.0

func (a *Agenda) OnEvent(ev Event)

OnEvent selects the event under an EventClick in whatever View is active and fires OnSelect (nil-safe) with its index. Each view has a hit-test that shares its paint geometry so the two can't drift: hitWeek walks the day/time blocks, hitMonth the day-cell chips, hitMini the mini-month day cells. Clicks that land on no event — headers, gutters, dead-space, "+N" markers — and any non-click event are no-ops. Overlapping candidates resolve to the visually-topmost (last-drawn) one.

type AgendaCalendar added in v0.96.0

type AgendaCalendar struct {
	Name   string
	Color  RGBA
	Hidden bool
}

AgendaCalendar is one named, colour-coded source of events — a "calendar" in the Google/Apple Calendar sense (typically a remote CalDAV/ICS feed the host syncs into Agenda.Events). Color tints every event that belongs to it, and Hidden hides all of its events at once (the toggle an AgendaSidebar row drives) without removing them from Agenda.Events. The toolkit does not fetch anything — the host feeds events in and assigns each event's Calendar index; AgendaCalendar only carries the presentation (name + colour + visibility).

type AgendaEvent added in v0.82.0

type AgendaEvent struct {
	Title            string
	Day              int
	StartMin, EndMin int
	Y, M, D          int
	Fill             RGBA
	Calendar         int
}

AgendaEvent is one appointment. In the week view (AgendaWeek) it draws as a coloured block: Title labels it, Day is the weekday column — an index in [0, len(DayNames)); events whose Day falls outside that range are skipped (no column to place them in) — and StartMin/EndMin are minutes-from-midnight (0..1440) bounding the block vertically as the half-open range [StartMin, EndMin), so EndMin must be greater than StartMin. In the calendar views (AgendaMonth/AgendaQuarter/AgendaYear) the event is placed by its absolute date instead: Y is the year, M the month (1..12) and D the day-of-month (1..31); Day/StartMin/EndMin are ignored there.

Colour resolution (see Agenda.eventFill): an explicit non-zero Fill always wins; otherwise, when Calendar indexes a calendar in Agenda.Calendars that carries a non-zero Color, that calendar's colour is used (so every event on a "Work" calendar shares one colour without repeating it per event); otherwise it falls back to the theme's Accent. Calendar is an index into Agenda.Calendars; its zero value points at the first calendar, and any value outside [0, len(Calendars)) means "no calendar" (Fill/Accent only). An event whose calendar is Hidden is not drawn or hit-tested (see eventVisible).

type AgendaSidebar added in v0.96.0

type AgendaSidebar struct {
	Base
	// Calendars is the shared list (see the type doc). Rows render + hit-test
	// in this order.
	Calendars []AgendaCalendar
	// Title is the header label above the rows; "" hides the header row
	// entirely (the first calendar then sits at the very top).
	Title string
	// OnToggle fires after a single click flips Calendars[i].Hidden, with that
	// row's index. Nil is safe. The flip has already been applied when it runs,
	// so a host can persist the new state or re-sync.
	OnToggle func(i int)
	// OnRename fires after CommitEdit writes a new Name onto Calendars[i] (open
	// the inline editor by double-clicking a row, then Enter to commit). It
	// carries the row index and the new name, already applied to Calendars[i]
	// when it runs, so the host/VM persists it — the MVVM seam, analogous to
	// OnToggle. Nil is safe.
	OnRename func(i int, name string)
	// contains filtered or unexported fields
}

AgendaSidebar is the calendar list that sits beside an Agenda (Google/Apple Calendar's left rail): an optional title row above one row per AgendaCalendar, each showing a colour swatch, the calendar name, and its visibility state. A single click on a row flips that calendar's Hidden flag and fires OnToggle; a double-click opens an inline editor over the row's name to rename the calendar (the remote agenda), firing OnRename on commit.

It shares the SAME AgendaCalendar slice as its Agenda — set both from one slice value (agenda.Calendars = cals; sidebar := NewAgendaSidebar(cals)) — so a visibility toggle here is reflected in the Agenda's rendering with no extra wiring: they mutate the one backing array. (Because the toggle mutates an element in place it never reallocates, so the shared view holds; only appending/replacing the slice would break the link.)

A host lays it out to the left of the Agenda with an HBox (a fixed-width sidebar column + a flexible Agenda column), exactly as it composes any other two widgets — the sidebar is a plain Widget, not a mode of the Agenda.

func NewAgendaSidebar added in v0.96.0

func NewAgendaSidebar(cals []AgendaCalendar) *AgendaSidebar

NewAgendaSidebar builds a sidebar over cals (the same slice the Agenda uses), titled "Calendars". A nil slice is normalised to a non-nil empty slice so range loops never special-case nil.

func (*AgendaSidebar) A11y added in v0.130.0

func (s *AgendaSidebar) A11y() A11yInfo

A11y reports the AgendaSidebar as navigation named by its title.

func (*AgendaSidebar) CancelEdit added in v0.99.0

func (s *AgendaSidebar) CancelEdit()

CancelEdit closes the rename editor without changing any Name. Safe (a no-op) when no editor is open.

func (*AgendaSidebar) CommitEdit added in v0.99.0

func (s *AgendaSidebar) CommitEdit()

CommitEdit writes the editor's text back onto Calendars[i].Name, fires OnRename with (i, name), and closes the editor. A no-op when no editor is open; guarded against an editing index gone stale (e.g. the calendar was removed while editing).

func (*AgendaSidebar) Draw added in v0.96.0

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

Draw paints the sidebar: a SurfaceAlt background with a right divider, the optional title row, then one row per calendar. A visible calendar shows a filled swatch and full-strength name; a Hidden one shows a hollow (outline) swatch and a dimmed name, so visibility reads at a glance. Every row is clipped to its rectangle so a long name never bleeds into the next row or past the rail. While a row is being renamed (see EditName) its inline Entry is painted over the name area in place of the static name; the swatch is unchanged.

func (*AgendaSidebar) EditName added in v0.99.0

func (s *AgendaSidebar) EditName(i int)

EditName opens the inline rename editor on calendar row i, seeding the Entry with its current Name and focusing it. Out-of-range i is a safe no-op (no editor opens). Opening a new editor replaces any editor already open, without committing it. It never touches Hidden.

func (*AgendaSidebar) Editing added in v0.99.0

func (s *AgendaSidebar) Editing() int

Editing returns the index of the calendar whose rename editor is open, or -1 when none is open. A host checks this to route keystrokes into OnEvent while editing (Enter/Esc commit/cancel) and to know the editor overlay is live.

func (*AgendaSidebar) OnEvent added in v0.96.0

func (s *AgendaSidebar) OnEvent(ev Event)

OnEvent drives the sidebar. When no rename editor is open: a double-click on a row (an EventClick tagged with AgendaSidebarDoubleClick) opens the inline rename editor over that row; an ordinary EventClick toggles the row's Hidden flag and fires OnToggle; clicks on the header or dead space, and any non-click event, are no-ops.

While a rename editor is open (Editing() >= 0), events route to it instead: EventChar and text-editing EventKeyDown feed the Entry (caret + text); "Enter" commits (CommitEdit), "Escape" cancels (CancelEdit); a click inside the editor keeps it focused, and a click anywhere else commits — the Google/Apple Calendar convention that clicking away saves the rename (the same rule the Agenda event editor uses). No visibility toggle happens while editing.

func (*AgendaSidebar) ScrollBy added in v0.106.0

func (s *AgendaSidebar) ScrollBy(delta int)

ScrollBy shifts scroll by delta rows (negative scrolls up), clamped to [0, maxScroll()] and written back immediately.

type AgendaView added in v0.82.0

type AgendaView int

AgendaView selects which of the four calendar layouts an Agenda draws. The zero value AgendaWeek keeps the original day/time week grid (so a zero-value Agenda is byte-identical to before this type existed); the other three plot events by their absolute Y/M/D date.

const (
	// AgendaWeek is the day-column × hour-row week grid (the default).
	AgendaWeek AgendaView = iota
	// AgendaMonth is a single month grid: a weekday header, up to six week
	// rows of day cells, event chips per day and a "+N" overflow marker.
	AgendaMonth
	// AgendaQuarter is three compact month grids (Month, Month+1, Month+2)
	// side by side, each dotting the days that carry events.
	AgendaQuarter
	// AgendaYear is twelve very compact month grids for Year in a 4×3 layout,
	// each dotting the days that carry events.
	AgendaYear
)

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 Animator added in v0.155.0

type Animator interface {
	// Tick advances the animation by dt seconds (the elapsed wall-clock time
	// since the previous frame).
	Tick(dt float64)
	// Animating reports whether the widget still needs fresh frames. When every
	// Animator in a tree returns false the host can stop repainting until the
	// next interaction restarts one.
	Animating() bool
}

Animator is an optional Widget capability: a widget that animates (a spinner, an indeterminate progress bar, a skeleton shimmer) implements it so a host present loop can advance it each frame and learn whether it still needs frames — so an idle UI stops repainting and a stopped animation costs nothing.

The contract is the same manual-clock one the rest of the toolkit uses: the widget owns no goroutine and no timer. The host calls Tick(dt) once per frame with the elapsed wall-clock seconds, then consults Animating to decide whether to schedule another frame. This retires the hand-rolled "am I still spinning?" bookkeeping that applications otherwise keep beside every spinner — the source of the classic frozen-spinner bug where the flag and the animation drift apart.

A container does not implement Animator: TickTree and TreeAnimating descend the widget tree through [childContainer] (the same convention WalkA11y and CollectRuns use) and apply the capability to whichever leaves carry it, so a host drives a whole composed UI with one call and never wires a spinner through by hand.

type AppDock added in v0.178.0

type AppDock struct {
	Base
	// Items are the entries, left to right.
	Items []AppDockItem
	// Magnify enables the hover swell. MaxScale (default 1.6) is the peak factor
	// under the pointer; Radius (default 1.5) is the falloff reach in item
	// widths. A non-positive Radius or a MaxScale <= 1 disables the swell.
	Magnify  bool
	MaxScale float64
	Radius   float64
	// OnActivate fires with the clicked item's index.
	OnActivate func(i int)
	// Style paints the ground + item faces + running/active indicators. Nil uses
	// ModernDockStyle (the macOS look); set BevelDockStyle{} for Fluxbox,
	// WindowsDockStyle{} for a taskbar, or a custom DockStyle.
	Style DockStyle
	// contains filtered or unexported fields
}

AppDock is a horizontal launcher bar (a macOS-style application dock) with optional hover magnification: the item under the pointer, and its neighbours with a smooth raised-cosine falloff, swell and the row reflows so swollen items never overlap and the point under the cursor stays put.

It is composed entirely from toolkit primitives — a Backdrop ground and, per item, a rounded Backdrop face, the host's icon painter, a clipped label, a running dot and an attention Badge — so it carries the toolkit look under any theme with no hand-drawn chrome. ItemRects publishes the live geometry and HitTest maps a point to an item, so paint and hit-testing read one layout.

(Not to be confused with Dock, the edge-docking LAYOUT container: AppDock is a visual launcher; Dock arranges bars around a body.)

func NewAppDock added in v0.178.0

func NewAppDock(items ...AppDockItem) *AppDock

NewAppDock builds a dock over items with magnification on at the default feel.

func (*AppDock) A11y added in v0.178.0

func (d *AppDock) A11y() A11yInfo

A11y describes the dock to a screen reader as a toolbar — a single node, the same way the other data-driven item widgets (ListBox) expose themselves.

func (*AppDock) Draw added in v0.178.0

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

Draw paints the ground bar then every item.

func (*AppDock) HitTest added in v0.178.0

func (d *AppDock) HitTest(x, y int) int

HitTest returns the index of the item at the absolute point (x,y), or -1 when the point hits no item.

func (*AppDock) ItemRects added in v0.178.0

func (d *AppDock) ItemRects() []Rect

ItemRects returns each item's current on-screen rectangle (magnified while hovered, resting otherwise), one per item in order — the geometry a host publishes and hit-tests against.

func (*AppDock) OnEvent added in v0.178.0

func (d *AppDock) OnEvent(ev Event)

OnEvent drives magnification from EventMouseMove (the pointer position sets the swell; a move that leaves the bounds flattens the row) and activation from EventClick (the hit item fires OnActivate). Coordinates are widget-local.

func (*AppDock) SetCursor added in v0.178.0

func (d *AppDock) SetCursor(x int, inside bool)

SetCursor updates the pointer position that drives magnification, in absolute coordinates; inside is whether the pointer is currently over the dock. A host that does not route EventMouseMove can drive the swell through this instead.

type AppDockItem added in v0.178.0

type AppDockItem struct {
	// Id is the caller's opaque identifier (echoed nowhere by the widget; handy
	// for the host's OnActivate switch).
	Id string
	// Label is drawn to the right of the icon; "" makes the item icon-only.
	Label string
	// Icon is the host-supplied leaf painter (the same seam as Browser's toolbar
	// icons): it is called with the glyph box and the item's ink. Nil draws no
	// glyph, leaving the label (or an empty face).
	Icon func(p painter.Painter, r Rect, ink RGBA)
	// Running raises the "app is open" dot under the glyph.
	Running bool
	// Active fills the item face in the accent (the "current app") instead of the
	// resting surface.
	Active bool
	// Badge, when > 0, overlays an attention count as a Badge at the top-right.
	Badge int
	// Width overrides the item's resting width in device pixels; 0 uses the
	// scaled default (AppDockItemW). A host with variable-width entries — window
	// task buttons sized to their title, say — sets it per item; layout,
	// magnification and hit-testing all honour it.
	Width int
}

AppDockItem is one entry in an AppDock: a leaf icon, an optional label, and the state flags that drive its face, running dot and attention badge.

type AreaChart added in v0.82.0

type AreaChart struct {
	Base
	Series   [][]float64
	Min, Max float64 // shared Y bounds; when equal, taken from the data
	Colors   []RGBA  // optional per-series palette override; cycles by index

	// Hover + HoverIndex drive a hover crosshair on the first series, like
	// LineChart. Zero value (Hover == false) draws none.
	Hover      bool
	HoverIndex int
}

AreaChart plots one or more series of Y values as polylines whose region down to the baseline is filled with a semi-transparent tint of the series colour -- the shaded sibling of LineChart. Values in each series spread evenly across the plot width and share a single Y scale (auto-derived from every series when Min == Max). Series paint back-to-front so an earlier (larger) band sits behind a later one. Colours cycle through the shared categorical palette unless Colors is set. Display-only.

It renders through painter.Painter, so the same chart draws as anti-aliased pixels (WUI/GUI) or promoted cells (TUI). An empty Series draws just the axes; a lone point per series marks a dot over a single filled column.

func NewAreaChart added in v0.82.0

func NewAreaChart(series [][]float64) *AreaChart

NewAreaChart builds an AreaChart over the given series with auto Y bounds.

func (*AreaChart) A11y added in v0.105.0

func (c *AreaChart) A11y() A11yInfo

A11y reports the AreaChart as an img carrying its series count, mirroring the LineChart/BarChart/PieChart convention (AreaChart plots one band per series).

func (*AreaChart) Draw added in v0.82.0

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

Draw paints the axis frame, then each series back-to-front as a filled band under its polyline plus the polyline stroke on top.

func (*AreaChart) OnEvent added in v0.100.0

func (c *AreaChart) OnEvent(ev Event)

OnEvent tracks the hover crosshair from the pointer (see LineChart.OnEvent).

func (*AreaChart) ValueAt added in v0.89.0

func (c *AreaChart) ValueAt(localX int) (index int, value float64, ok bool)

ValueAt maps a widget-local x to the nearest point of the FIRST series, returning its index and value (ok=false when there is no data). Exposed so a host can show the underlying value on hover.

type ArticleCard added in v0.155.0

type ArticleCard struct {
	Base
	// Title is the headline, wrapped to the content width over as many lines as
	// it needs. Empty draws no title.
	Title string
	// Body is the summary, wrapped to the content width and then clamped to at
	// most BodyLines lines. Empty draws no body.
	Body string
	// BodyLines caps the wrapped body. Zero or negative selects the default
	// (DefaultArticleBodyLines) so a caller can leave it unset.
	BodyLines int
	// Meta is the optional byline strip drawn under the body; nil (or an
	// all-hidden strip) draws nothing and reserves no space.
	Meta *CardMeta
}

ArticleCard is a text-led content card: a wrapped headline, a summary body wrapped and truncated to at most a few lines, and an optional CardMeta strip. It is the row a news / blog / discussion feed is built from, where the words carry the item and there is no lead image.

Layout (top to bottom, inside the CardPadX/Y inset):

┌──────────────────────────┐
│ Wrapped headline over as  │  ← Title, wrapped to the content width
│ many lines as it needs    │
│ A summary that wraps and  │  ← Body, wrapped then clamped to BodyLines,
│ is cut to BodyLines with…  │    the last kept line ellipsised on overflow
│ author · 3h · ▲12 · 💬4   │  ← Meta (optional)
└──────────────────────────┘

The body is wrapped to the content width and then truncated to at most BodyLines lines; when the summary is longer, the last kept line ends in an ellipsis so the cut reads as deliberate. ArticleCard is passive content — no hover, no selection.

func NewArticleCard added in v0.155.0

func NewArticleCard(title, body string, meta *CardMeta) *ArticleCard

NewArticleCard builds an ArticleCard with a title, a summary body and an optional meta strip (nil for none). The body uses DefaultArticleBodyLines; set the BodyLines field afterwards to change the cap.

func (*ArticleCard) A11y added in v0.155.0

func (c *ArticleCard) A11y() A11yInfo

A11y reports the article card as a group named by its title.

func (*ArticleCard) Children added in v0.155.0

func (c *ArticleCard) Children() []Widget

Children yields the meta strip when present so a generic walk (accessibility, text selection) reaches it. The title and body are drawn directly and are not sub-widgets.

func (*ArticleCard) Draw added in v0.155.0

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

Draw paints the frame, the wrapped title, the clamped body and the meta strip. Content fills exactly Measure(Bounds().W): the same layout drives both.

func (*ArticleCard) Measure added in v0.155.0

func (c *ArticleCard) Measure(width int) int

Measure reports the card's height at the given outer width — the wrapped title, the clamped body and the meta strip stacked with CardGapY between them, plus the CardPadY inset top and bottom.

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 Backdrop added in v0.71.0

type Backdrop struct {
	Base
	// Fill is the solid background colour. The zero value uses theme.Background.
	Fill painter.RGBA
	// Grid is the grid-line colour. The zero value uses theme.Border.
	Grid painter.RGBA
	// Step is the grid spacing in painter units. Step <= 0 draws no grid.
	Step int
	// Radius rounds the filled rectangle's corners by that many units. The zero
	// value (0) fills a plain rectangle, byte-identical to before this field
	// existed; a positive value fills a rounded rectangle — the ground of a pill /
	// chip / badge a host composites an icon or label over, so that ground is a
	// widget rather than a hand-drawn FillRoundRect. A grid (Step > 0) is drawn as
	// before, unaffected by the rounding.
	Radius int
	// Stroke, when its alpha is non-zero, outlines the (optionally rounded) fill in
	// that colour — the border of a pill / chip. The zero value (A==0) draws no
	// border, byte-identical to before this field existed.
	Stroke RGBA
	// StrokeWidth is the border thickness in units; it applies only when Stroke is
	// set, and a value < 1 is treated as 1.
	StrokeWidth int
	// NoFill suppresses the ground fill, leaving only the Stroke (and the grid, if
	// any): an outline-only decoration drawn OVER content that has to stay
	// visible — a focus ring around a pane, a drop-target highlight, a selection
	// marquee. Without it such an outline is a hand-drawn StrokeRoundRect in the
	// host, because a zero-value Fill means "the theme's Background" rather than
	// "no background", and there is no transparent colour that says otherwise.
	// The zero value (false) fills as before, byte-identical.
	NoFill bool
	// GradientTo, when its alpha is non-zero, fills the ground as a linear
	// gradient from Fill (the start edge) to GradientTo (the end edge) along
	// GradientDir, instead of a solid Fill — the toolbar/panel face a host would
	// otherwise hand-draw with a per-pixel PutPixel loop. Gradient fills a
	// rectangle (Radius is ignored while it is set). The zero value (A==0) keeps
	// the solid Fill, byte-identical to before this field existed.
	GradientTo painter.RGBA
	// GradientDir is the gradient's direction — vertical (the default), horizontal,
	// diagonal or cross-diagonal. Meaningful only when GradientTo is set.
	GradientDir GradientDir
	// Bevel draws a 1-pixel 3D bevel around the fill: none (the default), raised
	// (a bright top+left over a dark bottom+right — a pushed-out Fluxbox toolbar
	// section) or sunken (the inverse). The zero value (BevelNone) draws no bevel,
	// byte-identical to before this field existed.
	Bevel BevelKind

	// Interactive makes the Backdrop catch pointer events. The zero value
	// (false) is event-transparent: HitTest returns false so clicks pass
	// through to whatever is composited over the backdrop — the least-
	// surprising default for a decorative ground. Set it true for a backdrop
	// that should consume clicks (a modal scrim shielding the content beneath).
	Interactive bool
}

Backdrop is a decorative full-bounds ground: it fills its rectangle with a solid colour and, when Step > 0, overlays a regular grid of 1-unit lines every Step units. It draws no children and handles no events — the plain backing a host composites the rest of a scene on top of (a desktop wallpaper, a canvas backing sheet, a chart plotting area).

Both colours are optional: a zero-value Fill falls back to the theme's Background and a zero-value Grid to the theme's Border, so a Backdrop dropped in with no configuration reads sensibly under any theme. A host that wants an exact palette (a compositor matching its own desktop colours) sets Fill and Grid explicitly.

The grid is painted as 1-unit FillRects rather than StrokeRect hairlines so it renders identically on both the pixel and cell back-ends (a CellPainter has no sub-cell stroke); the lines start at the top-left of Bounds and repeat every Step, matching a host that draws a world-aligned grid from the origin.

A Backdrop is event-transparent by default. It is typically the first, full-cover child of a scene, over which a host composites the interactive widgets. Because a container routes an event to the first child whose HitTest covers the point (see Overlay), a full-cover Backdrop that reported hits would intercept every click meant for a widget drawn on top of it. So its HitTest returns false by default and pointer events pass THROUGH to the siblings/content behind it — the same "decorative, non-interactive" idiom as Label and Scrollbar. Set Interactive to opt back in (e.g. a modal scrim that deliberately swallows clicks aimed at the content beneath it).

func NewBackdrop added in v0.71.0

func NewBackdrop(fill, grid painter.RGBA, step int) *Backdrop

NewBackdrop builds a Backdrop with a solid fill and a grid every step units (step <= 0 = no grid). Passing the zero RGBA for either colour selects the theme's Background (fill) or Border (grid) at draw time.

func (*Backdrop) A11y added in v0.130.0

func (b *Backdrop) A11y() A11yInfo

A11y reports the Backdrop as presentational: it dims what is behind a modal and holds nothing to read.

func (*Backdrop) Draw added in v0.71.0

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

Draw fills the bounds and overlays the grid. An empty rectangle paints nothing; a non-positive Step paints only the fill. A positive Radius fills a rounded rectangle instead of a plain one; a non-zero Stroke outlines it. With NoFill set the fill is skipped entirely and only the outline (and grid) is painted, leaving whatever is already there showing through.

func (*Backdrop) HitTest added in v0.78.0

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

HitTest reports whether the Backdrop should receive a pointer event at (px, py). It returns false unless Interactive is set, so by default a full-cover backdrop lets clicks pass through to the widgets composited over it (the Label/Scrollbar pass-through idiom). When Interactive is set it behaves like any other widget, hit-testing against its Bounds.

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

	// Hover + HoverIndex outline the hovered bar's column. Opt-in; the zero
	// value draws none.
	Hover      bool
	HoverIndex int
}

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.

func (*BarChart) OnEvent added in v0.100.0

func (c *BarChart) OnEvent(ev Event)

OnEvent outlines the bar column under the pointer, clearing when it leaves.

func (*BarChart) ValueAt added in v0.88.0

func (c *BarChart) ValueAt(localX int) (index int, value float64, ok bool)

ValueAt maps a widget-local x to the bar under it, returning its index and value; ok is false for an empty chart or an x outside every bar slot. Exposed so a host can show the underlying value on hover.

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
	// Disabled, when true, makes an interactive widget inert: its OnEvent
	// early-returns (no click / drag / scroll / hover / key effect) and its
	// Draw paints a muted, greyed face. The zero value (false) is the normal
	// interactive state, so every widget is enabled by default and existing
	// renders are unchanged. Inherited by every widget that embeds Base, so a
	// caller disables any control with `w.Disabled = true`.
	Disabled bool
	// 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 BevelDockStyle added in v0.179.0

type BevelDockStyle struct{}

BevelDockStyle is the Fluxbox look: a flat ground and square, 3D-bevelled item faces — a RAISED bevel at rest, a SUNKEN one when active (the "pressed in" current app) — with a running dot. Ink stays OnSurface on the light face.

func (BevelDockStyle) DrawFace added in v0.179.0

func (BevelDockStyle) DrawFace(p painter.Painter, theme *Theme, r Rect, st DockItemState) RGBA

func (BevelDockStyle) DrawGround added in v0.179.0

func (BevelDockStyle) DrawGround(p painter.Painter, theme *Theme, r Rect)

type BevelKind added in v0.183.0

type BevelKind int

BevelKind selects a Backdrop's 1-pixel 3D edge bevel.

const (
	// BevelNone draws no bevel (the default).
	BevelNone BevelKind = iota
	// BevelRaised draws a bright top+left over a dark bottom+right, so the face
	// reads as pushed out toward the viewer.
	BevelRaised
	// BevelSunken is the inverse — dark top+left, bright bottom+right — so the
	// face reads as pressed in.
	BevelSunken
)

type Binding added in v0.151.0

type Binding struct {
	Chord  Chord
	Action string
	Scope  Scope
}

Binding pairs a chord with the action it triggers and the scope it applies in. Returned by Keymap.Bindings as an immutable snapshot.

type Border added in v0.58.0

type Border struct {
	Base
	North, South, East, West, Center         Widget
	NorthSize, SouthSize, EastSize, WestSize int

	// NorthSplit/… add a draggable splitter between that edge region and the
	// centre (with an optional resizable split). The app drives the drag — like Paned —
	// via SplitHandleAt (which handle a point is on) and ResizeSplit (set the new
	// size); OnResize fires after each resize.
	NorthSplit, SouthSplit, EastSplit, WestSplit bool
	OnResize                                     func(side DockSide, size int)
	// contains filtered or unexported fields
}

Border arranges up to five named regions — North, South, West, East and Center — the classic five-region border layout for application shells. North and South span the full width and take a fixed height; West and East then span the height that remains between them and take a fixed width; Center fills whatever is left. The precedence is structural, so regions may be assigned in any order and still lay out correctly (unlike Dock, which carves in insertion order).

Any region may be nil (that edge simply contributes no band). Sizes are the extent along each region's own axis — NorthSize/SouthSize are heights, West/EastSize are widths — clamped to what the container can give (negative → 0).

Border is a Widget: Draw paints every non-nil region; OnEvent routes by Bounds, translating into the matched region's local space.

func NewBorder added in v0.58.0

func NewBorder() *Border

NewBorder builds an empty Border; assign the region fields and their sizes directly before the first SetBounds.

func (*Border) A11y added in v0.130.0

func (b *Border) A11y() A11yInfo

A11y reports the Border layout as presentational.

func (*Border) Children added in v0.137.0

func (b *Border) Children() []Widget

Children yields the five regions in reading order: the edges clockwise from the top, then the centre.

func (*Border) Draw added in v0.58.0

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

Draw paints every non-nil region, then any splitter handles over the seams.

func (*Border) OnEvent added in v0.58.0

func (b *Border) OnEvent(ev Event)

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

func (*Border) ResizeSplit added in v0.63.0

func (b *Border) ResizeSplit(side DockSide, size int)

ResizeSplit sets the given edge region's size (clamped to [0, the border's extent on that axis]), re-lays out, and fires OnResize. The app computes size from the drag — e.g. NorthSize + dy for the north handle.

func (*Border) SetBounds added in v0.58.0

func (b *Border) SetBounds(r Rect)

SetBounds lays out the regions in border precedence (N, S, then W, E, then the Center fills the remainder), reusing dockCarve for each edge.

func (*Border) SplitHandleAt added in v0.63.0

func (b *Border) SplitHandleAt(px, py int) (side DockSide, ok bool)

SplitHandleAt reports which splitter handle (if any) contains the surface point (px,py) — the app calls it on mouse-down to start a region resize drag.

type BorderLayout added in v0.59.0

type BorderLayout struct{}

BorderLayout arranges items by Region: North/South span the full width, then West/East span the height between them, then Center fills the rest. Item.Size is the edge band's thickness. The border layout, over the shared dockCarve.

func (BorderLayout) Arrange added in v0.59.0

func (BorderLayout) Arrange(r Rect, items []Item)

Arrange carves the edge regions off in N,S,W,E order, then fills Center.

type BoxAlign added in v0.56.0

type BoxAlign int

BoxAlign controls how HBox/VBox position each child on the CROSS axis (the axis perpendicular to the flow: vertical for HBox, horizontal for VBox). The zero value BoxStretch fills the cross axis — the historical behaviour — so existing layouts are unchanged. The others place the child at its natural cross size (reported via the optional Measurer, else its current cross Bounds) against the start, centre, or end of the box. The box `align` model: stretch | start | center | end.

const (
	BoxStretch     BoxAlign = iota // fill the cross axis (default)
	BoxAlignStart                  // pin to the top (HBox) / left (VBox)
	BoxAlignCenter                 // centre on the cross axis
	BoxAlignEnd                    // pin to the bottom (HBox) / right (VBox)
)

type BoxLayout added in v0.59.0

type BoxLayout struct {
	Vertical bool
	Spacing  int
	Align    BoxAlign
	Pack     BoxPack
}

BoxLayout stacks items along one axis (horizontal by default; set Vertical for a column), honouring per-item Flex/Size and the Align/Pack options — the same horizontal/vertical box. It reuses the same sizing/alignment primitives as HBox/VBox.

Spacing is taken LITERALLY (negatives clamped to 0), matching HBox/VBox: the zero-value BoxLayout{} therefore has a flush, zero-gap axis. Use NewBoxLayout to get a layout pre-seeded with the DefaultBoxSpacing (4px) gap.

func NewBoxLayout added in v0.64.0

func NewBoxLayout() *BoxLayout

NewBoxLayout returns a *BoxLayout with Spacing seeded to DefaultBoxSpacing, the constructor analogue of NewHBox/NewVBox. The zero-value BoxLayout{} keeps a literal 0-gap axis; set fields on the returned value to configure it further.

func (*BoxLayout) Arrange added in v0.59.0

func (l *BoxLayout) Arrange(r Rect, items []Item)

Arrange lays the items out along the box axis. An empty rect (W<=0 or H<=0) collapses every item to Rect{} so a hidden box container leaves no leaf with stale non-empty bounds.

type BoxPack added in v0.56.0

type BoxPack int

BoxPack controls how HBox/VBox distribute SLACK on the MAIN axis (the flow axis) when the children do not fill it — i.e. when no flex child absorbs the space. The zero value PackStart leaves the slack after the last child (historical). PackCenter splits it either side; PackEnd puts it all before the first child. The box `pack` model (start|center|end). With any flex child the slack is zero, so Pack has no visible effect then.

const (
	PackStart  BoxPack = iota // flush to the start (default)
	PackCenter                // centre the group in the box
	PackEnd                   // flush to the end
)
type Breadcrumbs struct {
	Base
	Segments []string
	// OnSelect, when non-nil, fires with the 0-based index of the crumb the
	// user clicked. Nil (the zero value) keeps the widget passive.
	OnSelect func(i int)
}

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.

A click on a crumb fires OnSelect with that segment's index, so "Home > Docs > Reference" navigates up when the user clicks an ancestor crumb. OnSelect nil leaves the widget an inert display: the same per-segment X layout Draw builds is walked by OnEvent to hit-test the clicked crumb, so the click target and the drawn glyph can never drift apart.

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.

func (b *Breadcrumbs) OnEvent(ev Event)

OnEvent fires OnSelect(i) when a click lands on the i-th crumb. It walks the exact per-segment X layout Draw builds (textWidth(seg) then, between crumbs, gap + separator + gap), so the hit region matches the painted glyphs. Clicks in the inter-crumb separator gap, or when OnSelect is nil, are ignored. Event coordinates are widget-local, so the first crumb starts at local x == 0.

type Browser added in v0.112.0

type Browser struct {
	Base

	// OnNavigate is the host's async fetch/render trigger. The widget calls it
	// with the target to render and the pixel width the content area currently
	// offers; the host renders off-thread and calls Deliver / SetProgress back.
	// Nil is safe (navigation still updates history + loading state).
	OnNavigate func(target string, width int)

	// OnOpenExternal, when set, is the seam for an "open in the system browser"
	// affordance: OpenExternal() invokes it with the current URL. Optional; nil
	// is safe.
	OnOpenExternal func(url string)

	// HideScrollbar suppresses the Browser's OWN content scrollbars (both axes),
	// for a host that overlays its own — e.g. a reader that draws one shared
	// Scrollbar style down every panel and wants the preview's web view to match
	// the feed and sidebar exactly rather than show the embedded house style.
	// Only the paint is suppressed; wheel scrolling still works. The host reads
	// ScrollExtent to size and place its replacement bar, exactly as it does with
	// TreeView.HideScrollbar + TreeView.ScrollExtent.
	HideScrollbar bool

	// BackIcon / ForwardIcon / ReloadIcon / ZoomOutIcon / ZoomInIcon are the
	// host-supplied vector-icon painters for the toolbar buttons — the same seam
	// as SearchEntry.Icon. Each is invoked with its button's rect and the button
	// face ink (which already carries the enabled / disabled tint), so the host
	// draws a real arrow / refresh / minus / plus glyph centred in the button.
	// The toolkit ships no icon set of its own (keeping its zero-dependency
	// contract); a host wires these to, e.g., an Iconoir binding. Each is
	// nil-safe: a nil hook falls back to the plain text label, so headless
	// renders and existing callers keep working unchanged.
	BackIcon    func(p painter.Painter, r Rect, ink RGBA)
	ForwardIcon func(p painter.Painter, r Rect, ink RGBA)
	ReloadIcon  func(p painter.Painter, r Rect, ink RGBA)
	ZoomOutIcon func(p painter.Painter, r Rect, ink RGBA)
	ZoomInIcon  func(p painter.Painter, r Rect, ink RGBA)
	// FitIcon is the host-supplied painter for the best-fit zoom button (the
	// third member of the zoom group, next to zoom-out / zoom-in). Same nil-safe
	// seam as the other toolbar icons: a nil hook falls back to the text label.
	FitIcon func(p painter.Painter, r Rect, ink RGBA)

	// LeadingIcon, when set, paints a status glyph at the LEFT of the address
	// field — e.g. an SSL padlock whose look the host varies by certificate state
	// (secure / insecure / none). Same painter seam as the toolbar icons; the
	// address text indents to its right. Nil → no leading slot (text starts at
	// the normal inset).
	LeadingIcon func(p painter.Painter, r Rect, ink RGBA)

	// BookmarkIcon, when set, paints a toggle glyph at the RIGHT of the address
	// field — e.g. a star, filled when on. It takes the current Bookmarked state
	// so the host can draw the on/off variant. Clicking the slot flips Bookmarked
	// and fires OnBookmarkToggle. Nil → no bookmark slot.
	BookmarkIcon     func(p painter.Painter, r Rect, ink RGBA, on bool)
	Bookmarked       bool
	OnBookmarkToggle func(on bool)

	// OnChange fires once whenever any observable-relevant state mutates
	// (navigation, tab add/close/switch, loading/progress change, address edit,
	// delivered page). A mvvm binder subscribes to push state into Observables.
	// It is additive to OnNavigate and never replaces it. Nil is safe.
	OnChange func()

	// Phase drives the indeterminate loading bar animation (0..1); advance it
	// from the host frame loop via Tick, exactly like Spinner.Phase.
	Phase float64

	// Scale multiplies every chrome metric — the tab-strip and toolbar heights,
	// the pads, the button squares, the address slot, the loading-bar thickness
	// and the tab-pill sizing — for HiDPI / device-pixel hosts. A host that lays
	// the widget out in DEVICE pixels (e.g. a Retina surface at devicePixelRatio
	// 2, optionally times a UI zoom) sets Scale = devicePixelRatio*zoom so the
	// chrome stays physically the right size instead of shrinking to half. The
	// zero value (and 1) mean "no scaling": layout is byte-identical to a build
	// without the field, so existing callers are unaffected. Values <= 0 are
	// treated as 1. Metrics are rounded (not truncated) at every use so the
	// scaled buttons/tabs stay pixel-aligned and do not drift. The host-supplied
	// icon hooks fill the now-larger button rects, so the glyphs grow with the
	// buttons automatically.
	Scale float64

	// HideChrome, when true, hides BOTH the toolbar and the tab strip: neither is
	// drawn and neither takes any vertical space, so the page content area fills
	// the entire widget bounds. Toolbar clicks and address-field editing are then
	// inert (there are no hit targets), but navigation still works
	// programmatically (Open / Navigate / Back / Forward / Reload / SetZoom / …),
	// so a host can drive a chromeless page view. The loading bar still shows over
	// the content while a load is in flight. The zero value is false → the chrome
	// is shown exactly as before, so existing callers are unaffected.
	HideChrome bool
	// contains filtered or unexported fields
}

Browser is a reusable mini web-browser chrome: a tab strip, a Back / Forward / Reload toolbar, an editable address field, a loading progress bar and a scrollable content area that shows a page render. It is deliberately renderer-agnostic and fully synchronous — the widget NEVER fetches or renders a page itself and imports no networking or HTML engine. Instead it exposes a seam: the host sets OnNavigate, and whenever the widget needs a page rendered it invokes OnNavigate(target, width). The host runs the actual fetch/render asynchronously elsewhere and calls back into the widget's synchronous Deliver and SetProgress methods on its own UI thread. This mirrors the proven callback seam used by other host-driven widgets and keeps the toolkit's zero-dependency, no-network-in-tests contract intact.

Browser is a plain MVVM View: it holds view state, exposes it through exported getters (CurrentURL, CanBack, CanForward, TabCount, Loading, Progress, ActiveTitle, TabTitle), and offers command-style methods (Open, Navigate, Back, Forward, Reload, CloseTab) each with a matching Can… guard where relevant. A single OnChange hook fires whenever any observable-relevant state mutates, so a binder in the mvvm layer can push state into Observables WITHOUT the toolkit ever importing mvvm (which would invert the toolkit↔mvvm layering).

func NewBrowser added in v0.112.0

func NewBrowser() *Browser

NewBrowser builds an empty Browser in the default MultiTab mode at 1.0 zoom.

func (*Browser) A11y added in v0.130.0

func (b *Browser) A11y() A11yInfo

A11y reports the Browser as a document named by the page it is showing.

func (*Browser) ActiveIndex added in v0.112.0

func (b *Browser) ActiveIndex() int

ActiveIndex reports the active tab index (0 when there are no tabs).

func (*Browser) ActiveTitle added in v0.112.0

func (b *Browser) ActiveTitle() string

ActiveTitle returns the active tab's title, falling back to its URL when the title is empty; "" when there are no tabs.

func (*Browser) AddressFocused added in v0.124.0

func (b *Browser) AddressFocused() bool

AddressFocused reports whether the address field currently holds keyboard focus (a prior click landed in it), so a host can route a copy chord to Browser.CopyAddress instead of its own copy action.

func (*Browser) AddressText added in v0.124.0

func (b *Browser) AddressText() string

AddressText returns the text the address field shows: the editable buffer while focused, else the current page URL.

func (*Browser) Back added in v0.112.0

func (b *Browser) Back()

Back moves the active tab's cursor one step back and re-fetches that URL. It is a no-op with no active tab or at the start of history.

func (*Browser) CanBack added in v0.112.0

func (b *Browser) CanBack() bool

CanBack reports whether the active tab can go back (history behind the cursor).

func (*Browser) CanFit added in v0.134.0

func (b *Browser) CanFit() bool

CanFit reports whether a best-fit zoom is possible: there is an active tab with a delivered render and a non-empty content rect to fit it into.

func (*Browser) CanForward added in v0.112.0

func (b *Browser) CanForward() bool

CanForward reports whether the active tab can go forward (history ahead of the cursor).

func (*Browser) CanZoomIn added in v0.113.0

func (b *Browser) CanZoomIn() bool

CanZoomIn reports whether the zoom can still increase (below BrowserMaxZoom).

func (*Browser) CanZoomOut added in v0.113.0

func (b *Browser) CanZoomOut() bool

CanZoomOut reports whether the zoom can still decrease (above BrowserMinZoom).

func (*Browser) CloseTab added in v0.112.0

func (b *Browser) CloseTab(i int)

CloseTab drops tab i and its state; if it was the active tab a neighbour is activated. Out-of-range indices are ignored.

func (*Browser) CopyAddress added in v0.124.0

func (b *Browser) CopyAddress() (string, bool)

CopyAddress copies the address field's text to the toolkit-wide clipboard and flags a select-all highlight (visual feedback of what was copied), reporting the text and whether anything was copied. It is a no-op returning ("", false) when the field is not focused or is empty — so a host can try it first and fall back to another copy action. Mirrors Entry's "no selection → copy the whole value" model.

func (*Browser) CurrentURL added in v0.112.0

func (b *Browser) CurrentURL() string

CurrentURL returns the active tab's current URL, or "" when there are no tabs.

func (*Browser) Deliver added in v0.112.0

func (b *Browser) Deliver(target string, pixels []byte, imgW, imgH, width int, links []BrowserLink, title string)

Deliver hands the widget a finished render for target: it delivers a final stage (loading clears). See DeliverStage. The scroll position was reset when the navigation to target began (startLoad), so a delivered render is shown from wherever the user has scrolled to — deliveries do not yank the page.

func (*Browser) DeliverStage added in v0.133.0

func (b *Browser) DeliverStage(target string, pixels []byte, imgW, imgH, width int, links []BrowserLink, title string, final bool)

DeliverStage delivers one render for target, distinguishing a final render from an intermediate progressive frame. When target matches the active tab's current URL the render (pixels + dimensions + width), links and title are stored; a stale or non-active delivery is ignored.

With final=true the load is complete and loading clears. With final=false it is one staged frame of a still-running progressive render (a fast first paint, then refinements): the content updates but loading stays on, so the progress indicator keeps animating and the page does not read as "done" until the final frame lands. Neither form resets the scroll position — that happens once when the navigation begins (startLoad) — so a staged render refines in place instead of snapping to the top on every frame.

func (*Browser) Draw added in v0.112.0

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

Draw paints the chrome (tab strip when shown, toolbar) and the content area (page render + loading bar), strictly within Bounds.

func (*Browser) FitZoom added in v0.134.0

func (b *Browser) FitZoom()

FitZoom sets the zoom so the WHOLE current page/image fits within the content rect on both axes. The natural display size at zoom 1 is dispW0 = cr.W (pages render fit-to-width) and dispH0 = imgH*cr.W/imgW; the fit factor is min(1, cr.W/dispW0, cr.H/dispH0) — capped at 1 so a page already smaller than the pane is not blown up — then clamped to [BrowserMinZoom, BrowserMaxZoom] by SetZoom (which also re-clamps scroll). It is a no-op when there is no render or the content rect is empty.

func (*Browser) Forward added in v0.112.0

func (b *Browser) Forward()

Forward moves the active tab's cursor one step forward and re-fetches that URL. It is a no-op with no active tab or at the end of history.

func (*Browser) Loading added in v0.112.0

func (b *Browser) Loading() bool

Loading reports whether the active tab has an in-flight load.

func (*Browser) Mode added in v0.112.0

func (b *Browser) Mode() TabMode

Mode reports the current TabMode.

func (*Browser) Navigate added in v0.112.0

func (b *Browser) Navigate(href string)

Navigate performs in-tab navigation to href (a link click or a typed address): it truncates any forward history, appends href, marks the tab loading and invokes OnNavigate. With no active tab it falls back to Open.

func (*Browser) OnEvent added in v0.112.0

func (b *Browser) OnEvent(ev Event)

OnEvent routes widget-local input: clicks to the tab strip / toolbar / address field / page links, character + Backspace + Enter to the focused address field, and wheel scroll to the content. It early-returns when Disabled.

func (*Browser) Open added in v0.112.0

func (b *Browser) Open(target, title string)

Open opens target in a tab. In MultiTab it adds a new active tab (evicting the oldest past BrowserMaxTabs); in SingleTab it replaces the one tab. It seeds history, marks the tab loading, sets a pending render width and invokes OnNavigate.

func (*Browser) OpenExternal added in v0.112.0

func (b *Browser) OpenExternal()

OpenExternal invokes OnOpenExternal with the current URL, the seam for an "open in the system browser" affordance. No-op when the hook is unset or there is no current URL.

func (*Browser) Progress added in v0.112.0

func (b *Browser) Progress() float64

Progress reports the active tab's determinate download fraction (0 when there is no tab or SetProgress was never called this load).

func (*Browser) Reload added in v0.112.0

func (b *Browser) Reload()

Reload re-fetches the active tab's current URL. It is a no-op with no active tab.

func (*Browser) ResetZoom added in v0.113.0

func (b *Browser) ResetZoom()

ResetZoom returns the zoom to 1.0 (no-op when already there).

func (*Browser) ScrollExtent added in v0.173.0

func (b *Browser) ScrollExtent() (offset, viewport, total int, shown bool)

ScrollExtent reports the active page's VERTICAL scroll position in content pixels — the offset, the viewport height and the total (zoomed) page height — and whether the page overflows. A host that sets HideScrollbar and paints its own bar reads this to size and place a matching one, exactly as TreeView.ScrollExtent serves the same purpose for a windowed tree. It reports not-shown when there is no active tab, no render yet, or the page fits.

func (*Browser) SetProgress added in v0.112.0

func (b *Browser) SetProgress(frac float64)

SetProgress sets the active tab's determinate download progress (clamped to 0..1) for the in-flight load. If it is never called during a load the bar renders indeterminate (driven by Phase). No-op with no active tab.

func (*Browser) SetTabMode added in v0.112.0

func (b *Browser) SetTabMode(m TabMode)

SetTabMode selects MultiTab or SingleTab for subsequent Open calls.

func (*Browser) SetZoom added in v0.113.0

func (b *Browser) SetZoom(f float64)

SetZoom sets the page-display zoom, clamped to [BrowserMinZoom, BrowserMaxZoom]. A real change re-clamps the active tab's scroll to the new (smaller) extent and fires OnChange; setting the current value is a no-op (no notification).

func (*Browser) TabCount added in v0.112.0

func (b *Browser) TabCount() int

TabCount reports how many tabs are open.

func (*Browser) TabTitle added in v0.112.0

func (b *Browser) TabTitle(i int) string

TabTitle returns tab i's display title (title, or its URL when the title is empty); "" for an out-of-range index.

func (*Browser) Tick added in v0.112.0

func (b *Browser) Tick(deltaSeconds float64)

Tick advances Phase by deltaSeconds, wrapping modulo 1 (like Spinner.Tick), so the indeterminate loading bar animates in step with the host frame loop.

func (*Browser) Zoom added in v0.113.0

func (b *Browser) Zoom() float64

Zoom reports the current page-display zoom factor (1.0 is 1:1 fit-to-width).

func (*Browser) ZoomIn added in v0.113.0

func (b *Browser) ZoomIn()

ZoomIn increases the zoom by one step (no-op at BrowserMaxZoom).

func (*Browser) ZoomOut added in v0.113.0

func (b *Browser) ZoomOut()

ZoomOut decreases the zoom by one step (no-op at BrowserMinZoom).

type BrowserLink struct {
	Rect image.Rectangle
	Href string
}

BrowserLink is one clickable region of a delivered page render. Rect is in RENDER-pixel coordinates (the coordinate space of the pixels the host handed to Deliver, at the render width the host was told to use); the widget maps a content-area click back into that space to hit-test it.

type Button

type Button struct {
	Base

	Label   string
	OnClick func()
	Style   ButtonStyle // resting appearance; default is ButtonDefault

	// Icon, when set, lets the host paint a real vector glyph in the button's
	// face instead of the text Label — the seam other widgets use for
	// host-supplied icons (mirrors Banner.Icon / SearchEntry.Icon). Draw invokes
	// Icon with the button's full bounds and the current face ink (which already
	// carries the pressed / disabled tint), so the glyph tracks every button
	// state; the callback is responsible for centring + sizing itself within the
	// rect. When nil the button falls back to drawing Label, so existing callers
	// are unaffected and headless renders still show text.
	Icon func(p painter.Painter, r Rect, ink RGBA)

	// Selected is a sticky, app-managed "active" state (a pill in a selector, the
	// current tab/provider): when true the button fills with Accent regardless of
	// Style. The app sets it from its own model; the button never flips it itself.
	Selected bool

	// PressFeedback shows the pressed face on EventClick (until EventMouseUp).
	// NewButton enables it; set it false to opt a button out (e.g. one whose
	// action already navigates away so the flash would just flicker).
	PressFeedback bool

	// Flat suppresses the button's own rounded border + fill rounding, painting a
	// square-cornered face only — so it can sit inside a container that owns the
	// shared chrome (see ButtonGroup, which sets it on its members). The zero
	// value (false) keeps the standalone rounded look, so existing callers are
	// unaffected.
	Flat bool
	// 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. Value surfaces the button's state when it is not resting: "selected" for the sticky, app-managed Selected flag (a pill in a selector, the current tab), else "pressed" while the transient press-feedback face is showing — so a screen reader no longer hears an active or held button identically to an idle one.

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) Focused added in v0.101.0

func (f *Button) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*Button) OnEvent

func (b *Button) OnEvent(ev Event)

OnEvent drives the button from pointer events: EventClick presses it (shows the pressed face + fires OnClick) and EventMouseUp releases it. Self-managing the pressed state means any host that routes the press/release pair gets the click feedback for free, without also wiring SetPressed. Other event kinds are ignored. (SetPressed remains for hosts that drive press state their own way, e.g. enter/leave dispatch.)

func (*Button) SetFocused added in v0.101.0

func (f *Button) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 ButtonGroup added in v0.116.0

type ButtonGroup struct {
	Base
	Orientation Orientation
	Buttons     []*Button
}

ButtonGroup is a segmented cluster of adjacent Buttons rendered as one connected control: a single rounded border around the whole group, 1-pixel dividers between members, and no per-button border (the members are drawn Flat, so the group owns the chrome). Use it for related actions that read as a unit — a Back/Forward/Reload nav cluster, a zoom -/+ pair, a view switcher.

The members are ordinary *Button widgets: set each one's Icon / Label / OnClick / Disabled / Selected as usual; the group lays them out equally along its axis, routes clicks to the member under the pointer, and paints the shared frame. Orientation is Horizontal (the zero value) or Vertical.

func NewButtonGroup added in v0.116.0

func NewButtonGroup(buttons ...*Button) *ButtonGroup

NewButtonGroup builds a group over the given buttons, marking each Flat so the group draws the shared border instead of per-button outlines.

func (*ButtonGroup) A11y added in v0.130.0

func (g *ButtonGroup) A11y() A11yInfo

A11y reports the ButtonGroup as a group carrying how many buttons it holds.

func (*ButtonGroup) Draw added in v0.116.0

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

Draw paints the group background, each Flat member, the inter-member dividers, and one rounded border around the whole cluster.

func (*ButtonGroup) OnEvent added in v0.116.0

func (g *ButtonGroup) OnEvent(ev Event)

OnEvent forwards the event to the member under its (group-local) coordinates. Button.OnEvent handles the press/release itself, so a routed EventClick fires that member's OnClick.

func (*ButtonGroup) SetBounds added in v0.116.0

func (g *ButtonGroup) SetBounds(r Rect)

SetBounds positions the members: equal slices along the layout axis (the last member absorbs any rounding remainder so the group fills its bounds exactly).

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
	// ButtonDanger is a Surface-faced button with a red border + red label -- a
	// destructive action (Delete, Remove).
	ButtonDanger
)

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)
	// OnMonthChange fires after PrevMonth / NextMonth (or a header-arrow click)
	// moves the view to a new (year, month). Nil-safe.
	OnMonthChange func(y, m int)
	// contains filtered or unexported fields
}

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.

The header carries prev/next arrows ("<" / ">"): clicking them steps the viewed month (wrapping the year at the Dec/Jan boundary) and fires OnMonthChange with the new (year, month). PrevMonth / NextMonth expose the same navigation programmatically.

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) Focused added in v0.101.0

func (f *Calendar) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*Calendar) NextMonth added in v0.86.0

func (c *Calendar) NextMonth()

NextMonth advances the view one month, wrapping December to the next January, re-clamps the selected day into the new month, and fires OnMonthChange.

func (*Calendar) OnEvent

func (c *Calendar) OnEvent(ev Event)

OnEvent dispatches a header-arrow click to Prev/NextMonth and a day-cell click to OnSelect.

func (*Calendar) PrevMonth added in v0.86.0

func (c *Calendar) PrevMonth()

PrevMonth steps the view one month back, wrapping January to the previous December, re-clamps the selected day into the new month, and fires OnMonthChange.

func (*Calendar) SetDate

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

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

func (*Calendar) SetFocused added in v0.101.0

func (f *Calendar) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 CardLayout added in v0.59.0

type CardLayout struct {
	Active int
}

CardLayout shows exactly one item — the one at Active — filling the container, and collapses the rest to an empty rectangle so the Container skips them. The card layout (wizards, tab bodies, view switching).

func (*CardLayout) Arrange added in v0.59.0

func (l *CardLayout) Arrange(r Rect, items []Item)

Arrange fills the active item and empties the others.

type CardMeta added in v0.155.0

type CardMeta struct {
	Base
	// Author is the byline (a user / source name); empty hides it.
	Author string
	// Time is a pre-formatted relative or absolute time ("3h", "2026-08-14");
	// empty hides it. CardMeta does not format time — the caller passes a string.
	Time string
	// Score is an up-vote / points count; negative hides it (use −1).
	Score int
	// Comments is a reply count; negative hides it (use −1).
	Comments int
}

CardMeta is a horizontal strip of small metadata for a content card: an author, a relative time, a score and a comment count, laid out left to right as "author · time · ▲score · 💬comments" and elided to its width. It is the reusable footer/byline the MediaCard, ArticleCard and LinkCard all share, so the byline of a feed reads the same whatever the card type.

A field is shown only when it carries a value: Author / Time when non-empty, Score / Comments when NON-NEGATIVE. Set Score or Comments to a negative value (the sentinel −1 reads well) to hide that count entirely — a story with no score, an item with comments disabled. An all-hidden strip measures and paints as nothing, so a card can carry an empty CardMeta without reserving space for it.

CardMeta is passive content: it never reads input. Colour is the theme's dim-label tone (see dimInk) so the strip reads as subordinate to the title above it.

func NewCardMeta added in v0.155.0

func NewCardMeta(author, time string, score, comments int) *CardMeta

NewCardMeta builds a meta strip. Pass −1 for score or comments to hide that count; pass "" for author or time to hide those.

func (*CardMeta) A11y added in v0.155.0

func (m *CardMeta) A11y() A11yInfo

A11y reports the meta strip as static text carrying its joined byline; a hidden strip names nothing.

func (*CardMeta) Draw added in v0.155.0

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

Draw paints the strip within Bounds, vertically centred when the bounds are taller than one glyph row, and ellipsised to the bounds width. A hidden (empty) strip paints nothing.

func (*CardMeta) Measure added in v0.155.0

func (m *CardMeta) Measure(width int) int

Measure reports the strip's height at the given width — one glyph row when any field is shown, zero when the strip is entirely hidden. Width does not change the height (the strip is a single elided row); it is accepted for the uniform Measure(width) signature the card family shares.

type Carousel struct {
	Base

	Slides  []Widget
	Current int
	Wrap    bool

	// OnChange fires whenever the shown slide (Current) changes through a user
	// interaction: a gutter-arrow step (Prev/Next), a dot-indicator click, or an
	// arrow key. i is the new Current index. It runs only when Current actually
	// changes, so re-selecting the shown slide is silent. Nil is safe.
	OnChange func(i int)
	// contains filtered or unexported fields
}

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) Children added in v0.137.0

func (c *Carousel) Children() []Widget

Children yields every slide, including those currently off-screen: a walker asking for structure wants the whole model, and a reader can say which one is showing from the widget's own state.

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) Focused added in v0.102.0

func (f *Carousel) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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.

func (*Carousel) SetFocused added in v0.102.0

func (f *Carousel) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type CellEditor added in v0.150.0

type CellEditor interface {
	Widget
	// CellValue returns the editor's current text, read when the edit commits
	// and written back into the row.
	CellValue() string
	// SetCellValue seeds the editor with the cell's current text when the edit
	// opens.
	SetCellValue(string)
	// OnCellSubmit registers the callback the editor fires when the user
	// accepts the value (e.g. Enter) so the Table commits the edit.
	OnCellSubmit(func())
	// Focus gives (true) or removes (false) keyboard focus.
	Focus(bool)
}

CellEditor is the editing control a Table overlays on a cell while an inline edit is in progress. The default is a text field (see newTextCellEditor); TableColumn.Editor is the per-column seam that swaps in another control -- a numeric field, a drop-down, a date picker. A CellEditor is a Widget (so the Table sizes and draws it over the cell) plus the four hooks the commit machinery needs.

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

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) Focused added in v0.101.0

func (f *CheckButton) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*CheckButton) OnEvent

func (c *CheckButton) OnEvent(ev Event)

OnEvent flips Checked + fires OnToggle on click. A Disabled checkbox ignores every kind.

func (*CheckButton) SetFocused added in v0.101.0

func (f *CheckButton) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type Chip added in v0.9.0

type Chip struct {
	Base
	Text     string
	Closable bool
	OnClose  func()
	// Dot is an optional leading swatch colour. When Dot.A != 0 the
	// widget draws a small filled circle near the left edge (vertically
	// centred) and shifts the Text right so it does not overlap the
	// swatch -- handy for prefixing a chip with a category/source colour
	// (e.g. a coloured dot before "Reddit · golang"). The zero value
	// (A == 0) draws no dot and reproduces the original layout exactly,
	// so the field is fully backward-compatible.
	Dot RGBA
}

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 + optional leading dot + text + optional close affordance. Auto-sizes Bounds when W is zero (adding ChipDotD + ChipDotGap when a dot is present). The pill body is a filled SurfaceAlt rectangle stroked with a Border outline; when Dot.A != 0 a small filled circle in Dot colour is drawn at the left inset and the Text is shifted right past it; the Text is otherwise 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 Chord added in v0.151.0

type Chord []Accelerator

Chord is an ordered sequence of accelerators pressed in turn — the "Ctrl+K Ctrl+S" or "g d" multi-stroke binding pattern. A single-accelerator chord is the common case; a Keymap resolves longer chords stroke-by-stroke.

func MustParseChord added in v0.151.0

func MustParseChord(s string) Chord

MustParseChord is ParseChord that panics on error, for package-level chord literals known to be valid at author time.

func ParseChord added in v0.151.0

func ParseChord(s string) (Chord, error)

ParseChord parses a whitespace-separated sequence of accelerators into a Chord ("Ctrl+K Ctrl+S", "g d"). An empty string, or any segment that is not a valid accelerator, returns an error.

func (Chord) String added in v0.151.0

func (c Chord) String() string

String renders the chord as space-joined canonical accelerators, the inverse of ParseChord.

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 CodeEditor added in v0.180.0

type CodeEditor struct {
	*TextView

	// Language is the lexer hint handed to Syntax.Highlight (e.g. "go",
	// "ruby", "python"). An empty string lets the Highlighter decide
	// (guess / leave plain). Changing it re-lexes on the next Draw.
	Language string

	// Syntax is the pluggable highlighter. When nil (the zero value) the
	// buffer is painted in the theme's default ink exactly like a bare
	// TextView — the core toolkit ships no lexer, so a CodeEditor is
	// uncoloured until a consumer sets this to e.g. rougelex.New().
	Syntax Highlighter

	// HighlightCurrentLine paints a full-width tint behind the caret's
	// line. NewCodeEditor enables it; the zero value (a struct literal
	// built without the constructor) leaves it off.
	HighlightCurrentLine bool

	// CurrentLineColor overrides the current-line band colour. Its zero
	// value (A == 0, "unset") derives a subtle, theme-safe tint from the
	// theme passed to Draw.
	CurrentLineColor RGBA
	// contains filtered or unexported fields
}

CodeEditor is a multi-language source editor: a TextView (the editing model — lines, cursor, insert / split / backspace, undo/redo, selection, IME, scrolling) enriched with a line-number gutter, pluggable syntax highlighting and a current-line highlight. It is the one shared widget every wasmdesk code surface builds on (the wasmbox "code" client, go-loom, the reader source-preview) so they converge on a single implementation instead of each re-wiring a TextView by hand.

It embeds *TextView, so the whole editing API is available directly on a CodeEditor (Text, SetText, OnEvent, Undo, Lines, CursorLine, …); Draw is overridden to refresh the highlight cache, wire the gutter + current-line band, and paint through the embedded view.

func NewCodeEditor added in v0.180.0

func NewCodeEditor(initial string) *CodeEditor

NewCodeEditor builds a CodeEditor pre-loaded with initial source (split on "\n", empty yields a single empty line, per NewTextView). The line-number gutter and current-line highlight are on by default; Syntax is nil until a caller plugs a highlighter in.

func (*CodeEditor) A11y added in v0.180.0

func (c *CodeEditor) A11y() A11yInfo

A11y reports the editor as a textbox whose accessible name is the language (a hint to assistive tech about what is being edited) and whose value is the current buffer text. It shadows the promoted TextView.A11y so a screen reader hears the code editor, not a bare textbox.

func (*CodeEditor) Draw added in v0.180.0

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

Draw refreshes the highlight cache and paints the editor through the embedded TextView (which draws the gutter, the current-line band via the wired RowBackground hook, and the coloured text via the wired Highlighter hook).

type ColorChooser

type ColorChooser struct {
	Base
	Color    RGBA
	OnChange func(c RGBA)
	// contains filtered or unexported fields
}

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 moves a channel knob by press + drag. An EventClick on a track grabs that channel (remembered in active) and sets it from the pointer X; each following EventMouseDrag re-runs the set for the grabbed channel from the new X -- so a drag scrubs the value continuously, even once the pointer strays out of the row -- and EventMouseUp releases the grab. A click that misses every track (e.g. on the preview/hex area) grabs nothing. Coordinates are widget-local.

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 ColumnBrowser added in v0.136.0

type ColumnBrowser struct {
	Base

	// ColumnWidth is the pixel width of each directory column and the preview
	// pane. Set before SetRoot / SetBounds; defaults via NewColumnBrowser.
	ColumnWidth int

	// OnActivate fires when an already-selected leaf is picked again, with its
	// node — the "open this file" gesture. Nil-guarded.
	OnActivate func(node ColumnNode)
	// contains filtered or unexported fields
}

ColumnBrowser is a Miller-column ("columns") view of a tree: N side-by-side columns, each listing the children of a node, where picking a container opens the next column to its right and picking a leaf opens a compact preview column. The strip scrolls horizontally to keep the deepest columns visible. Each row carries a leading type icon and, for a container, a disclosure chevron.

It is driven entirely by a caller-supplied ColumnProvider, so it navigates any tree (a filesystem, a settings hierarchy, an object graph) without the widget knowing anything about the domain. Internally each column is a toolkit ListBox (composed over its public API — the ColumnBrowser never modifies ListBox), so a column inherits vertical scrolling, keyboard roving and selection for free.

Layout: a body filled with Theme.Surface; columns laid out left to right at ColumnWidth, anchored so the newest column stays in view, with a hairline Theme.Border between them; an optional preview pane (Theme.SurfaceAlt) after the last column showing a leaf's big icon, name and provider-supplied detail lines. Everything is clipped to the widget bounds.

Example

ExampleColumnBrowser navigates a two-level tree and reports the open column count after drilling into a folder.

cv := NewColumnBrowser(sampleTree())
cv.SetBounds(Rect{X: 0, Y: 0, W: 700, H: 300})
cv.SetRoot("root")
cv.Draw(newP(makeSurface(700, 300), 700), DefaultLight())
cv.OnEvent(Event{Kind: EventClick, X: 50, Y: 10}) // open "Docs"
fmt.Printf("open columns: %d\n", cv.ColumnCount())
Output:
open columns: 2

func NewColumnBrowser added in v0.136.0

func NewColumnBrowser(provider ColumnProvider) *ColumnBrowser

NewColumnBrowser builds a ColumnBrowser over provider with a default column width. Call SetRoot to list the first column, then SetBounds to lay it out.

func (*ColumnBrowser) A11y added in v0.136.0

func (cv *ColumnBrowser) A11y() A11yInfo

A11y reports the ColumnBrowser as a tree. Value names the deepest picked node (the leaf/folder at the end of the open chain), or is empty when nothing has been picked yet.

func (*ColumnBrowser) ColumnCount added in v0.136.0

func (cv *ColumnBrowser) ColumnCount() int

ColumnCount is the number of open directory columns (excluding the preview).

func (*ColumnBrowser) Draw added in v0.136.0

func (cv *ColumnBrowser) Draw(p painter.Painter, theme *Theme)

Draw paints the columns, their separators and the preview pane, clipped to the widget bounds so the horizontally-scrolled strip stays within its region.

func (*ColumnBrowser) OnEvent added in v0.136.0

func (cv *ColumnBrowser) OnEvent(ev Event)

OnEvent routes a click/scroll to the column under the pointer, translating the widget-local pointer X into that column's own local space; inert while Disabled.

func (*ColumnBrowser) SetBounds added in v0.136.0

func (cv *ColumnBrowser) SetBounds(r Rect)

SetBounds records bounds and lays out the columns.

func (*ColumnBrowser) SetRoot added in v0.136.0

func (cv *ColumnBrowser) SetRoot(rootKey string)

SetRoot resets the strip to a single column listing rootKey (or to an empty strip when the provider rejects it).

type ColumnInfo added in v0.180.0

type ColumnInfo struct {
	Name string
	// Type is the optional SQL data type (e.g. "INTEGER", "TEXT"); "" when the
	// adapter does not report one.
	Type string
}

ColumnInfo is one column of a table or view.

type ColumnNode added in v0.136.0

type ColumnNode struct {
	Name      string
	Key       string
	Icon      *Image
	Container bool
}

ColumnNode is one entry a ColumnProvider lists for a container. Container marks a node that opens a further column when picked (a folder); a non-container is a leaf that opens the preview pane. Icon is the optional leading type icon, Name the displayed label, and Key the opaque identity the provider uses to list the node's children and describe it.

type ColumnProvider added in v0.136.0

type ColumnProvider interface {
	Children(key string) (nodes []ColumnNode, ok bool)
	Preview(node ColumnNode) []string
}

ColumnProvider supplies the tree a ColumnBrowser navigates. Children returns the entries under the container identified by key (SetRoot's key for the first column); ok=false rejects the key — a permission error, a leaf mistaken for a container, an empty listing the caller wants to suppress — and no column opens. Preview returns the detail lines (kind, size, ...) shown under a picked leaf's name in the preview pane, or nil for none.

type ComboBox added in v0.73.0

type ComboBox struct {
	Base

	Options []string
	// Text is the current field value — either free text the user typed or an
	// option they selected. Filtered() narrows Options against it.
	Text string
	// Placeholder is shown in the muted tone when Text is empty (a hint such as
	// "search…" or "pick a colour").
	Placeholder string
	// Open reports whether the filtered popover list is showing.
	Open bool
	// OnChange fires whenever Text changes (a keystroke edit or a selection).
	OnChange func(string)
	// OnSelect fires when an option is chosen (click or Enter).
	OnSelect func(string)
	// contains filtered or unexported fields
}

ComboBox is an editable, type-to-filter dropdown: a single-line text field the user can type into, backed by a popover list of Options filtered to those containing the typed Text. It sits between Entry (a free-text field with no list) and DropDown (a closed list with no typing): the field accepts free text AND offers the matching options for one-click / Enter selection.

Like DropDown and DatePicker, the popover appears just below the field. The widget renders that list itself when Open (so it works standalone), while a host that composites overlays on a separate surface can instead read Open + PopoverBounds and draw the list there.

func NewComboBox added in v0.73.0

func NewComboBox(options []string) *ComboBox

NewComboBox builds a ComboBox with the given options and an empty field.

func (*ComboBox) A11y added in v0.105.0

func (c *ComboBox) A11y() A11yInfo

A11y reports the ComboBox as a combobox named by its current field text (either free text typed or a picked option).

func (*ComboBox) Draw added in v0.73.0

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

Draw paints the field (rounded border, Text or muted Placeholder, an end-of-text caret, and a right-side chevron) and, when Open, the filtered options as a plain list in PopoverBounds.

func (*ComboBox) Filtered added in v0.73.0

func (c *ComboBox) Filtered() []string

Filtered returns the Options whose lowercased text contains the lowercased Text. When Text is empty every option matches, so the full list is returned.

func (*ComboBox) Focused added in v0.101.0

func (f *ComboBox) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*ComboBox) OnEvent added in v0.73.0

func (c *ComboBox) OnEvent(ev Event)

OnEvent drives the type-to-filter behaviour: printable characters and Backspace edit Text (firing OnChange) and open the popover; a click on the field toggles Open; a click on a listed option selects it; Enter selects the first filtered option.

func (*ComboBox) PopoverBounds added in v0.73.0

func (c *ComboBox) PopoverBounds() Rect

PopoverBounds returns the Rect the filtered list occupies below the field: same X and W as the field, height proportional to the visible option count. Mirrors DropDown.PopoverBounds / DatePicker.PopoverBounds.

func (*ComboBox) SetFocused added in v0.101.0

func (f *ComboBox) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type CommandPalette added in v0.35.0

type CommandPalette struct {
	Base
	Commands []PaletteCommand

	Visible   bool
	OnDismiss func()
	// contains filtered or unexported fields
}

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.

The selection index always addresses the FILTERED list (the indices returned by filtered()), never Commands directly, and is re-clamped after any mutation to the query so it can never point past the end of a shrinking list.

The query string and selection index are private so every mutation flows through the clamping accessors (SetQuery / SetSelected / MoveSelection) or the key-feed (HandleKey); this lets a host — e.g. the wasmdesk Spotlight — drive and read the palette (Query, Selected, FilteredCommands) instead of re-implementing the filter + navigation itself, while the invariant above always holds.

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) FilteredCommands added in v0.86.0

func (c *CommandPalette) FilteredCommands() []PaletteCommand

FilteredCommands returns the commands currently visible under the query, in display order — the exact list the result rows render. A host can read it to mirror the palette's filtering (e.g. to show a live count) without duplicating the match logic.

func (*CommandPalette) HandleKey added in v0.86.0

func (c *CommandPalette) HandleKey(ev Event)

HandleKey feeds one keyboard event to the palette so a host can drive it directly (the wasmdesk Spotlight forwards its key events here): a printable EventChar extends the query + re-filters, Backspace trims it, ArrowUp/ ArrowDown move the selection, Enter activates the selected command, and Escape dismisses (firing OnDismiss). Non-keyboard events are ignored. Unlike OnEvent it does not gate on Visible, so a host managing its own visibility can still feed keys; it is the exact keyboard path OnEvent routes through.

func (*CommandPalette) MoveSelection added in v0.86.0

func (c *CommandPalette) MoveSelection(delta int)

MoveSelection shifts the selection by delta (negative = up, positive = down) within the filtered list, clamped at both ends (no wraparound), matching the ArrowUp/ArrowDown behaviour.

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.

func (*CommandPalette) Query added in v0.35.0

func (c *CommandPalette) Query() string

Query returns the current search text. Host-driver accessor: pair with SetQuery to read/write the query without touching internal state.

func (*CommandPalette) Selected added in v0.35.0

func (c *CommandPalette) Selected() int

Selected returns the current selection index within the FILTERED list.

func (*CommandPalette) SetActions added in v0.151.0

func (c *CommandPalette) SetActions(r *ActionRegistry)

SetActions replaces the palette's commands with the registry's current visible actions. Wire it to the registry's OnChange (c.SetActions(r)) so the palette rebuilds whenever an action's visibility flips or the set changes.

func (*CommandPalette) SetQuery added in v0.86.0

func (c *CommandPalette) SetQuery(q string)

SetQuery replaces the search text and re-clamps the selection into the newly filtered list, exactly as typing would. Use it to seed or override the query from a host.

func (*CommandPalette) SetSelected added in v0.86.0

func (c *CommandPalette) SetSelected(i int)

SetSelected sets the selection index (clamped into the filtered list).

type Container added in v0.59.0

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

Container holds a list of Items and positions them via its Layout. It is a Widget: Draw paints every item with a non-empty rectangle (so a CardLayout's inactive cards and collapsed box cells are skipped), and OnEvent routes by Bounds into the matched item's local space.

func NewContainer added in v0.59.0

func NewContainer(layout Layout) *Container

NewContainer builds a Container with the given layout (nil = items keep the bounds they are given). Add items with Add/AddWidget.

func (*Container) A11y added in v0.130.0

func (c *Container) A11y() A11yInfo

A11y reports the Container as presentational.

func (*Container) Add added in v0.59.0

func (c *Container) Add(it Item) *Container

Add appends a configured item and re-arranges. Returns the container for fluent, declarative construction.

func (*Container) AddWidget added in v0.59.0

func (c *Container) AddWidget(w Widget) *Container

AddWidget appends a plain widget with the zero item config (equal flex share in a box, centre in a border, a fit/card cell otherwise).

func (*Container) Children added in v0.123.0

func (c *Container) Children() []Widget

Children yields the container's child widgets in insertion order. It lets generic tree walkers (e.g. CollectRuns) descend without knowing the concrete container type.

func (*Container) Draw added in v0.59.0

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

Draw paints every item that has a non-empty rectangle.

func (*Container) Items added in v0.59.0

func (c *Container) Items() []Item

Items returns the container's items in insertion order.

func (*Container) OnEvent added in v0.59.0

func (c *Container) OnEvent(ev Event)

OnEvent forwards to the first non-empty item whose Bounds contains the point, translated into that item's local space. EventMouseMove is the exception: it is forwarded to EVERY non-empty item (translated), so the item under the pointer raises its hover face while the ones the pointer just left clear theirs — hover-enter and hover-leave both propagate without host wiring.

Keyboard events are handled by the focus system first: Tab/Shift+Tab move focus through the focusable descendants, and any other key/char is routed to the currently-focused descendant (routeFocusKey), never positionally. A click additionally moves focus to whichever focusable descendant it lands on.

func (*Container) SetBounds added in v0.59.0

func (c *Container) SetBounds(r Rect)

SetBounds positions the container and re-arranges its items.

func (*Container) SetItems added in v0.60.0

func (c *Container) SetItems(items ...Item) *Container

SetItems replaces every item and re-arranges — the seam a data-driven view uses to rebuild its children (e.g. an mvvm ObservableList binding). SetItems() with no arguments clears the container. Returns the container for chaining.

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 CycleButton added in v0.76.0

type CycleButton struct {
	Base

	Options  []string
	Index    int // index of the shown option; advanced on click
	OnChange func(index int, value string)

	// OnChangeIndex fires with the new Index every time the shown option
	// advances, alongside the multi-argument OnChange. It is the single-argument
	// counterpart that a value binder can wire to (OnChange's (index, value)
	// signature cannot drive an int field binding). Nil is safe.
	OnChangeIndex func(index int)
	// contains filtered or unexported fields
}

CycleButton is a button that steps through a fixed set of Options, showing the active one and advancing to the next on each click (wrapping past the end). It is the compact alternative to a radio group or dropdown when the choice set is small and cycling is natural (e.g. a view mode: List → Grid → Compact).

func NewCycleButton added in v0.76.0

func NewCycleButton(options ...string) *CycleButton

NewCycleButton builds a CycleButton over options (the first shown).

func (*CycleButton) A11y added in v0.105.0

func (c *CycleButton) A11y() A11yInfo

A11y reports the CycleButton as a button named by its currently-shown option (the value that advances on each click), or "" when it has no options.

func (*CycleButton) Draw added in v0.76.0

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

Draw paints the button body + the active option's label, centred, using the widget's font.

func (*CycleButton) Focused added in v0.101.0

func (f *CycleButton) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*CycleButton) OnEvent added in v0.76.0

func (c *CycleButton) OnEvent(ev Event)

OnEvent advances to the next option on a click (wrapping), firing OnChange. A Disabled cycle button ignores every kind.

func (*CycleButton) SetFocused added in v0.101.0

func (f *CycleButton) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

func (*CycleButton) Value added in v0.76.0

func (c *CycleButton) Value() string

Value returns the currently shown option, or "" when there are none (or Index is out of range).

type DataSource added in v0.180.0

type DataSource interface {
	// Schema returns the object tree the left pane renders: databases, each
	// with its tables/views, each with its columns.
	Schema() (Schema, error)
	// Query runs a read statement and returns the result set that fills the
	// grid: the column titles and the rows (each already stringified, one cell
	// per column).
	Query(sql string) (columns []string, rows [][]string, err error)
}

DataSource is the driver-agnostic seam a DatabaseEditor renders over. The toolkit deliberately keeps it tiny so the widget stays a lean pure-UI layer: a host injects an adapter that maps these methods onto a real engine, and a test injects an in-memory fake.

type DatabaseEditor added in v0.180.0

type DatabaseEditor struct {
	Base

	// OnCellEdit forwards the results grid's committed inline edits: row and col
	// index into the last result set plus the new value. Nil is safe.
	OnCellEdit func(row, col int, value string)

	// OnError fires when an operation fails, with the surfaced error. Nil is
	// safe; the error is painted in the error strip regardless of this hook.
	OnError func(err error)

	// OnQuery fires after a successful Run with the fetched result set, so a host
	// can update a status bar ("42 rows"). Nil is safe.
	OnQuery func(columns []string, rows [][]string)

	// Layout metrics. A non-positive value selects the constant default, so a
	// zero-valued field is the stock layout (see the dbEditorDefault* consts).
	TreeWidth    int
	BarHeight    int
	EditorHeight int
	ErrorHeight  int
	// contains filtered or unexported fields
}

DatabaseEditor assembles the toolkit's leaf widgets into a database workbench — the missing piece vs a standalone TreeView / TextView / Table: a schema/object tree on the left, a SQL editor at the top-right and an editable results grid at the bottom-right, wired to a run/execute toolbar.

Layout (absolute pixel regions, computed in SetBounds):

+---------------------------------------------------+
| toolbar (Run · Refresh)               full width  |  BarHeight
+----------------+----------------------------------+
|                | SQL editor (TextView)            |  EditorHeight
| schema tree    +----------------------------------+
| (TreeView)     | error strip                      |  ErrorHeight
|                +----------------------------------+
|                | results grid (Table, editable)   |  remainder
+----------------+----------------------------------+
  TreeWidth

CRITICAL — driver-agnostic: the toolkit bundles NO real database drivers. DatabaseEditor is pure UI over an injected DataSource; a host wires in a go-ruby-{pg,mysql,sqlite3,mongodb,redis} adapter (or, in tests, an in-memory fake) that speaks the three-method seam below.

func NewDatabaseEditor added in v0.180.0

func NewDatabaseEditor(source DataSource) *DatabaseEditor

NewDatabaseEditor builds a DatabaseEditor over source and eagerly loads its schema into the tree. A schema-load error is surfaced (lastErr / OnError) but does not stop construction, so a caller always gets a usable widget it can Refresh later. source must be non-nil.

func (*DatabaseEditor) A11y added in v0.180.0

func (d *DatabaseEditor) A11y() A11yInfo

A11y reports the DatabaseEditor as a labelled group; WalkA11y then descends through Children to announce the toolbar, tree, editor and grid.

func (*DatabaseEditor) Children added in v0.180.0

func (d *DatabaseEditor) Children() []Widget

Children yields the interactive child widgets in visual order so the a11y walker (WalkA11y) descends into them. The error strip is self-painted, not a child, so it is not listed.

func (*DatabaseEditor) Draw added in v0.180.0

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

Draw paints the toolbar, tree, SQL editor, error strip and results grid.

func (*DatabaseEditor) Editor added in v0.180.0

func (d *DatabaseEditor) Editor() *TextView

Editor is the SQL editor pane (top-right). A host reads Editor().Text() or seeds it with SetText. Swapping this TextView for a future CodeEditor widget is a follow-up; the highlighter seam (SQLHighlight) already lives here.

func (*DatabaseEditor) Err added in v0.180.0

func (d *DatabaseEditor) Err() error

Err reports the most recent operation error, or nil once an operation succeeded. It is the programmatic counterpart of the painted error strip.

func (*DatabaseEditor) Exec added in v0.180.0

func (d *DatabaseEditor) Exec() (affected int64, ok bool)

Exec runs the SQL editor's text as a non-result statement through the source's optional Execer half. It reports the affected-row count and true on success; on a missing Execer or an execution error it surfaces the error and returns false.

func (*DatabaseEditor) Grid added in v0.180.0

func (d *DatabaseEditor) Grid() *Table

Grid is the results Table (bottom-right). Its columns are made Editable by Run so a cell edit fires OnCellEdit.

func (*DatabaseEditor) OnEvent added in v0.180.0

func (d *DatabaseEditor) OnEvent(ev Event)

OnEvent routes a widget-local event to the child whose absolute bounds contain it, translated into that child's local space. A click that lands on the self-painted error strip (no child) is a no-op.

func (*DatabaseEditor) Refresh added in v0.180.0

func (d *DatabaseEditor) Refresh() error

Refresh reloads the schema from the source and rebuilds the tree. On error it surfaces the error (setError) and leaves the previous tree in place, returning the error so a caller can react.

func (*DatabaseEditor) Run added in v0.180.0

func (d *DatabaseEditor) Run()

Run executes the SQL editor's current text as a query and fills the results grid. A query error is surfaced and leaves the previous grid contents intact; a success clears the error, replaces the grid's columns + rows and fires OnQuery.

func (*DatabaseEditor) SQL added in v0.180.0

func (d *DatabaseEditor) SQL() string

SQL returns the current text of the SQL editor.

func (*DatabaseEditor) SetBounds added in v0.180.0

func (d *DatabaseEditor) SetBounds(r Rect)

SetBounds positions every child region. Bounds in this toolkit are absolute (surface) coordinates, so children receive absolute rects (see WalkA11y).

func (*DatabaseEditor) SetSQL added in v0.180.0

func (d *DatabaseEditor) SetSQL(sql string)

SetSQL replaces the SQL editor's text.

func (*DatabaseEditor) Toolbar added in v0.180.0

func (d *DatabaseEditor) Toolbar() *Toolbar

Toolbar is the run/execute action strip (top). Item 0 is Run, item 2 is Refresh (item 1 is a separator).

func (*DatabaseEditor) Tree added in v0.180.0

func (d *DatabaseEditor) Tree() *TreeView

Tree is the schema/object TreeView (left pane). Exposed so a host wires selection (Tree().OnActivate) — e.g. to seed a "SELECT * FROM <table>" query.

type DatabaseInfo added in v0.180.0

type DatabaseInfo struct {
	Name   string
	Tables []TableInfo
}

DatabaseInfo is one database (schema / catalog) and its tables and views.

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 DecoButton added in v0.72.0

type DecoButton struct {
	Rect     Rect
	Shape    DecoButtonShape
	Face     RGBA
	Outline  RGBA
	Glyph    DecoGlyph
	GlyphInk RGBA
}

DecoButton is one title-bar button: a face (rectangle or circle) filled with Face, an optional Outline (circle only; A=0 = none) and an optional Glyph stroked in GlyphInk. Rect is frame-local.

type DecoButtonShape added in v0.72.0

type DecoButtonShape int

DecoButtonShape selects how a title-bar button's face is drawn.

const (
	// DecoButtonRect draws a filled rectangular face (the Openbox close/minimize
	// box), with any Glyph stroked on top in GlyphInk.
	DecoButtonRect DecoButtonShape = iota
	// DecoButtonCircle draws a filled circle with an optional 1-unit outline (the
	// macOS traffic-light dot); a circle button usually carries no glyph.
	DecoButtonCircle
)

type DecoGlyph added in v0.72.0

type DecoGlyph int

DecoGlyph selects the symbol stroked inside a button face.

const (
	// DecoGlyphNone draws no symbol (a bare face / traffic-light dot).
	DecoGlyphNone DecoGlyph = iota
	// DecoGlyphClose draws an "×" (two diagonals).
	DecoGlyphClose
	// DecoGlyphMinimize draws a low horizontal bar.
	DecoGlyphMinimize
	// DecoGlyphMaximize draws a square outline.
	DecoGlyphMaximize
)

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) Children added in v0.137.0

func (d *Dialog) Children() []Widget

Children yields the dialog's content.

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 Dock added in v0.57.0

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

Dock arranges bars against the edges of its bounds and lets a single body widget fill whatever space is left in the centre — a docked-items model. Bars are docked in insertion order, so a top bar added before a left bar spans the full width above the left bar, and the left bar only gets the height that remains. Any edge may hold several bars, which stack inward in order.

Dock is a Widget: Draw paints the body then the bars; OnEvent routes by Bounds, translating into the matched child's local space. The body may be nil (a bars-only frame).

func NewDock added in v0.57.0

func NewDock(body Widget) *Dock

NewDock builds a Dock around body (nil for a bars-only frame). Add bars with Dock().

func (*Dock) A11y added in v0.130.0

func (d *Dock) A11y() A11yInfo

A11y reports the Dock as a toolbar carrying its docked entries.

func (*Dock) Children added in v0.137.0

func (d *Dock) Children() []Widget

Children yields the docked bars and then the body.

func (*Dock) Dock added in v0.57.0

func (d *Dock) Dock(w Widget, side DockSide, size int)

Dock attaches w to the given edge with size pixels along the dock axis (its cross extent fills the space still available). size is clamped to ≥0.

func (*Dock) Draw added in v0.57.0

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

Draw paints the body first, then the bars over any shared edge (they never overlap, so order is cosmetic).

func (*Dock) OnEvent added in v0.57.0

func (d *Dock) OnEvent(ev Event)

OnEvent forwards to the first bar whose Bounds contains the point, else the body, translating into that child's local space.

func (*Dock) SetBounds added in v0.57.0

func (d *Dock) SetBounds(r Rect)

SetBounds carves each docked bar off the current available rectangle in insertion order, then gives the body whatever remains.

type DockItemState added in v0.179.0

type DockItemState struct {
	Active   bool
	Running  bool
	GlyphBox Rect
}

DockItemState is the per-item information a DockStyle needs to paint an item's face and its running/active indicators. GlyphBox is where the widget will draw the icon, so a style can centre an indicator (e.g. a running dot) under it.

type DockSide added in v0.57.0

type DockSide int

DockSide names the edge a docked bar attaches to.

const (
	DockTop    DockSide = iota // bar spans the top, given height = size
	DockBottom                 // bar spans the bottom
	DockLeft                   // bar spans the left, given width = size
	DockRight                  // bar spans the right
)

type DockStyle added in v0.179.0

type DockStyle interface {
	// DrawGround paints the bar background over r.
	DrawGround(p painter.Painter, theme *Theme, r Rect)
	// DrawFace paints one item's face and its running/active indicators over r,
	// returning the ink the widget should use for that item's icon + label.
	DrawFace(p painter.Painter, theme *Theme, r Rect, st DockItemState) (ink RGBA)
}

DockStyle paints an AppDock's decorative surfaces — the ground bar and each item's face plus its running/active indicators — so the same dock model, layout, magnification and hit-testing can wear different looks, exactly the way PostCard / GroupCard give one card model different faces. The widget draws the content (icon, label, attention badge); the style owns the chrome and returns the ink the content should use so a dark face can pick a light ink.

Ship-with styles: ModernDockStyle (macOS — flat rounded faces, a dot under a running icon), BevelDockStyle (Fluxbox — raised/sunken 3D bevels) and WindowsDockStyle (taskbar — flat buttons with an accent underline). A host may implement its own.

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

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) DrawPopover(p painter.Painter, theme *Theme)

DrawPopover paints the open options list at PopoverBounds, with the current selection highlighted. A no-op when the DropDown is closed. The host calls it in its overlay pass (after the rest of the scene) so the popover — which extends past the control's Bounds — sits on top; that z-ordering is the one thing the widget can't decide for itself.

func (f *DropDown) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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. A Disabled dropdown ignores every kind (it cannot be opened).

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) PopoverClick(x, y int) bool

PopoverClick routes a click at (x, y) — in the DropDown's own coordinate frame, the same one Bounds/PopoverBounds use — while the popover is open: a click inside it selects that option (firing OnSelect and closing), a click anywhere else just closes it. Returns true when the open popover consumed the click, false when the DropDown is closed (so the host falls through to its normal hit-testing, where a click on the control reopens it).

func (d *DropDown) Select(idx int)

Select picks idx, closes the popover + fires OnSelect.

func (f *DropDown) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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)]
	OnChange func(text string)
	OnSubmit func(text string)

	// Placeholder is shown in the muted tone when Text is empty and no IME
	// composition is in flight (a hint like "search…" or "client id").
	Placeholder string

	// Mask, when non-zero, is the rune each character is displayed as (e.g. '•')
	// instead of the real text — for secrets/passwords. Text/Value keep the real
	// contents; only the display is masked.
	Mask rune

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

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

func (f *Entry) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*Entry) OnEvent

func (e *Entry) OnEvent(ev Event)

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

func (*Entry) SetFocused added in v0.101.0

func (f *Entry) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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
	// Alt is the Option (⌥) / Alt modifier; Meta is the Command (⌘) / Super
	// (Windows/logo) modifier. Both default false. See the type doc above.
	Alt, Meta bool
	// Delta is the scroll amount, in ROWS, for an EventScroll: positive
	// scrolls down / forward (toward the end of the content), negative
	// scrolls up / back. It is zero and ignored on every other event kind.
	// Scrollable widgets pass it straight to ScrollBy, which clamps at
	// both ends, so an over-large Delta simply pins to the last (or first)
	// row instead of running off.
	Delta int
}

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.

Alt and Meta report the two remaining desktop modifiers, so a host can deliver a platform-native accelerator a widget could not otherwise tell apart from a plain Ctrl chord. Alt is the ⌥ Option key on macOS and the Alt key on X11/Wayland/Windows; Meta is the ⌘ Command key on macOS and the Super/Windows/logo key elsewhere. They let a file manager distinguish, for example, ⌘V (paste) from ⌘⌥V (paste-as-move) — a distinction Ctrl/Shift alone cannot express. Like Ctrl/Shift they default false, so a host that does not track them (or a widget that ignores them) behaves exactly as before; only code that opts in by reading them sees any change.

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

	// EventScroll fires when the user scrolls the wheel (or a trackpad
	// two-finger swipe) over the widget — the toolkit's native scroll
	// intent. Delta carries the scroll amount in ROWS: positive scrolls
	// down / forward (toward the end of the content), negative scrolls
	// up / back. X/Y carry the widget-local pointer position at the time
	// of the scroll, so a container can hit-test which child the wheel is
	// over. Scrollable widgets (ListBox, Table, TreeTable, TreeView,
	// ScrollView) handle it by calling their own ScrollBy(Delta), which
	// clamps at both ends; every other widget ignores it. Hosts translate
	// the browser's wheel event (or a native scroll gesture) into this
	// kind so no app has to hand-roll wheel routing.
	EventScroll

	// EventMouseMove fires when the pointer moves over the widget with NO
	// button pressed — the plain hover-tracking move (its pressed-button
	// counterpart is EventMouseDrag). X/Y carry the widget-local pointer
	// position. Containers forward it to their children (translating
	// coordinates like every other kind) so a leaf sets its hover face when
	// the pointer is over it and clears it when the pointer moves off; a
	// widget that draws no hover state simply ignores it. Purely a
	// visual-feedback signal — it never activates anything. Appended last
	// (rather than beside EventMouseDrag) so the pre-existing kinds keep
	// their integer values for any host that persists them.
	EventMouseMove

	// EventSecondaryClick fires on a secondary (right / two-finger / long-press)
	// button press inside the widget — the gesture a desktop user expects to open
	// a context menu. It carries the same widget-local X/Y and modifier fields as
	// EventClick; it is a press, with no paired release event, because opening a
	// menu needs only the down. A host that has no secondary button (or chooses
	// not to map one) simply never sends it, and a widget that does not handle it
	// is unaffected.
	EventSecondaryClick
)

type Execer added in v0.180.0

type Execer interface {
	// Exec runs a statement that returns no rows and reports how many were
	// affected.
	Exec(sql string) (affected int64, err error)
}

Execer is the OPTIONAL write half of a DataSource. A source that also runs non-result statements (INSERT / UPDATE / DELETE / DDL) implements it; DatabaseEditor detects it with a type assertion so the core DataSource interface stays lean and a read-only source need not implement it.

type Expander

type Expander struct {
	Base

	Label    string
	Expanded bool
	Content  Widget
	OnExpand func(expanded bool)
	// contains filtered or unexported fields
}

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) Children added in v0.137.0

func (e *Expander) Children() []Widget

Children yields the expander's content, whether or not it is expanded — see Carousel for why hidden content still belongs in the structure.

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) Focused added in v0.102.0

func (f *Expander) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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). While focused, Enter/Space toggles the header (same path as a header click).

func (*Expander) SetFocused added in v0.102.0

func (f *Expander) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 FitLayout added in v0.59.0

type FitLayout struct{}

FitLayout sizes every item to fill the container (typically one item, e.g. a card body). The fit layout.

func (FitLayout) Arrange added in v0.59.0

func (FitLayout) Arrange(r Rect, items []Item)

Arrange fills each item to the container bounds.

type FlowLayout added in v0.65.0

type FlowLayout struct {
	RowHeight int
	HGap      int
	VGap      int
}

FlowLayout places items left-to-right and wraps to a new row when the next item would overflow the container width — a wrapping row of pills/tags/buttons. Each item's width is its Item.Size (or, when unset, its widget's current Bounds width); every row is RowHeight tall, with HGap between items on a row and VGap between rows.

func (*FlowLayout) Arrange added in v0.65.0

func (l *FlowLayout) Arrange(r Rect, items []Item)

Arrange flows the items, wrapping on overflow.

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 when focus enters (true) or leaves (false) this
	// item.
	SetFocused(focused bool)
	// Focused reports whether this item currently holds keyboard focus.
	Focused() bool
}

Focusable is a widget that can hold keyboard focus. A widget satisfies it by embedding focusState (SetFocused/Focused come for free) — that embedding is what makes a widget focusable: display-only widgets omit it and are therefore never focused, drawn with a focus ring, nor visited by container Tab traversal.

The surface is deliberately minimal — narrower than the terminal sibling's tui.Focusable, which embeds the whole Widget interface because tui's FocusRing also lays out, draws, and routes events to its members. A toolkit widget instead draws its own focus ring (see focusState.drawFocusRing) and a focus-managing Container/HBox/VBox/Grid/Frame routes keys and clicks to it, so Focusable only has to expose and toggle the focused flag.

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 font widgets lay out and draw with: the one a host set, or the built-in bitmap at the current metric scale.

The built-in scales because a host that turned the one documented HiDPI knob should not get chrome at twice the size around type that stayed put -- which is a worse interface than the one it had before it asked. A host that chose a font chose its size too, so that one is left alone: the same rule Menu and Browser follow for their own Scale fields.

func DefaultOpenTypeFont added in v0.77.0

func DefaultOpenTypeFont(sizePx int) (Font, error)

DefaultOpenTypeFont returns the toolkit's bundled default face — Atkinson Hyperlegible, designed by the Braille Institute for maximum character distinction — as an anti-aliased, shaped Font at sizePx pixels. A parse failure (which the bundled face never triggers) is returned wrapped.

Use it to install AA text yourself, or to build a multi-script fallback chain before installing:

base, _ := toolkit.DefaultOpenTypeFont(16)
cjk, _ := toolkit.NewTrueTypeFont(notosanssc.TTF, 16)
f, _ := toolkit.NewFallbackFont(base, cjk)
toolkit.SetFont(f)

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 NewFallbackFont added in v0.54.0

func NewFallbackFont(fonts ...Font) (Font, error)

NewFallbackFont chains fonts so glyphs missing from earlier fonts are rendered by later ones. The first font is the primary (its metrics and baseline drive layout). Every argument must be a font from NewTrueTypeFont; at least one is required. This is how the app renders scripts outside the primary UI face — pass a CJK face after the Latin one to display Chinese/Japanese text.

func NewSyntheticBoldFont added in v0.128.0

func NewSyntheticBoldFont(f Font) (Font, error)

NewSyntheticBoldFont returns a Font that draws f at a faux-bold weight. Use it only when the family has no true bold instance to load; a designed bold is always better. It errors on a nil font.

The wrapper composes over any Font, including a fallback chain, so the whole chain is emboldened rather than only its primary face.

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) Children added in v0.137.0

func (f *FormField) Children() []Widget

Children yields the field's control.

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
	// Title, when non-empty, draws a title bar across the top of the frame
	// (inside the border), turning the plain group-box into a titled panel.
	// The zero value "" keeps the original border-only box.
	Title string
	// Collapsible shows a ▼/▶ disclosure chevron in the title bar; a click
	// on the bar toggles Collapsed (and fires OnCollapse). It forces a title
	// bar even when Title is "".
	Collapsible bool
	// Collapsed hides the child, drawing only the title bar (Ext panel
	// collapse). Meaningful only when a title bar is present.
	Collapsed bool
	// OnCollapse fires when a title-bar click toggles Collapsed; the new
	// state is passed. Nil is safe.
	OnCollapse func(collapsed bool)
	// 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) Children added in v0.137.0

func (f *Frame) Children() []Widget

Children yields the framed widget.

func (*Frame) Draw

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

Draw paints the 1-pixel border, the title bar (if any) then the child. A collapsed frame draws only the title bar + a border around it.

func (*Frame) OnEvent

func (f *Frame) OnEvent(ev Event)

OnEvent toggles Collapsed on a title-bar click (when Collapsible), else forwards to the child if the event lands inside its Bounds. Keyboard events go through the focus system first (Tab/Shift+Tab traversal + routing to the focused descendant); a click inside the child also moves focus to the focusable it hits.

func (*Frame) SetBounds

func (f *Frame) SetBounds(r Rect)

SetBounds positions the Frame + resizes its child to fit inside the border, title bar (if any) + padding. A collapsed frame hides the child.

type GalleryItem added in v0.142.0

type GalleryItem struct {
	Image  *Image
	Label  string
	Key    string
	Raster bool
}

GalleryItem is one gallery entry: a thumbnail/icon and a label. Raster marks Image as a real raster thumbnail (a photo, a rendered preview) so a light chip is painted behind it; leave it false for a flat vector/symbol icon that needs no backing. A nil Image with Raster false draws a document glyph. Key is an opaque caller identity the widget never interprets.

type GalleryView added in v0.142.0

type GalleryView struct {
	Base

	// Items is the ordered gallery content. Mutate it directly and call
	// SetItems (or SetBounds) to re-normalize the selection and strip scroll.
	Items []GalleryItem

	// Empty is the message centred when there are no items; a blank Empty falls
	// back to a generic default.
	Empty string

	// OnSelect fires when a click or key moves the selection to a NEW item, with
	// its index. Nil-guarded.
	OnSelect func(index int)

	// OnActivate fires when the already-selected item is clicked again, or
	// Enter/Return/Space is pressed, with its index. Nil-guarded.
	OnActivate func(index int)
	// contains filtered or unexported fields
}

GalleryView is a preview-plus-filmstrip browser: a large preview of the current item filling the top region, and a horizontally-scrolling row of small thumbnails along the bottom. It generalizes a file-manager "gallery view" (macOS Finder's Gallery) — where an icon grid shows every item at the same small size, a GalleryView commits most of its area to ONE big preview and relegates the rest to a filmstrip, so a caller browsing photos or documents reads the current item large while still seeing its neighbours.

Layout: the body is filled with Theme.Surface; the top region (70% of the height) is the preview — the current item's raster thumbnail fit and centred inside a subtle rounded frame (a dark raster sits on a light backing chip so it stays visible on a dark theme), or a document glyph for a non-image item, with the item's label in a caption band beneath it. The bottom region (30%) is the filmstrip: a Theme.SurfaceAlt band under a hairline Theme.Border, laid out left to right at a uniform thumbnail size, scrolled horizontally and clipped to the widget bounds. The selected thumbnail is centred in the band and drawn with a soft accent field and an accent ring.

Selection + navigation: a click selects the thumbnail under the pointer (firing OnSelect); a second click on the already-selected thumbnail activates it (firing OnActivate). Left/Right (or ArrowLeft/ArrowRight) move the selection and auto-scroll the strip to keep it centred; Home/End jump to the ends; Enter/Return/Space activate the current item. Selected / SetSelected read and drive the selection programmatically. Because a gallery always shows a current item, a fresh GalleryView with at least one item selects index 0.

Example

ExampleGalleryView builds a small gallery, moves the selection with a key and reports the current item.

g := NewGalleryView(
	GalleryItem{Label: "Sunset.jpg", Key: "sunset", Raster: true},
	GalleryItem{Label: "Notes.txt", Key: "notes"},
)
g.SetBounds(Rect{X: 0, Y: 0, W: 320, H: 240})
g.Draw(newP(makeSurface(320, 240), 320), DefaultLight())
g.OnEvent(Event{Kind: EventKeyDown, Code: "ArrowRight"})
fmt.Printf("selected item %d\n", g.Selected())
Output:
selected item 1

func NewGalleryView added in v0.142.0

func NewGalleryView(items ...GalleryItem) *GalleryView

NewGalleryView builds a GalleryView over items. With at least one item the first is selected (a gallery always shows a current item); with none nothing is selected (Selected returns -1). Call SetBounds to lay it out before drawing.

func (*GalleryView) A11y added in v0.142.0

func (g *GalleryView) A11y() A11yInfo

A11y reports the GalleryView as a grid. Value is the current item's label, or empty when nothing is selected.

func (*GalleryView) Draw added in v0.142.0

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

Draw paints the preview and filmstrip (or the empty-state message), clipped to the widget bounds.

func (*GalleryView) OnEvent added in v0.142.0

func (g *GalleryView) OnEvent(ev Event)

OnEvent moves the selection on Left/Right/Home/End, activates on Enter/Return/Space, selects on a click, activates on a second click of the selected thumbnail, and is inert while Disabled or empty.

func (*GalleryView) PreviewRect added in v0.142.0

func (g *GalleryView) PreviewRect() Rect

PreviewRect is the top region that shows the large preview (the body minus the filmstrip band).

func (*GalleryView) Selected added in v0.142.0

func (g *GalleryView) Selected() int

Selected returns the current item index, or -1 when nothing is selected.

func (*GalleryView) SetBounds added in v0.142.0

func (g *GalleryView) SetBounds(r Rect)

SetBounds records the widget bounds and re-anchors the strip scroll so the current selection stays centred at the new size.

func (*GalleryView) SetItems added in v0.142.0

func (g *GalleryView) SetItems(items []GalleryItem)

SetItems replaces the gallery content and re-normalizes the selection (a now-out-of-range or unset selection snaps to the first item, or clears when there are no items) and the strip scroll.

func (*GalleryView) SetSelected added in v0.142.0

func (g *GalleryView) SetSelected(index int)

SetSelected selects item index and auto-scrolls the strip to keep it centred; an out-of-range index clears the selection to -1 (the preview goes blank). Unlike a key/click move it does not fire OnSelect.

func (*GalleryView) StripRect added in v0.142.0

func (g *GalleryView) StripRect() Rect

StripRect is the bottom filmstrip band.

func (*GalleryView) ThumbAt added in v0.142.0

func (g *GalleryView) ThumbAt(x, y int) int

ThumbAt maps a widget-local point to a thumbnail index, or -1 for the gap between thumbnails, past the last thumbnail, or outside the strip band.

func (*GalleryView) ThumbRect added in v0.142.0

func (g *GalleryView) ThumbRect(i int) (Rect, bool)

ThumbRect returns the on-screen rectangle of thumbnail i (accounting for the strip scroll) and whether it is at least partly visible in the strip band. ok is false for an out-of-range index.

type Gantt added in v0.82.0

type Gantt struct {
	Base
	Tasks    []GanttTask
	Units    int
	OnSelect func(i int)
	Selected int
	// OnTaskChange fires when a drag edits a task's span, with the task index
	// and its new [start, end) columns. Nil is safe -- Tasks is still mutated
	// in place, so the chart reflects the edit whether or not a host listens.
	OnTaskChange func(i, start, end int)
	// contains filtered or unexported fields
}

Gantt is a horizontal project-schedule chart: a left gutter of task Labels, a tick header naming the time-unit columns, and one row per task carrying a bar that spans its [Start, End) columns across the shared axis. Units is the total number of columns on that axis; when it is <= 0 it is derived from the largest task End so a caller can leave it unset. Progress paints a darker overlay on each bar, and Selected (when it indexes a task) tints that row and fires OnSelect on a click.

Gantt renders through painter.Painter, so the same schedule draws as pixels (WUI/GUI) or promoted cells (TUI). An empty task slice draws just the gutter separator, header band and axis ticks.

func NewGantt added in v0.82.0

func NewGantt(tasks []GanttTask) *Gantt

NewGantt builds a Gantt over the given tasks with no selection (Selected = -1) and an auto-derived axis (Units = 0). A nil slice is normalised to a non-nil empty slice so range loops and len() checks never special-case nil.

func (*Gantt) A11y added in v0.130.0

func (g *Gantt) A11y() A11yInfo

A11y reports the Gantt chart as an img carrying its task count, matching the other charts.

func (*Gantt) Draw added in v0.82.0

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

Draw paints the surface, the label gutter + its separator, the tick header band with one rule per axis column, and one row per task: a selection tint (Selected only), the task Label in the gutter, and a bar spanning [Start, End) with its Progress overlay. The plotting area (everything right of the gutter) and the gutter itself are clipped so a long label or an over-long bar never bleeds across the boundary.

func (*Gantt) OnEvent added in v0.82.0

func (g *Gantt) OnEvent(ev Event)

OnEvent drives selection and bar editing. On EventClick it selects the task row (firing OnSelect) and, from where in the bar the press landed, arms a drag: near the left/right edge resizes Start/End, inside the bar moves the whole span, and elsewhere in the row is a plain select. EventMouseDrag applies the edit live; EventMouseUp commits it and fires OnTaskChange. All callbacks are nil-safe.

func (*Gantt) ScrollBy added in v0.108.0

func (g *Gantt) ScrollBy(delta int)

ScrollBy shifts scroll by delta rows (negative scrolls up), clamped to [0, maxScroll()] and written back immediately.

func (*Gantt) TaskAt added in v0.84.0

func (g *Gantt) TaskAt(x, y int) int

TaskAt returns the index of the task row under widget-local (x, y), or -1 for the header band or empty space past the last task. The scroll offset is folded in so a hit-test after scrolling resolves to the task actually shown in that viewport slot. Exposed so a host can hit-test a right-click and build a context menu for that task.

type GanttTask added in v0.82.0

type GanttTask struct {
	Label      string
	Start, End int
	Fill       RGBA
	Progress   float64
}

GanttTask is one horizontal bar in a Gantt chart. Label names the task and is drawn in the left gutter; Start and End are integer time-unit columns on the shared axis (End must be greater than Start) so the bar spans the half-open range [Start, End). Fill is the bar colour — its zero value falls back to the theme's Accent so a task added without an explicit colour still paints in the app's palette. Progress in [0, 1] draws a darker overlay across that leading fraction of the bar, the usual "% complete" cue.

type Gauge added in v0.75.0

type Gauge struct {
	Base
	Min, Max  float64
	Value     float64
	Bands     []GaugeBand
	Caption   string
	Thickness int
}

Gauge is a radial arc gauge: a 270° track from the lower-left to the lower-right representing the range Min..Max, filled up to Value, with optional coloured threshold Bands and a centred Caption. Unlike ProgressCircle (a full ring that "fills up") it carries a value scale and colour zones, the display-only counterpart to a dashboard dial.

It rasterises the arc per-pixel over painter.Painter's putPixel (the same approach as PieChart — no arc primitive is added): the background track paints in theme.SurfaceAlt across the whole sweep, and the value arc paints from the start up to frac() in theme.Accent (or, when Bands is set, the colour of the band matching Value). The Caption is drawn centred in Base.Font. Value is clamped to [Min, Max] via frac.

func NewGauge added in v0.75.0

func NewGauge(min, max, value float64) *Gauge

NewGauge constructs a Gauge over the range min..max at the given value.

func (*Gauge) A11y added in v0.105.0

func (g *Gauge) A11y() A11yInfo

A11y reports the Gauge as a meter carrying its current value both as a Value string and as the numeric Min/Max/Now range triple.

func (*Gauge) Draw added in v0.75.0

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

Draw paints the background track arc, the value arc up to frac(), and the centred Caption. It is a no-op for an empty or sub-pixel bounds so a hidden or collapsed gauge draws nothing and never panics.

type GaugeBand added in v0.75.0

type GaugeBand struct {
	Upto  float64
	Color RGBA
}

GaugeBand is a coloured segment of a Gauge's track up to the value Upto: the band whose Upto first reaches (>=) the current Value gives the value arc its colour. Bands are consulted in slice order, so callers list them by ascending Upto (e.g. a green "ok" band, then a yellow "warn" band, then a red "critical" band).

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 GradientDir added in v0.183.0

type GradientDir int

GradientDir is the direction of a Backdrop's linear gradient fill.

const (
	// GradientVertical runs the gradient top (Fill) to bottom (GradientTo).
	GradientVertical GradientDir = iota
	// GradientHorizontal runs it left (Fill) to right (GradientTo).
	GradientHorizontal
	// GradientDiagonal runs it top-left (Fill) to bottom-right (GradientTo).
	GradientDiagonal
	// GradientCrossDiagonal runs it top-right (Fill) to bottom-left (GradientTo).
	GradientCrossDiagonal
)

type Grid

type Grid struct {
	Base
	// Spacing is the inter-cell gutter in pixels applied on both axes (negatives
	// clamped to 0 at layout time). Default 0 keeps the historical flush grid.
	Spacing int
	// ColWidths/RowHeights pin individual tracks to a fixed pixel size; a 0 entry
	// (or a missing index) is a flexible track sharing the remaining space equally.
	// Absent/empty = all-flexible (the historical equal-cell layout).
	ColWidths  []int
	RowHeights []int
	// contains filtered or unexported fields
}

Grid lays children out in a fixed cols x rows table. Children are placed via Attach(child, col, row); a cell with no attached child stays empty.

By default every cell is the same size (container W/cols, H/rows) with no gutter — the historical, zero-config behaviour. Two additive fields refine that:

  • Spacing adds an inter-cell gutter (in pixels) on BOTH axes. The gutters are subtracted from the extent before the cells are sized. Default 0 = flush.
  • ColWidths/RowHeights pin individual tracks to a fixed pixel size. An entry of 0 (or a track index past the slice) is a FLEXIBLE track: after the fixed tracks and gutters are removed, the remaining space is split equally among the flexible tracks. An absent/empty slice makes every track flexible, i.e. the all-equal historical layout.

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

func (g *Grid) A11y() A11yInfo

A11y reports the Grid as presentational. A data table is RoleGrid; this is a layout grid, which is a different thing wearing a similar name.

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) Children added in v0.137.0

func (g *Grid) Children() []Widget

Children yields the cells in insertion order.

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. EventMouseMove is forwarded to every attached child instead, so hover-enter and hover-leave both propagate (see HBox.OnEvent). Keyboard events go through the focus system and a click also moves focus to the focusable it hits (see HBox.OnEvent).

func (*Grid) SetBounds

func (g *Grid) SetBounds(r Rect)

SetBounds positions the Grid + sizes every attached child to its (col, row) cell, honouring Spacing gutters and any fixed ColWidths/RowHeights tracks. An empty incoming rect (W<=0 or H<=0) collapses every child to Rect{} so a hidden grid leaves no leaf with stale bounds.

type GroupCard added in v0.164.0

type GroupCard struct {
	Base
	// Pill is the coloured source tag (e.g. "Usenet"). Empty draws no pill.
	Pill string
	// PillColor / PillInk colour the source pill; the zero value (A==0) falls back
	// to Theme.Accent / a readable ink (see PostCard.pillInk).
	PillColor, PillInk RGBA
	// Status is the optional status pill beside the source pill (e.g. "complete").
	// Empty hides it.
	Status string
	// StatusColor / StatusInk colour the status pill; the zero value falls back to
	// Theme.Accent / a readable ink.
	StatusColor, StatusInk RGBA
	// Title is the group's headline (e.g. the release base name), one elided line.
	Title string
	// Meta is the muted summary line (e.g. "12 parts · 3 files · 40 MB").
	Meta string
	// Expanded reports whether the member list is shown below the header.
	Expanded bool
	// Members are the expanded part lines, one preformatted string per row.
	Members []string
	// Actionable enables the header affordance: a download checkbox and the Action
	// pill. When false neither is drawn and CheckRect / ActionRect are empty.
	Actionable bool
	// Action is the pill label shown when Actionable (e.g. "Reconstruct"). Empty
	// draws no pill but still reserves the checkbox when Actionable.
	Action string
	// Checked is the download checkbox state.
	Checked bool

	// Per-element fonts, each optional (nil falls back to EffectiveFont). TitleFont
	// sizes the headline, MetaFont the meta + member lines, PillFont the badges and
	// the action pill.
	TitleFont, MetaFont, PillFont Font
	// contains filtered or unexported fields
}

GroupCard is a collapsible summary card for a set of related items — a multi-part post, a thread, a release split across files. Its header is always shown: a disclosure chevron, a coloured source pill, an optional status pill (e.g. "complete" / "incomplete"), a title, and a muted meta line. When the post is Actionable it also carries, right-aligned in the header, a download checkbox and an action pill (e.g. "Reconstruct"). Expanding the card lists its Members — one preformatted line per part — beneath the header, divider-separated.

Layout (inside the CardPadX/Y inset):

┌──────────────────────────────────────────────┐
│ ▸ [Pill] [Status]              [x] (Action)    │  ← header: chevron, badges, affordance
│   Title over one elided line                   │
│   meta · line · here                           │
│   ── member line 1 ──────────────              │  ← Members, only when Expanded
│   ── member line 2 ──────────────              │
└──────────────────────────────────────────────┘

Like PostCard it is passive content: it lays out and paints itself and reports its exact height through Measure(width) (taller when Expanded); a feed list (CardList / VirtualList) puts selection / hover affordances on top, and reads the chevron / checkbox / action hit rectangles (ChevronRect / CheckRect / ActionRect) to route clicks. The title, meta and member lines are real Labels exposed through Children, so CollectRuns lifts them out as selectable text runs.

func NewGroupCard added in v0.164.0

func NewGroupCard(pill, title, meta string) *GroupCard

NewGroupCard builds a collapsed GroupCard from its header text fields.

func (*GroupCard) A11y added in v0.164.0

func (c *GroupCard) A11y() A11yInfo

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

func (*GroupCard) ActionRect added in v0.164.0

func (c *GroupCard) ActionRect() Rect

ActionRect is the Action pill's rectangle, right-aligned and vertically centred on the header. Empty when the card is not Actionable or Action is unset.

func (*GroupCard) CheckRect added in v0.164.0

func (c *GroupCard) CheckRect() Rect

CheckRect is the download checkbox's rectangle, left of the Action pill (or right-aligned when there is no Action pill). Empty when not Actionable.

func (*GroupCard) ChevronRect added in v0.164.0

func (c *GroupCard) ChevronRect() Rect

ChevronRect is the square hit target for the disclosure chevron, vertically centred on the header content at the card's left.

func (*GroupCard) Children added in v0.164.0

func (c *GroupCard) Children() []Widget

Children yields the card's selectable Labels in visual order — the title, the meta line, then each expanded member line — so CollectRuns lifts them out as text runs. Chevron, badges, checkbox and action pill are decoration and are not returned. Calling Children re-assembles the tree at the card's current bounds.

func (*GroupCard) Draw added in v0.164.0

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

Draw paints the card frame, the header (chevron, source + status pills, download checkbox + action pill), the title and meta, and — when expanded — the divider-separated member rows. Muted inks are theme-derived here, at paint time.

func (*GroupCard) Measure added in v0.164.0

func (c *GroupCard) Measure(width int) int

Measure reports the card's exact height at outer width width: the CardPadY inset top and bottom, the header content, and — when expanded — the member rows.

func (*GroupCard) MemberRect added in v0.164.0

func (c *GroupCard) MemberRect(i int) Rect

MemberRect is the i-th member row's rectangle within the expanded body.

type HBox

type HBox struct {
	Base
	// Spacing is the gap in pixels between adjacent children. NewHBox seeds it to
	// DefaultBoxSpacing (4); it is then honoured literally, so setting it to 0
	// yields a flush box and negative values are clamped to zero at layout time.
	Spacing int
	// Align positions each child on the cross (vertical) axis; the zero value
	// BoxStretch fills the height (the historical behaviour). Pack distributes
	// leftover width when the children do not fill the box (no flex child).
	Align BoxAlign
	Pack  BoxPack
	// 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 with Spacing seeded to DefaultBoxSpacing. Add children via Append/AddFlex/AddFixed.

func (*HBox) A11y added in v0.130.0

func (b *HBox) A11y() A11yInfo

A11y reports the HBox as presentational: it arranges its children and carries no meaning of its own.

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) Children added in v0.123.0

func (h *HBox) Children() []Widget

Children yields the box's child widgets in insertion order, so generic tree walkers (e.g. CollectRuns) can descend without knowing the box type.

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. EventMouseMove is forwarded to EVERY child (translated) instead, so the child under the pointer raises its hover face while the ones it left clear theirs. Keyboard events go through the focus system (routeFocusKey): Tab/Shift+Tab move focus, other keys route to the focused descendant; a click also moves focus to the focusable it hits.

func (*HBox) SetBounds

func (h *HBox) SetBounds(r Rect)

SetBounds positions the HBox + lays out its children across the width. An empty incoming rect (W<=0 or H<=0) collapses every child to Rect{} so a hidden box (e.g. an inactive CardLayout item) leaves no leaf with stale non-empty bounds.

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 positions its Start/End children in SetBounds (so their Bounds are correct before the first paint) and forwards pointer events to them in OnEvent, hit-testing each child and translating the event into its local frame -- the same dispatch HBox does. A child whose Bounds contains the event handles it; a click elsewhere on the bar is ignored.

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) Children added in v0.137.0

func (h *HeaderBar) Children() []Widget

Children yields the leading widgets then the trailing ones, which is how the bar reads left to right.

func (*HeaderBar) Draw added in v0.7.0

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

Draw paints the bar body, (re)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.

func (*HeaderBar) OnEvent added in v0.104.0

func (h *HeaderBar) OnEvent(ev Event)

OnEvent forwards a pointer event to the first Start / End child whose Bounds contains it, translated into that child's local frame -- mirroring HBox's dispatch. EventMouseMove goes to every child (so each raises/clears its hover face); every other kind lands only on the child under the pointer. Event coordinates are widget-local. layout() runs first so a child added after the last SetBounds still has current Bounds to hit-test against.

func (*HeaderBar) SetBounds added in v0.104.0

func (h *HeaderBar) SetBounds(r Rect)

SetBounds positions the bar + lays out its Start/End children so their Bounds are correct before the first paint (and before any OnEvent dispatch).

type Highlighter added in v0.180.0

type Highlighter interface {
	Highlight(language string, lines []string, theme *Theme) [][]TextSpan
}

Highlighter turns a source buffer into per-line coloured spans. It is the pluggable seam a CodeEditor uses for syntax highlighting, so the toolkit core carries no lexer of its own: a consumer supplies a rouge-backed implementation (github.com/go-widgets/toolkit/rougelex) — or any other — and importing the core toolkit never pulls a highlighting engine in.

Highlight receives the WHOLE buffer (as lines) rather than one line at a time, so a multi-line construct — a block comment, a heredoc, a triple quoted string — is coloured correctly across the lines it spans. The returned slice is indexed by line: element i holds the spans covering lines[i] in that line's rune coordinates ([Start, End), the same half-open convention as TextSpan). An implementation returns one entry per input line (len(result) == len(lines)); CodeEditor tolerates a short or long result by treating a missing row as "no spans".

type IconButton added in v0.9.0

type IconButton struct {
	Base

	Icon    string
	OnClick func()
	// contains filtered or unexported fields
}

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) Focused added in v0.101.0

func (f *IconButton) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*IconButton) OnEvent added in v0.9.0

func (i *IconButton) OnEvent(ev Event)

OnEvent drives the button from pointer events: EventClick presses it (showing the pressed face) and fires OnClick, EventMouseUp releases it, EventMouseMove tracks the hover face. A Disabled button ignores every kind. OnClick is nil-safe.

func (*IconButton) SetFocused added in v0.101.0

func (f *IconButton) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type IconCell added in v0.136.0

type IconCell struct {
	Image  *Image
	Label  string
	Key    string
	Raster bool
}

IconCell is one cell of an IconGrid: a thumbnail/icon and a label. Raster marks the Image as a real raster thumbnail (a photo, a rendered preview) so the cell paints a light chip behind it; leave it false for a flat vector/symbol icon that needs no backing. Key is an opaque caller identity carried by DragData.

type IconFunc added in v0.80.0

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

IconFunc paints a vector/stock icon into rect r using ink as its stroke colour — the same shape the toolkit's own DrawIcon*** helpers (DrawIconNew, DrawIconSettings, ...) and Table's RowIcon already use. A StatusIcon takes one so a caller can hang any of the stock icons, or a hand-drawn one, in a tray slot without shipping a bitmap.

type IconGrid added in v0.136.0

type IconGrid struct {
	Base

	// Cells is the ordered grid content. Mutating it is reflected on the next
	// Draw; the scroll offset is clamped on the fly, so shrinking Cells never
	// scrolls past the end.
	Cells []IconCell

	// IconSize is the icon square side in pixels; it drives the whole cell
	// footprint. Set it via SetIconSize (which enforces a sane minimum).
	IconSize int

	// Empty is the message centred in an empty grid; a blank Empty falls back to
	// a generic default.
	Empty string

	// OnSelect fires when a click moves the selection to a new cell, with its
	// index. Nil-guarded.
	OnSelect func(index int)

	// OnActivate fires when the already-selected cell is clicked again, with its
	// index. Nil-guarded.
	OnActivate func(index int)
	// contains filtered or unexported fields
}

IconGrid is a selectable, size-driven grid of icon/thumbnail cells that reflows to the widget width. It generalizes a file-manager "icon view": unlike a bare reflowing grid it owns per-cell chrome — the icon is centred and fit in a subtle rounded frame, a real raster thumbnail sits on a light backing chip so a dark image stays visible on a dark theme, and the label is centred and elided to the cell width — plus selection and hit-testing. The cell footprint is driven by IconSize, so a host slider can resize every cell live.

Layout: a body filled with Theme.Surface (or a centred empty-state message when there are no cells), then a reflowing grid of uniform cells centred within any left-over horizontal slack, scrolled vertically and clipped to the widget bounds. Each cell reserves padding above the icon, the icon square, a gap, and a label band; the selected cell paints a soft rounded field behind its icon and a rounded accent highlight behind its label.

Selection + navigation: a click selects the cell under the pointer (firing OnSelect); a second click on the already-selected cell activates it (firing OnActivate). The wheel scrolls the grid. Selected / SetSelected read and drive the selection programmatically, and DragData makes a selected cell a DragSource carrying its Key.

Example

ExampleIconGrid builds a small thumbnail grid, selects a cell and reports it.

g := NewIconGrid(
	IconCell{Label: "Report.pdf", Key: "report"},
	IconCell{Label: "Photo.jpg", Key: "photo", Raster: true},
)
g.SetBounds(Rect{X: 0, Y: 0, W: 200, H: 160})
g.Draw(newP(makeSurface(200, 160), 200), DefaultLight())
g.OnEvent(Event{Kind: EventClick, X: 40, Y: 40})
fmt.Printf("selected cell %d\n", g.Selected())
Output:
selected cell 0

func NewIconGrid added in v0.136.0

func NewIconGrid(cells ...IconCell) *IconGrid

NewIconGrid builds an IconGrid over cells with a default icon size. Nothing is selected initially.

func (*IconGrid) A11y added in v0.136.0

func (v *IconGrid) A11y() A11yInfo

A11y reports the IconGrid as a grid. Value is the selected cell's label, or empty when nothing is selected.

func (*IconGrid) DragData added in v0.136.0

func (v *IconGrid) DragData() string

DragData reports the selected cell's Key, or "" when nothing is selected. It makes the IconGrid a DragSource.

func (*IconGrid) Draw added in v0.136.0

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

Draw paints the visible cells (or the empty-state message), clipped to the widget bounds.

func (*IconGrid) IndexAt added in v0.136.0

func (v *IconGrid) IndexAt(x, y int) int

IndexAt maps a widget-local point to a cell index, or -1 for empty space.

func (*IconGrid) OnEvent added in v0.136.0

func (v *IconGrid) OnEvent(ev Event)

OnEvent scrolls on the wheel, selects on a click, activates on a second click of the selected cell, and is inert while Disabled.

func (*IconGrid) Selected added in v0.136.0

func (v *IconGrid) Selected() int

Selected returns the selected cell index, or -1 when nothing is selected.

func (*IconGrid) SetIconSize added in v0.136.0

func (v *IconGrid) SetIconSize(px int)

SetIconSize sets the icon square side, clamped to a readable minimum, and resets the scroll so the reflow stays anchored at the top.

func (*IconGrid) SetSelected added in v0.136.0

func (v *IconGrid) SetSelected(index int)

SetSelected selects cell index; an out-of-range index clears the selection.

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)
	// Alt is the image's accessible name — the short description a reader
	// announces in place of the picture. Set it to whatever the source calls the
	// image (a photo's caption, a chart's summary, a post's alt text); leave it
	// empty ONLY for decoration that carries no information, which is the same
	// rule as an empty HTML alt attribute.
	Alt string
}

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 named by its Alt text.

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 Item added in v0.59.0

type Item struct {
	Widget Widget
	Flex   int
	Size   int
	Region Region
}

Item wraps a widget with its per-layout configuration — the analog of an Ext child component's layout config. Box layouts read Flex/Size (a positive Flex is a proportional weight; else a positive Size is a fixed main-axis extent; both zero means an equal flex share, like HBox.Append). Border layouts read Region (with Size the edge band's thickness). Fit/Card ignore the config.

type Kanban added in v0.82.0

type Kanban struct {
	Base
	// Columns are the board's lists, left to right.
	Columns []KanbanColumn
	// OnCardClick fires when a card is clicked, with the 0-indexed column
	// and card. Nil is safe (the click still updates Selected*).
	OnCardClick func(col, card int)
	// SelectedCol / SelectedCard identify the highlighted card, or -1 (the
	// value NewKanban seeds) for "no selection". An out-of-range pair
	// collapses to "no highlight" in Draw the same defensive way Table
	// collapses a stale Selected.
	SelectedCol  int
	SelectedCard int
	// OnCardMove fires when a drag drops a card at a new position, with the
	// source (fromCol, fromCard) and the destination (toCol, toIdx) the card
	// now occupies. Nil is safe -- the board still updates Columns in place.
	OnCardMove func(fromCol, fromCard, toCol, toIdx int)
	// contains filtered or unexported fields
}

Kanban renders a Trello-style board: a row of equal-width columns (lists) laid side by side across the widget's Bounds, each a titled header band above a vertical stack of rounded cards. It is the missing "workflow board" primitive next to Table (a data grid) and ListBox (a single column of items) -- Kanban is many columns, each a stack of two-line cards, the shape a to-do / progress board needs.

Visual (per column):

+-----------------------+
| Title            (n)  |  <- KanbanHeaderH, SurfaceAlt, count Badge
+-----------------------+
| |  Card title         |  <- KanbanCardH, Surface, accent stripe
| |  muted subtitle     |
+-----------------------+
| |  Card title         |
| |  muted subtitle     |
+-----------------------+

Columns share the horizontal budget equally (like a fixed HBox), separated by KanbanColGap. Each card carries a left accent stripe -- KanbanCard.Accent, or Theme.Accent when that is the zero value -- so a board can colour-code cards by category without the host hand-drawing anything. The selected card (SelectedCol/SelectedCard) paints on an accent-tinted fill with an accent border.

A column whose cards overflow its height clips to its body (via painter.Clipper, the same graceful degradation Table relies on) AND scrolls independently: the wheel over a column shifts that column's card stack (colScroll) so cards past the fold are reachable, each column clipped to its own window. Clicking a card selects it and fires OnCardClick; clicks on headers, gaps or dead space are no-ops.

func NewKanban added in v0.82.0

func NewKanban(cols []KanbanColumn) *Kanban

NewKanban builds a Kanban from cols with no card selected (SelectedCol / SelectedCard both -1), mirroring how NewTable seeds Selected to -1.

func (*Kanban) A11y added in v0.105.0

func (k *Kanban) A11y() A11yInfo

A11y reports the Kanban board as a group carrying the selected card's title, or "" when nothing is selected (the -1/-1 sentinel or an out-of-range pair), mirroring how Draw collapses a stale selection.

func (*Kanban) CardAt added in v0.84.0

func (k *Kanban) CardAt(x, y int) (col, card int)

CardAt maps widget-local (x, y) to the (column, card) it lands on, or (-1, -1) for a header, gap or dead space. Exposed so a host can hit-test a right-click and build a context menu for the card under the cursor.

func (*Kanban) Draw added in v0.82.0

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

Draw paints every column: a SurfaceAlt panel + header band with the title and a count Badge, a Border divider, then the column's cards clipped to the column body. The selected card (if any) paints on an accent-tinted fill with an accent border; every other card on Surface with a Border stroke and its left accent stripe.

func (*Kanban) MoveCard added in v0.84.0

func (k *Kanban) MoveCard(fromCol, fromCard, toCol, toIdx int)

MoveCard removes the card at (fromCol, fromCard) and re-inserts it at toIdx in toCol, updating Selected* to its landing spot (out-of-range sources are ignored; toIdx is clamped). Exposed so a host can drive the same move a drag performs from a menu action.

func (*Kanban) OnEvent added in v0.82.0

func (k *Kanban) OnEvent(ev Event)

OnEvent drives selection and card drag-and-drop. On EventClick it grabs the card under the pointer (selecting it and firing OnCardClick); on EventMouseDrag it tracks the pointer and marks the gesture a drag; on EventMouseUp it drops the grabbed card at the target column/slot, mutating Columns and firing OnCardMove. A press-release with no intervening drag leaves the board a plain click. Both callbacks are nil-safe.

type KanbanCard added in v0.82.0

type KanbanCard struct {
	Title    string
	Subtitle string
	Accent   RGBA
}

KanbanCard is one card in a column: a bold Title over a muted Subtitle, with a left accent stripe drawn in Accent -- or Theme.Accent when Accent is the zero RGBA (A==0), the same "unset falls back to the theme" convention Badge.Fill uses. Either text may be "" to omit that line.

type KanbanColumn added in v0.82.0

type KanbanColumn struct {
	Title string
	Cards []KanbanCard
}

KanbanColumn is one list: a header Title above its stack of Cards.

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 Keymap added in v0.151.0

type Keymap struct {

	// OnChange, when set, fires after any mutation to the bindings
	// (Bind/Rebind/Unbind), so a menu or palette can refresh the shortcut
	// hints it shows for its actions.
	OnChange func()
	// contains filtered or unexported fields
}

Keymap maps chords to action ids across scopes, resolves keystrokes to actions one stroke at a time (so multi-stroke chords work), detects binding conflicts, and supports live rebinding. It holds no reference to an ActionRegistry: it stores action ids, and the caller runs the resolved id against a registry. This keeps the binding layer independent of the actions themselves — the same map can be inspected, serialised or rebound without the actions existing yet.

A Keymap is not safe for concurrent use; drive it from the UI goroutine.

func NewKeymap added in v0.151.0

func NewKeymap() *Keymap

NewKeymap returns an empty Keymap.

func (*Keymap) Bind added in v0.151.0

func (k *Keymap) Bind(chord Chord, action string, scope Scope) error

Bind binds chord to action in scope. Binding the same chord to the same action in the same scope is idempotent; binding it to a DIFFERENT action in the same scope returns ErrConflict (the existing binding is left intact). Binding the same chord in a different scope is always allowed — that is how a widget-scope binding shadows a global one.

func (*Keymap) Bindings added in v0.151.0

func (k *Keymap) Bindings() []Binding

Bindings returns an independent snapshot of every binding in registration order (chords deep-copied), for inspection or serialisation.

func (*Keymap) Conflict added in v0.151.0

func (k *Keymap) Conflict(chord Chord, scope Scope) (action string, conflict bool)

Conflict reports the action already bound to chord in scope, if any — the query a rebinding dialog runs to warn "this key is already used by X" before committing. conflict is false when the chord is free in that scope.

func (*Keymap) Feed added in v0.151.0

func (k *Keymap) Feed(ev Event, active ScopeMask) (action string, state MatchState)

Feed resolves one input event against the active scopes, tracking multi-stroke chords across calls:

  • Complete: the event finished a binding; the returned id is the action to run and the pending chord is cleared.
  • Partial: the event is a valid prefix of a longer chord; "" is returned and the pending chord is retained for the next Feed.
  • NoMatch: the event matched nothing; the pending chord is cleared. If a chord was in progress it is abandoned and the event is retried as a fresh first stroke, so a stray key mid-chord can itself begin a new binding.

Non-keyboard events return NoMatch without disturbing the pending chord. ScopeGlobal is always considered active; the mask adds window/widget scopes.

func (*Keymap) Pending added in v0.151.0

func (k *Keymap) Pending() Chord

Pending returns the chord typed so far but not yet resolved — the prefix a status line shows as "waiting for the next key". It is empty except between the strokes of a multi-stroke chord.

func (*Keymap) Rebind added in v0.151.0

func (k *Keymap) Rebind(action string, chord Chord, scope Scope) error

Rebind changes the chord bound to action in scope, live: it removes every existing binding for that action in that scope and installs the new chord. If the new chord is already bound to a DIFFERENT action in the same scope it returns ErrConflict and makes no change. This is the hot-rebinding entry point a "customise shortcuts" UI calls.

func (*Keymap) Reset added in v0.151.0

func (k *Keymap) Reset()

Reset clears any pending chord, abandoning a half-typed multi-stroke binding (e.g. on focus loss or Escape).

func (*Keymap) ShortcutFor added in v0.151.0

func (k *Keymap) ShortcutFor(action string) (Chord, bool)

ShortcutFor returns the chord bound to action at its most specific scope (widget over window over global), for a menu or palette to display as the action's current shortcut. It reflects live rebinds. ok is false when the action has no binding.

func (*Keymap) Unbind added in v0.151.0

func (k *Keymap) Unbind(chord Chord, scope Scope) bool

Unbind removes the binding for chord in scope, returning whether one was found and removed.

func (*Keymap) UnbindAction added in v0.151.0

func (k *Keymap) UnbindAction(action string) int

UnbindAction removes every binding for action across all scopes, returning how many were removed.

type Label

type Label struct {
	Base
	Text  string
	Align Align
	// VAlign is the vertical alignment of the text within the Label's bounds
	// height. The zero value VAuto keeps the original layout (centred when taller than the
	// text, else top); VTop/VMiddle/VBottom force a specific edge.
	VAlign VAlign
	// Ellipsis truncates the text with a trailing "…" when it is wider than the
	// Label's bounds width. The zero value false renders the full text, which
	// may overflow the bounds (the original behaviour).
	Ellipsis bool
	// Ink overrides the text colour. The zero value (A==0) means "inherit the
	// theme's OnSurface colour"; set a colour with a non-zero alpha to paint the
	// label in it (e.g. a muted or accent tone).
	Ink RGBA

	// FontSize, when positive, renders this label at that pixel size regardless
	// of the global font size — e.g. a large clock face over the app's default
	// body text. It re-renders the widget's base face (its Font override, else
	// the active font) at FontSize px via NewTrueTypeFont; all metrics + bounds
	// (width, glyph height, ellipsis) honour the resized face. The zero value
	// (or any non-positive value) keeps the base face at its own size, so an
	// unset FontSize is byte-identical to a pre-FontSize Label.
	//
	// FontSize only applies when the base face is a scalable TrueType/OpenType
	// font (one exposing its sfnt bytes); over the built-in bitmap font — which
	// has no outline to re-scale — it is ignored and the base face is used.
	FontSize int
	// contains filtered or unexported fields
}

Label is a passive widget that displays Text in the theme's OnSurface colour, drawn with the toolkit's 5x7 bitmap font. It is horizontally aligned per Align (left by default) and vertically aligned per VAlign (VAuto keeps the original centre-when-taller layout by default). When Ellipsis is set, over-wide text is truncated with a trailing "…" to fit the bounds width.

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. The text is positioned vertically per VAlign (VAuto keeps the original centre-when-taller) and horizontally per Align. When Ellipsis is set and the text is wider than the bounds it is truncated with a trailing "…" so it fits the width.

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.

func (*Label) SetFontSize added in v0.86.0

func (l *Label) SetFontSize(px int) *Label

SetFontSize sets the per-label pixel font size (see FontSize) and returns the Label for fluent chaining. A non-positive size clears the override, restoring the base face's own size.

func (*Label) TextRuns added in v0.122.0

func (l *Label) TextRuns() []TextRun

TextRuns exposes the label's text to the selection subsystem as a single run at its current bounds, measured with the face it paints with. It implements SelectableText, so a label is selectable/copyable when its container is fed into a TextSelection (e.g. via CollectRuns). An empty label contributes nothing.

type Layout added in v0.59.0

type Layout interface {
	Arrange(r Rect, items []Item)
}

Layout positions a container's items within its content rectangle. Swapping the Layout re-arranges the same items — the heart of the Ext operating model.

type LevelBar

type LevelBar struct {
	Base
	Value, Max  int
	Orientation Orientation
	// Label, when non-empty, is centred over the bar (horizontal only) in
	// Theme.OnSurface ink. The zero value draws no caption (the original look).
	Label string
	// Thresholds recolour the filled cells by value band. Empty (the default)
	// keeps the Accent fill, so an unset LevelBar is byte-identical to before.
	Thresholds []LevelThreshold
}

LevelBar is the discrete cousin of ProgressBar: Max equal cells, the first Value cells filled + the rest in SurfaceAlt. Useful for battery / signal-strength / VU-meter style indicators. Orientation Horizontal (default) fills left→right; Vertical fills bottom→top.

Two optional refinements layer on without changing the default look (no Label, no Thresholds renders exactly as before, filling in Accent):

  • Label: a caption centred over the bar (horizontal orientation only, where it fits), in Theme.OnSurface ink — e.g. "72%".
  • Thresholds: value bands that recolour the filled cells (e.g. red when low, amber mid, green high). The band whose Min is the greatest value not exceeding Value wins; with no matching band (or none configured) the fill stays Theme.Accent.

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, plus the numeric Min/Max/Now range triple (Min is 0, the empty meter).

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 the threshold fill (Theme.Accent by default), the rest Theme.SurfaceAlt. For the horizontal orientation an optional Label is centred over the bar.

type LevelThreshold added in v0.86.0

type LevelThreshold struct {
	Min   int
	Color RGBA
}

LevelThreshold recolours a LevelBar's fill once Value reaches Min. Several thresholds partition the range into coloured bands (e.g. {0,red}, {4,amber}, {8,green}); the band with the greatest Min not exceeding Value is applied.

type LineChart added in v0.12.0

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

	// Hover + HoverIndex drive a hover crosshair: when Hover is set, Draw paints
	// a vertical rule at data point HoverIndex and a marker where it meets the
	// curve. A host sets these from ValueAt on pointer motion; the zero value
	// (Hover == false) draws no crosshair, so existing renders are unchanged.
	Hover      bool
	HoverIndex int
}

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

func (*LineChart) OnEvent added in v0.100.0

func (c *LineChart) OnEvent(ev Event)

OnEvent tracks the hover crosshair from the pointer: a move over the chart sets Hover/HoverIndex to the nearest data point, a move off the chart (a container forwards moves to every child) clears Hover.

func (*LineChart) ValueAt added in v0.88.0

func (c *LineChart) ValueAt(localX int) (index int, value float64, ok bool)

ValueAt maps a widget-local x to the nearest plotted point, returning its index and value (ok=false only for an empty series). Exposed so a host can show the underlying value on hover.

type LinkCard added in v0.155.0

type LinkCard struct {
	Base
	// Favicon is the site glyph shown at the left; nil drops it and the title
	// column takes the full width. It is scaled into a square the height of one
	// text line.
	Favicon *image.RGBA
	// Title is the link headline, wrapped to the title column over as many lines
	// as it needs. Empty draws no title.
	Title string
	// Domain is the source host ("example.com"), drawn dim under the title as a
	// single elided line. Empty draws no domain.
	Domain string
	// Meta is the optional byline strip drawn under the title column; nil (or an
	// all-hidden strip) draws nothing and reserves no space.
	Meta *CardMeta
}

LinkCard is a content card for an external link: a small square favicon on the left, a wrapped title beside it and the source domain under the title in the dim tone, with an optional CardMeta strip closing the card. It is the unfurled-link tile a bookmarks list or a link-sharing feed is built from.

Layout (inside the CardPadX/Y inset):

┌────────────────────────────┐
│ ▣  Wrapped link title over  │  ← Favicon (left), Title wrapped in the
│    as many lines as needed  │    column to its right
│    example.com              │  ← Domain, dim, under the title
│ author · 3h · ▲12 · 💬4     │  ← Meta (optional), full content width
└────────────────────────────┘

The favicon is a one-line-tall square; a nil Favicon drops it and the title column spans the full content width. The domain is a single elided line. LinkCard is passive content — no hover, no selection.

func NewLinkCard added in v0.155.0

func NewLinkCard(favicon *image.RGBA, title, domain string, meta *CardMeta) *LinkCard

NewLinkCard builds a LinkCard with an optional favicon (nil for none), a title, a domain and an optional meta strip (nil for none).

func (*LinkCard) A11y added in v0.155.0

func (c *LinkCard) A11y() A11yInfo

A11y reports the link card as a group named by its title, with the source domain as its value.

func (*LinkCard) Children added in v0.155.0

func (c *LinkCard) Children() []Widget

Children yields the meta strip when present so a generic walk (accessibility, text selection) reaches it. The favicon, title and domain are drawn directly and are not sub-widgets.

func (*LinkCard) Draw added in v0.155.0

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

Draw paints the frame, the favicon, the wrapped title, the dim domain line and the meta strip. Content fills exactly Measure(Bounds().W): the same layout drives both.

func (*LinkCard) Measure added in v0.155.0

func (c *LinkCard) Measure(width int) int

Measure reports the card's height at the given outer width — the head row (favicon beside the stacked title and domain) and the meta strip stacked with CardGapY between them, plus the CardPadY inset top and bottom.

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

	// ItemRenderer, when non-nil, draws each row's CONTENT instead of the
	// default single line of text. It is handed the row's content rectangle
	// rc (full row height, minus the scrollbar gutter), the row index, the
	// item string, whether the row is selected, and the resolved text ink
	// (theme.OnSurface, or theme.Background when selected). The ListBox still
	// paints the row background (selection highlight) and owns scrolling,
	// selection and drag-reorder -- the renderer only fills in the content, so
	// a host can draw an icon + multi-line text, badges, a progress bar, etc.
	// This is the DataView seam. The zero value (nil) keeps the original
	// one-line text render, byte-identical to before this field existed.
	ItemRenderer func(p painter.Painter, theme *Theme, rc Rect, index int, item string, selected bool, ink RGBA)

	// 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) Focused added in v0.101.0

func (f *ListBox) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*ListBox) IndexAt added in v0.85.0

func (l *ListBox) IndexAt(x, y int) int

onClick handles a click at (X, Y): it selects the row idx = ScrollRow + Y/RowHeight -- Y/RowHeight locates the row within the visible window, ScrollRow maps that back to an absolute Items index (clamped to the list length); OnActivate fires with that idx.

When MultiSelect is false, a click simply moves Selected to idx -- unchanged from the widget's original single-selection behaviour, and Ctrl/Shift are ignored entirely.

When MultiSelect is true:

  • a plain click selects ONLY idx (clearing any other selected rows) and moves the anchor (Selected) to idx;
  • a Ctrl-click toggles idx's membership in the selection set and moves the anchor to idx;
  • a Shift-click selects the inclusive range between the current anchor (Selected) and idx, replacing the selection set, and leaves the anchor itself unchanged so successive Shift-clicks keep extending/shrinking from the same origin.

Every valid click also records idx as pressedRow (see DragData) -- unconditionally, regardless of Reorderable, since it costs nothing and DragData itself already gates on Reorderable. IndexAt returns the Items index under widget-local (x, y), accounting for the scroll offset, or -1 for empty space past the last row (or a zero RowHeight). Exposed so a host can hit-test a right-click and build a context menu for the item under the cursor (x is accepted for signature symmetry; the row is determined by y).

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. EventScroll (wheel) scrolls the visible window; the Arrow/Page/Home/End keys move the selection cursor and Enter/Space activate it (see handleKey). 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) SetFocused added in v0.101.0

func (f *ListBox) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 LoadMask added in v0.80.0

type LoadMask struct {
	Base
	// Active gates the whole widget: false draws nothing and is
	// event-transparent; true dims + shows the spinner/message + swallows
	// events. The zero value is inactive.
	Active bool
	// Message is an optional caption shown under the spinner (e.g. "Loading…").
	Message string
	// Scrim is the dimming colour painted over the bounds. The zero value
	// uses a translucent black (src-over blended by the pixel back-end), so a
	// LoadMask dropped in with no configuration reads as a subtle dim.
	Scrim painter.RGBA
	// contains filtered or unexported fields
}

LoadMask is a "busy" overlay: while Active it dims its whole bounds with a translucent scrim, paints a centred indeterminate Spinner and an optional Message, and swallows pointer events so the content beneath cannot be interacted with mid-load. Inactive, it draws nothing and lets events pass through (HitTest false), so it is safe to leave permanently mounted as the topmost Overlay layer and just toggle Active.

Drive the spinner animation by calling Tick(dt) from the host's frame loop (no goroutine/timer), the same cadence contract as Spinner and ProgressCircle.

func NewLoadMask added in v0.80.0

func NewLoadMask(message string) *LoadMask

NewLoadMask builds an inactive LoadMask with the given message (may be "").

func (*LoadMask) A11y added in v0.130.0

func (m *LoadMask) A11y() A11yInfo

A11y reports the LoadMask as a status region: it exists precisely to say that work is in progress, which is something a reader must be able to announce. The value distinguishes a mask that is actually up from one merely composed into the tree — an inactive mask draws nothing and blocks nothing.

func (*LoadMask) Animating added in v0.155.0

func (m *LoadMask) Animating() bool

Animating reports whether the mask still needs frames: true exactly when it is Active. Together with Tick this makes LoadMask an Animator, so a host drives its busy spinner through TickTree / TreeAnimating with no manual bookkeeping.

func (*LoadMask) Draw added in v0.80.0

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

Draw dims the bounds and paints the spinner + message while Active; inactive or empty-bounds it paints nothing.

func (*LoadMask) HitTest added in v0.80.0

func (m *LoadMask) HitTest(px, py int) bool

HitTest reports whether the mask should catch a pointer event: only while Active, so an inactive mask is fully transparent to clicks and an active one shields the content beneath it (the modal-scrim idiom, see Backdrop). While Active a covered event routes to the inherited Base.OnEvent no-op, i.e. it is swallowed and never reaches the content underneath.

func (*LoadMask) Tick added in v0.80.0

func (m *LoadMask) Tick(deltaSeconds float64)

Tick advances the spinner animation by deltaSeconds (a no-op visual while inactive, but cheap to keep calling).

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

func (m *MarkdownEditor) A11y() A11yInfo

A11y reports the MarkdownEditor as a textbox carrying its editable source text (Text() yields "" when the source pane is nil).

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 MatchState added in v0.151.0

type MatchState int

MatchState is the outcome of feeding one keystroke to a Keymap.

const (
	// NoMatch means the keystroke completed no binding and continues no
	// pending chord; the chord state is reset.
	NoMatch MatchState = iota
	// Partial means the keystroke is a valid prefix of one or more longer
	// chords; the [Keymap] is now awaiting the next stroke.
	Partial
	// Complete means the keystroke completed a binding; the returned action
	// id should be run and the chord state is reset.
	Complete
)

func (MatchState) String added in v0.151.0

func (s MatchState) String() string

String returns the state's name for diagnostics.

type Material added in v0.149.0

type Material struct {
	Base

	// Kind selects the default blur radius and colour wash, and is what a native
	// back-end maps to its system material. See MaterialKind.
	Kind MaterialKind

	// Blend selects behind-window vs within-window blur. Honoured by a native
	// back-end; the fallback blurs Source either way.
	Blend MaterialBlend

	// Source is the RGBA content behind the material (SW*SH*4 bytes) in surface
	// coordinates, that the fallback blurs. An invalid or nil buffer degrades the
	// fallback to a plain translucent tint.
	Source []byte
	SW, SH int

	// Sigma overrides the Gaussian blur radius. Zero uses the Kind's default;
	// a Kind whose default is 0 (Selection) paints no blur.
	Sigma float64

	// Tint overrides the colour wash. The zero value (A == 0) uses the Kind's
	// default derived from the theme; a non-zero value is used verbatim.
	Tint painter.RGBA

	// Child is composited on top of the material, laid out to fill its Bounds.
	// nil draws no child.
	Child Widget
	// contains filtered or unexported fields
}

Material is a translucent, blurred background panel with an optional child composited on top. It is the sidebar/menu/HUD "vibrancy" surface, expressed as an ordinary widget so a layout drops it in like any other.

It renders one of two ways:

  • Native backing. A back-end that can install a real platform vibrancy view (the Cocoa backend's NSVisualEffectView) discovers every Material in the tree with CollectMaterials, places a system effect view behind each one's Bounds, punches a transparent hole in the framebuffer there, and calls Material.SetNativeBacked(true). Draw then skips the fallback entirely — the system blur shows through the hole and applies its own material tint — and paints only the child. This is the seam; the mapping from MaterialKind to a system material lives in the back-end, not here, so this package names no platform.

  • Pure-Go fallback. Everywhere else (X11, Wayland, wasm, an image render) the material blurs a caller-supplied Source through a Gaussian and washes a translucent Tint over it. Source is the content behind the material in SURFACE coordinates — a compositor has the desktop wallpaper, a window shell has the pixels drawn before the material — because the Painter has no read-back, exactly as Thumbnail takes an explicit source buffer. The Gaussian is github.com/go-images/images.GaussianBlur, the fleet's image library, not a kernel written here.

With no Source and no native backing the material degrades to a plain translucent panel (just the Tint wash over whatever is already in the buffer), which still reads as a material, only without the softened backdrop.

func CollectMaterials added in v0.149.0

func CollectMaterials(root Widget) []*Material

CollectMaterials returns every Material in the tree rooted at root, in visual order. A native back-end calls it each frame to reconcile its system vibrancy views: for each returned material it reads Kind/Blend/Bounds, installs or moves an effect view, and calls SetNativeBacked so the fallback stands down.

It descends the same way WalkA11y does — through any widget exposing its children — so a material nested in a layout is still found. Bounds are surface coordinates (this toolkit's bounds are absolute), which is what a back-end compositing native views needs.

func NewMaterial added in v0.149.0

func NewMaterial(kind MaterialKind) *Material

NewMaterial builds a Material of the given kind with no source and no child. Set Source (or SetSource) for the fallback blur, and Child for content on top.

func (*Material) A11y added in v0.149.0

func (m *Material) A11y() A11yInfo

A11y marks the material itself as presentational: it is decorative backing, so a screen reader looks through it to the child, which the walk still descends into (see WalkA11y).

func (*Material) Children added in v0.149.0

func (m *Material) Children() []Widget

Children exposes the material's child so generic walks (accessibility, material collection) descend into it.

func (*Material) Draw added in v0.149.0

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

Draw paints the material and its child. See the type doc for the two paths.

func (*Material) HitTest added in v0.149.0

func (m *Material) HitTest(px, py int) bool

HitTest passes pointer events through the decorative backing to whatever is composited behind it UNLESS a child covers the point — the same event-transparent idiom as Backdrop, but a material carrying interactive content (a sidebar's list) must let that content be clicked.

func (*Material) Invalidate added in v0.149.0

func (m *Material) Invalidate()

Invalidate drops the cached blur. Call it after overwriting the contents of Source in place; SetSource already does.

func (*Material) NativeBacked added in v0.149.0

func (m *Material) NativeBacked() bool

NativeBacked reports whether a native vibrancy view backs this material.

func (*Material) OnEvent added in v0.149.0

func (m *Material) OnEvent(ev Event)

OnEvent forwards events to the child (translated to child-local coordinates), matching the single-child container convention. A move is forwarded unconditionally so the child can clear a hover face; other kinds land only when the point is inside the child.

func (*Material) SetBounds added in v0.149.0

func (m *Material) SetBounds(r Rect)

SetBounds positions the material and lays the child out to fill it.

func (*Material) SetNativeBacked added in v0.149.0

func (m *Material) SetNativeBacked(v bool)

SetNativeBacked records whether a back-end has installed a real platform vibrancy view behind this material. A back-end calls it after placing (true) or removing (false) the system effect view; the default is false (fallback).

func (*Material) SetSource added in v0.149.0

func (m *Material) SetSource(pixels []byte, w, h int)

SetSource replaces the background buffer the fallback blurs and drops the cached blur. The buffer is referenced, not copied; length must be w*h*4.

func (*Material) Spec added in v0.149.0

func (m *Material) Spec() MaterialSpec

Spec returns the material's placement and role as plain data.

type MaterialBlend added in v0.149.0

type MaterialBlend int

MaterialBlend selects what a material blurs: the content BEHIND the window (the desktop showing through) or the content WITHIN the window drawn behind the material. It mirrors the two blending modes every platform vibrancy API offers. The pure-Go fallback treats both identically — it blurs whatever Source it is given — but a native back-end honours the distinction when it installs its system effect view.

const (
	// BlendBehindWindow blurs what is behind the window itself.
	BlendBehindWindow MaterialBlend = iota
	// BlendWithinWindow blurs the window content drawn behind the material.
	BlendWithinWindow
)

type MaterialKind added in v0.149.0

type MaterialKind int

MaterialKind names a standard translucent-background material — a blurred backdrop washed with a semi-transparent colour, the "vibrancy" a modern desktop paints behind a sidebar, a menu or a heads-up panel so the content behind the surface shows through, softened.

The vocabulary is deliberately generic UI roles (a sidebar, a menu, a titlebar, a HUD) rather than any one platform's material names: a native back-end maps each kind onto its own system material (see the window package's Cocoa backend, which maps them to NSVisualEffectView materials), and the pure-Go fallback maps each onto a blur radius (sigma) plus a colour wash. A widget tree therefore asks for "a sidebar material" and renders correctly on every back-end without naming a platform.

const (
	// MaterialWindowBackground is the whole-window translucent ground.
	MaterialWindowBackground MaterialKind = iota
	// MaterialSidebar is the list rail beside primary content.
	MaterialSidebar
	// MaterialTitlebar is the strip along the top of a window.
	MaterialTitlebar
	// MaterialMenu is a menu / dropdown surface.
	MaterialMenu
	// MaterialPopover is a transient floating panel anchored to a control.
	MaterialPopover
	// MaterialHUD is a dark heads-up panel (always dark, independent of theme).
	MaterialHUD
	// MaterialSelection is a light accent wash over a selected region; it has
	// no blur of its own (sigma 0), only a translucent tint.
	MaterialSelection
)

type MaterialSpec added in v0.149.0

type MaterialSpec struct {
	Kind  MaterialKind
	Blend MaterialBlend
	Rect  Rect
}

MaterialSpec is one material's placement and role, as a native back-end reads it: what system material to install (Kind), how to blend it (Blend) and where (Rect, in surface coordinates). It is the plain-data view returned alongside the widget by CollectMaterials.

type Measurer added in v0.56.0

type Measurer interface {
	Measure(availW, availH int) (w, h int)
}

Measurer is an optional interface a widget may implement to advertise its natural size — an optional natural-size query. Box layouts consult it for cross-axis alignment (Align != BoxStretch); widgets that do not implement it fall back to their current cross Bounds, and failing that stretch to fill. availW/availH is the space the box can offer the child on each axis.

type MediaCard added in v0.155.0

type MediaCard struct {
	Base
	// Title is the headline, wrapped to the content width over as many lines as
	// it needs. Empty draws no title.
	Title string
	// Thumbnail is the lead image; nil drops the image band. It is scaled to the
	// full content width at its own aspect ratio.
	Thumbnail *image.RGBA
	// Meta is the optional byline strip drawn under the title; nil (or an
	// all-hidden strip) draws nothing and reserves no space.
	Meta *CardMeta
}

MediaCard is a content card led by a prominent thumbnail: the image spans the full content width at the top, the wrapped title sits below it, and an optional CardMeta strip closes the card. It is the tile a media / video / photo feed is built from.

Layout (top to bottom, inside the CardPadX/Y inset):

┌──────────────────────────┐
│   full-width thumbnail    │  ← Thumbnail, at the image's own aspect
│                          │
├──────────────────────────┤
│ Wrapped title over as     │  ← Title, wrapped to the content width
│ many lines as it needs    │
│ author · 3h · ▲12 · 💬4   │  ← Meta (optional)
└──────────────────────────┘

The thumbnail is scaled to the full content width at its own aspect ratio, so it fills the width with no letterbox; a nil Thumbnail drops the image band entirely. MediaCard is passive content — no hover, no selection.

func NewMediaCard added in v0.155.0

func NewMediaCard(title string, thumb *image.RGBA, meta *CardMeta) *MediaCard

NewMediaCard builds a MediaCard with a title, an optional thumbnail (nil for none) and an optional meta strip (nil for none).

func (*MediaCard) A11y added in v0.155.0

func (c *MediaCard) A11y() A11yInfo

A11y reports the media card as a group named by its title.

func (*MediaCard) Children added in v0.155.0

func (c *MediaCard) Children() []Widget

Children yields the meta strip when present, so a generic walk (accessibility, text selection) reaches it. The title and thumbnail are drawn directly and are not sub-widgets.

func (*MediaCard) Draw added in v0.155.0

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

Draw paints the frame, the full-width thumbnail, the wrapped title and the meta strip. Content fills exactly Measure(Bounds().W): the same layout drives both.

func (*MediaCard) Measure added in v0.155.0

func (c *MediaCard) Measure(width int) int

Measure reports the card's height at the given outer width — the thumbnail band, the wrapped title and the meta strip stacked with CardGapY between them, plus the CardPadY inset top and bottom.

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

	// Scale multiplies every fixed metric — row height, insets, gutters, the
	// check/submenu glyphs — so a HiDPI host gets a crisp, correctly sized menu
	// instead of one laid out in raw pixels. A host that renders at the backing
	// pixel ratio (optionally times a UI zoom) sets Scale to it, mirroring
	// Browser.Scale. Zero or negative defers to the toolkit-wide [MetricScale],
	// which is 1 unless a host set it -- so a menu that is told nothing still
	// follows the one knob a HiDPI host is documented to turn.
	Scale float64

	// OnItemToggle fires when activating a checkable or radio row changes its
	// Checked state (before the row's Action and OnClose run). i is the row
	// index and checked is its NEW state: for a Checkable row the flipped value,
	// for a RadioGroup member always true (its just-selected state; the siblings
	// it cleared are not separately reported). Plain (non-checkish) rows never
	// fire it. Nil is safe.
	OnItemToggle func(i int, checked bool)
	// contains filtered or unexported fields
}

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 and openSub 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); a click (or hover, or ArrowRight) on a submenu-parent row opens its child Menu beside the row. Keyboard: ArrowUp/ArrowDown move the Hover highlight to the previous/next navigable row (skipping separators and disabled rows, wrapping at both ends); ArrowRight opens the hovered submenu; Enter/Space activate the hovered row (opening its submenu, or firing its Action); Escape calls OnClose. While a submenu is open, pointer events over the child route into it, keys drive the child, and ArrowLeft/Escape close it. A disabled Menu ignores keys.

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

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

	// Icon, when set, paints a leading glyph in the row's icon cell, to the left
	// of the label. The Menu reserves an icon gutter (shifting every row's label
	// right) whenever ANY item has one, so labels stay aligned. It is handed the
	// square cell rect to fill and the row's current ink (so the glyph inverts on
	// a hovered row and greys out on a disabled one), keeping the toolkit free of
	// any particular icon set — the host draws whatever it likes into the cell.
	Icon func(p painter.Painter, cell Rect, ink RGBA)
}

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 ModernDockStyle added in v0.179.0

type ModernDockStyle struct{}

ModernDockStyle is the macOS look: a SurfaceAlt ground and flat, rounded item faces — Surface at rest, Accent when active — with a running dot under the icon. It is the AppDock default.

func (ModernDockStyle) DrawFace added in v0.179.0

func (ModernDockStyle) DrawFace(p painter.Painter, theme *Theme, r Rect, st DockItemState) RGBA

func (ModernDockStyle) DrawGround added in v0.179.0

func (ModernDockStyle) DrawGround(p painter.Painter, theme *Theme, r Rect)

type Node added in v0.61.0

type Node struct {
	Widget   Widget // leaf content; when set, Layout/Children are ignored
	Layout   Layout // container content: how Children are arranged
	Children []Node

	Flex   int    // parent box layout: proportional weight
	Size   int    // parent box layout: fixed main-axis size; border: band thickness
	Region Region // parent border layout: which region this node occupies
	// contains filtered or unexported fields
}

Node describes one piece of a UI tree. It is either a leaf (Widget set) or a container (Layout + Children). Flex/Size/Region are the layout configuration this node contributes to ITS PARENT's layout (the parent reads them when it adds this node as an Item); they are ignored on the root.

func BorderNode added in v0.61.0

func BorderNode(children ...Node) Node

BorderNode builds a border container node; children carry their Region via At.

func CardNode added in v0.61.0

func CardNode(active int, children ...Node) Node

CardNode builds a card container node showing the child at active.

func FitNode added in v0.61.0

func FitNode(children ...Node) Node

FitNode builds a container node whose children each fill it (FitLayout).

func HBoxNode added in v0.61.0

func HBoxNode(children ...Node) Node

HBoxNode builds a horizontal box container node. Like NewHBox it seeds the DefaultBoxSpacing gutter (via NewBoxLayout).

func Leaf added in v0.61.0

func Leaf(w Widget) Node

Leaf wraps a widget as a leaf node.

func VBoxNode added in v0.61.0

func VBoxNode(children ...Node) Node

VBoxNode builds a vertical box container node. Like NewVBox it seeds the DefaultBoxSpacing gutter.

func (Node) At added in v0.61.0

func (n Node) At(region Region) Node

At sets this node's border region and returns it (fluent).

func (Node) Build added in v0.61.0

func (n Node) Build() Widget

Build instantiates the node into a Widget: a leaf returns its Widget as-is; a non-leaf builds a *Container with the node's Layout and its recursively-built children (each added with its parent-layout config).

func (Node) Flexed added in v0.61.0

func (n Node) Flexed(flex int) Node

Flexed sets this node's parent-box flex weight and returns it (fluent).

func (Node) Ref added in v0.62.0

func (n Node) Ref(name string) Node

Ref tags this node with a lookup name so a ViewController built from the tree can retrieve its widget by name (a named reference). Empty names are ignored.

func (Node) Sized added in v0.61.0

func (n Node) Sized(size int) Node

Sized sets this node's fixed size (box main-axis extent, or border band thickness) and returns it (fluent).

type Notebook

type Notebook struct {
	Base

	Tabs         []NotebookTab
	Active       int
	TabSide      TabSide
	OnTabChanged func(idx int)
	// contains filtered or unexported fields
}

Notebook is a tabbed container. A scaled(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 scaled(NotebookTabWidth) wide, shrunk to fit); for Left/Right they stack vertically (each NotebookTabStripH tall) and the strip SCROLLS: the mouse wheel over a vertical strip shifts the stacked tabs (clamped at both ends), and arrow-key tab switching scrolls the strip to keep the active tab in view, so a strip with more tabs than fit stays fully reachable. 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) Children added in v0.137.0

func (n *Notebook) Children() []Widget

Children yields every tab's page.

func (*Notebook) Draw

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

Draw paints the strip (on the chosen side) + the active page. The whole render is clipped to Bounds() so nothing ever escapes the widget's box (a defence-in-depth over tabW's fit-to-width), and the active page is clipped to its body rect so an oversized page cannot paint over the tab strip.

func (*Notebook) Focused added in v0.101.0

func (f *Notebook) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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.

func (*Notebook) ScrollTabsBy added in v0.108.0

func (n *Notebook) ScrollTabsBy(delta int)

ScrollTabsBy shifts a vertical strip's scroll offset by delta tabs (negative scrolls up), clamped to [0, maxTabScroll()] and written back. A no-op for a horizontal strip.

func (*Notebook) SetFocused added in v0.101.0

func (f *Notebook) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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) Children added in v0.137.0

func (o *Overlay) Children() []Widget

Children yields the content first and then the layers, bottom to top, which is the order they are painted in.

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

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) Focused added in v0.101.0

func (f *Pagination) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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.

func (*Pagination) SetFocused added in v0.101.0

func (f *Pagination) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type PagingToolbar added in v0.80.0

type PagingToolbar struct {
	Base
	// Page is the 1-based current page.
	Page int
	// PageCount is the total number of pages.
	PageCount int
	// ShowRefresh adds a trailing reload button that fires OnRefresh.
	ShowRefresh bool
	// OnChange fires with the new page whenever navigation changes Page.
	OnChange func(page int)
	// OnRefresh fires when the Refresh button is clicked. Nil is safe.
	OnRefresh func()
}

PagingToolbar is a record-navigation toolbar: First (|<), Prev (<), a "Page N of M" indicator, Next (>), Last (>|) and an optional Refresh button. Navigation clamps to [1, PageCount] and fires OnChange only when the page actually changes; the extreme buttons render in a disabled tone and swallow clicks at the ends of the range. Unlike Pagination (a strip of numbered page buttons), this is the compact toolbar Ext's PagingToolbar provides for a data grid's footer.

func NewPagingToolbar added in v0.80.0

func NewPagingToolbar(page, count int) *PagingToolbar

NewPagingToolbar builds a toolbar at the given page/count. page is clamped into [1, count] and count to a floor of 1 (an empty grid still reads as "Page 1 of 1" with every nav button disabled).

func (*PagingToolbar) A11y added in v0.130.0

func (t *PagingToolbar) A11y() A11yInfo

A11y reports the PagingToolbar as a toolbar whose value is the position it controls — the one thing a reader needs from it.

func (*PagingToolbar) Draw added in v0.80.0

func (pt *PagingToolbar) Draw(p painter.Painter, theme *Theme)

Draw paints the toolbar body, buttons (extreme ones dimmed at the range ends), the "Page N of M" indicator and the optional Refresh button.

func (*PagingToolbar) OnEvent added in v0.80.0

func (pt *PagingToolbar) OnEvent(ev Event)

OnEvent routes an EventClick to whichever element contains it: First/Last jump to the ends, Prev/Next step by one (all clamped, firing OnChange only on an actual change), Refresh fires OnRefresh. The indicator + gaps are inert.

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

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) Children added in v0.137.0

func (p *Paned) Children() []Widget

Children yields the two panes in order.

func (*Paned) Draw

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

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

	// Hover + HoverIndex outline the hovered slice (its two boundary radii).
	// Opt-in; the zero value draws none.
	Hover      bool
	HoverIndex int
}

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.

func (*PieChart) OnEvent added in v0.100.0

func (c *PieChart) OnEvent(ev Event)

OnEvent outlines the pie slice under the pointer, clearing when it leaves.

func (*PieChart) SliceAt added in v0.90.0

func (c *PieChart) SliceAt(localX, localY int) (index int, value float64, ok bool)

SliceAt returns the slice under widget-local (x, y): its index, value and ok=true; ok=false for a point outside the pie or an empty chart. Exposed so a host can show the value on hover.

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) Children added in v0.137.0

func (p *Popover) Children() []Widget

Children yields the popover's content.

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 PostCard added in v0.157.0

type PostCard struct {
	Base
	// Pill is the coloured tag text (e.g. the source). Empty draws no pill.
	Pill string
	// PillColor is the pill body colour; the zero value (A==0) lets the pill fall
	// back to Theme.Accent.
	PillColor RGBA
	// PillInk is the pill text colour. The zero value (A==0) derives a readable
	// ink from PillColor (near-black on a light pill, near-white on a dark one),
	// or falls back to Theme.Background when PillColor is also unset.
	PillInk RGBA
	// Subtitle is the muted text beside the pill (e.g. the channel). Empty hides
	// it; when both Pill and Subtitle are empty the whole badge row collapses.
	Subtitle string
	// Title is the headline, wrapped to the content width over as many lines as it
	// needs, capped at MaxTitleLines with the last shown line ellipsised. Empty
	// draws no title.
	Title string
	// Meta is the muted footer line (e.g. "▲128 · 3h"). Empty hides it.
	Meta string
	// Thumbnail is the optional lead image; nil drops the whole thumbnail column.
	// It is scaled to fit its box preserving aspect ratio.
	Thumbnail *image.RGBA
	// ThumbW / ThumbH size the thumbnail column; a non-positive value selects the
	// default (DefaultPostCardThumbW / DefaultPostCardThumbH).
	ThumbW, ThumbH int
	// ThumbPlaceholder is the muted label drawn in the thumbnail box when the post
	// declares media but no decoded Thumbnail has landed yet (e.g. "image", "video").
	// A non-empty value reserves the thumbnail column even while Thumbnail is nil, so
	// the card does not reflow (grow a column) when the image finally arrives; the
	// box shows the label centred on the SurfaceAlt ground until then, reproducing
	// the historical "loading" card. Empty draws no column unless Thumbnail is set.
	ThumbPlaceholder string
	// MaxTitleLines caps the wrapped title; a non-positive value selects the
	// default (DefaultPostCardTitleLines).
	MaxTitleLines int

	// Per-element fonts give the card its type hierarchy — a larger bold title
	// over a smaller muted subtitle / meta, and a small pill — instead of one
	// uniform size. Each is optional: a nil font falls back to the card's
	// EffectiveFont (Base.Font, else the package font), so the zero value keeps
	// the previous single-font behaviour. TitleFont sizes the headline lines,
	// SubtitleFont the channel text, MetaFont the footer, PillFont the badge label.
	TitleFont, SubtitleFont, MetaFont, PillFont Font
	// contains filtered or unexported fields
}

PostCard is a rich feed row: a coloured pill (a source / category tag) with a muted subtitle beside it, a wrapped multi-line title, a muted meta line pinned to the bottom, and an optional lead thumbnail pinned to the top of a right-hand column. It is the generic form of a social / news / discussion card — an app maps its own item onto the fields (Pill = source, Subtitle = channel, Title = headline, Meta = score · age, Thumbnail = cached image) rather than hand-drawing the row.

Layout (inside the CardPadX/Y inset):

┌──────────────────────────────┬────────┐
│ [Pill] subtitle               │        │  ← badge row: pill + muted subtitle
│ A wrapped title over as many  │ thumb  │  ← Title, one Label per wrapped line
│ lines as it needs to fit      │        │
│                              │        │  ← flex spacer pushes meta down
│ meta · line · here            │        │  ← Meta (muted), bottom-pinned
└──────────────────────────────┴────────┘

The content column is an HBox flex child; the thumbnail (when present) is a fixed column whose image is pinned to the top, so a tall (multi-line) card leaves blank space below the thumbnail rather than stretching it. The subtitle, each wrapped title line and the meta line are real Labels exposed through Children, so CollectRuns lifts them out as selectable text runs.

PostCard is passive content: it lays out and paints itself and reports its exact height through Measure(width); a feed list (CardList / VirtualList) puts selection / hover / disabled affordances on top.

func NewPostCard added in v0.157.0

func NewPostCard(pill, subtitle, title, meta string) *PostCard

NewPostCard builds a PostCard from its four text fields. Set PillColor, Thumbnail and the sizing fields afterwards as needed.

func (*PostCard) A11y added in v0.157.0

func (c *PostCard) A11y() A11yInfo

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

func (*PostCard) Children added in v0.157.0

func (c *PostCard) Children() []Widget

Children yields the card's selectable Labels in visual order — the subtitle, each wrapped title line, then the meta line — so CollectRuns lifts them out as text runs and a11y / selection walks reach them. The pill and thumbnail are decoration and are not returned. Calling Children re-assembles the tree at the card's current bounds, so the returned Labels carry laid-out positions.

func (*PostCard) Draw added in v0.157.0

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

Draw paints the shared card frame, then the assembled content and thumbnail. The muted inks (subtitle, meta) and the title ink are theme-derived here, at paint time. Content fills exactly Measure(Bounds().W): the same layout drives both.

func (*PostCard) Measure added in v0.157.0

func (c *PostCard) Measure(width int) int

Measure reports the card's exact height at outer width width: the content column height plus the CardPadY inset top and bottom. A feed list allocates exactly this, and Draw fills exactly this, because both drive off contentHeight.

type ProgressBar

type ProgressBar struct {
	Base
	Fraction      float64
	Label         string
	Orientation   Orientation
	Indeterminate bool
	Phase         float64 // 0..1, only used when Indeterminate
}

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

When Indeterminate is set the bar ignores Fraction and instead animates a short chunk sliding along the track, driven by Phase (0..1, advance it from the host frame loop like a Spinner) — for work whose completion is unknown (a page fetch, an open-ended request).

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, plus the numeric range triple over the fraction's natural [0, 1] span (Now is the raw fraction).

func (*ProgressBar) Animating added in v0.155.0

func (pb *ProgressBar) Animating() bool

Animating reports whether the bar still needs frames: true exactly when it is Indeterminate (a determinate bar is a static fill and needs no repaint).

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.

func (*ProgressBar) Tick added in v0.155.0

func (pb *ProgressBar) Tick(deltaSeconds float64)

Tick advances the indeterminate sweep by deltaSeconds, wrapping Phase modulo 1 so it stays bounded. A determinate bar (the default) has no animation, so Tick is a no-op for it — matching what Animating reports. Together they make an indeterminate ProgressBar an Animator, driven by TickTree / TreeAnimating.

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 scaled(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 PropertyGrid added in v0.80.0

type PropertyGrid struct {
	Base
	// OnChange fires when a Value cell edit is committed (Enter): name is the
	// edited property, value its new text. Nil is safe.
	OnChange func(name, value string)
	// contains filtered or unexported fields
}

PropertyGrid is a two-column Name/Value grid for viewing and editing a set of named properties: the Value column is inline-editable, and committing an edit fires OnChange with the property's name and new value. It composes an editable Table (see NewTable / TableColumn.Editable), so it inherits the Table's rendering, selection, windowed scrolling and per-cell editing -- PropertyGrid just adds the property-oriented API on top (Add / SetValue / Value, keyed by name rather than row index).

The Name column is read-only; only Value cells open an editor on click.

func NewPropertyGrid added in v0.80.0

func NewPropertyGrid() *PropertyGrid

NewPropertyGrid builds an empty property grid (Name | Value, Value editable). Populate it with Add / SetValue.

func (*PropertyGrid) A11y added in v0.105.0

func (pg *PropertyGrid) A11y() A11yInfo

A11y reports the PropertyGrid as a grid carrying the selected property's name, or "" with no selection (or before the backing table is built).

func (*PropertyGrid) Add added in v0.80.0

func (pg *PropertyGrid) Add(name, value string)

Add appends a property row. Duplicate names are allowed (Value/SetValue address the first match), mirroring how a raw Table allows duplicate rows.

func (*PropertyGrid) Clear added in v0.80.0

func (pg *PropertyGrid) Clear()

Clear removes every property.

func (*PropertyGrid) Draw added in v0.80.0

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

Draw paints the grid via its backing Table.

func (*PropertyGrid) OnEvent added in v0.80.0

func (pg *PropertyGrid) OnEvent(ev Event)

OnEvent forwards to the backing Table (selection, and Value-cell editing).

func (*PropertyGrid) RemoveAt added in v0.87.0

func (pg *PropertyGrid) RemoveAt(i int)

RemoveAt removes the property at row index i, keeping the name index and the backing Table in sync and clearing a now-stale selection. An out-of-range i is a no-op. Exposed so a host can implement a "delete property" menu action (hit-test the row with PropertyGrid.Table().RowAt).

func (*PropertyGrid) SetBounds added in v0.80.0

func (pg *PropertyGrid) SetBounds(r Rect)

SetBounds positions the grid and its backing Table.

func (*PropertyGrid) SetValue added in v0.80.0

func (pg *PropertyGrid) SetValue(name, value string)

SetValue updates the named property's value in place, or appends it if it is not present yet.

func (*PropertyGrid) Table added in v0.80.0

func (pg *PropertyGrid) Table() *Table

Table exposes the underlying Table for advanced configuration (column widths, RowIcon, sorting, ...). Mutating Rows directly is not supported -- go through Add / SetValue so the name index stays in sync.

func (*PropertyGrid) Value added in v0.80.0

func (pg *PropertyGrid) Value(name string) string

Value returns the current value of the named property, or "" if there is no such property.

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 RadarChart added in v0.82.0

type RadarChart struct {
	Base
	Axes   []string
	Series [][]float64
	Max    float64 // normalisation max; when <= 0, taken from the data
	Colors []RGBA  // optional per-series palette override; cycles by index

	// Hover + HoverAxis highlight the hovered axis spoke. Opt-in; the zero
	// value draws none.
	Hover     bool
	HoverAxis int
}

RadarChart plots one or more series over a set of shared axes as closed polygons on a polygonal (spider) grid -- the multivariate complement to LineChart. Each of the N Axes gets a spoke from the centre (the first at 12 o'clock, the rest clockwise); a series' value on each axis, normalised to Max, sets how far out along that spoke its polygon vertex sits. Faint concentric N-gon rings and the spokes form the grid, each axis labelled just past its outer tip. Every series polygon is filled with a translucent tint of its colour and outlined in the solid colour. Colours cycle through the shared categorical palette unless Colors is set. Display-only.

It renders through painter.Painter, so the same chart draws as pixels (WUI/GUI) or promoted cells (TUI). No axes (or a degenerate size) draws nothing; a series shorter than the axis count treats the missing values as 0.

func NewRadarChart added in v0.82.0

func NewRadarChart(axes []string, series [][]float64) *RadarChart

NewRadarChart builds a RadarChart over the given axis labels and series.

func (*RadarChart) A11y added in v0.105.0

func (c *RadarChart) A11y() A11yInfo

A11y reports the RadarChart as an img carrying its axis count -- the salient dimension of a radar plot.

func (*RadarChart) AxisAt added in v0.90.0

func (c *RadarChart) AxisAt(localX, localY int) (axis int, ok bool)

AxisAt returns the axis whose spoke is nearest (in angle) to widget-local (x, y), and ok=false when the chart has no axes. Exposed so a host can show that axis's values on hover.

func (*RadarChart) Draw added in v0.82.0

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

Draw paints the grid rings, spokes and axis labels, then each series as a translucent fill under a solid outline.

func (*RadarChart) OnEvent added in v0.100.0

func (c *RadarChart) OnEvent(ev Event)

OnEvent highlights the radar spoke nearest the pointer, clearing when it leaves.

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) Focused added in v0.101.0

func (f *RadioButton) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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.

func (*RadioButton) SetFocused added in v0.101.0

func (f *RadioButton) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type RadioGroup

type RadioGroup struct {
	Members []*RadioButton
	Active  int

	// OnChange fires whenever the checked member changes through a user
	// interaction: a click on a member, or an arrow key moving the checked
	// member through the group. active is the new Active index. It fires
	// alongside the newly-checked member's own OnToggle, giving the group a
	// single-argument slot the whole selection can be observed through (e.g.
	// via mvvmtk.BindRadioGroup). Nil is safe.
	OnChange func(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)
	// Step is the increment an arrow key applies to the keyboard-focused handle.
	// When it is <= 0 the slider falls back to 1% of the range, so a caller that
	// never sets Step still gets sensible keyboard nudges.
	Step 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. The numeric triple exposes the track bounds in Min/Max; Now carries the Low handle (the arrow keys' default handle), since a single aria-valuenow cannot hold both -- the full band stays in Value.

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) Focused added in v0.101.0

func (f *RangeSlider) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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) SetFocused added in v0.101.0

func (f *RangeSlider) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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

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, plus the numeric Min/Max/Now range triple (Min is 0, the empty rating).

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) Focused added in v0.101.0

func (f *Rating) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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.

func (*Rating) SetFocused added in v0.101.0

func (f *Rating) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 Region added in v0.59.0

type Region int

Region names a Border layout edge (or the centre) an Item occupies. The zero value RegionCenter fills what the edge regions leave.

const (
	RegionCenter Region = iota
	RegionNorth
	RegionSouth
	RegionWest
	RegionEast
)

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)
	// Step is the increment an arrow key applies to Value. When it is <= 0 the
	// scale falls back to keyStep (1% of the range), so a caller that never sets
	// Step still gets sensible keyboard nudges. PageUp/PageDown always move a
	// whole page (keyPage, 10% of the range) regardless of Step.
	Step float64
	// contains filtered or unexported fields
}

Scale is a horizontal slider over a continuous Min..Max range. Click on the track jumps the thumb to that x-position + fires OnChange; dragging the thumb (or anywhere along the track with the button held) scrubs the value continuously through the same math. 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, both as a human-readable Value string and as the numeric Min/Max/Now range triple.

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) Focused added in v0.101.0

func (f *Scale) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*Scale) OnEvent

func (s *Scale) OnEvent(ev Event)

OnEvent: a click (or a drag while the button is held) moves the thumb to the pointer's position along the track + fires OnChange; arrow / Home / End / Page keys move Value while focused. A single thumb needs no drag-grab state -- the position->SetValue->OnChange math handles any coordinate identically, so a drag is just a click that keeps arriving.

func (*Scale) SetFocused added in v0.101.0

func (f *Scale) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 ScatterChart added in v0.82.0

type ScatterChart struct {
	Base
	Series [][]ScatterPoint
	Colors []RGBA // optional per-series palette override; cycles by index

	// Hover + HoverSeries/HoverPoint ring the hovered point. Opt-in; the zero
	// value draws none.
	Hover                   bool
	HoverSeries, HoverPoint int
}

ScatterChart plots one or more series of (X, Y) points as small filled dots over a left+bottom axis frame -- the two-dimensional companion to LineChart. Both axes auto-scale to the combined data range (a flat range on either axis pads by ±1 so the points sit mid-plot rather than on an edge). Colours cycle through the shared categorical palette unless Colors is set. 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.

func NewScatterChart added in v0.82.0

func NewScatterChart(series [][]ScatterPoint) *ScatterChart

NewScatterChart builds a ScatterChart over the given series.

func (*ScatterChart) A11y added in v0.105.0

func (c *ScatterChart) A11y() A11yInfo

A11y reports the ScatterChart as an img carrying its series count.

func (*ScatterChart) Draw added in v0.82.0

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

Draw paints the axis frame then one dot per point, coloured by series.

func (*ScatterChart) NearestPoint added in v0.90.0

func (c *ScatterChart) NearestPoint(localX, localY int) (series, point int, pt ScatterPoint, ok bool)

NearestPoint returns the point closest (in pixels) to widget-local (x, y) — its series index, point index, the point, and ok=false when the chart has no data. Exposed so a host can show the value on hover.

func (*ScatterChart) OnEvent added in v0.100.0

func (c *ScatterChart) OnEvent(ev Event)

OnEvent rings the scatter point nearest the pointer, clearing when it leaves.

type ScatterPoint added in v0.82.0

type ScatterPoint struct{ X, Y float64 }

ScatterPoint is one (X, Y) sample plotted by a ScatterChart.

type Schema added in v0.180.0

type Schema struct {
	Databases []DatabaseInfo
}

Schema is the whole object tree a DataSource exposes.

type Scope added in v0.151.0

type Scope int

Scope is where a key binding applies, and therefore its resolution priority. A binding on a more specific scope shadows a less specific one that shares the same chord: the focused widget's bindings win over the window's, which win over the application-global ones. This is what lets a text field bind Ctrl+A to "select all" while the app keeps Ctrl+A as "select all items" elsewhere — same chord, different active scope.

const (
	// ScopeGlobal applies application-wide and is ALWAYS active during
	// resolution regardless of the active mask; it has the lowest priority.
	ScopeGlobal Scope = iota
	// ScopeWindow applies while a particular window/view is focused; it
	// overrides ScopeGlobal.
	ScopeWindow
	// ScopeWidget applies while a particular widget is focused; it has the
	// highest priority and overrides both ScopeWindow and ScopeGlobal.
	ScopeWidget
)

func (Scope) String added in v0.151.0

func (s Scope) String() string

String returns the scope's name for hints and diagnostics.

type ScopeMask added in v0.151.0

type ScopeMask uint8

ScopeMask is the set of scopes that are active for one resolution — the window and/or widget contexts currently focused. ScopeGlobal is implicitly always active, so a zero mask still resolves global bindings.

const (
	// MaskGlobal marks ScopeGlobal active (implied by every resolution).
	MaskGlobal ScopeMask = 1 << ScopeGlobal
	// MaskWindow marks ScopeWindow active.
	MaskWindow ScopeMask = 1 << ScopeWindow
	// MaskWidget marks ScopeWidget active.
	MaskWidget ScopeMask = 1 << ScopeWidget
)

func ActiveScopes added in v0.151.0

func ActiveScopes(scopes ...Scope) ScopeMask

ActiveScopes builds a ScopeMask from the given scopes, always including ScopeGlobal so global bindings resolve even when no window/widget context is supplied.

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) ChildOffset added in v0.137.0

func (s *ScrollView) ChildOffset() (int, int)

ChildOffset reports how far the scrolled content is painted from where its bounds say it is — see childOffsetter for why that difference exists at all.

func (*ScrollView) Children added in v0.137.0

func (s *ScrollView) Children() []Widget

Children yields the scrolled content.

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) OnEvent added in v0.95.0

func (s *ScrollView) OnEvent(ev Event)

OnEvent gives ScrollView native wheel + keyboard scrolling. A ScrollView measures its content in pixels rather than rows, so it converts the EventScroll Delta (expressed in ROWS) into a pixel offset using its effective font's line height — one wheel notch moves one text line. The arrow keys scroll a line, Page{Up,Down} a viewport height, and Home / End jump to the top / bottom; Scroll() clamps every result. All conversions go through Scroll(0, dy) (vertical only — horizontal scrolling stays under the host's control via Scroll directly). Any other event kind is ignored, so a ScrollView remains a passive viewport for clicks exactly as before.

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 Scrollbar added in v0.53.0

type Scrollbar struct {
	Base
	Total      int  // total content length along the scroll axis
	Viewport   int  // visible length
	Offset     int  // scroll offset; clamped to [0, Total-Viewport]
	Horizontal bool // false = vertical (the default for a scrollbar)
	// OnScroll, when non-nil, fires with the new (clamped) Offset whenever a
	// drag or track-page changes it. Nil keeps the scrollbar silent -- it still
	// updates its own Offset, but reports nothing.
	OnScroll func(offset int)
	// contains filtered or unexported fields
}

Scrollbar is a slim, grabbable scrollbar for scrollable content: a rounded track with a thumb sized to Viewport/Total and positioned by Offset, showing where the view sits within the whole. Vertical by default; set Horizontal for a bottom scrollbar. When everything fits (Total <= Viewport) the thumb fills the track.

The thumb is a live affordance, not merely an indicator: dragging it, or clicking the track above/below it, moves Offset and fires OnScroll. Both the paint (ThumbRect) and the interaction (OnEvent) read one shared geometry (geom), the same sbGeom the embedded ScrollView/Table scrollbars use, so the drawn thumb and the drag target can never drift apart. A host that only wants a read-only indicator simply leaves OnScroll nil and never routes events to it.

func NewScrollbar added in v0.53.0

func NewScrollbar() *Scrollbar

NewScrollbar builds an empty vertical scrollbar.

func (*Scrollbar) A11y added in v0.130.0

func (s *Scrollbar) A11y() A11yInfo

A11y reports the Scrollbar as presentational. Scroll position is a property of the region being scrolled, not a control to announce on its own.

func (*Scrollbar) Draw added in v0.53.0

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

Draw paints the track and the thumb. The thumb is drawn in Theme.Border so it reads against the SurfaceAlt track in both light and dark themes.

func (*Scrollbar) OnEvent added in v0.104.0

func (s *Scrollbar) OnEvent(ev Event)

OnEvent makes the thumb grabbable: an EventClick on the thumb starts a drag (recording the grab offset); an EventClick on the track above/below the thumb pages one viewport toward the click; an EventMouseDrag maps the pointer to a clamped Offset while the grab is active; EventMouseUp releases it. All of it runs through the shared scrollDrag policy against geom(), so a standalone Scrollbar drags exactly like an embedded one. A Disabled scrollbar ignores input. Coordinates are widget-local.

func (*Scrollbar) ThumbRect added in v0.53.0

func (s *Scrollbar) ThumbRect() Rect

ThumbRect returns the thumb's rectangle for the current Total/Viewport/Offset, so callers can hit-test or animate it. It is empty when the widget has no area.

type SearchEntry added in v0.8.0

type SearchEntry struct {
	Base

	Text     string
	OnChange func(s string)
	Icon     func(p painter.Painter, r Rect, ink RGBA)
	// contains filtered or unexported fields
}

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) Focused added in v0.48.0

func (f *SearchEntry) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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.

func (*SearchEntry) SetFocused added in v0.101.0

func (f *SearchEntry) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 SelectableText added in v0.122.0

type SelectableText interface {
	TextRuns() []TextRun
}

SelectableText is implemented by widgets that expose their drawn text to the selection subsystem. Runs are returned in absolute coordinates (the widget offsets by its own Bounds), so a container can concatenate the runs of all its children into one TextSelection.

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 Size added in v0.124.0

type Size struct{ W, H int }

Size is a width/height pair in painter units (pixels for a PixelPainter, cells for a CellPainter). It is the dimensional companion of Rect for the places a widget needs an extent without a position — e.g. the fixed cell footprint a VirtualGrid reflows its items into. Kept a plain value type (no methods, no state) so it costs nothing and composes freely.

type Skeleton added in v0.8.0

type Skeleton struct {
	Base
	Kind  SkeletonKind
	Lines int

	// LineH / LineGap / LastFrac tune SkeletonText. Zero (the default)
	// falls through to SkeletonLineH / SkeletonLineGap / SkeletonLastFrac
	// so an untuned SkeletonText renders like a paragraph of body text.
	LineH    int
	LineGap  int
	LastFrac float64

	// Radius is the corner radius for SkeletonRect and for SkeletonText
	// bars. Zero falls through to a shape-appropriate default
	// (SkeletonRectRadius for a rect; a third of the line height for a
	// text bar). SkeletonCircle ignores it (a circle is fully rounded).
	Radius int

	// Animated turns the shimmer band on. Phase (0..1) is the sweep
	// position: 0 parks the band just off the leading edge (flat grey),
	// rising to 1 sweeps it off the trailing edge.
	Animated bool
	Phase    float64
}

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.

When Animated is set, Draw overlays a diagonal shimmer band — a lighter tint that sweeps across the base grey. The band position is Phase (0..1); the consumer advances Phase every frame (typically via SetPhase(elapsed*speed), which wraps for you). A stopped Skeleton (Animated == false, the zero value) renders flat grey, so the widget is cheap when the host has no animation loop.

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 the non-text kinds 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) Animating added in v0.155.0

func (s *Skeleton) Animating() bool

Animating reports whether the skeleton still needs frames: true exactly when its shimmer is Animated (a static placeholder needs no repaint).

func (*Skeleton) Draw added in v0.8.0

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

Draw paints the placeholder appropriate for Kind, then (when Animated) sweeps the shimmer band over each filled region.

func (*Skeleton) SetPhase added in v0.109.0

func (s *Skeleton) SetPhase(t float64) *Skeleton

SetPhase sets the shimmer sweep position and switches the shimmer on. t may be any float (e.g. elapsedSeconds*speed); it is wrapped into [0,1) so the caller can feed a monotonically increasing clock without tracking the cycle. Returns the Skeleton so the call chains.

func (*Skeleton) Tick added in v0.155.0

func (s *Skeleton) Tick(deltaSeconds float64)

Tick advances the shimmer sweep by deltaSeconds, wrapping Phase modulo 1 so it stays bounded. It advances only while Animated (a flat, un-animated Skeleton needs no frames), matching what Animating reports. Together they make an animated Skeleton an Animator, driven by TickTree / TreeAnimating — the per-frame counterpart of the absolute-clock SetPhase.

type SkeletonGroup added in v0.109.0

type SkeletonGroup struct {
	Base

	// Animated + Phase mirror Skeleton; SetPhase drives both and they
	// cascade to every child at Draw time.
	Animated bool
	Phase    float64
	// contains filtered or unexported fields
}

SkeletonGroup composes several primitive Skeletons into one loading placeholder — an avatar + text lines + a media block, a whole loading page, etc. It is a thin container: Draw positions each child relative to the group's Bounds and forwards the group's shimmer Phase so the whole composition gleams in sync.

SkeletonGroup is passive and decorative (A11y reports it as a presentation element, like the primitive Skeleton).

func NewPageSkeleton added in v0.109.0

func NewPageSkeleton(bounds Rect) *SkeletonGroup

NewPageSkeleton builds a loading web-page placeholder inside bounds: a top bar, alternating paragraph line-groups and media blocks. This is what a webengine browser client shows while the browserproxy fetches a page. It is a preset (a composition of the primitives), not a bespoke widget, so it is reusable + inspectable via Items().

func NewSkeletonCard added in v0.109.0

func NewSkeletonCard(bounds Rect) *SkeletonGroup

NewSkeletonCard builds a content-card skeleton inside bounds: a circle avatar top-left, a two-line text header beside it, and a rounded media block filling the rest — the classic "post is loading" placeholder. It is a composition of the primitives, so callers can inspect / tweak the children via Items().

func (*SkeletonGroup) A11y added in v0.109.0

func (g *SkeletonGroup) A11y() A11yInfo

A11y reports the SkeletonGroup as a decorative presentation element, for the same reason as the primitive Skeleton: a composed loading placeholder is not content.

func (*SkeletonGroup) Add added in v0.109.0

func (g *SkeletonGroup) Add(s *Skeleton, local Rect) *SkeletonGroup

Add appends a primitive Skeleton at the given LOCAL rectangle and returns the group so calls chain.

func (*SkeletonGroup) Animating added in v0.155.0

func (g *SkeletonGroup) Animating() bool

Animating reports whether the group still needs frames: true exactly when its shimmer is Animated.

func (*SkeletonGroup) Draw added in v0.109.0

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

Draw positions each child relative to the group's Bounds, forwards the shimmer state, and paints it.

func (*SkeletonGroup) Items added in v0.109.0

func (g *SkeletonGroup) Items() []SkeletonItem

Items returns the group's children with their local rectangles, for inspection / layout tests.

func (*SkeletonGroup) SetPhase added in v0.109.0

func (g *SkeletonGroup) SetPhase(t float64) *SkeletonGroup

SetPhase sets the group's shimmer position (wrapped into [0,1)) and switches the shimmer on for every child. Returns the group so calls chain. The consumer advances this every frame.

func (*SkeletonGroup) Tick added in v0.155.0

func (g *SkeletonGroup) Tick(deltaSeconds float64)

Tick advances the group's shimmer sweep by deltaSeconds, wrapping Phase modulo 1. It advances only while Animated and cascades to every child at Draw time (Draw copies the group's Phase into each child), so ticking the group is enough to animate the whole composition. Together with Animating this makes SkeletonGroup an Animator driven by TickTree / TreeAnimating.

type SkeletonItem added in v0.109.0

type SkeletonItem struct {
	Skel  *Skeleton
	Local Rect
}

SkeletonItem is one positioned child of a SkeletonGroup: a primitive Skeleton plus its rectangle in the group's LOCAL coordinate system (relative to the group's top-left).

type SkeletonKind added in v0.8.0

type SkeletonKind int

SkeletonKind selects the placeholder shape. The kinds cover the dominant loading-state patterns:

  • SkeletonText draws N rounded bars stacked vertically (a paragraph or a list row).
  • SkeletonRect draws one rounded block — the modern "media / card body loading" affordance, corner-radius configurable.
  • SkeletonCircle draws a true circle — an avatar / status-dot placeholder.
  • SkeletonAvatar / SkeletonBlock are the original pixel-exact swap-parity variants (a three-band pill matching Avatar, and a square inset fill). They are kept for callers that swap a Skeleton for the Avatar / Block widget pixel-for-pixel.

Every kind fills in Theme.SurfaceAlt (the muted "content coming" tone) and, when Animated, is swept by a diagonal shimmer band (a lighter tint) whose position is driven by Phase.

const (
	// SkeletonText draws Lines horizontal bars stacked vertically. The
	// last bar is LastFrac of the 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 three-band pill — so a Skeleton row lines up
	// pixel-for-pixel with the real Avatar it will be swapped for.
	SkeletonAvatar
	// SkeletonBlock draws one filled square rectangle covering Bounds()
	// inset by SkeletonLinePad — the original media-thumbnail affordance.
	SkeletonBlock
	// SkeletonRect draws one rounded-corner block covering Bounds(). The
	// corner radius is Skeleton.Radius (default SkeletonRectRadius).
	SkeletonRect
	// SkeletonCircle draws a true circle inscribed in (and centred
	// within) Bounds() — the avatar placeholder for the rounded family.
	SkeletonCircle
)

type SourceItem added in v0.136.0

type SourceItem struct {
	Icon  *Image
	Label string
	Key   string
}

SourceItem is one row of a SourceList: an optional leading icon and a label. Key is an opaque caller-supplied identity (a path, a mailbox id, ...) the host can read back after OnSelect; the widget itself never interprets it.

type SourceList added in v0.136.0

type SourceList struct {
	Base

	// Sections is the ordered list of labelled groups. Mutating it and calling
	// SetBounds (or letting the next SetBounds run) re-lays the rows out.
	Sections []SourceSection

	// OnSelect fires after a click selects an item row, with the section index
	// and the row index within that section. Nil-guarded.
	OnSelect func(section, row int)

	// OnReorder fires after a successful drag-reorder within a section, with the
	// section index and the row's original + final indices. Nil-guarded.
	OnReorder func(section, from, to int)
	// contains filtered or unexported fields
}

SourceList is a macOS-style "source list" (an NSOutlineView sidebar): one or more labelled sections, each a run of rows carrying a leading icon and a label, with the selected row drawn as a rounded accent pill. A section can be marked Reorderable, in which case its rows can be dragged to reorder within that section (via the toolkit's DragSource/DropTarget contract). It generalizes the file-manager sidebar / mail-and-settings navigator: a flat ListBox cannot express section headers or per-section reorderability, which is exactly the gap SourceList fills.

Layout: a thin panel filled with Theme.SurfaceAlt, a hairline Theme.Border on its right edge, then top-to-bottom a section header (drawn in muted ink, and only when the section has a non-empty Title) followed by its item rows. Each item row shows its icon (when non-nil) left-aligned, then the label elided to the remaining width; the selected row paints a Theme.Accent pill behind it and switches the ink to Theme.Background. All painting is clipped to the widget bounds, so a panel shorter than its content never bleeds a row below its edge.

Selection + navigation: a click selects the row under the pointer and fires OnSelect(section, row). Selected / SetSelected read and drive the highlighted row programmatically.

Drag-to-reorder: pressing a row in a Reorderable section arms a drag whose payload is SourceRowDragPrefix + "<section>:<row>" (see DragData); a host wires its native pointer gestures to the toolkit's EventDragMove / EventDragLeave / EventDrop, and the SourceList tracks the pressed row, paints an insertion line on EventDragMove, and reorders the section's items on EventDrop, firing OnReorder. A press on a non-reorderable section arms nothing, so those rows can be selected but never reordered.

Example

ExampleSourceList builds a two-section sidebar, selects a row and reports it.

sl := NewSourceList(
	SourceSection{Title: "Favourites", Reorderable: true, Items: []SourceItem{
		{Label: "Documents", Key: "docs"},
		{Label: "Downloads", Key: "dl"},
	}},
	SourceSection{Title: "Locations", Items: []SourceItem{
		{Label: "Home", Key: "home"},
	}},
)
sl.SetBounds(Rect{X: 0, Y: 0, W: 200, H: 300})
sl.Draw(newP(makeSurface(200, 300), 200), DefaultLight())
sl.OnEvent(Event{Kind: EventClick, Y: 40}) // select the first favourite
sec, row := sl.Selected()
fmt.Printf("selected section %d row %d\n", sec, row)
Output:
selected section 0 row 0

func NewSourceList added in v0.136.0

func NewSourceList(sections ...SourceSection) *SourceList

NewSourceList builds a SourceList over sections. Nothing is selected initially (Selected returns -1, -1) and no press is armed; call SetBounds to lay the rows out before drawing.

func (*SourceList) A11y added in v0.136.0

func (s *SourceList) A11y() A11yInfo

A11y reports the SourceList as navigation. Value is the selected item's label, or empty when nothing is selected.

func (*SourceList) AcceptsDrop added in v0.136.0

func (s *SourceList) AcceptsDrop(payload string) bool

AcceptsDrop reports whether payload is one of this widget's own reorder payloads. It makes the SourceList a DropTarget for its own rows.

func (*SourceList) DragData added in v0.136.0

func (s *SourceList) DragData() string

DragData reports the reorder payload for the pressed row (a Reorderable section's row), or "" when no reorderable press is armed. It makes the SourceList a DragSource.

func (*SourceList) Draw added in v0.136.0

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

Draw paints the panel, its sections and rows, and (while dragging) the reorder insertion line, clipped to the widget bounds.

func (*SourceList) OnEvent added in v0.136.0

func (s *SourceList) OnEvent(ev Event)

OnEvent selects on a click, arms/drives a reorder drag on a Reorderable section, and is inert while Disabled.

func (*SourceList) Selected added in v0.136.0

func (s *SourceList) Selected() (section, row int)

Selected returns the highlighted item as (section, row), or (-1, -1) when nothing is selected.

func (*SourceList) SetBounds added in v0.136.0

func (s *SourceList) SetBounds(r Rect)

SetBounds records the widget bounds and recomputes the row layout.

func (*SourceList) SetSelected added in v0.136.0

func (s *SourceList) SetSelected(section, row int)

SetSelected highlights the item at (section, row). An out-of-range pair clears the selection to (-1, -1).

type SourceSection added in v0.136.0

type SourceSection struct {
	Title       string
	Items       []SourceItem
	Reorderable bool
}

SourceSection is a labelled group of SourceItems. When Reorderable is true its rows can be dragged to reorder within the section; when false (the default) its rows are selectable but fixed in order.

type SparkKind added in v0.82.0

type SparkKind int

SparkKind selects a Sparkline's visual form.

const (
	// SparkLine (the default) draws the series as a polyline.
	SparkLine SparkKind = iota
	// SparkBar draws the series as a row of thin vertical bars.
	SparkBar
)

type Sparkline added in v0.82.0

type Sparkline struct {
	Base
	// Values is the data series, plotted left-to-right and auto-scaled between
	// its own min and max across the bounds height.
	Values []float64
	// Kind selects the form: SparkLine (polyline, default) or SparkBar.
	Kind SparkKind
	// Fill is the ink for the line/bars. The zero value (A == 0) inherits the
	// theme's Accent colour.
	Fill RGBA
	// ShowLast emphasises the final data point: a small dot on a SparkLine, a
	// brighter final bar on a SparkBar.
	ShowLast bool

	// Hover + HoverIndex draw a hover crosshair (SparkLine) or highlight the
	// hovered bar (SparkBar). Opt-in; the zero value draws none.
	Hover      bool
	HoverIndex int
}

Sparkline is a tiny, axis-less inline trend chart -- the kind embedded in a KPI card, a table cell or a status row to show a series' shape at a glance. Unlike LineChart / BarChart it draws no axes, labels or gridlines: the whole bounds is plot area, and the Values are auto-scaled to fit it. Display-only.

It renders through painter.Painter, so the same spark draws as anti-aliased pixels (WUI/GUI) or promoted cells (TUI). An empty series draws nothing; a single value renders as a dot.

func NewSparkline added in v0.82.0

func NewSparkline(values []float64) *Sparkline

NewSparkline builds a SparkLine over the given values.

func (*Sparkline) A11y added in v0.105.0

func (s *Sparkline) A11y() A11yInfo

A11y reports the Sparkline as an img carrying its data-point count (it plots a single series, like LineChart).

func (*Sparkline) Draw added in v0.82.0

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

Draw paints the spark: nothing for an empty series or a sub-pixel bounds, a dot for a lone value, otherwise a polyline (SparkLine) or bar row (SparkBar).

func (*Sparkline) HitTest added in v0.82.0

func (s *Sparkline) HitTest(_, _ int) bool

HitTest returns false unconditionally: a Sparkline is decorative, like Label.

func (*Sparkline) OnEvent added in v0.100.0

func (s *Sparkline) OnEvent(ev Event)

OnEvent tracks the sparkline crosshair / bar highlight from the pointer.

func (*Sparkline) ValueAt added in v0.90.0

func (s *Sparkline) ValueAt(localX int) (index int, value float64, ok bool)

ValueAt maps a widget-local x to the nearest data point, returning its index and value (ok=false only for an empty series). Exposed for hover.

type SpinButton

type SpinButton struct {
	Base

	Min, Max int
	Value    int
	Step     int
	OnChange func(v int)
	// contains filtered or unexported fields
}

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, both as a Value string and as the numeric Min/Max/Now range triple.

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) Focused added in v0.101.0

func (f *SpinButton) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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) SetFocused added in v0.101.0

func (f *SpinButton) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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
	Style  SpinnerStyle // look; zero value = SpinnerHand
}

Spinner is an indeterminate loading indicator. When Active, Draw paints the selected Style advanced by Phase in Theme.Accent. The 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 in the default (hand) style.

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) Animating added in v0.155.0

func (s *Spinner) Animating() bool

Animating reports whether the spinner still needs frames: true exactly when it is Active, so a host stops repainting once the spinner is stopped. It makes Spinner an Animator, driven by TickTree / TreeAnimating.

func (*Spinner) Draw

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

Draw paints the spinner when Active, dispatching on Style. It is a no-op when inactive or given empty bounds.

func (*Spinner) Tick

func (s *Spinner) Tick(deltaSeconds float64)

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

type SpinnerStyle added in v0.110.0

type SpinnerStyle int

SpinnerStyle selects an indeterminate-spinner look. The zero value is SpinnerHand, so an untouched Spinner keeps the original rotating-hand rendering.

const (
	// SpinnerHand is a single rotating radial line from the centre (the
	// original, and the zero-value default).
	SpinnerHand SpinnerStyle = iota
	// SpinnerDots is a ring of dots orbiting the centre, the leading dot in
	// Accent and the trail fading toward SurfaceAlt.
	SpinnerDots
	// SpinnerRing is a comet-like arc sweeping around the circle, its head in
	// Accent fading to SurfaceAlt along the tail.
	SpinnerRing
	// SpinnerBars is a row of vertical bars whose heights pulse out of phase,
	// like an audio equalizer.
	SpinnerBars
)

type SplitButton added in v0.9.0

type SplitButton struct {
	Base

	Label   string
	Arrow   bool
	OnClick func()
	OnArrow func()
	// contains filtered or unexported fields
}

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) Focused added in v0.101.0

func (f *SplitButton) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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.

func (*SplitButton) SetFocused added in v0.101.0

func (f *SplitButton) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type Spreadsheet added in v0.180.0

type Spreadsheet struct {
	Base

	// OnCellChange, when set, fires after a committed edit with the cell and the
	// raw text that was stored — the seam a view model observes.
	OnCellChange func(ref formula.Ref, raw string)
	// contains filtered or unexported fields
}

Spreadsheet is an A1-addressed formula grid: columns A, B, ..., Z, AA, ... across 1-based rows, each cell holding a literal (number or text) or a leading-"=" formula. It composes the toolkit's shared grid primitives — the same fillRect / strokeRect / cellTextX painting, the stock Entry as the inline editor, and the scrollDrag / sbGeom scrollbar machinery ScrollView and Table already use — rather than duplicating Table's data-grid rendering.

It is a DISTINCT widget from Table, not a "formula mode" bolted onto it, because the two contracts are orthogonal. Table is a data grid: named, individually-sized, sortable/groupable columns over Rows [][]string. A spreadsheet is uniform A1 cells over a formula engine with a dependency graph and recomputation. Forcing Table's Columns/Rows model to also carry cell references, formulas and recalculation would bloat exactly the data-grid contract that makes Table simple; a separate widget keeps both clean while still sharing the low-level painting and scrolling helpers.

The formula engine (parse, evaluate, dependency-ordered recalc, cycle detection) lives in internal/formula; the widget is a thin view over a formula.Model.

func NewSpreadsheet added in v0.180.0

func NewSpreadsheet(cols, rows int) *Spreadsheet

NewSpreadsheet builds an empty cols x rows sheet with cell A1 active. Negative dimensions clamp to 0 (the underlying model's contract).

func (*Spreadsheet) A11y added in v0.180.0

func (s *Spreadsheet) A11y() A11yInfo

A11y reports the Spreadsheet as a grid named by its active cell's A1 address, with the cell's displayed value.

func (*Spreadsheet) Active added in v0.180.0

func (s *Spreadsheet) Active() (col, row int)

Active reports the currently selected cell.

func (*Spreadsheet) BeginEdit added in v0.180.0

func (s *Spreadsheet) BeginEdit()

BeginEdit opens an inline editor over the active cell, seeded with its current raw text — the command entry point Enter / F2 and a view model use.

func (*Spreadsheet) CancelEdit added in v0.180.0

func (s *Spreadsheet) CancelEdit()

CancelEdit discards the open editor without touching the cell. A no-op when no edit is open.

func (*Spreadsheet) CellDisplay added in v0.180.0

func (s *Spreadsheet) CellDisplay(col, row int) string

CellDisplay is the computed text shown in cell (col,row).

func (*Spreadsheet) CellRaw added in v0.180.0

func (s *Spreadsheet) CellRaw(col, row int) string

CellRaw is the raw text stored in cell (col,row) — the formula or literal a user typed, which the editor re-opens.

func (*Spreadsheet) Cols added in v0.180.0

func (s *Spreadsheet) Cols() int

Cols reports the sheet's column count.

func (*Spreadsheet) CommitEdit added in v0.180.0

func (s *Spreadsheet) CommitEdit()

CommitEdit stores the open editor's text into the active cell (recomputing dependents), fires OnCellChange, and closes the editor. A no-op when no edit is open.

func (*Spreadsheet) Draw added in v0.180.0

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

Draw paints the sheet: the cell grid (clipped to its viewport), the frozen column-letter and row-number header bands, the active-cell selection box, the scrollbars, and finally the inline editor overlay when a cell is being edited.

func (*Spreadsheet) Editing added in v0.180.0

func (s *Spreadsheet) Editing() bool

Editing reports whether an inline cell edit is open.

func (*Spreadsheet) Focused added in v0.180.0

func (f *Spreadsheet) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*Spreadsheet) OnEvent added in v0.180.0

func (s *Spreadsheet) OnEvent(ev Event)

OnEvent drives selection, scrolling and inline editing.

While an editor is open it owns the keyboard: characters and edit keys route to it, Enter commits and moves down, Tab commits and moves right, Escape cancels, and a click elsewhere commits first and then selects the clicked cell. With no editor open, arrow keys move the active cell, Enter/F2 opens an editor seeded with the cell's current text, a printable character opens one seeded with that character, the wheel scrolls, and a scrollbar press/drag scrolls; a grid click selects the cell under the pointer.

func (*Spreadsheet) Rows added in v0.180.0

func (s *Spreadsheet) Rows() int

Rows reports the sheet's row count.

func (*Spreadsheet) ScrollBy added in v0.180.0

func (s *Spreadsheet) ScrollBy(dCol, dRow int)

ScrollBy shifts the visible window by (dCol, dRow) cells, clamped.

func (*Spreadsheet) ScrollOffset added in v0.180.0

func (s *Spreadsheet) ScrollOffset() (col, row int)

ScrollOffset reports the top-left visible cell (the current scroll position), in cell units — the observable a view model (or a test) reads.

func (*Spreadsheet) SetCell added in v0.180.0

func (s *Spreadsheet) SetCell(col, row int, raw string)

SetCell stores raw (a literal or a leading-"=" formula) in cell (col,row) and recomputes every dependent cell. An out-of-bounds cell is a no-op.

func (*Spreadsheet) SetFocused added in v0.180.0

func (f *Spreadsheet) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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

func (s *Stack) A11y() A11yInfo

A11y reports the Stack as presentational: only the visible child is content.

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 StatusArea added in v0.80.0

type StatusArea struct {
	Base
	// Icons are laid out in order. Mutate through Add or set directly then call
	// SetBounds to re-flow.
	Icons []*StatusIcon
	// Gap is the spacing between cells; zero selects StatusAreaGap. A negative
	// value is clamped to 0 (flush icons).
	Gap int
	// IconSize is each cell's square dimension; zero selects StatusIconSize.
	IconSize int
	// Background, when its alpha is non-zero, is painted as a solid plate behind
	// the whole icon row (the full area Bounds) before the icons draw — so a host
	// gets a tray bar without drawing the plate itself. The zero value (A==0)
	// keeps the original fully-transparent behaviour: only the icons paint.
	Background RGBA
}

StatusArea is a tray container: it lays out N StatusIcons in a left-to-right row, each in a square IconSize cell centred vertically in the area, with Gap pixels between them — a mini Dock/HBox specialised for status indicators. It routes a pointer event to the icon whose cell contains it (in the icon's local space), so each StatusIcon's OnClick/OnRightClick fires correctly.

func NewStatusArea added in v0.80.0

func NewStatusArea(icons ...*StatusIcon) *StatusArea

NewStatusArea builds a StatusArea over the given icons (any number, including none). Call SetBounds to lay them out.

func (*StatusArea) A11y added in v0.130.0

func (a *StatusArea) A11y() A11yInfo

A11y reports the StatusArea as a toolbar of status icons.

func (*StatusArea) Add added in v0.80.0

func (a *StatusArea) Add(ic *StatusIcon)

Add appends an icon and re-flows the row against the current Bounds.

func (*StatusArea) Draw added in v0.80.0

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

Draw paints the optional Background plate behind the row (when its alpha is non-zero), then every icon in insertion order.

func (*StatusArea) OnEvent added in v0.80.0

func (a *StatusArea) OnEvent(ev Event)

OnEvent forwards to the first icon whose cell contains the point, translating the event into that icon's local space.

func (*StatusArea) SetBounds added in v0.80.0

func (a *StatusArea) SetBounds(r Rect)

SetBounds places each icon in a square cell along the row, vertically centred in the area's height.

type StatusIcon added in v0.80.0

type StatusIcon struct {
	Base
	// Icon paints the glyph when Pixels is not a valid image. May be nil.
	Icon IconFunc
	// Pixels is an optional RGBA image (IW*IH*4 bytes). When valid it is drawn
	// instead of Icon, aspect-preserved and centred in the cell.
	Pixels []byte
	IW, IH int
	// Ink overrides the icon colour; the zero RGBA (A==0) falls back to
	// Theme.OnSurface.
	Ink RGBA
	// Badge, when non-nil, is painted in the top-right corner (an unread count,
	// a status dot). It is auto-sized + positioned by Draw.
	Badge *Badge
	// Tooltip is the hover text the host surfaces (via a Tooltip widget). The
	// StatusIcon only stores it; it does not pop the bubble itself.
	Tooltip string
	// OnClick fires on a primary EventClick; OnRightClick on a secondary one
	// (Event.Code == StatusIconSecondary). Both are nil-safe.
	OnClick      func()
	OnRightClick func()
}

StatusIcon is a small tray/status-area indicator: it paints one icon (an IconFunc vector glyph or an RGBA image) at a fixed cell, optionally overlays a Badge in the top-right corner, carries a Tooltip string the host shows on hover, and fires OnClick / OnRightClick when activated.

The cell has no background of its own, so whatever the tray sits on (a panel fill, a Wallpaper) shows through around the glyph — the least-surprising look for a status-area icon. A caller wanting a chip behind the glyph draws it under the StatusIcon.

Image vs icon: when Pixels is a valid RGBA buffer it is drawn (aspect- preserving, centred — a ScaleFit Image) and Icon is ignored; otherwise the Icon func is called with Ink (falling back to Theme.OnSurface). Either may be absent, in which case only the optional Badge paints.

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

func NewStatusIcon added in v0.80.0

func NewStatusIcon(icon IconFunc) *StatusIcon

NewStatusIcon builds a StatusIcon that paints the given vector icon. Bounds default to zero so the first Draw() auto-sizes the cell.

func NewStatusIconImage added in v0.80.0

func NewStatusIconImage(pixels []byte, w, h int) *StatusIcon

NewStatusIconImage builds a StatusIcon that paints the given RGBA image (length must equal w*h*4). The image is drawn aspect-preserved + centred.

func (*StatusIcon) A11y added in v0.130.0

func (i *StatusIcon) A11y() A11yInfo

A11y reports the StatusIcon as a status region. Its badge, when present, carries the count that makes the icon worth announcing at all.

func (*StatusIcon) Draw added in v0.80.0

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

Draw paints the icon (image or vector) then the optional Badge overlay. If Bounds().W is zero the widget resizes itself to StatusIconSize square (H preserved when non-zero) before painting.

func (*StatusIcon) OnEvent added in v0.80.0

func (s *StatusIcon) OnEvent(ev Event)

OnEvent fires OnRightClick on a secondary EventClick (Code == StatusIconSecondary) and OnClick on any other EventClick; other event kinds are ignored. Both callbacks are nil-safe.

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
	// OnSelect, when non-nil, fires with the clicked badge's 0-based index
	// (after Current has been updated to it). Nil keeps Steps display-only.
	OnSelect func(i 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).

A click on a badge jumps to that step: OnEvent hit-tests the same badge layout Draw paints, sets Current to the clicked index and fires OnSelect(i). When OnSelect is nil (the zero value) Steps stays a passive progress display — no click has any effect — so a caller that wants a plain indicator opts out simply by leaving the callback unset.

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.

func (*Steps) OnEvent added in v0.104.0

func (s *Steps) OnEvent(ev Event)

OnEvent jumps to a clicked step: it hit-tests each badge against the same layout Draw paints (badge i advances by StepBoxW/StepBoxH plus one StepConnectorW per gap along the layout axis; the cross axis is the pinned badge column, vertically centred in a tall bar for the horizontal case), and on a hit sets Current to that index and fires OnSelect(i). Only the badge box is sensitive -- a click on a caption or a connector is ignored -- and a nil OnSelect keeps Steps a passive display. Coordinates are widget-local, so the first badge's top-left is (0, cross-offset).

type Surface added in v0.147.0

type Surface struct {
	Base

	// Frame is asked for the buffer to show, once per Draw. It returns the
	// RGBA pixels and their dimensions; a nil Frame, or one returning a buffer
	// too short for w*h*4, paints nothing rather than guessing.
	//
	// It is a function rather than a field so the application can hand over
	// whatever it has this frame without copying it anywhere first.
	Frame func() (pix []byte, w, h int)

	// Elements, when set, is asked what the surface is currently showing, in
	// reading order. Rects are in the BUFFER's own pixel coordinates — the same
	// space Frame's pixels and OnInput's events use — and Surface offsets them
	// onto the surface, because that is the one space the application and this
	// widget already agree on.
	Elements func() []SurfaceElement

	// OnInput receives events with coordinates translated into the buffer's
	// space. A nil OnInput drops them.
	OnInput func(Event)
}

Surface shows a framebuffer the application renders itself.

Most applications describe what they want and let the toolkit paint it. Some cannot: a game, a video player, a browser engine, a news reader with its own scene and hit-testing. They produce finished pixels, and what they need from a widget set is somewhere to put them, input in the coordinates those pixels use, and a way to still be readable by a screen reader.

Historically such an application had to reach past the painter for the raw buffer, which is exactly what stopped it being hosted by a back-end that hands out a Painter and nothing else — a recording painter, a damage-tracked one, a remote one. Surface is the seam that removes the excuse: it blits through the painter's image primitive and degrades to a per-pixel loop on a back-end without one.

The buffer is drawn 1:1 at the widget's bounds, and no scaling is invented: the application is told the size it has (through Resize on its own side) and renders at it. Anything else would resample pixels that were composed for a specific size.

Accessibility

A surface is otherwise opaque — one rectangle of pixels, which is what a screen reader would be told, and useless. An application that can say what it is showing sets Elements, and each entry becomes a child the accessibility walk reads in order. That is what keeps WalkA11y and the platform bridges working for an application whose widgets the toolkit never sees.

func NewSurface added in v0.147.0

func NewSurface(frame func() (pix []byte, w, h int)) *Surface

NewSurface returns a Surface fed by frame.

func (*Surface) A11y added in v0.147.0

func (s *Surface) A11y() A11yInfo

A11y reports the surface itself as presentation: it is a container of what the application describes, not a thing in its own right, and announcing it would put an unnamed group between the reader and the content.

func (*Surface) Children added in v0.147.0

func (s *Surface) Children() []Widget

Children returns one proxy widget per element the application reports, with the element's rectangle moved onto the surface.

The proxies are built fresh on every call and never drawn. That is deliberate: what the application is showing changes as it renders, and a cached child would describe the frame before last. The accessibility walk is not a per-frame path, so building a handful of small structs when a screen reader asks costs nothing worth keeping stale answers for.

func (*Surface) Draw added in v0.147.0

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

Draw blits the application's current frame at the widget's bounds, clipped to them so a buffer larger than the space it was given cannot paint over its neighbours.

func (*Surface) OnEvent added in v0.147.0

func (s *Surface) OnEvent(ev Event)

OnEvent hands the event to the application with its coordinates moved into the buffer's space.

type SurfaceElement added in v0.147.0

type SurfaceElement struct {
	Role  Role
	Name  string
	Value string
	// X, Y, W and H are in the buffer's pixel coordinates.
	X, Y, W, H int
}

SurfaceElement is one thing a Surface is showing: what it is, what it says, and where it sits in the buffer.

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

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) Focused added in v0.101.0

func (f *Switch) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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

func (*Switch) SetFocused added in v0.101.0

func (f *Switch) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type TabMode added in v0.112.0

type TabMode int

TabMode selects how Open allocates tabs.

const (
	// MultiTab (the zero value / default) makes Open add a new tab and activate
	// it, evicting the oldest once BrowserMaxTabs is exceeded.
	MultiTab TabMode = iota
	// SingleTab makes Open reuse the single tab instead of adding one.
	SingleTab
)

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

	// RowIcon, when non-nil, supplies an optional leading icon for each
	// body row -- the missing piece for file-list-style views (a
	// per-row file-type glyph before the name) that previously forced a
	// host to hand-compose custom rows instead of using Table directly.
	// It is called with a 0-indexed body row and returns the icon's
	// painter (any of the stock DrawIconXxx functions in icons.go, or a
	// caller's own of the same TableIconFunc signature) plus an ok flag;
	// returning ok == false (or a nil painter) means "this row has no
	// icon" while still reserving the gutter, so text stays aligned down
	// the first column whether or not a given row carries a glyph.
	//
	// When RowIcon is set, Draw reserves a fixed leading gutter
	// (TableCellPadX + scaled(TableIconSize)) inside the FIRST column, paints the
	// row's icon there in the row's own ink (accent-inverted on the
	// selected row, OnSurface otherwise), and shifts that column's text
	// right by the gutter. Column boundaries, separators, sort, scroll
	// and row/column hit-testing are all unchanged -- the gutter is
	// carved out of column 0's interior, so a click on the icon still
	// resolves to column 0 and to its row exactly as a click on the text
	// would.
	//
	// The zero value (nil) is the original, pre-feature behaviour: no
	// gutter is reserved and Draw renders byte-for-byte identically to
	// before this field existed.
	RowIcon func(row int) (draw TableIconFunc, ok bool)

	// 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

	// FrozenColumns is the number of leading columns pinned in place while
	// the rest scroll horizontally (see ScrollX). Clamped into
	// [0, len(Columns)]. Meaningful only when the columns are all
	// fixed-width and overflow the viewport (hScrollable); with any
	// auto-width column the table fits to width and never scrolls
	// horizontally, so this is inert. The zero value (0) pins nothing --
	// byte-identical to before this field existed.
	FrozenColumns int

	// ScrollX is the horizontal pixel offset of the SCROLLABLE (non-frozen)
	// columns. Read through clampScrollX, so an out-of-range value never
	// scrolls past the content. The zero value (0) shows the columns from
	// their left edge. Inert unless hScrollable (fixed columns wider than
	// the viewport); use ScrollXTo / ScrollXBy to move it clamped.
	ScrollX 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)

	// OnCellEdit fires when an inline cell edit is committed (Enter): the
	// Table has already written the new value into Rows[row][col]. Nil is
	// safe. Only cells in a column with Editable set can be edited.
	OnCellEdit func(row, col int, value string)

	// OnSelect fires whenever the highlighted row (Selected, which doubles
	// as the keyboard cursor) changes through a user interaction: a
	// MultiSelect body-row click that moves the anchor, or a keyboard cursor
	// move (Arrow / Page / Home / End). row is the new Selected index. The
	// Table has already updated Selected before firing, and the callback runs
	// only when the value actually changes, so a re-select of the same row is
	// silent. Nil is safe -- a host that does not track selection leaves it
	// unset. This single-argument slot is what makes Selected observable (e.g.
	// via mvvmtk.BindTableSelection).
	OnSelect func(row int)

	// EditActivation selects how a click begins an inline edit on an Editable
	// cell (see TableEditActivation): the zero value EditOnSingleClick keeps
	// the original "one click edits" behaviour, EditOnDoubleClick makes a
	// single click select and a double-click (or Enter on the cursor row) edit
	// -- the desktop details-view idiom -- and EditManual disables click/key
	// activation so only BeginEdit opens an editor.
	EditActivation TableEditActivation

	// OnCellEditRejected fires when a committed edit fails its column's
	// Validate rule: the Table has NOT written the value and leaves the editor
	// open. row/col name the cell, value is the rejected text, err is the
	// rule's error (its Error() is the message to surface). Nil is safe.
	OnCellEditRejected func(row, col int, value string, err error)

	// SelfSort opts the Table into sorting its own Rows on a Sortable header
	// click: it calls SortByColumn (reordering Rows in place through the
	// column's Comparator) before firing OnSort, so a host need not re-sort
	// and hand the data back. The zero value (false) keeps the original
	// content-only behaviour -- a header click only updates the indicator and
	// fires OnSort, never touching Rows.
	SelfSort bool

	// ShowSummary, when true, appends a grand-total footer line (a distinct
	// SurfaceAlt band, drawn as the LAST visual line of the body) that shows
	// each column's aggregate over every row -- blank for a column whose
	// Aggregate is AggregateNone. When GroupBy is also active, a per-group
	// summary line is additionally emitted after each (expanded) group's rows,
	// aggregating just that group's members. The footer folds into the same
	// visual-line model group headers use, so lineCount/rowAt/scrollbar stay
	// consistent. The zero value (false) is the original, pre-feature
	// behaviour: no summary line is emitted and, ungrouped, the body renders
	// byte-for-byte as it did before this field existed.
	ShowSummary bool

	// GroupBy, when in [0,len(Columns)), turns on row grouping: consecutive
	// rows sharing that column's value are gathered under a clickable group
	// header (value + member count + a disclosure triangle) that collapses
	// its members. -1 (the default, seeded by NewTable) is ungrouped -- the
	// body then renders byte-identically to a Table that never had this
	// field. Grouping assumes Rows are already ordered by the group column
	// (consecutive runs); it does not sort. Drag-to-reorder is suppressed
	// while grouped (a cross-group move has no well-defined meaning).
	GroupBy 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)

	// RowDetail, when non-nil, opts each body row into an expander: a leading
	// disclosure ▶/▼ appears at the left of column 0, and clicking it toggles
	// that row's expansion. An expanded row shows a detail line (one
	// TableRowHeight-tall band, its text supplied by RowDetail(row), indented)
	// inserted right after the row via the same visual-line model group
	// headers use, so all geometry (rowAt/visualIndex/scrollbar/cellRect)
	// keeps working. Clicking the disclosure toggles expansion; a click on the
	// cell itself still selects/edits as before. The zero value (nil) is the
	// original, pre-feature behaviour: no gutter is reserved, no row can
	// expand, and Draw renders byte-for-byte as before this field existed.
	RowDetail func(row int) string
	// 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    |  <- scaled(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) ArrangeGroups added in v0.150.0

func (t *Table) ArrangeGroups()

ArrangeGroups reorders Rows so the GroupBy column's groups become contiguous and are themselves ordered by that column's Comparator (applied to the group keys, honouring GroupKey), the prerequisite the grouped line model needs (it gathers CONSECUTIVE runs of a shared key and does not sort). The sort is stable, so rows within a group keep their prior order, and Selected / selection / expansion follow their rows. It is a no-op unless grouping is active (GroupBy names a real column). Call it after loading or mutating Rows out of group order; a host that already delivers grouped-ordered rows need not.

func (*Table) BeginEdit added in v0.150.0

func (t *Table) BeginEdit(row, col int)

BeginEdit opens an inline editor over cell (row,col) programmatically -- the command-style entry point a view model (or a host in EditManual mode) uses to start an edit without a click. It applies the same guards as a click-driven edit: an out-of-range cell or a column that is not Editable is a no-op.

func (*Table) CancelEdit added in v0.150.0

func (t *Table) CancelEdit()

CancelEdit is the public trigger for discarding the open edit (Escape's path) -- a command a view model binds to. It is a no-op when no edit is in progress.

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) CommitEdit added in v0.150.0

func (t *Table) CommitEdit()

CommitEdit is the public trigger for committing the open edit (Enter's path) -- a command a view model binds to. It is a no-op when no edit is in progress, and honours the column's Validate rule exactly like an Enter commit.

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) EditError added in v0.150.0

func (t *Table) EditError() error

EditError returns the validation error of the open edit's last rejected commit, or nil when the pending value is valid (or no edit is in progress). It is what surfaces the reason an edit would not commit.

func (*Table) Editing added in v0.150.0

func (t *Table) Editing() (row, col int, editing bool)

Editing reports the cell whose inline editor is currently open. editing is false (and row/col are -1) when no edit is in progress -- the observable getter a view model reads to reflect edit state.

func (*Table) Focused added in v0.101.0

func (f *Table) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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) RowAt added in v0.85.0

func (t *Table) RowAt(x, y int) int

rowAt returns the body row index whose vertical band contains localY (a Table-local y coordinate, i.e. relative to the widget's own top edge and therefore still including the header offset), or -1 if localY lands in/above the header or at/past the last row -- the same "collapse to -1 outside the valid range" idiom columnAt and ColumnSeparatorAt already use for x coordinates. The offset within the body is added to clampScrollRow() (not raw ScrollRow) so a click always resolves to whatever row Draw actually painted at that y, even with an out-of-range ScrollRow. RowAt returns the data-row index under widget-local (x, y), or -1 for the header band, a group-header/summary line, or empty space past the last row. Exposed so a host can hit-test a right-click and build a context menu for the row under the cursor (x is accepted for signature symmetry with the other widgets' hit helpers; the row is determined by y).

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) ScrollXBy added in v0.78.0

func (t *Table) ScrollXBy(delta int)

ScrollXBy adjusts ScrollX by delta pixels (positive scrolls right), clamped the same way as ScrollXTo.

func (*Table) ScrollXTo added in v0.78.0

func (t *Table) ScrollXTo(px int)

ScrollXTo sets ScrollX to px, clamped into [0, maxScrollX()] -- the direct entry point a horizontal scrollbar drag or a wheel handler 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) SetFocused added in v0.101.0

func (f *Table) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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) SortByColumn added in v0.150.0

func (t *Table) SortByColumn(col int, ascending bool)

SortByColumn reorders Rows in place by column col, ascending or descending, using the column's Comparator (or defaultCellCompare when it has none), and records the result in SortColumn/SortAsc so the header shows the indicator. The sort is stable, so rows equal under the comparator keep their prior order. Selected, the multi-row selection set and the expanded-row set all follow their rows to the new positions, and ScrollRow is re-clamped. An out-of-range col is a no-op. This is the opt-in counterpart to the content-only OnSort path -- a host or view model calls it (or sets SelfSort to have header clicks call it) to let the Table own its ordering.

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 TableAggregate added in v0.82.0

type TableAggregate int

TableAggregate names the reduction applied to a column's cells on a summary line (see TableColumn.Aggregate + Table.ShowSummary). The zero value AggregateNone means "no aggregate" -- the column stays blank on summary lines.

const (
	// AggregateNone is the default: the column contributes nothing to a
	// summary line (it renders blank there).
	AggregateNone TableAggregate = iota
	// AggregateSum totals the column's numeric cells (non-numeric cells are
	// skipped; strconv.ParseFloat decides what parses).
	AggregateSum
	// AggregateAvg is the mean of the column's numeric cells.
	AggregateAvg
	// AggregateCount is the number of rows covered by the summary line,
	// regardless of whether their cells are numeric.
	AggregateCount
	// AggregateMin is the smallest of the column's numeric cells.
	AggregateMin
	// AggregateMax is the largest of the column's numeric cells.
	AggregateMax
)

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
	// Editable opts this column's body cells into inline editing: a click
	// on such a cell opens a text editor over it (see Table.OnCellEdit).
	// The zero value (false) keeps the original read-only behaviour.
	Editable bool
	// Aggregate selects how this column's cells are reduced to a single
	// value on a summary line (see Table.ShowSummary). The zero value
	// (AggregateNone) leaves the column blank on every summary line, so a
	// column that opts out is byte-identical to before this field existed.
	Aggregate TableAggregate

	// Editor is the per-column editor seam: when set, beginEdit calls it to
	// build the CellEditor overlaid on a cell of this column instead of the
	// stock text field, so a column can edit through a numeric field, a
	// drop-down, a date picker, ... The zero value (nil) uses the default
	// text editor, byte-identical to before this field existed. See
	// CellEditor.
	Editor func() CellEditor
	// Validate, when set, is run against an edit's proposed value at commit
	// time (Enter). A non-nil error rejects the commit: the value is NOT
	// written into Rows, the editor stays open for correction, EditError
	// reports the error, and OnCellEditRejected fires. The zero value (nil)
	// accepts every value, so a column that opts out commits exactly as
	// before. It is the toolkit's own validation.Rule shape, so the stock
	// rules (Required, MinLen, Pattern, All, ...) wire straight in.
	Validate Rule
	// Comparator orders two of this column's cell strings for SortByColumn
	// (and, on the GroupBy column, for ArrangeGroups): it returns <0 when a
	// sorts before b, 0 when equal, >0 when after. The zero value (nil) uses
	// defaultCellCompare (numeric when both cells parse as numbers, else
	// lexicographic), so a column that opts out sorts sensibly without any
	// wiring.
	Comparator func(a, b string) int
	// GroupKey derives the grouping key from a cell value when this column is
	// the GroupBy column: rows sharing a key fall in one group and the key is
	// the group header's label (e.g. a first-letter or date-bucket key). The
	// zero value (nil) groups by the raw cell value, byte-identical to before
	// this field existed.
	GroupKey func(cell string) string
	// AggregateFunc is the custom-aggregate seam: when set, it reduces the
	// column's cells over a summary line's row range to the displayed string,
	// overriding the built-in Aggregate (so a column can show a median, a
	// "min–max" span, a distinct count, ...). It receives one entry per row in
	// range (ragged rows contribute ""). The zero value (nil) uses the
	// built-in Aggregate, byte-identical to before this field existed.
	AggregateFunc func(cells []string) string
}

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 TableEditActivation added in v0.150.0

type TableEditActivation int

TableEditActivation selects how a click (and Enter) begins an inline edit on an Editable cell. It only governs how an edit STARTS; commit/cancel and the editor itself are unchanged across the modes.

const (
	// EditOnSingleClick is the default: a single click on an Editable cell
	// opens its editor immediately, byte-identical to before this field
	// existed.
	EditOnSingleClick TableEditActivation = iota
	// EditOnDoubleClick makes a single click select the cell's row and a
	// double-click (an EventClick tagged Code == TableDoubleClick) open the
	// editor -- the desktop details-view rename idiom. In this mode Enter on
	// the cursor row also opens the first Editable column's editor.
	EditOnDoubleClick
	// EditManual disables click- and key-driven activation entirely: an
	// Editable cell edits only when the host calls BeginEdit.
	EditManual
)

type TableIconFunc added in v0.74.0

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

TableIconFunc paints a Table's optional per-row leading icon into the square rect r using ink. It is deliberately the exact signature of the stock DrawIconXxx painters in icons.go (DrawIconOpen, DrawIconNew, ...), so a caller wires any of those straight into Table.RowIcon -- e.g. `func(row int) (toolkit.TableIconFunc, bool) { return toolkit.DrawIconOpen, true }` -- or supplies its own painter of the same shape for a file-type glyph the stock set doesn't cover.

type TableInfo added in v0.180.0

type TableInfo struct {
	Name    string
	IsView  bool
	Columns []ColumnInfo
}

TableInfo is one table or view and its columns.

type TagField added in v0.70.0

type TagField struct {
	Base
	// Tags is the committed token set, in insertion order.
	Tags []string
	// Text is the in-progress input shown (with a caret) after the last tag.
	Text string
	// Placeholder is the muted hint drawn when there are no tags and no text.
	Placeholder string
	// OnChange fires whenever the tag set changes (commit / backspace / close).
	OnChange func(tags []string)
	// contains filtered or unexported fields
}

TagField is a token / multi-tag text input: the user types into an in-progress buffer (Text) and each committed value becomes an inline removable pill (a Chip). Tokens flow left-to-right and wrap to a new row when the next one would overflow the widget's width; after the last token the in-progress Text is drawn with a caret, or -- when there are no tags and no text -- the muted Placeholder hint.

Editing mirrors the toolkit's other text widgets: EventChar appends a rune to Text; Enter (or a comma) commits strings.TrimSpace(Text) as a new tag, skipping blank + duplicate values; Backspace on an empty Text removes the last tag; and a click on a token's "x" close slot removes that specific tag. Every change to the tag set fires OnChange with the current slice.

Each token is rendered by reusing the Chip widget (Closable: true) so the pill body, label + close "x" all match a standalone Chip exactly; hit-testing routes the click through the very same Chip.OnEvent against a rectangle computed the same way Draw lays it out, so the visible "x" and its click target never drift apart.

func NewTagField added in v0.70.0

func NewTagField(tags ...string) *TagField

NewTagField builds a TagField seeded with the given tags (a nil / empty slice is fine) and an empty in-progress Text.

func (*TagField) A11y added in v0.105.0

func (t *TagField) A11y() A11yInfo

A11y reports the TagField as a group carrying its tag labels joined into one string -- the tokens are plain strings, not independent widgets, so the field as a whole is the accessible unit.

func (*TagField) Draw added in v0.70.0

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

Draw flows each tag as a closable Chip, then draws the in-progress Text with a caret, or the muted Placeholder when the field is entirely empty. It renders through the widget's effective font (Base.Font).

func (*TagField) Focused added in v0.70.0

func (f *TagField) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*TagField) OnEvent added in v0.70.0

func (t *TagField) OnEvent(ev Event)

OnEvent applies keyboard editing + click-to-remove. Character input appends to Text (a bare comma is swallowed because it is the commit key); Enter and comma commit the trimmed Text; Backspace on empty Text drops the last tag; and a click routes through the token's Chip so its "x" close slot removes that tag. Event coordinates are widget-local per the toolkit convention, so hit-testing lays the tokens out from (0, 0).

func (*TagField) SetFocused added in v0.101.0

func (f *TagField) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type TermCell added in v0.68.0

type TermCell struct {
	Rune   rune
	FG, BG RGBA
}

TermCell is one character cell in a TerminalView grid: a single rune plus its foreground and background colours. A zero Rune (or a space) paints no glyph — only the background fill. A colour left at its zero value (RGBA with A==0) is "unset" and falls back at paint time to the view's DefaultFG / DefaultBG (and, failing those, to the theme). Keeping colour per cell is what a console needs for coloured prompts, error text and ANSI-styled output.

type TerminalView added in v0.68.0

type TerminalView struct {
	Base

	// Cols and Rows are the grid dimensions. Cells is row-major: the
	// cell at (col, row) lives at Cells[row*Cols+col].
	Cols, Rows int
	Cells      []TermCell

	// CursorCol / CursorRow are the block cursor's cell position, and
	// the next write position for Write / Put. CursorVisible gates
	// whether Draw paints the (inverted) block cursor.
	CursorCol, CursorRow int
	CursorVisible        bool

	// DefaultFG / DefaultBG are the pen colours Write / Put stamp into
	// new cells and the fallback Draw uses for cells whose own colour is
	// unset. When they too are unset (A==0) Draw falls back to the
	// theme's OnSurface / Surface.
	DefaultFG, DefaultBG RGBA

	// OnKey, when non-nil, receives every event delivered to OnEvent
	// (key down/up, char, click, …) so the host can feed a shell.
	OnKey func(ev Event)

	// CellW / CellH, when positive, pin an explicit per-cell pixel size
	// that OVERRIDES the font-derived metrics — the escape hatch a host
	// needs to lock a fixed cell geometry (e.g. a terminal that sizes its
	// grid to an exact character box) without swapping in a metrics-only
	// font shim. A zero value on an axis means "derive that axis from the
	// active font", so leaving both unset reproduces the original
	// font-metric behaviour exactly. Set them via SetCellSize (which
	// recomputes immediately when bounds are already established) or by
	// assigning the fields before the first SetBounds.
	CellW, CellH int
	// contains filtered or unexported fields
}

TerminalView is a fixed Cols×Rows grid of character cells — the reusable building block a terminal, console or REPL renders into. It owns the cell buffer, a block cursor and the scroll/wrap bookkeeping a shell needs; it does NOT parse escape sequences or run a PTY (that belongs to the host). SetBounds derives the cell size from the active font's metrics, Draw blits each cell (background fill + glyph in the foreground), and OnEvent forwards input to OnKey so the host can drive a shell.

func NewTerminalView added in v0.68.0

func NewTerminalView(cols, rows int) *TerminalView

NewTerminalView allocates a blank cols×rows grid with the cursor homed at (0, 0). A non-positive dimension panics: a zero-sized grid cannot render usefully and a silent fallback would hide the bug.

func (*TerminalView) A11y added in v0.105.0

func (t *TerminalView) A11y() A11yInfo

A11y reports the TerminalView as a textbox carrying its visible cell text, rows joined with newlines and trailing blank cells/rows trimmed.

func (*TerminalView) Cell added in v0.68.0

func (t *TerminalView) Cell(col, row int) TermCell

Cell returns the cell at (col, row), or a zero TermCell when the position is out of range.

func (*TerminalView) CellHeight added in v0.68.0

func (t *TerminalView) CellHeight() int

CellHeight is the pixel height of one cell (0 before SetBounds).

func (*TerminalView) CellWidth added in v0.68.0

func (t *TerminalView) CellWidth() int

CellWidth is the pixel width of one cell (0 before SetBounds).

func (*TerminalView) Draw added in v0.68.0

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

Draw blits the grid: a single span-fill clears the whole bounds to the default background (covering every default-background cell and any margin the grid does not tile), then each cell whose resolved background DIFFERS from that clear colour is over-filled with a rect span and every non-blank glyph is stamped. When CursorVisible the cursor cell is drawn inverted (foreground/background swapped), which reads as a block cursor over both empty and occupied cells.

This is one background pass, not two: the earlier form redundantly span-filled every cell on top of an already-painted whole-bounds clear, doubling the background cost for the common all-default grid. Skipping cells that match the clear colour is pixel-identical — those pixels already hold that exact colour — while non-default cells still get their own span so the final image is unchanged. Draw is a no-op until SetBounds has established a positive cell size.

func (*TerminalView) OnEvent added in v0.68.0

func (t *TerminalView) OnEvent(ev Event)

OnEvent forwards the event to OnKey when one is set, letting the host drive a shell from the grid's key and character input.

func (*TerminalView) Put added in v0.68.0

func (t *TerminalView) Put(col, row int, ru rune)

Put writes ru at (col, row) using the view's pen colours (DefaultFG / DefaultBG). It is the convenience form of SetCell for output that shares one colour. Out-of-range is a no-op.

func (*TerminalView) Resize added in v0.68.0

func (t *TerminalView) Resize(newCols, newRows int)

Resize reshapes the grid to newCols×newRows, preserving the top-left rectangle that still fits and blanking any newly exposed cells. The cursor is clamped into the new bounds. A non-positive dimension panics, matching NewTerminalView.

func (*TerminalView) ScrollUp added in v0.68.0

func (t *TerminalView) ScrollUp(n int)

ScrollUp shifts the grid up by n rows: the top n rows are discarded, the rest move up, and the bottom n rows are blanked. n<=0 is a no-op; n>=Rows clears the whole grid.

func (*TerminalView) SetBounds added in v0.68.0

func (t *TerminalView) SetBounds(r Rect)

SetBounds records the placement and recomputes the per-cell pixel size: the CellW / CellH override on each axis where it is positive, otherwise the active font's metrics (advance × height), so the grid tracks a font change while honouring any pinned geometry.

func (*TerminalView) SetCell added in v0.68.0

func (t *TerminalView) SetCell(col, row int, ru rune, fg, bg RGBA)

SetCell writes ru with explicit fg/bg at (col, row). An out-of-range position is a no-op, so callers need not bounds-check every write.

func (*TerminalView) SetCellSize added in v0.73.0

func (t *TerminalView) SetCellSize(w, h int)

SetCellSize pins an explicit per-cell pixel size, overriding the font-derived metrics. A non-positive value on an axis clears that axis's override, restoring derivation from the active font. When bounds are already established the resolved size updates immediately (so CellWidth / CellHeight and the next Draw reflect it at once); otherwise it takes effect at the first SetBounds.

func (*TerminalView) Write added in v0.68.0

func (t *TerminalView) Write(s string)

Write stamps s at the cursor using the pen colours, advancing the cursor cell by cell. '\n' returns the cursor to column 0 of the next row (scrolling when it overflows the bottom); '\r' returns it to column 0 of the current row; every other rune is written and the cursor advances, wrapping to the next row at end-of-line. It is the terminal-style output helper a shell's stdout feeds.

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 TextRun added in v0.122.0

type TextRun struct {
	Text   string
	Bounds Rect
	Font   Font
}

TextRun is one contiguous piece of drawn text at an absolute pixel position. Bounds is the run's rectangle in the same coordinate space the pointer events use (screen / surface pixels). Font measures per-character widths so a click resolves to a character boundary within the run.

func CollectRuns added in v0.122.0

func CollectRuns(w Widget) []TextRun

CollectRuns gathers the [TextRun]s of every widget in the tree rooted at w that implements SelectableText, descending into any widget that exposes its children via [childContainer] (Container / HBox / VBox / …). It is the bridge a host uses to feed a widget tree into a TextSelection.

type TextSelection added in v0.122.0

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

TextSelection is a screen-space, multi-run text selection driven by pointer drag. A host sets the current runs each frame (SetRuns), then routes press/drag/release (Begin/Drag/End) in the same coordinate space as the runs' Bounds. SelectedText returns the covered text; Draw paints the highlight.

The zero value is an empty, inactive selection.

func (*TextSelection) Begin added in v0.122.0

func (s *TextSelection) Begin(x, y int)

Begin starts a selection at the caret nearest (x, y): it sets the anchor and collapses the cursor onto it (an empty selection until the pointer drags).

func (*TextSelection) Clear added in v0.122.0

func (s *TextSelection) Clear()

Clear discards the selection entirely.

func (*TextSelection) CopySelection added in v0.122.0

func (s *TextSelection) CopySelection() string

CopySelection writes the selected text to the toolkit-wide clipboard (when non-empty) and returns it, so a host's copy chord is a one-liner. An empty selection leaves the clipboard untouched.

func (*TextSelection) Drag added in v0.122.0

func (s *TextSelection) Drag(x, y int)

Drag extends the selection to the caret nearest (x, y). Ignored unless a drag is in progress (between Begin and End).

func (*TextSelection) Draw added in v0.122.0

func (s *TextSelection) Draw(p painter.Painter, col RGBA)

Draw paints the selection highlight (a filled rect per covered run span) in col. Nothing is drawn for an empty selection. Runs are highlighted from their start-char x to their end-char x, full run height.

func (*TextSelection) End added in v0.122.0

func (s *TextSelection) End()

End finishes the drag, leaving the selection in place (so SelectedText / Draw keep working until the next Begin or Clear).

func (*TextSelection) IsEmpty added in v0.122.0

func (s *TextSelection) IsEmpty() bool

IsEmpty reports whether the selection covers zero characters (no drag, or the cursor still on the anchor).

func (*TextSelection) SelectedText added in v0.122.0

func (s *TextSelection) SelectedText() string

SelectedText returns the text the selection covers. Runs on the same visual line (equal Bounds.Y) are joined with a single space when the boundary is not already whitespace; a change of line inserts a newline. An empty selection returns "".

func (*TextSelection) SetRuns added in v0.122.0

func (s *TextSelection) SetRuns(runs []TextRun)

SetRuns replaces the selectable runs, sorted into document order (top-to- bottom, then left-to-right). Any run with empty Text or a zero-area rect is dropped. An active selection's endpoints are clamped to the new run set so a relayout (e.g. a resize that re-wraps text) can't leave them dangling.

type TextSpan added in v0.68.0

type TextSpan struct {
	Start, End int
	Color      RGBA
}

TextSpan is a coloured run within a single line, in half-open rune coordinates [Start, End). A TextView.Highlighter returns a slice of these to paint syntax-highlighted source: any rune not covered by a span keeps the default ink. Spans may overlap — a later span in the slice wins for the runes it covers. Start/End are clamped to the line's rune length at paint time, so a highlighter need not worry about off-by-one bounds. Color reuses the toolkit's RGBA (a painter colour), so a highlighter composes theme colours directly.

func SQLHighlight added in v0.180.0

func SQLHighlight(_ int, line string) []TextSpan

SQLHighlight is a TextView.Highlighter that colours one line of SQL: line comments (-- to end of line), single-quoted string literals, numeric literals, and reserved keywords. Any run it does not classify keeps the default ink. It is line-local (no multi-line string / block-comment state), which matches the TextView's per-line Highlighter contract.

type TextView

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

	// ScrollLine is the index of the buffer line painted at the top of the
	// viewport — the vertical scroll offset that makes a buffer taller than the
	// bounds reachable. Draw windows from here, the wheel (EventScroll) shifts
	// it, and every cursor move scrolls it to keep the caret visible. Reads
	// clamp on the fly (clampedScrollLine), so a stale value after the buffer
	// shrank is harmless; at ScrollLine == 0 rendering is byte-identical to
	// before scrolling existed.
	ScrollLine int

	// 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

	// Highlighter, when non-nil, turns each line into coloured runs at
	// paint time: Draw calls Highlighter(lineIndex, line) and paints
	// the returned TextSpans in their colours, with any uncovered runes
	// falling back to the default ink (theme.OnSurface). When nil (the
	// zero value) Draw paints every line in a single ink exactly as it
	// always has — a host adds syntax highlighting by setting this hook
	// without the widget growing a lexer of its own.
	Highlighter func(lineIndex int, line string) []TextSpan

	// ShowLineNumbers, when true, reserves a left gutter sized to the
	// widest line number and paints right-aligned 1-based line numbers
	// there; the text, caret and selection all shift right by the
	// gutter width. When false (the zero value) there is no gutter and
	// layout is byte-identical to before this field existed.
	ShowLineNumbers bool

	// GutterColor is the ink for the line-number gutter. The zero value
	// (a fully-transparent RGBA, A==0) means "unset": Draw then falls
	// back to a muted tone (dimInk) that reads on any theme.
	GutterColor RGBA

	// RowBackground, when non-nil, is consulted once per visible buffer
	// line to paint a full-width background band behind that line — over
	// the Surface fill, under the gutter number, the ink and the caret.
	// It returns (colour, true) to paint the band in colour, or
	// (_, false) to leave the row on the plain Surface. This is the seam
	// a CodeEditor uses for its current-line highlight, a search UI for
	// match rows, or a diff view for added / removed rows, without
	// TextView growing any of those concerns. When nil (the zero value)
	// no band is painted and rendering is byte-identical to before this
	// field existed.
	RowBackground func(lineIndex int) (RGBA, bool)
	// 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 AdwaitaDark added in v0.153.0

func AdwaitaDark() *Theme

AdwaitaDark returns the Adwaita dark palette as a Theme, the drop-in dark sibling of AdwaitaLight.

func AdwaitaLight added in v0.153.0

func AdwaitaLight() *Theme

AdwaitaLight returns the Adwaita light palette as a Theme. Like DefaultLight it never fails, so callers use it as a drop-in.

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 FluentDark added in v0.153.0

func FluentDark() *Theme

FluentDark returns the Fluent dark palette as a Theme, the drop-in dark sibling of FluentLight. Its accent (#4CC2FF) is a bright cyan, so on-accent ink is BLACK for contrast.

func FluentLight added in v0.153.0

func FluentLight() *Theme

FluentLight returns the Fluent light palette as a Theme. Like DefaultLight it never fails, so callers use it as a drop-in.

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 Thumbnail added in v0.80.0

type Thumbnail struct {
	Base
	// Pixels is the source RGBA image (IW*IH*4 bytes); an invalid buffer paints
	// just the frame + label.
	Pixels []byte
	IW, IH int
	// Label is an optional caption drawn in a strip along the bottom; empty
	// gives the whole cell to the image.
	Label string
	// Alt is the tile's accessible name when it should differ from the visible
	// Label — a filename shown as the caption, say, while the picture itself is
	// worth describing. Empty falls back to Label.
	Alt string
	// Selected / Hover drive the border (see the type doc). Area selects the
	// box-averaging downscale over the default nearest-neighbour.
	Selected bool
	Hover    bool
	Area     bool
	// OnClick fires on EventClick (nil-safe) so a container can select the tile.
	OnClick func()
	// contains filtered or unexported fields
}

Thumbnail renders a source RGBA buffer scaled down (aspect-preserved, centred) into its bounds, with an optional caption strip and a selected/hover border. It is the window-preview tile an Exposé grid, an Alt-Tab switcher, or a dock-hover peek is built from: give it the client's framebuffer + a title, size it into a grid cell, and it paints a shrunk snapshot with a label under it.

Downscale quality: Nearest (the zero value) is one sample per destination pixel — fast, fine for a live-updating peek. Area averages the source region each destination pixel covers, so a large snapshot shrunk to a small tile stays legible instead of shimmering; use it for static previews.

The average comes from go-images (images.Area, the same box filter as PIL's Image.BOX and OpenCV's INTER_AREA) rather than from a loop written here: it is an image-processing operation and go-images is where those live. It is also weighted by fractional coverage where the toolkit's own loop truncated the box to whole source pixels, so an uneven ratio now averages what it actually covers.

Because a scaled image depends only on the source and the target size, it is computed once and kept. A caller that overwrites the CONTENTS of Pixels in place must call Invalidate; assigning a new buffer through SetPixels does it for them. This is the static-preview case by construction — a live-updating peek wants Nearest, which keeps no cache and reads Pixels every frame.

Selection: Selected draws a 2-px Accent border (the switcher's current choice); Hover draws a 1-px Accent border (the pointer is over the tile). Selected wins when both are set. With neither, a plain 1-px Border frames the image. OnClick fires on EventClick so a grid can select the tile.

func NewThumbnail added in v0.80.0

func NewThumbnail(pixels []byte, w, h int) *Thumbnail

NewThumbnail wraps a source image (length must equal w*h*4) in a nearest- neighbour thumbnail with no caption.

func (*Thumbnail) A11y added in v0.130.0

func (t *Thumbnail) A11y() A11yInfo

A11y reports the Thumbnail as an img named by its Alt text, falling back to the caption it already shows.

func (*Thumbnail) Draw added in v0.80.0

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

Draw paints the (downscaled) image into the image area, the optional caption strip, then the selection/hover/plain border over the whole cell. An empty rectangle paints nothing.

func (*Thumbnail) Invalidate added in v0.143.0

func (t *Thumbnail) Invalidate()

Invalidate drops the cached Area downscale. Call it after overwriting the contents of Pixels in place; SetPixels already does.

func (*Thumbnail) OnEvent added in v0.80.0

func (t *Thumbnail) OnEvent(ev Event)

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

func (*Thumbnail) SetPixels added in v0.143.0

func (t *Thumbnail) SetPixels(pixels []byte, w, h int)

SetPixels replaces the source image and drops any cached downscale.

type TimePicker added in v0.75.0

type TimePicker struct {
	Base
	Hour       int  // 0..23, always stored as 24-hour
	Minute     int  // 0..59
	MinuteStep int  // increment per minute step (default 1; e.g. 5 or 15)
	Use12h     bool // display as 12-hour with an AM/PM segment
	OnChange   func(hour, minute int)
}

TimePicker is a stepper-based time-of-day picker — the pixel sibling of the calendar-oriented DatePicker. It shows two spinners laid left-to-right, one for the hour and one for the minute, each with a ▲ (up) and ▼ (down) affordance the user clicks to increment / decrement. A ":" separates them. When Use12h is set an extra AM/PM toggle cell is drawn on the right and the hour reads as a 12-hour clock, while the stored Hour stays 0..23.

The widget never reads the wall clock: the initial hour+minute are supplied by the caller (NewTimePicker), so it stays deterministic and host-agnostic.

Minute wrap policy: StepMinute wraps the minute within 0..59 WITHOUT carrying into the hour — stepping past :59 rolls back to the low end of the same hour. This keeps each spinner independent (the hour only ever changes via the hour spinner or the AM/PM toggle), matching how a native two-field time stepper behaves.

func NewTimePicker added in v0.75.0

func NewTimePicker(hour, minute int) *TimePicker

NewTimePicker builds a TimePicker initialised to (hour, minute). The inputs are normalised into range (hour into 0..23, minute into 0..59) so an out-of-range caller value can never desync the display, and MinuteStep defaults to 1.

func (*TimePicker) A11y added in v0.105.0

func (t *TimePicker) A11y() A11yInfo

A11y reports the TimePicker as a group carrying its selected time as a 24-hour "HH:MM" string (the picker always stores 24-hour internally).

func (*TimePicker) Draw added in v0.75.0

func (tp *TimePicker) Draw(p painter.Painter, theme *Theme)

Draw paints the frame, the two spinner cells (value text + ▲/▼ buttons), the ":" separator and, when Use12h, the AM/PM toggle cell.

func (*TimePicker) OnEvent added in v0.75.0

func (tp *TimePicker) OnEvent(ev Event)

OnEvent handles EventClick: a click on an ▲/▼ button steps the matching field, a click on the AM/PM cell toggles the meridiem, and a click anywhere else (the value text, the ":" separator, outside a button) does nothing. The hit regions are computed from the same layout() Draw uses, so clicks always line up with what is painted.

func (*TimePicker) StepHour added in v0.75.0

func (tp *TimePicker) StepHour(delta int)

StepHour adjusts the hour by delta (typically +1 / -1), wrapping 0..23 in both directions, then fires OnChange.

func (*TimePicker) StepMinute added in v0.75.0

func (tp *TimePicker) StepMinute(delta int)

StepMinute adjusts the minute by delta*MinuteStep (delta is a direction, +1 / -1), wrapping within 0..59 without carrying into the hour, then fires OnChange. A non-positive MinuteStep is treated as 1 so a click is never a silent no-op.

func (*TimePicker) String added in v0.75.0

func (tp *TimePicker) String() string

String is the formatted time: "15:04" (24-hour, zero-padded) by default, or "3:04 PM" when Use12h. Midnight (0) and noon (12) both render as 12 in 12-hour form, as AM and PM respectively.

func (*TimePicker) ToggleAmPm added in v0.75.0

func (tp *TimePicker) ToggleAmPm()

ToggleAmPm flips between AM and PM by shifting Hour ±12, keeping the stored value in 0..23, then fires OnChange.

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

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.

A vertical Timeline scrolls: the mouse wheel (EventScroll) shifts the event window up/down, clamped at both ends, and the events are clipped to Bounds so a long log never bleeds past the widget's box. EventAt maps a point to the event under it through the same offset, so a caller who wants click-to-focus can hit-test the scrolled list without redoing the layout math. A horizontal Timeline stays a fixed left-to-right ribbon (no scroll).

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.

func (*Timeline) EventAt added in v0.108.0

func (tl *Timeline) EventAt(x, y int) int

EventAt maps a widget-local (x, y) to the index of the vertical-timeline event under it, accounting for the scroll offset, or -1 for the padding bands, a point outside the widget's width, or empty space below the last event. A horizontal timeline always returns -1 (its ribbon layout is hit-tested by the caller). It is the offset-aware inverse of Draw's row walk, so a click after scrolling resolves to the event actually shown.

func (*Timeline) OnEvent added in v0.108.0

func (tl *Timeline) OnEvent(ev Event)

OnEvent handles the mouse wheel: a vertical timeline scrolls its event list by EventScroll.Delta rows (clamped at both ends by ScrollBy) so a long log stays reachable. Every other event -- and any event on a horizontal timeline -- is ignored, preserving Timeline's otherwise passive-display contract.

func (*Timeline) ScrollBy added in v0.108.0

func (tl *Timeline) ScrollBy(delta int)

ScrollBy shifts the vertical scroll offset by delta rows (negative scrolls up), converting rows to pixels through TimelineEventH and clamping to [0, maxScrollY()]. A no-op for a horizontal timeline, which does not scroll.

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. Superseded by Actions when that slice is non-empty.
	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()

	// Lines, when non-empty, supplies the message as distinct rows (a title
	// line plus one or more body lines) stacked top-to-bottom, instead of the
	// single joined Text. The zero value (nil/empty) falls back to Text, so a
	// one-line toast is unchanged.
	Lines []string

	// Actions, when non-empty, supplies several action buttons (superseding
	// the single ActionLabel/Action pair). Buttons are laid out along the
	// right edge in slice order, each with its own divider + label. The zero
	// value (nil/empty) falls back to the ActionLabel/Action pair.
	Actions []ToastAction

	// Icon paints a vector glyph to the left of the text when Pixels is not a
	// valid image. May be nil (no icon).
	Icon IconFunc
	// Pixels is an optional RGBA image (IW*IH*4 bytes) drawn to the left of the
	// text instead of Icon, aspect-preserved + centred. IW/IH are its source
	// dimensions.
	Pixels []byte
	IW, IH int
}

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.

Three optional enrichments layer on top without disturbing the plain path (Icon nil, Lines empty, Actions empty renders byte-identically to the original single-line / single-action Toast):

  • Icon: an IconFunc vector glyph or an RGBA image ([Pixels]/[IW]/[IH]) painted, vertically centred, to the LEFT of the text.
  • Lines: distinct message rows (e.g. a bold-reading title line plus a body line) stacked instead of a single joined Text.
  • Actions: a slice of (ToastAction) buttons (each a label + callback) laid out right-to-left along the pill's right edge, superseding the single ActionLabel/Action pair.

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 content (icon + text lines + action buttons, each 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) ButtonRects added in v0.114.0

func (t *Toast) ButtonRects() []Rect

ButtonRects returns the laid-out rectangle of each action button in the toast's local (painted) coordinate space: X measured from the pill's LEFT edge and Y from its TOP (independent of the toast's current Bounds() origin), each rect spanning the full pill height. The i-th rect is the click target for the i-th action -- the Actions slice in order, else the single button synthesised from the legacy ActionLabel/Action pair. It returns nil when the toast carries no actions.

The rects use the toast's current Bounds() width + height, so call it AFTER sizing the pill (AnchorIn, or a direct SetBounds). A host that hit-tests a click itself -- rather than routing it through OnEvent -- maps the click into the toast's local space (click minus the pill's top-left) and finds the button whose rect contains it; OnEvent hit-tests against these very rects, so the two paths can never disagree.

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; the icon (when set) at the left, then the message line(s) in the accent-inverted ink so they stay legible against every Kind's face. Each action button is a 1-px Border divider plus its label, laid out along the right edge in Actions order. Nothing drawn when hidden.

func (*Toast) OnEvent added in v0.36.0

func (t *Toast) OnEvent(ev Event)

OnEvent runs the clicked button's Callback + hides the toast when a click lands inside an action button; a click anywhere else in the pill (or when there are no actions) is a no-op. ev.X/ev.Y are widget-local. The Callback is nil-checked, so an action-less button still dismisses the toast on click. The hit-test runs against Toast.ButtonRects, the same geometry a host reads to route a click itself.

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 ToastAction added in v0.86.0

type ToastAction struct {
	Label    string
	Callback func()
}

ToastAction is one button in a multi-action Toast: a Label the user clicks and a Callback run on click. Callback is nil-safe (the toast still dismisses when it is nil), matching the legacy single-Action contract.

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

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) Focused added in v0.101.0

func (f *ToggleButton) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*ToggleButton) OnEvent

func (t *ToggleButton) OnEvent(ev Event)

OnEvent: click flips Pressed + fires OnToggle; a move tracks the hover face. A Disabled toggle ignores every kind.

func (*ToggleButton) SetFocused added in v0.101.0

func (f *ToggleButton) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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

func (t *TreeTable) A11y() A11yInfo

A11y reports the TreeTable as a tree carrying the selected node's first-column label (the column that carries the tree structure), or "" with no selection.

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) Focused added in v0.101.0

func (f *TreeTable) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

func (*TreeTable) NodeAt added in v0.87.0

func (t *TreeTable) NodeAt(x, y int) *TreeTableNode

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 NodeAt returns the TreeTableNode at widget-local (x, y) in the current visible-flattened, scrolled body, or nil for the header band or empty space below the last row. It does not mutate ScrollRow (unlike OnEvent). Exposed so a host can hit-test a right-click and build a context menu for that node.

func (*TreeTable) OnEvent added in v0.42.0

func (t *TreeTable) OnEvent(ev Event)

a row selects the node. Y is mapped through ScrollRow back to the flattened index it targets, exactly like TreeView.OnEvent.

func (*TreeTable) Remove added in v0.87.0

func (t *TreeTable) Remove(n *TreeTableNode) bool

Remove detaches node n from the forest — from a top-level Root slot or from its parent's Children. It returns true when n was found and removed; false for a nil node or one not in the tree. Exposed so a host can implement a "delete node" menu action (TreeTableNode has no parent pointer).

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.

func (*TreeTable) SetFocused added in v0.101.0

func (f *TreeTable) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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

	// RowRenderer, when non-nil, draws each row's CONTENT (right of the
	// chevron) instead of the default node.Label text. contentRect is the
	// row rectangle AFTER the chevron + indent, already inset for the
	// scrollbar gutter; the TreeView still paints the selection background,
	// the chevron, and owns scroll/hit-test/keyboard. selected reports
	// whether the row is the current Selected node (or, in MultiSelect mode,
	// a member of the selection set); ink is the resolved text colour
	// (theme.OnSurface, or theme.Background when selected). This is the
	// rich-row seam: a host can draw an icon/pastille + label + count badge +
	// spinner. The zero value (nil) keeps the original one-line Label render,
	// byte-identical to before this field existed.
	RowRenderer func(p painter.Painter, theme *Theme, contentRect Rect, node *TreeNode, selected bool, ink RGBA)

	// 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

	// HideRoot omits the Root node's own row and renders its children as the
	// top-level rows (at depth 0), turning the single-rooted tree into a forest.
	// The Root still owns the children (its Expanded flag is ignored — its
	// children are always shown), but it is never itself a visible row, so it is
	// not selectable or hit-testable; a host that wants a "select everything" row
	// makes it the first child instead of the root. Selection, keyboard and
	// hit-testing operate on the visible children exactly as when the root is
	// shown. The zero value (false) keeps the original root-visible behaviour.
	HideRoot bool

	// HideScrollbar suppresses the TreeView's own overflow scrollbar (track +
	// thumb) while keeping every other behaviour — the gutter inset, row-based
	// scrolling, keyboard and hit-testing — unchanged. It is for a host that draws
	// its OWN scrollbar over the tree so every panel in its UI shares one bar
	// style; that host reads ScrollExtent to size and position it. The zero value
	// (false) draws the built-in scrollbar as before.
	HideScrollbar 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) Focused added in v0.101.0

func (f *TreeView) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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) NodeAt added in v0.85.0

func (t *TreeView) NodeAt(x, y int) *TreeNode

NodeAt returns the TreeNode at widget-local (x, y) in the current visible-flattened, scrolled layout, or nil for empty space below the last row. It does not mutate ScrollRow (unlike OnEvent). Exposed so a host can hit-test a right-click and build a context menu for that node.

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) Remove added in v0.85.0

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

Remove detaches node n from the tree, removing it from its parent's Children. It returns true when n was found and removed; false for a nil node, an empty tree, or an attempt to remove the Root (which has no parent). Exposed so a host can implement a "delete node" menu action without threading parent pointers (TreeNode has none).

func (*TreeView) RowContentWidth added in v0.160.0

func (t *TreeView) RowContentWidth(depth int) int

RowContentWidth returns the pixel width RowRenderer's contentRect gets for a row at the given depth: the widget width, minus the scrollbar gutter when the tree currently overflows its window, minus the chevron column and this depth's indent. It uses the same windowing decision Draw does, so a host can lay out (measure/elide) rich content before painting. Never negative (clamped to 0).

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) ScrollExtent added in v0.172.0

func (t *TreeView) ScrollExtent() (offset, window, total int, shown bool)

ScrollExtent reports the tree's vertical scroll state in ROW units: the clamped index of the first visible row, how many whole rows fit the window, and the total visible (expand-aware) row count. shown is false when the tree fits its window and no scrollbar is warranted. A host that suppresses the built-in bar (HideScrollbar) and draws its own reads this to size and position a matching one.

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) SetFocused added in v0.101.0

func (f *TreeView) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

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 VAlign added in v0.69.0

type VAlign int

VAlign is a widget's vertical text alignment within its bounds height. The zero value VAuto preserves the label's original layout (centred when the bounds are taller than the text, else top-anchored), so existing labels are unchanged; VTop/VMiddle/VBottom force a specific edge.

const (
	// VAuto keeps the original behaviour: vertically centred when Bounds.H
	// exceeds the glyph height, otherwise top-anchored. The default.
	VAuto VAlign = iota
	// VTop anchors text to the top edge.
	VTop
	// VMiddle centres text vertically within the bounds height.
	VMiddle
	// VBottom anchors text to the bottom edge.
	VBottom
)

type VBox

type VBox struct {
	Base
	// Spacing is the gap in pixels between adjacent children; same semantics as
	// HBox.Spacing (NewVBox seeds DefaultBoxSpacing, honoured literally, negatives
	// clamped to 0).
	Spacing int
	// Align positions each child on the cross (horizontal) axis; the zero value
	// BoxStretch fills the width. Pack distributes leftover height when the
	// children do not fill the box (no flex child). Same semantics as HBox.
	Align BoxAlign
	Pack  BoxPack
	// 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 with Spacing seeded to DefaultBoxSpacing.

func (*VBox) A11y added in v0.130.0

func (b *VBox) A11y() A11yInfo

A11y reports the VBox as presentational (see HBox).

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) Children added in v0.123.0

func (v *VBox) Children() []Widget

Children yields the box's child widgets in insertion order, so generic tree walkers (e.g. CollectRuns) can descend without knowing the box type.

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. EventMouseMove is forwarded to every child instead, so hover-enter and hover-leave both propagate (see HBox.OnEvent). Keyboard events go through the focus system and a click also moves focus to the focusable it hits (see HBox.OnEvent).

func (*VBox) SetBounds

func (v *VBox) SetBounds(r Rect)

SetBounds positions the VBox + stacks its children down the height. An empty incoming rect (W<=0 or H<=0) collapses every child to Rect{} — see HBox.SetBounds.

type ViewController added in v0.62.0

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

ViewController builds a declarative Node tree once and then lets logic reach the widgets that matter by name, instead of threading pointers through the construction code. Tag nodes with Node.Ref("name"); the controller collects those widgets while building and exposes them via Lookup / the typed LookupAs. Event handlers are wired the Go-idiomatic way — look a widget up and assign its callback — rather than by resolving handler-name strings, so the compiler checks every wire.

vc := NewViewController(VBoxNode(
	Leaf(list).Ref("list").Flexed(1),
	Leaf(saveBtn).Ref("save").Sized(32),
))
if b, ok := LookupAs[*Button](vc, "save"); ok {
	b.OnClick = onSave
}
vc.Root().SetBounds(screen)

func NewViewController added in v0.62.0

func NewViewController(root Node) *ViewController

NewViewController builds root and collects every Ref-tagged widget in the tree.

func (*ViewController) Lookup added in v0.62.0

func (vc *ViewController) Lookup(name string) Widget

Lookup returns the widget tagged with Ref(name), or nil if there is none.

func (*ViewController) RefAt added in v0.65.0

func (vc *ViewController) RefAt(px, py int) (name string, ok bool)

RefAt returns the name of the Ref-tagged widget whose Bounds contains the surface point (px,py) — hit-testing by reference. Refs are scanned in reverse build order, so a later (typically deeper or on-top) ref shadows an earlier one when they overlap. ok is false when no ref-tagged widget covers the point.

func (*ViewController) Root added in v0.62.0

func (vc *ViewController) Root() Widget

Root is the built root widget — call SetBounds/Draw/OnEvent on it.

type ViewSwitcher added in v0.8.0

type ViewSwitcher struct {
	Base

	Views    []string
	Current  int
	OnChange func(i int)
	// contains filtered or unexported fields
}

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) Focused added in v0.101.0

func (f *ViewSwitcher) Focused() bool

Focused reports whether this widget currently holds keyboard focus.

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.

func (*ViewSwitcher) SetFocused added in v0.101.0

func (f *ViewSwitcher) SetFocused(focused bool)

SetFocused records whether this widget holds keyboard focus.

type Viewport added in v0.148.0

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

Viewport is the application root: a single widget that fills the whole surface a window hands it and parcels that surface out to five slots — a top bar, a bottom bar, a left bar, a right bar, and a centre that fills the rest. It is what a native window or a wasmbox client sets as its root and resizes to the drawable area, so the shell re-fills to the window on every resize.

The edges are carved in a FIXED precedence — top, then bottom, then left, then right — so the top and bottom bars span the full width and the side bars only take the height that remains between them (the five-region shell shape). This makes the layout independent of the order regions are assigned (unlike Dock, which carves in insertion order); the centre always fills whatever is left, even when it holds no widget.

Viewport is a Widget: SetBounds re-parcels every slot, Draw paints the centre then the edges, and OnEvent routes to the slot under the pointer, translating into that slot's local space. Any slot may be nil (that region simply contributes no bar and its space folds into the centre).

func NewViewport added in v0.148.0

func NewViewport() *Viewport

NewViewport builds an empty Viewport. Assign slots with Set before (or after) the first SetBounds; an unset Viewport lays out a single full-surface centre.

func (*Viewport) A11y added in v0.148.0

func (v *Viewport) A11y() A11yInfo

A11y marks the Viewport as a presentational shell: it holds no content of its own, so a screen reader announces its docked panels and its centre directly rather than the container. CollectA11y skips a RolePresentation node but still descends into its Children — the same treatment as Container, Border and Dock.

func (*Viewport) Children added in v0.148.0

func (v *Viewport) Children() []Widget

Children yields the present slots in reading order: the edges clockwise from the top, then the centre — the order a screen reader announces a shell in, so a generic tree walk reaches every docked panel and the content area.

func (*Viewport) Draw added in v0.148.0

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

Draw paints the centre first, then the edge bars over it (they never overlap, so the order only decides which wins a shared seam).

func (*Viewport) OnEvent added in v0.148.0

func (v *Viewport) OnEvent(ev Event)

OnEvent forwards to the first slot whose Bounds contains the surface point, translated into that slot's local space. The edges are tested before the centre so a bar wins any seam it shares with the content area.

func (*Viewport) RegionRect added in v0.148.0

func (v *Viewport) RegionRect(region ViewportRegion) Rect

RegionRect reports the surface-space rectangle currently allotted to a region (the zero Rect for an out-of-range region, or for an edge with no widget). The centre's rectangle is always the remainder, whether or not it holds a widget.

func (*Viewport) Set added in v0.148.0

func (v *Viewport) Set(region ViewportRegion, w Widget, size int)

Set places w in the given region with size pixels along that edge's axis (height for top/bottom, width for left/right; ignored for ViewportCenter, which always fills). A negative size clamps to 0; a nil w clears the slot. An out-of-range region is ignored. Re-lays out immediately so RegionRect is current.

func (*Viewport) SetBounds added in v0.148.0

func (v *Viewport) SetBounds(r Rect)

SetBounds fills the surface: it carves each present edge off the available rectangle in the fixed precedence order, then gives the centre whatever remains. Every slot's rectangle is cached for RegionRect, and an absent edge gets a zero rect so a stale bar cannot linger.

type ViewportRegion added in v0.148.0

type ViewportRegion int

ViewportRegion names one of the five slots a Viewport fills: the four dockable edges plus the centre that takes whatever is left.

const (
	// ViewportCenter is the flexible middle slot. It always fills the space the
	// edges leave behind, so it has no size of its own.
	ViewportCenter ViewportRegion = iota
	// ViewportTop docks a bar across the full width at the top, given height.
	ViewportTop
	// ViewportBottom docks a bar across the full width at the bottom, given height.
	ViewportBottom
	// ViewportLeft docks a bar down the left, given width, between the top and
	// bottom bars.
	ViewportLeft
	// ViewportRight docks a bar down the right, given width, between the top and
	// bottom bars.
	ViewportRight
)

type Wallpaper added in v0.80.0

type Wallpaper struct {
	Base
	// Pixels is the RGBA image (IW*IH*4 bytes); nil/empty paints only the
	// fallback.
	Pixels []byte
	IW, IH int
	// Mode selects the image placement (default WallpaperFill / cover).
	Mode WallpaperMode
	// Top / Bottom are the fallback gradient stops (top → bottom). Zero Top
	// (A==0) => Theme.Background; zero Bottom (A==0) => solid Top (no gradient).
	Top, Bottom RGBA
	// Interactive makes the Wallpaper catch pointer events; the zero value is
	// event-transparent (clicks pass through to the content above it).
	Interactive bool
}

Wallpaper is a full-bounds desktop backdrop: it paints an optional RGBA image scaled by Mode (fill / fit / center / tile) over a solid or vertical-gradient fallback. It complements Backdrop (a flat fill + optional grid) for the case a compositor actually wants a picture behind the scene.

Fallback: the ground under (and around) the image is a vertical gradient from Top to Bottom. A zero (A==0) Top falls back to Theme.Background; a zero Bottom makes the fill a solid Top (no gradient). So a bare Wallpaper with no colours set reads as the theme background, a single opaque Top is a solid colour, and Top+Bottom is a gradient — all without an image.

Like the corrected Backdrop, a Wallpaper is event-transparent by default: its HitTest returns false so clicks pass THROUGH to the widgets composited over it (a full-cover backdrop that reported hits would swallow every click). Set Interactive to opt back in (a picker preview that should catch clicks).

func NewWallpaper added in v0.80.0

func NewWallpaper(pixels []byte, w, h int, mode WallpaperMode) *Wallpaper

NewWallpaper builds an image Wallpaper (length must equal w*h*4) in the given mode. The fallback colours are left zero so uncovered margins read as the theme background.

func NewWallpaperGradient added in v0.80.0

func NewWallpaperGradient(top, bottom RGBA) *Wallpaper

NewWallpaperGradient builds an image-less Wallpaper that paints a vertical gradient from top to bottom (pass an equal pair for a solid colour).

func (*Wallpaper) A11y added in v0.130.0

func (w *Wallpaper) A11y() A11yInfo

A11y reports the Wallpaper as presentational — decoration by definition.

func (*Wallpaper) Draw added in v0.80.0

func (w *Wallpaper) Draw(p painter.Painter, theme *Theme)

Draw paints the fallback ground then, if a valid image is present, the image placed per Mode. An empty rectangle paints nothing.

func (*Wallpaper) HitTest added in v0.80.0

func (w *Wallpaper) HitTest(px, py int) bool

HitTest returns false unless Interactive is set, so by default a full-cover wallpaper lets clicks pass through to the widgets composited over it (the Backdrop / Label pass-through idiom). When Interactive is set it hit-tests against its Bounds like any other widget.

type WallpaperMode added in v0.80.0

type WallpaperMode int

WallpaperMode selects how a Wallpaper's source image maps onto its bounds.

const (
	// WallpaperFill ("cover") scales the image — preserving aspect — to the
	// smallest size that covers the whole bounds, centring it and cropping the
	// overflow. It is the zero value: the desktop-wallpaper default where the
	// picture fills the screen edge-to-edge.
	WallpaperFill WallpaperMode = iota
	// WallpaperFit ("contain") scales the image — preserving aspect — to the
	// largest size that fits entirely within the bounds and centres it; the
	// margin around it shows the fallback fill.
	WallpaperFit
	// WallpaperCenter paints the image 1:1 (no scaling) centred in the bounds;
	// an image smaller than the bounds shows the fallback around it, a larger
	// one is cropped to the bounds.
	WallpaperCenter
	// WallpaperTile repeats the image 1:1 from the top-left to cover the whole
	// bounds — the classic pattern backdrop.
	WallpaperTile
)

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 Window added in v0.70.0

type Window struct {
	Base
	Title string
	Body  Widget // optional; nil leaves the body area empty

	// Tool flags enable the matching title-bar buttons; each fires its callback
	// (when non-nil) on a click. A disabled tool is neither drawn nor hit-tested.
	Closable, Minimizable, Maximizable bool
	OnClose, OnMinimize, OnMaximize    func()

	// Resizable draws the bottom-right resize grip and makes HitRegion report
	// WindowResize over it; the app drives the actual resize via ResizeTo.
	Resizable bool
	// contains filtered or unexported fields
}

Window is a draggable, resizable floating panel: a title-bar band carrying a left-aligned Title and a right-aligned cluster of window tools (close, minimize, maximize), above a body area that hosts an optional Body widget. Unlike Dialog — a fixed, centred, non-movable modal — a Window is meant to be moved and resized around the surface.

Following the toolkit's app-driven drag model (see Paned / Border), Window draws its chrome and exposes hit-testing plus explicit move/resize methods, but it does NOT run its own mouse-tracking loop. The host app owns pointer tracking: on mouse-down it calls HitRegion to learn whether the press landed on the title bar (start a move) or the resize grip (start a resize), then feeds the deltas to MoveBy / ResizeTo on each drag tick. Single clicks on the tools and forwarding into Body are handled by OnEvent as usual.

func NewWindow added in v0.70.0

func NewWindow(title string, body Widget) *Window

NewWindow builds a Window with the given title and (optional) body, seeded with the default minimum size. Enable the tools / Resizable and wire the callbacks on the returned value.

func (*Window) A11y added in v0.130.0

func (w *Window) A11y() A11yInfo

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

func (*Window) Children added in v0.137.0

func (w *Window) Children() []Widget

Children yields the window's body.

func (*Window) Draw added in v0.70.0

func (w *Window) Draw(p painter.Painter, theme *Theme)

Draw paints the title bar (band + title + enabled tools), the body area (fill + border + optional Body) and, when Resizable, the resize grip.

func (*Window) HitRegion added in v0.70.0

func (w *Window) HitRegion(px, py int) WindowRegion

HitRegion reports which part of the window the surface point (px, py) lands on. Tools win over the bare title bar; the grip (when Resizable) wins over the body it overlaps.

func (*Window) MoveBy added in v0.70.0

func (w *Window) MoveBy(dx, dy int)

MoveBy shifts the window by (dx, dy) and re-lays out its body — the app calls this on each drag tick after a WindowTitleBar press.

func (*Window) OnEvent added in v0.70.0

func (w *Window) OnEvent(ev Event)

OnEvent handles single clicks: a click on an enabled tool fires its callback; a click in the body forwards to Body in body-local coordinates. Title-bar and resize-grip DRAGS are not handled here — the app drives those via MoveBy / ResizeTo — so a press on either falls through silently.

func (*Window) ResizeTo added in v0.70.0

func (w *Window) ResizeTo(width, height int)

ResizeTo sets the window's size to (width, height), clamped to the minimum, and re-lays out its body — the app calls this on each drag tick after a WindowResize press.

func (*Window) SetBounds added in v0.70.0

func (w *Window) SetBounds(r Rect)

SetBounds stores the placement and lays the body out below the title bar.

type WindowDecoration added in v0.72.0

type WindowDecoration struct {
	Base

	// Title is the title-bar caption; TitleInk is its colour. Titlebar is the
	// band rect (frame-local); TitleColor fills it. TitleCenter centres the
	// caption horizontally (macOS style) instead of left-aligning it (the
	// default). Hairline, when non-zero, paints a 1-unit line along the band's
	// bottom edge (the macOS titlebar separator).
	Title       string
	TitleInk    RGBA
	TitleColor  RGBA
	Titlebar    Rect
	TitleCenter bool
	Hairline    RGBA

	// Border is the full frame extent (frame-local); BorderColor strokes its
	// 1-unit outline (zero = no border). Shadow, when non-zero, paints a 1-unit
	// faux drop shadow one unit past the border's right and bottom edges.
	Border      Rect
	BorderColor RGBA
	Shadow      RGBA

	// Grip is the bottom-right resize handle rect; GripColor draws its two
	// diagonal rules. ShowGrip gates the whole grip (a shaded/undecorated window
	// has none).
	Grip      Rect
	ShowGrip  bool
	GripColor RGBA

	// Buttons is the ordered title-bar button cluster (close/minimize/maximize).
	// Each carries its own rect + colours, so the host mixes box buttons and
	// traffic-lights freely.
	Buttons []DecoButton
}

WindowDecoration paints a window's frame chrome — a title-bar band (fill, title text, optional bottom hairline), a cluster of title-bar buttons (rectangular close/minimize/maximize boxes OR round "traffic-light" dots), a frame border, an optional faux drop shadow and an optional bottom-right resize grip — with EXPLICIT colours and EXPLICIT frame-local geometry.

Unlike Window (a self-contained draggable panel that reads the theme and owns its own hit-testing + body), WindowDecoration is a pure painter for a host compositor that already owns the window model: the host passes the exact rects it hit-tests against (title-bar, each button, border, grip — all in the decoration's own coordinate space) plus the exact palette its style demands, and the widget just paints them. This keeps geometry a SINGLE source of truth on the host side (the same rects drive both hit-testing and paint) while the pixels are produced by the toolkit painter — so a compositor can drop its hand-rolled canvas draws and blit a rendered buffer instead, and every style (a red Openbox bar, a macOS traffic-light bar, any themed palette) keeps its own colours rather than collapsing to a shared theme.

The BODY region between the title bar and the bottom border is never touched: a decoration rendered into a zeroed RGBA buffer leaves the body fully transparent (A=0), so a host composites the decoration OVER the live window body (source-over) and the body shows through the hole.

Every colour follows the toolkit convention that a zero-value RGBA (A=0) means "absent": a zero Hairline / Border / Shadow paints nothing, and a zero button Outline draws no outline. TitleColor / TitleInk / GripColor are painted as given (a host that wants them absent gives the enclosing rect a zero size).

func NewWindowDecoration added in v0.72.0

func NewWindowDecoration() *WindowDecoration

NewWindowDecoration builds an empty decoration; set the exported fields (or use the ruby-widgets Decoration binding) before rendering.

func (*WindowDecoration) A11y added in v0.130.0

func (d *WindowDecoration) A11y() A11yInfo

A11y reports the WindowDecoration as a banner named by its title: it is the titlebar region, not the window itself.

func (*WindowDecoration) AddButton added in v0.72.0

func (d *WindowDecoration) AddButton(b DecoButton) *WindowDecoration

AddButton appends a button to the cluster and returns the decoration for fluent construction.

func (*WindowDecoration) Draw added in v0.72.0

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

Draw paints the decoration: title-bar band + hairline + caption, the button cluster, then (over the body area, which stays untouched) the resize grip, the faux shadow and the border last so it sits on top of the body edges.

type WindowRegion added in v0.70.0

type WindowRegion int

WindowRegion names the part of a Window a surface point falls on, as reported by HitRegion. The app uses it on mouse-down to decide whether to begin a move (WindowTitleBar), begin a resize (WindowResize), or leave the press to OnEvent (the tools / body).

const (
	// WindowNone is returned for a point outside the window entirely.
	WindowNone WindowRegion = iota
	// WindowTitleBar is the title band excluding the tool buttons — the app
	// starts a move drag here.
	WindowTitleBar
	// WindowClose / WindowMinimize / WindowMaximize are the tool buttons.
	WindowClose
	WindowMinimize
	WindowMaximize
	// WindowResize is the bottom-right grip (only when Resizable) — the app
	// starts a resize drag here.
	WindowResize
	// WindowBody is the content area below the title bar.
	WindowBody
)

type WindowsDockStyle added in v0.179.0

type WindowsDockStyle struct{}

WindowsDockStyle is the taskbar look: a flat SurfaceAlt bar and flat, rectangular buttons — transparent at rest, a subtle highlight when running, an accent-tinted highlight when active — each running/active button carrying a centred accent underline (short for a background app, wider for the current one), the Windows 10/11 running marker.

func (WindowsDockStyle) DrawFace added in v0.179.0

func (WindowsDockStyle) DrawFace(p painter.Painter, theme *Theme, r Rect, st DockItemState) RGBA

func (WindowsDockStyle) DrawGround added in v0.179.0

func (WindowsDockStyle) DrawGround(p painter.Painter, theme *Theme, r Rect)

type Wizard added in v0.35.0

type Wizard struct {
	Base
	Steps    []WizardStep
	Current  int
	OnFinish func()

	// PressFeedback shows the pressed face on the Back / Next button while it is
	// held (EventClick → EventMouseUp). NewWizard enables it; set false to opt
	// out.
	PressFeedback bool
	// contains filtered or unexported fields
}

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) Children added in v0.137.0

func (w *Wizard) Children() []Widget

Children yields every step's body.

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

Source Files

Directories

Path Synopsis
Package anim is a backend-agnostic timeline driver for the go-widgets toolkit.
Package anim is a backend-agnostic timeline driver for the go-widgets toolkit.
internal
formula
Package formula is the spreadsheet formula engine behind the toolkit's Spreadsheet widget: it lexes, parses and evaluates "=" expressions over an A1-addressed grid of cells, and maintains a dependency graph so an edit recomputes exactly the cells that (transitively) depend on it, with cycle detection that yields a #CIRC! error value instead of looping forever.
Package formula is the spreadsheet formula engine behind the toolkit's Spreadsheet widget: it lexes, parses and evaluates "=" expressions over an A1-addressed grid of cells, and maintains a dependency graph so an edit recomputes exactly the cells that (transitively) depend on it, with cycle detection that yields a #CIRC! error value instead of looping forever.
rougelex module
Package scene adds an OPT-IN Evas-style damage / scene layer on top of the immediate-mode go-widgets/toolkit widget set.
Package scene adds an OPT-IN Evas-style damage / scene layer on top of the immediate-mode go-widgets/toolkit widget set.
Package virtual adds live-data list virtualization on top of the go-widgets/toolkit widget set.
Package virtual adds live-data list virtualization on top of the go-widgets/toolkit widget set.

Jump to

Keyboard shortcuts

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