From 8af4472672772c3b779c050d3830ec6424b01fa9 Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Fri, 17 Feb 2023 20:07:44 +0100 Subject: [PATCH] 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 --- layout/list.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/layout/list.go b/layout/list.go index c2e46303..dff237ae 100644 --- a/layout/list.go +++ b/layout/list.go @@ -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)) +}