widget/material: use offsetlast in scroll position calculations

This commit updates the logic that computes scroll viewport coordinates to correctly
consume layout.Position.OffsetLast, which was previously ignored. The impact of ignoring
that field was that dragging on a scroll indicator could sometimes fail to reach the
end of the list.

I've updated the logic to consume that field, which increased the amount of visual
jitter in the position of the scrollbar. I then also added a mechanism for smoothing
the jitter by using both methods of deriving the viewport and synthesizing a viewport
from both.

This new strategy exhibits a lower standard deviation than the other options on each of:

- the length of the scroll indicator
- the change in the start coordinate of the viewport when scrolling smoothly
- the change in the end coordinate of the viewport when scrolling smoothly

Fixes: https://todo.sr.ht/~eliasnaur/gio/504
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
Chris Waldon
2023-06-30 14:45:45 -04:00
committed by Elias Naur
parent e5fe3a0732
commit cc2d2c0abf
+27 -6
View File
@@ -23,18 +23,39 @@ import (
// start will be less than or equal to end.
func fromListPosition(lp layout.Position, elements int, majorAxisSize int) (start, end float32) {
// Approximate the size of the scrollable content.
lengthPx := float32(lp.Length)
meanElementHeight := lengthPx / float32(elements)
lengthEstPx := float32(lp.Length)
elementLenEstPx := lengthEstPx / float32(elements)
// Determine how much of the content is visible.
listOffsetF := float32(lp.Offset)
listOffsetL := float32(lp.OffsetLast)
// Compute the location of the beginning of the viewport using estimated element size and known
// pixel offsets.
viewportStart := clamp1((float32(lp.First)*elementLenEstPx + listOffsetF) / lengthEstPx)
viewportEnd := clamp1((float32(lp.First+lp.Count)*elementLenEstPx + listOffsetL) / lengthEstPx)
viewportFraction := viewportEnd - viewportStart
// Compute the expected visible proportion of the list content based solely on the ratio
// of the visible size and the estimated total size.
visiblePx := float32(majorAxisSize)
visibleFraction := visiblePx / lengthPx
visibleFraction := visiblePx / lengthEstPx
// Compute the location of the beginning of the viewport.
viewportStart := (float32(lp.First)*meanElementHeight + listOffsetF) / lengthPx
// Compute the error between the two methods of determining the viewport and diffuse the
// error on either end of the viewport based on how close we are to each end.
err := visibleFraction - viewportFraction
adjStart := viewportStart
adjEnd := viewportEnd
if viewportFraction < 1 {
startShare := viewportStart / (1 - viewportFraction)
endShare := (1 - viewportEnd) / (1 - viewportFraction)
startErr := startShare * err
endErr := endShare * err
return viewportStart, clamp1(viewportStart + visibleFraction)
adjStart -= startErr
adjEnd += endErr
}
return adjStart, adjEnd
}
// rangeIsScrollable returns whether the viewport described by start and end