core/gridview/gridview.go

Functions Structs

Functions

func A11yLabel

A11yLabel sets the accessibility label for the grid.

func A11yLabel(label string) Option {
	return func(c *config) { c.a11yLabel = label }
}

func AccessibilityActions

AccessibilityActions returns the list of supported actions.

func (w *Widget) AccessibilityActions() []a11y.Action {
	return []a11y.Action{a11y.ActionScrollUp, a11y.ActionScrollDown}
}

func AccessibilityHint

AccessibilityHint returns the accessibility hint.

func (w *Widget) AccessibilityHint() string {
	return ""
}

func AccessibilityLabel

AccessibilityLabel returns the accessibility label.

func (w *Widget) AccessibilityLabel() string {
	if w.cfg.a11yLabel != "" {
		return w.cfg.a11yLabel
	}
	return "Grid"
}

func AccessibilityRole

AccessibilityRole returns the ARIA role for this widget.

func (w *Widget) AccessibilityRole() a11y.Role {
	return a11y.RoleGrid
}

func AccessibilityState

AccessibilityState returns the current accessibility state.

func (w *Widget) AccessibilityState() a11y.State {
	return a11y.State{
		Disabled: w.cfg.ResolvedDisabled(),
	}
}

func AccessibilityValue

AccessibilityValue returns the current grid state as a string.

func (w *Widget) AccessibilityValue() string {
	count := w.cfg.ResolvedItemCount()
	selected := w.cfg.ResolvedSelectedIndex()
	if selected >= 0 && selected < count {
		return fmt.Sprintf("Item %d of %d selected", selected+1, count)
	}
	return fmt.Sprintf("%d items", count)
}

func BuildCell

BuildCell sets the callback that creates a widget for each visible cell.

The callback receives the item index and a [CellContext] with the cell's state.

This is the primary convenience API for providing grid content.

 

Internally, the function is wrapped into a [cdk.FuncContent] so that the

grid view uniformly operates on [cdk.Content] for cell rendering.

func BuildCell(fn func(index int, ctx CellContext) widget.Widget) Option {
	return func(c *config) {
		c.cellContent = cdk.FuncContent[CellContext]{Fn: func(ctx CellContext) widget.Widget {
			return fn(ctx.Index, ctx)
		}}
	}
}

func CellContent

CellContent sets the [cdk.Content] used to render each visible cell.

This is the enterprise API for providing grid content -- use it when you

need reusable content implementations or progressive complexity.

 

For most use cases, [BuildCell] is simpler and sufficient.

func CellContent(content cdk.Content[CellContext]) Option {
	return func(c *config) { c.cellContent = content }
}

func Children

Children returns the internal scroll view as the single child.

func (w *Widget) Children() []widget.Widget {
	if w.scroll == nil {
		return nil
	}
	return []widget.Widget{w.scroll}
}

func Children

Children returns nil; visible cell widgets are ephemeral and managed by the cache.

func (vc *virtualContent) Children() []widget.Widget {
	return nil
}

func Columns

Columns sets the fixed number of columns in the grid.

Default: 4. See also [ColumnsAuto] for auto-fit mode.

func Columns(n int) Option {
	return func(c *config) {
		c.columns = n
		c.columnsAuto = false
	}
}

func ColumnsAuto

ColumnsAuto enables auto-fit column count based on viewport width.

When enabled, the column count is calculated as floor(viewportWidth / (itemWidth + gap)).

func ColumnsAuto(enabled bool) Option {
	return func(c *config) { c.columnsAuto = enabled }
}

func ColumnsReadonlySignal

ColumnsReadonlySignal binds the column count to a read-only signal.

When set, this takes highest precedence. Ignored if [ColumnsAuto] is enabled.

func ColumnsReadonlySignal(sig state.ReadonlySignal[int]) Option {
	return func(c *config) { c.readonlyColumnsSignal = sig }
}

func ColumnsSignal

ColumnsSignal binds the column count to a reactive signal.

When set, the signal takes precedence over [Columns] but not over

[ColumnsReadonlySignal]. Ignored if [ColumnsAuto] is enabled.

func ColumnsSignal(sig state.Signal[int]) Option {
	return func(c *config) { c.columnsSignal = sig }
}

func Disabled

Disabled sets the grid view's disabled state.

func Disabled(d bool) Option {
	return func(c *config) { c.disabled = d }
}

func DisabledFn

