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:
| Constructor | Scrolls | Minimum size |
|---|---|---|
container.NewVScroll | Up and down | As wide as the content, but short |
container.NewHScroll | Left and right | As tall as the content, but narrow |
container.NewScroll | Both ways | Small 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)OnScrolledis called when the user scrolls. Do not changeOffsetfrom inside it.ScrollToTop(),ScrollToBottom()andScrollToOffset(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:
ReserveBarSpacemakes 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 tofalseto let the bar float over the content.HideShadows = trueturns 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)
}