Skip to content

Commit

Permalink
layout: add API for efficiently scrolling to and by items
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
dominikh authored and eliasnaur committed Feb 24, 2023
1 parent bb12508 commit 8af4472
Showing 1 changed file with 31 additions and 0 deletions.
31 changes: 31 additions & 0 deletions layout/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package layout

import (
"image"
"math"

"gioui.org/gesture"
"gioui.org/op"
Expand Down Expand Up @@ -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))
}

0 comments on commit 8af4472

Please sign in to comment.