DisabledFn sets a dynamic function for the disabled state.

When set, this takes precedence over [Disabled] but not over [DisabledSignal].

func DisabledFn(fn func() bool) Option {
	return func(c *config) { c.disabledFn = fn }
}

func DisabledReadonlySignal

DisabledReadonlySignal binds the disabled state to a read-only signal.

When set, this takes highest precedence over all other disabled sources.

func DisabledReadonlySignal(sig state.ReadonlySignal[bool]) Option {
	return func(c *config) { c.readonlyDisabledSignal = sig }
}

func DisabledSignal

DisabledSignal binds the disabled state to a reactive signal.

When set, the signal takes precedence over [DisabledFn] and [Disabled]

but not over [DisabledReadonlySignal].

func DisabledSignal(sig state.Signal[bool]) Option {
	return func(c *config) { c.disabledSignal = sig }
}

func Draw

Draw renders only the visible cells within the current viewport.

func (vc *virtualContent) Draw(ctx widget.Context, canvas widget.Canvas) {
	if vc.grid == nil {
		return
	}
	vc.grid.drawVisibleCells(ctx, canvas)
}

func Draw

Draw renders the grid view to the canvas.

func (w *Widget) Draw(ctx widget.Context, canvas widget.Canvas) {
	if !w.IsVisible() {
		return
	}
	bounds := w.Bounds()
	if bounds.IsEmpty() {
		return
	}

	// Set scroll view bounds to match our bounds.
	w.scroll.SetBounds(bounds)

	// Stamp screen origin on the internal scroll view so its ScreenBounds()
	// returns correct window-space coordinates for dirty region collection.
	widget.StampScreenOrigin(w.scroll, canvas)

	// Delegate drawing to the internal scroll view.
	w.scroll.Draw(ctx, canvas)
}

func Event

Event delegates events back to the parent grid for cell interaction.

func (vc *virtualContent) Event(ctx widget.Context, e event.Event) bool {
	if vc.grid == nil {
		return false
	}
	return handleContentEvent(vc.grid, ctx, e)
}

func Event

Event handles an input event and returns true if consumed.

func (w *Widget) Event(ctx widget.Context, e event.Event) bool {
	if !w.IsVisible() || !w.IsEnabled() {
		return false
	}

	// Handle keyboard events at the grid level.
	if ke, ok := e.(*event.KeyEvent); ok {
		if w.handleKeyEvent(ctx, ke) {
			return true
		}
	}

	// Delegate other events to the scroll view.
	return w.scroll.Event(ctx, e)
}

func Gap

Gap sets the spacing between cells in pixels. Default: 0.

func Gap(g float32) Option {
	return func(c *config) { c.gap = g }
}

func GetColumns

GetColumns returns the current effective column count.

func (w *Widget) GetColumns() int {
	return w.effectiveColumns()
}

func GetItemCount

GetItemCount returns the current item count.

func (w *Widget) GetItemCount() int {
	return w.cfg.ResolvedItemCount()
}

func InvalidateData

InvalidateData signals that the underlying data has changed.

This invalidates the widget cache and triggers re-layout.

func (w *Widget) InvalidateData() {
	w.cache.invalidate()
}

func IsFocusable

IsFocusable reports whether the grid view can currently receive focus.

func (w *Widget) IsFocusable() bool {
	return w.IsVisible() && w.IsEnabled() && !w.cfg.ResolvedDisabled()
}

func ItemCount

ItemCount sets the total number of items in the grid.

func ItemCount(n int) Option {
	return func(c *config) { c.itemCount = n }
}

func ItemCountFn

ItemCountFn sets a dynamic function that returns the item count.

When set, this takes precedence over [ItemCount] but not over [ItemCountSignal].

func ItemCountFn(fn func() int) Option {
	return func(c *config) { c.itemCountFn = fn }
}

func ItemCountReadonlySignal

ItemCountReadonlySignal binds the item count to a read-only signal.

When set, this takes highest precedence over all other item count sources.

func ItemCountReadonlySignal(sig state.ReadonlySignal[int]) Option {
	return func(c *config) { c.readonlyItemCountSignal = sig }
}

func ItemCountSignal

ItemCountSignal binds the item count to a reactive signal.

When set, the signal takes precedence over [ItemCountFn] and [ItemCount]

but not over [ItemCountReadonlySignal].

