Skip to content
This repository has been archived by the owner on Jan 16, 2024. It is now read-only.

Commit

Permalink
layout: add List.ScrollTo, PageUp, and PageDown
Browse files Browse the repository at this point in the history
ScrollTo: Scroll a list so the given index is visible.
PageUp & PageDown: Scroll up and down by the list height.
  • Loading branch information
theclapp committed Jun 28, 2020
1 parent 2f8833d commit 2e89019
Showing 1 changed file with 72 additions and 10 deletions.
82 changes: 72 additions & 10 deletions layout/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (
)

type scrollChild struct {
size image.Point
call op.CallOp
index int
size image.Point
call op.CallOp
}

// List displays a subsection of a potentially infinitely
Expand Down Expand Up @@ -41,12 +42,21 @@ type List struct {
// before calling Layout.
Position Position

len int
doScrollTo bool
scrollTo int
fromEnd bool

len int
FirstDrawn int
lastDrawn int

// maxSize is the total size of visible children.
maxSize int
children []scrollChild
dir iterationDir

// height is the height in pixels at the last layout, used for PgUp & PgDn.
height int
}

// ListElement is a function that computes the dimensions of
Expand All @@ -69,7 +79,7 @@ type Position struct {
// First is the index of the first visible child.
First int
// Offset is the distance in pixels from the top edge to the child at index
// First.
// First. Positive offsets are above the edge.
Offset int
}

Expand All @@ -91,7 +101,25 @@ func (l *List) init(gtx Context, len int) {
l.children = l.children[:0]
l.len = len
l.update()
if l.scrollToEnd() || l.Position.First > len {
if l.doScrollTo {
if l.scrollTo >= len {
l.scrollTo = len - 1
}
// If l.scrollTo is already in view, do nothing.
if l.FirstDrawn < l.scrollTo &&
l.scrollTo < l.LastDrawn {
l.doScrollTo = false
} else {
l.Position.Offset = 0
l.Position.First = l.scrollTo
if l.LastDrawn > 0 && l.scrollTo >= l.LastDrawn {
l.fromEnd = true
l.Position.First++
} else {
l.fromEnd = false
}
}
} else if l.scrollToEnd() || l.Position.First > len {
l.Position.Offset = 0
l.Position.First = len
}
Expand All @@ -106,20 +134,44 @@ func (l *List) Layout(gtx Context, len int, w ListElement) Dimensions {
cs := axisConstraints(l.Axis, 0, inf, crossMin, crossMax)
i := l.index()
gtx.Constraints = cs
l.end(w(gtx, i))
l.end(i, w(gtx, i))
}
return l.layout()
}

func (l *List) scrollToEnd() bool {
return l.ScrollToEnd && !l.Position.BeforeEnd
return (l.doScrollTo && l.fromEnd) || (l.ScrollToEnd && !l.Position.BeforeEnd)
}

// Dragging reports whether the List is being dragged.
func (l *List) Dragging() bool {
return l.scroll.State() == gesture.StateDragging
}

// ScrollTo makes sure list index item i is in view. If it's above the top,
// it becomes the top item. If it's below the bottom, it becomes the bottom
// item. If i < 0, uses 0. If you ScrollTo(n) and then later layout a list
// shorter than n, Layout scrolls to the end of the list.
func (l *List) ScrollTo(i int) {
l.doScrollTo = true
if i < 0 {
i = 0
}
l.scrollTo = i
}

func (l *List) PageUp() {
l.Position.Offset -= l.height
// If you don't do this and l.ScrollToEnd == true, Position.Offset is
// ignored.
l.Position.BeforeEnd = true
}

func (l *List) PageDown() {
l.Position.Offset += l.height
l.Position.BeforeEnd = true
}

func (l *List) update() {
d := l.scroll.Scroll(l.ctx.Metric, l.ctx, l.ctx.Now, gesture.Axis(l.Axis))
l.scrollDelta = d
Expand Down Expand Up @@ -162,7 +214,11 @@ func (l *List) nextDir() iterationDir {
_, vsize := axisMainConstraint(l.Axis, l.ctx.Constraints)
last := l.Position.First + len(l.children)
// Clamp offset.
if l.maxSize-l.Position.Offset < vsize && last == l.len {
if l.maxSize-l.Position.Offset < vsize &&
(last == l.len ||
(l.doScrollTo &&
l.fromEnd &&
last == l.scrollTo+1)) {
l.Position.Offset = l.maxSize - vsize
}
if l.Position.Offset < 0 && l.Position.First == 0 {
Expand All @@ -180,9 +236,9 @@ func (l *List) nextDir() iterationDir {
}

// End the current child by specifying its dimensions.
func (l *List) end(dims Dimensions) {
func (l *List) end(i int, dims Dimensions) {
call := l.child.Stop()
child := scrollChild{dims.Size, call}
child := scrollChild{i, dims.Size, call}
mainSize := axisMain(l.Axis, child.size)
l.maxSize += mainSize
switch l.dir {
Expand Down Expand Up @@ -235,6 +291,10 @@ func (l *List) layout() Dimensions {
if space := mainMax - size; l.ScrollToEnd && space > 0 {
pos += space
}
if len(children) > 0 {
l.FirstDrawn = children[0].index
l.LastDrawn = children[len(children)-1].index
}
for _, child := range children {
sz := child.size
var cross int
Expand Down Expand Up @@ -282,5 +342,7 @@ func (l *List) layout() Dimensions {
pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
l.scroll.Add(ops)
call.Add(ops)
l.doScrollTo = false
l.height = dims.Y
return Dimensions{Size: dims}
}

0 comments on commit 2e89019

Please sign in to comment.