~eliasnaur/gio

8af4472672772c3b779c050d3830ec6424b01fa9 — Dominik Honnef 3 months ago bb12508
layout: add API for efficiently scrolling to and by items

The majority of scrolling happens by manipulating the index of the first
displayed item instead of by just manipulating the offset. This lets us
avoid having to render all items that were scrolled past.

Instead of numbers of items we could've accepted a ratio in [0, 1] to
scroll by or to, to match the data we get from scrollbars. However,
there are more use cases for scrolling by items, such as keyboard
shortcuts, go-to dialogs, etc. And converting from [0, 1] to items is
trivial for the user as long as they know the number of items, and will
usually be handled for them by a theme.

Signed-off-by: Dominik Honnef <dominik@honnef.co>
1 files changed, 31 insertions(+), 0 deletions(-)

M layout/list.go
M layout/list.go => layout/list.go +31 -0
@@ 4,6 4,7 @@ package layout

import (
	"image"
	"math"

	"gioui.org/gesture"
	"gioui.org/op"


@@ 354,3 355,33 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
	call.Add(ops)
	return Dimensions{Size: dims}
}

// ScrollBy scrolls the list by a relative amount of items. The result will only be accurate if all items have the same
// height. Otherwise, it will be approximate.
func (l *List) ScrollBy(num float32) {
	// Split number of items into integer and fractional parts
	i, f := math.Modf(float64(num))

	// Scroll by integer amount of items
	l.Position.First += int(i)

	// Adjust Offset to account for fractional items. If Offset gets so large that it amounts to an entire item, then
	// the layout code will handle that for us and adjust First and Offset accordingly.
	itemHeight := float64(l.Position.Length) / float64(l.len)
	l.Position.Offset += int(math.Round(itemHeight * f))

	// First and Offset can go out of bounds, but the layout code knows how to handle that.

	// Ensure that the list pays attention to the Offset field when the scrollbar drag
	// is started while the bar is at the end of the list. Without this, the scrollbar
	// cannot be dragged away from the end.
	l.Position.BeforeEnd = true
}

// ScrollTo scrolls to the specified item. THe result will only be accurate if all items have the same height.
// Otherwise, it will be approximate.
func (l *List) ScrollTo(n int) {
	l.Position.First = 0
	l.Position.Offset = 0
	l.ScrollBy(float32(n))
}