func ItemCountSignal(sig state.Signal[int]) Option {
	return func(c *config) { c.itemCountSignal = sig }
}

func ItemSize

ItemSize sets the fixed width and height for all grid cells.

Default: 120x120.

func ItemSize(width, height float32) Option {
	return func(c *config) {
		c.itemWidth = width
		c.itemHeight = height
	}
}

func Layout

Layout returns the total content size.

func (vc *virtualContent) Layout(_ widget.Context, c geometry.Constraints) geometry.Size {
	if vc.grid == nil {
		return geometry.Size{}
	}

	totalHeight := vc.grid.totalContentHeight()

	width := c.MaxWidth
	if width >= geometry.Infinity {
		width = c.MinWidth
	}
	if width < c.MinWidth {
		width = c.MinWidth
	}

	return geometry.Sz(width, totalHeight)
}

func Layout

Layout calculates the grid view's size within the given constraints.

func (w *Widget) Layout(ctx widget.Context, constraints geometry.Constraints) geometry.Size {
	size := constraints.Biggest()
	if size.Width >= geometry.Infinity {
		size.Width = constraints.Constrain(geometry.Sz(defaultViewportWidth, 0)).Width
	}
	if size.Height >= geometry.Infinity {
		totalH := w.totalContentHeight()
		if totalH > defaultViewportHeight {
			totalH = defaultViewportHeight
		}
		size.Height = totalH
	}
	if size.Width <= 0 {
		size.Width = defaultViewportWidth
	}
	if size.Height <= 0 {
		size.Height = defaultViewportHeight
	}

	w.viewportWidth = size.Width
	w.viewportHeight = size.Height

	// Compute effective column count.
	w.effectiveCols = w.computeEffectiveColumns()

	// Layout the internal scroll view with concrete constraints.
	svConstraints := geometry.Tight(size)
	w.scroll.Layout(ctx, svConstraints)

	return size
}

func Mount

Mount creates signal bindings for push-based invalidation.

Implements [widget.Lifecycle].

func (w *Widget) Mount(ctx widget.Context) {
	sched := ctx.Scheduler()
	if sched == nil {
		return
	}

	// Bind item count signals.
	if w.cfg.readonlyItemCountSignal != nil {
		b := state.BindToScheduler(w.cfg.readonlyItemCountSignal, w, sched)
		w.AddBinding(b)
	} else if w.cfg.itemCountSignal != nil {
		b := state.BindToScheduler(w.cfg.itemCountSignal, w, sched)
		w.AddBinding(b)
	}

	// Bind selected index signals.
	if w.cfg.readonlySelectedIndexSignal != nil {
		b := state.BindToScheduler(w.cfg.readonlySelectedIndexSignal, w, sched)
		w.AddBinding(b)
	} else if w.cfg.selectedIndexSignal != nil {
		b := state.BindToScheduler(w.cfg.selectedIndexSignal, w, sched)
		w.AddBinding(b)
	}

	// Bind disabled signals.
	if w.cfg.readonlyDisabledSignal != nil {
		b := state.BindToScheduler(w.cfg.readonlyDisabledSignal, w, sched)
		w.AddBinding(b)
	} else if w.cfg.disabledSignal != nil {
		b := state.BindToScheduler(w.cfg.disabledSignal, w, sched)
		w.AddBinding(b)
	}

	// Bind columns signals.
	if w.cfg.readonlyColumnsSignal != nil {
		b := state.BindToScheduler(w.cfg.readonlyColumnsSignal, w, sched)
		w.AddBinding(b)
	} else if w.cfg.columnsSignal != nil {
		b := state.BindToScheduler(w.cfg.columnsSignal, w, sched)
		w.AddBinding(b)
	}

	// Mount internal scroll view.
	w.scroll.Mount(ctx)
}

func New

New creates a new grid view Widget with the given options.

 

The returned widget is visible, enabled, and focusable by default.

If no [BuildCell] callback is provided, the grid renders empty.

