Skip to content

Scrolling

A scroll container shows part of a larger object and lets the user move around it with the mouse wheel, the touchpad, the scroll bars or a finger. Wrap the content in one of:

ConstructorScrollsMinimum size
container.NewVScrollUp and downAs wide as the content, but short
container.NewHScrollLeft and rightAs tall as the content, but narrow
container.NewScrollBoth waysSmall in both directions
rows := container.NewVBox()
for i := 1; i <= 100; i++ {
	rows.Add(widget.NewLabel(fmt.Sprintf("Row %d", i)))
}

// Only vertical scrolling: the content is as wide as the scroll.
scroll := container.NewVScroll(rows)

Why the minimum size matters

A scroll container’s purpose is to have a smaller minimum size than its content. That is what lets a long list fit in a small window. Two things follow from it:

  • In a VBox, a VScroll gets its own small minimum height, not the height of the content, so it shows only a sliver. Put the scroll where it is stretched instead: the middle of a Border, a split pane, a tab, or the window itself.
  • If the scroll is still too small, give it a floor with scroll.SetMinSize(size).

A VScroll keeps the content’s minimum width, so the window cannot be made narrower than the content. An HScroll does the same for height.

Reacting to and controlling the position

The scroll position is the Offset field, a geo.Position: how far the content is moved from its top-left corner.

where := widget.NewLabel("Offset 0")
show := func(offset geo.Position) {
	where.SetText(fmt.Sprintf("Offset %.0f", offset.Y))
}
// Called when the user scrolls, and by ScrollToBottom.
scroll.OnScrolled = show

top := widget.NewButton("Top", func() {
	scroll.ScrollToTop() // does not call OnScrolled
	show(scroll.Offset)
})
bottom := widget.NewButton("Bottom", scroll.ScrollToBottom)
  • OnScrolled is called when the user scrolls. Do not change Offset from inside it.
  • ScrollToTop(), ScrollToBottom() and ScrollToOffset(pos) move the content from code.
  • AddScrollListener(fn) registers any number of extra listeners and returns a function that removes yours.
ScrollToTop and ScrollToOffset move the content without calling OnScrolled or the listeners. ScrollToBottom does call them. If you keep something in step with the offset, update it yourself after ScrollToTop, as the example does.

Two fields change how the container looks:

  • ReserveBarSpace makes the content narrower (or shorter) by the width of the scroll bar, so the bar never covers it. The three constructors turn it on; set it to false to let the bar float over the content.
  • HideShadows = true turns off the fading edge shown where content continues out of view.

The Direction field holds which way the container scrolls: container.ScrollBoth, ScrollHorizontalOnly, ScrollVerticalOnly or ScrollNone. The three constructors set it for you.

Long lists

Everything inside a scroll container exists at once, including the parts out of view. For long data, such as thousands of rows, use widget.List or widget.Table instead. They scroll by themselves and reuse a pool of row (or cell) objects, filling them in with your data as the user scrolls.

The whole program

// A long list of labels in a vertical scroll, with buttons that jump to the
// ends and a label that follows the scroll position.
package main

import (
	"fmt"

	"github.com/belluikit/bellui"
	"github.com/belluikit/bellui/app"
	"github.com/belluikit/bellui/container"
	"github.com/belluikit/bellui/geo"
	"github.com/belluikit/bellui/widget"
)

func main() {
	a := app.NewWithID("com.example.scroll")
	w := a.NewWindow("Scrolling")

	rows := container.NewVBox()
	for i := 1; i <= 100; i++ {
		rows.Add(widget.NewLabel(fmt.Sprintf("Row %d", i)))
	}

	// Only vertical scrolling: the content is as wide as the scroll.
	scroll := container.NewVScroll(rows)

	where := widget.NewLabel("Offset 0")
	show := func(offset geo.Position) {
		where.SetText(fmt.Sprintf("Offset %.0f", offset.Y))
	}
	// Called when the user scrolls, and by ScrollToBottom.
	scroll.OnScrolled = show

	top := widget.NewButton("Top", func() {
		scroll.ScrollToTop() // does not call OnScrolled
		show(scroll.Offset)
	})
	bottom := widget.NewButton("Bottom", scroll.ScrollToBottom)

	w.SetContent(container.NewBorder(
		container.NewHBox(top, bottom, where), nil, nil, nil,
		scroll,
	))
	w.Resize(geo.NewSize(320, 400))
	bellui.ShowAndRun(w)
}