Files
gio/widget/list.go
T
Elias Naur 799ee3374d widget,widget/material: scroll using only drag position delta
This commit is based on a patch by Elias that improved drag scrolling
on the scrollbar by locking some parameters of the math at the start
of the scroll event.

I discovered while playing with that implementation that there was
an even simpler approach within his changeset. You can actually
use no information other than the delta between the current and
previous frame's scroll position to compute the scroll distance.
By simplifying the math to rely on no other inputs, the jitter that
we've been fighting simply disappears (it came from other inputs).

Turns out my attempts to make the logic smart were the problem.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2021-09-17 08:47:13 +02:00

130 lines
3.9 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package widget
import (
"image"
"gioui.org/gesture"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
)
// Scrollbar holds the persistent state for an area that can
// display a scrollbar. In particular, it tracks the position of a
// viewport along a one-dimensional region of content. The viewport's
// position can be adjusted by drag operations along the display area,
// or by clicks within the display area.
//
// Scrollbar additionally detects when a scroll indicator region is
// hovered.
type Scrollbar struct {
track, indicator gesture.Click
drag gesture.Drag
delta float32
dragging bool
oldDragPos float32
}
// Layout updates the internal state of the scrollbar based on events
// since the previous call to Layout. The provided axis will be used to
// normalize input event coordinates and constraints into an axis-
// independent format. viewportStart is the position of the beginning
// of the scrollable viewport relative to the underlying content expressed
// as a value in the range [0,1]. viewportEnd is the position of the end
// of the viewport relative to the underlying content, also expressed
// as a value in the range [0,1]. For example, if viewportStart is 0.25
// and viewportEnd is .5, the viewport described by the scrollbar is
// currently showing the second quarter of the underlying content.
func (s *Scrollbar) Layout(gtx layout.Context, axis layout.Axis, viewportStart, viewportEnd float32) layout.Dimensions {
// Calculate the length of the major axis of the scrollbar. This is
// the length of the track within which pointer events occur, and is
// used to scale those interactions.
trackHeight := float32(axis.Convert(gtx.Constraints.Max).X)
s.delta = 0
// Jump to a click in the track.
for _, event := range s.track.Events(gtx) {
if event.Type != gesture.TypeClick ||
event.Modifiers != key.Modifiers(0) ||
event.NumClicks > 1 {
continue
}
pos := axis.Convert(image.Point{
X: int(event.Position.X),
Y: int(event.Position.Y),
})
normalizedPos := float32(pos.X) / trackHeight
s.delta += normalizedPos - viewportStart
}
// Offset to account for any drags.
for _, event := range s.drag.Events(gtx.Metric, gtx, gesture.Axis(axis)) {
switch event.Type {
case pointer.Drag:
case pointer.Release:
s.dragging = false
case pointer.Cancel:
s.dragging = false
continue
default:
continue
}
dragOffset := axis.FConvert(event.Position).X
normalizedDragOffset := dragOffset / trackHeight
if !s.dragging {
s.dragging = true
s.oldDragPos = normalizedDragOffset
}
s.delta += normalizedDragOffset - s.oldDragPos
s.oldDragPos = normalizedDragOffset
}
// Process events from the indicator so that hover is
// detected properly.
_ = s.indicator.Events(gtx)
return layout.Dimensions{}
}
// AddTrack configures the track click listener for the scrollbar to use
// the most recently added pointer.AreaOp.
func (s *Scrollbar) AddTrack(ops *op.Ops) {
s.track.Add(ops)
}
// AddIndicator configures the indicator click listener for the scrollbar to use
// the most recently added pointer.AreaOp.
func (s *Scrollbar) AddIndicator(ops *op.Ops) {
s.indicator.Add(ops)
}
// AddDrag configures the drag listener for the scrollbar to use
// the most recently added pointer.AreaOp.
func (s *Scrollbar) AddDrag(ops *op.Ops) {
s.drag.Add(ops)
}
// IndicatorHovered returns whether the scroll indicator is currently being
// hovered by the pointer.
func (s *Scrollbar) IndicatorHovered() bool {
return s.indicator.Hovered()
}
// ScrollDistance returns the normalized distance that the scrollbar
// moved during the last call to Layout as a value in the range [-1,1].
func (s *Scrollbar) ScrollDistance() float32 {
return s.delta
}
// List holds the persistent state for a layout.List that has a
// scrollbar attached.
type List struct {
Scrollbar
layout.List
}