func New(opts ...Option) *Widget {
	cfg := defaultConfig()
	for _, opt := range opts {
		opt(&cfg)
	}

	w := &Widget{
		cfg:		cfg,
		painter:	DefaultPainter{},
		hoveredIndex:	noHoveredIndex,
	}
	w.SetVisible(true)
	w.SetEnabled(true)

	if w.cfg.painter != nil {
		w.painter = w.cfg.painter
	}

	// Create virtual content widget.
	w.virtual = &virtualContent{grid: w}
	w.virtual.SetVisible(true)

	// Build internal scroll view options.
	svOpts := []scrollview.Option{
		scrollview.DirectionOpt(scrollview.Vertical),
	}
	if w.cfg.scrollYSignal != nil {
		svOpts = append(svOpts, scrollview.ScrollYSignal(w.cfg.scrollYSignal))
	}
	if w.cfg.onScroll != nil {
		fn := w.cfg.onScroll
		svOpts = append(svOpts, scrollview.OnScroll(func(_, y float32) {
			fn(y)
			w.cache.invalidate()
		}))
	} else {
		svOpts = append(svOpts, scrollview.OnScroll(func(_, _ float32) {
			w.cache.invalidate()
		}))
	}

	w.scroll = scrollview.New(w.virtual, svOpts...)

	// ADR-028: parent chain for upward dirty propagation.
	// Flutter: RenderObject.adoptChild sets parent on each child.
	w.scroll.SetParent(w)

	return w
}

func OnCellClick

OnCellClick sets the callback invoked when a cell is clicked.

func OnCellClick(fn func(index int)) Option {
	return func(c *config) { c.onCellClick = fn }
}

func OnScroll

OnScroll sets the callback invoked when the scroll position changes.

func OnScroll(fn func(offset float32)) Option {
	return func(c *config) { c.onScroll = fn }
}

func OnSelectionChange

OnSelectionChange sets the callback invoked when the selected cell changes.

func OnSelectionChange(fn func(index int)) Option {
	return func(c *config) { c.onSelectionChange = fn }
}

func PainterOpt

PainterOpt sets the painter used to render grid-specific visuals.

Each design system provides its own painter. If not set,

[DefaultPainter] is used.

func PainterOpt(p Painter) Option {
	return func(c *config) { c.painter = p }
}

func ResolvedColumns

ResolvedColumns returns the current column count.

Priority: ReadonlySignal > Signal > Static.

func (c *config) ResolvedColumns() int {
	if c.readonlyColumnsSignal != nil {
		return c.readonlyColumnsSignal.Get()
	}
	if c.columnsSignal != nil {
		return c.columnsSignal.Get()
	}
	return c.columns
}

func ResolvedDisabled

ResolvedDisabled returns the current disabled state.

Priority: ReadonlySignal > Signal > Fn > Static.

func (c *config) ResolvedDisabled() bool {
	if c.readonlyDisabledSignal != nil {
		return c.readonlyDisabledSignal.Get()
	}
	if c.disabledSignal != nil {
		return c.disabledSignal.Get()
	}
	if c.disabledFn != nil {
		return c.disabledFn()
	}
	return c.disabled
}

func ResolvedItemCount

ResolvedItemCount returns the current item count.

Priority: ReadonlySignal > Signal > Fn > Static.

func (c *config) ResolvedItemCount() int {
	if c.readonlyItemCountSignal != nil {
		return c.readonlyItemCountSignal.Get()
	}
	if c.itemCountSignal != nil {
		return c.itemCountSignal.Get()
	}
	if c.itemCountFn != nil {
		return c.itemCountFn()
	}
	return c.itemCount
}

func ResolvedSelectedIndex

ResolvedSelectedIndex returns the current selected index.

Priority: ReadonlySignal > Signal > Static.

Returns -1 if no selection.

func (c *config) ResolvedSelectedIndex() int {
	if c.readonlySelectedIndexSignal != nil {
		return c.readonlySelectedIndexSignal.Get()
	}
	if c.selectedIndexSignal != nil {
		return c.selectedIndexSignal.Get()
	}
	return c.selectedIndex
}

func ScrollToIndex

ScrollToIndex scrolls to make the cell at the given index visible.

If the cell is already fully visible, this is a no-op.

func (w *Widget) ScrollToIndex(index int) {
	itemCount := w.cfg.ResolvedItemCount()
	if index < 0 || index >= itemCount {
		return
	}

	cols := w.effectiveColumns()
	if cols <= 0 {
		return
	}
	row := index / cols
	cellTop := float32(row) * (w.cfg.itemHeight + w.cfg.gap)
	cellBottom := cellTop + w.cfg.itemHeight
	scrollY := w.currentScrollY()

	// Already visible: no-op.
	if cellTop >= scrollY && cellBottom <= scrollY+w.viewportHeight {
		return
	}

	var newScrollY float32
	if cellTop < scrollY {
		newScrollY = cellTop
	} else {
		newScrollY = cellBottom - w.viewportHeight
		if newScrollY < 0 {
			newScrollY = 0
		}
	}

	w.setScrollY(newScrollY)
}

func ScrollYSignal

ScrollYSignal binds the vertical scroll offset to a reactive signal.

This is a TWO-WAY binding passed through to the internal ScrollView.

func ScrollYSignal(sig state.Signal[float32]) Option {
	return func(c *config) { c.scrollYSignal = sig }
}

func SelectedIndex

SelectedIndex sets the initially selected cell index.

Use -1 for no selection.

func SelectedIndex(index int) Option {
	return func(c *config) { c.selectedIndex = index }
}

func SelectedIndexReadonlySignal

SelectedIndexReadonlySignal binds the selected index to a read-only signal.

When set, this takes highest precedence over all other selection sources.

func SelectedIndexReadonlySignal(sig state.ReadonlySignal[int]) Option {
	return func(c *config) { c.readonlySelectedIndexSignal = sig }
}

func SelectedIndexSignal

SelectedIndexSignal binds the selected index to a reactive signal.

This is a TWO-WAY binding: the widget reads from the signal, and

when the user selects a cell, the new index is written back.

func SelectedIndexSignal(sig state.Signal[int]) Option {
	return func(c *config) { c.selectedIndexSignal = sig }
}

func SelectionModeOpt

SelectionModeOpt sets the selection mode for the grid.

Default is [SelectionNone].

func SelectionModeOpt(mode SelectionMode) Option {
	return func(c *config) { c.selectionMode = mode }
}

func String

String returns a human-readable name for the selection mode.

func (m SelectionMode) String() string {
	switch m {
	case SelectionNone:
		return selectionNoneStr
	case SelectionSingle:
		return selectionSingleStr
	default:
		return selectionUnknownStr
	}
}

func Unmount

Unmount is called when the grid view is removed from the widget tree.

Implements [widget.Lifecycle].

func (w *Widget) Unmount() {
	w.scroll.Unmount()
	// Bindings are cleaned up automatically by WidgetBase.CleanupBindings().
}

func VisibleRange

VisibleRange returns the indices of currently visible cells [start, end).

func (w *Widget) VisibleRange() (start, end int) {
	cols := w.effectiveColumns()
	if cols <= 0 {
		return 0, 0
	}
	itemCount := w.cfg.ResolvedItemCount()
	if itemCount == 0 {
		return 0, 0
	}

	scrollY := w.currentScrollY()
	firstRow, lastRow := w.visibleRowRange(scrollY, w.viewportHeight)

	startIdx := firstRow * cols
	endIdx := (lastRow + 1) * cols
	if endIdx > itemCount {
		endIdx = itemCount
	}
	if startIdx > itemCount {
		startIdx = itemCount
	}
	return startIdx, endIdx
}

Structs

type CellContext struct

CellContext provides contextual information to the cell builder callback.

 

The builder receives a CellContext for each visible cell, allowing it to

customize the returned widget based on selection, hover, and position state.

type CellContext struct {
	// Index is the zero-based item index in the data source.
	Index	int

	// Row is the cell's row index (zero-based).
	Row	int

	// Col is the cell's column index (zero-based).
	Col	int

	// IsSelected is true if this cell is the currently selected cell.
	IsSelected	bool

	// IsHovered is true if the mouse cursor is over this cell.
	IsHovered	bool
}

type Widget struct

Widget implements a virtualized grid that renders only visible cells.

It composes an internal [scrollview.Widget] for scroll handling and

delegates cell rendering to a builder callback.

 

A grid view is created with [New] using functional options:

 

gv := gridview.New(

gridview.Columns(4),

gridview.ItemCount(1000),

gridview.ItemSize(120, 120),

gridview.BuildCell(func(index int, ctx gridview.CellContext) widget.Widget {

return buildCell(index, ctx.IsSelected)

}),

)

type Widget struct {
	widget.WidgetBase
	cfg	config
	painter	Painter

	// Internal scroll view (composition).
	scroll	*scrollview.Widget
	virtual	*virtualContent

	// Widget cache for visible cells.
	cache	cellCache

	// Layout state.
	viewportWidth	float32
	viewportHeight	float32
	effectiveCols	int	// actual columns after auto-fit

	// Interaction state.
	hoveredIndex	int
}