~eliasnaur/gio-patches

layout: add List.ScrollTo, ScrollPages, and 2 more v1 PROPOSED

Larry Clapp: 1
 layout: add List.ScrollTo, ScrollPages, and 2 more

 1 files changed, 121 insertions(+), 27 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio-patches/patches/11379/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] layout: add List.ScrollTo, ScrollPages, and 2 more Export this patch

- Add PagePrev & PageNext
- Simplify List.init, Layout, and end.
Signed-off-by: Larry Clapp <larry@theclapp.org>
---
 layout/list.go | 148 ++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 121 insertions(+), 27 deletions(-)

Arguably this is v3 of
https://lists.sr.ht/~eliasnaur/gio-patches/%3C20200217205034.23776-1-larry%40theclapp.org%3E
but it's been so long I thought I should start over.

Let me know what you think of the code and I'll work on a test case.

diff --git a/layout/list.go b/layout/list.go
index 68b63f7..00f6e49 100644
--- a/layout/list.go
+++ b/layout/list.go
@@ -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
@@ -23,15 +24,12 @@ type List struct {
	Axis Axis
	// ScrollToEnd instructs the list to stay scrolled to the far end position
	// once reached. A List with ScrollToEnd == true and Position.BeforeEnd ==
	// false draws its content with the last item at the bottom of the list
	// area.
	// false draws its content with the last item at the end of the list area.
	ScrollToEnd bool
	// Alignment is the cross axis alignment of list elements.
	Alignment Alignment

	ctx         Context
	macro       op.MacroOp
	child       op.MacroOp
	scroll      gesture.Scroll
	scrollDelta int

@@ -41,12 +39,25 @@ type List struct {
	// before calling Layout.
	Position Position

	len int
	// If fromEnd is true, then then draw fromEndItem at the end of the list
	// area and go back from there, as if ScrollToEnd was true and this item
	// was the last item in the list. If fromEnd is false, fromEndItem is
	// ignored.
	fromEnd     bool
	fromEndItem int

	len        int
	firstDrawn int
	lastDrawn  int

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

	// size is the width or height, in pixels, at the last layout, used in
	// ScrollPages.
	size int
}

// ListElement is a function that computes the dimensions of
@@ -69,7 +80,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
}

@@ -91,28 +102,31 @@ 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.fromEnd && l.scrollToEnd()) || l.Position.First > len {
		l.Position.Offset = 0
		l.Position.First = len
	}
	l.macro = op.Record(gtx.Ops)
	l.next()
}

// Layout the List.
func (l *List) Layout(gtx Context, len int, w ListElement) Dimensions {
	for l.init(gtx, len); l.more(); l.next() {
		crossMin, crossMax := axisCrossConstraint(l.Axis, l.ctx.Constraints)
		cs := axisConstraints(l.Axis, 0, inf, crossMin, crossMax)
	l.init(gtx, len)
	crossMin, crossMax := axisCrossConstraint(l.Axis, l.ctx.Constraints)
	cs := axisConstraints(l.Axis, 0, inf, crossMin, crossMax)
	gtx.Constraints = cs
	macro := op.Record(gtx.Ops)
	for l.next(); l.more(); l.next() {
		child := op.Record(l.ctx.Ops)
		i := l.index()
		gtx.Constraints = cs
		l.end(w(gtx, i))
		dims := w(gtx, i)
		call := child.Stop()
		l.end(i, dims, call)
	}
	return l.layout()
	return l.layout(macro)
}

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

// Dragging reports whether the List is being dragged.
@@ -120,6 +134,77 @@ 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, with said item drawn starting at the end of the
// item. (This means that if the item is taller/longer than the list area, the
// beginning of the item will be out of view.)
//
// If i < 0, uses 0.
//
// If you ScrollTo(n) and then layout a list shorter than n, Layout scrolls to
// the end of the list.
func (l *List) ScrollTo(i int) {
	if i < 0 {
		i = 0
	} else if i >= l.len {
		if l.len > 0 {
			i = l.len - 1
		} else {
			i = 0
		}
	}

	// Set defaults -- override a possible previous ScrollTo w/out an
	// intervening Layout.
	l.fromEnd = false

	// If i is already entirely in view, do nothing.
	if l.firstDrawn < i && i < l.lastDrawn {
		return
	}

	if l.firstDrawn > 0 && i <= l.firstDrawn {
		l.Position.Offset = 0
		l.Position.First = i
		l.Position.BeforeEnd = true
	} else if l.lastDrawn > 0 && i >= l.lastDrawn {
		l.Position.First = i + 1
		l.fromEnd = true
		l.fromEndItem = i
	}
}

// ScrollPages scrolls a number of pages. n < 0 is up or left, n > 0 is down
// or right. n == 0 is a no-op.
func (l *List) ScrollPages(n int) {
	if n == 0 {
		return
	}
	// If going up/left and already at the beginning, do nothing
	if n < 0 && l.Position.BeforeEnd && l.Position.First == 0 && l.Position.Offset == 0 {
		return
	}
	// If going down/right and already at the end, do nothing.
	if n > 0 && !l.Position.BeforeEnd {
		return
	}

	l.Position.Offset += (l.size * n)
	// If you don't do this and l.ScrollToEnd == true, Position.Offset is
	// ignored.
	l.Position.BeforeEnd = true
}

func (l *List) PagePrev() {
	l.ScrollPages(-1)
}

func (l *List) PageNext() {
	l.ScrollPages(1)
}

func (l *List) update() {
	d := l.scroll.Scroll(l.ctx.Metric, l.ctx, l.ctx.Now, gesture.Axis(l.Axis))
	l.scrollDelta = d
@@ -136,9 +221,6 @@ func (l *List) next() {
		l.Position.Offset += l.scrollDelta
		l.dir = l.nextDir()
	}
	if l.more() {
		l.child = op.Record(l.ctx.Ops)
	}
}

// index is current child's position in the underlying list.
@@ -162,7 +244,8 @@ 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.fromEnd && last == l.fromEndItem+1)) {
		l.Position.Offset = l.maxSize - vsize
	}
	if l.Position.Offset < 0 && l.Position.First == 0 {
@@ -180,9 +263,8 @@ func (l *List) nextDir() iterationDir {
}

// End the current child by specifying its dimensions.
func (l *List) end(dims Dimensions) {
	call := l.child.Stop()
	child := scrollChild{dims.Size, call}
func (l *List) end(i int, dims Dimensions, call op.CallOp) {
	child := scrollChild{i, dims.Size, call}
	mainSize := axisMain(l.Axis, child.size)
	l.maxSize += mainSize
	switch l.dir {
@@ -199,7 +281,7 @@ func (l *List) end(dims Dimensions) {
}

// Layout the List and return its dimensions.
func (l *List) layout() Dimensions {
func (l *List) layout(macro op.MacroOp) Dimensions {
	if l.more() {
		panic("unfinished child")
	}
@@ -235,6 +317,13 @@ func (l *List) layout() Dimensions {
	if space := mainMax - size; l.ScrollToEnd && space > 0 {
		pos += space
	}
	if len(children) == 0 {
		l.firstDrawn = 0
		l.lastDrawn = 0
	} else {
		l.firstDrawn = children[0].index
		l.lastDrawn = children[len(children)-1].index
	}
	for _, child := range children {
		sz := child.size
		var cross int
@@ -277,10 +366,15 @@ func (l *List) layout() Dimensions {
		pos = mainMax
	}
	dims := axisPoint(l.Axis, pos, maxCross)
	call := l.macro.Stop()
	call := macro.Stop()
	defer op.Push(l.ctx.Ops).Pop()
	pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
	l.scroll.Add(ops)
	call.Add(ops)
	l.fromEnd = false
	l.size = axisMain(l.Axis, dims)
	return Dimensions{Size: dims}
}

func (l *List) FirstDrawn() int { return l.firstDrawn }
func (l *List) LastDrawn() int  { return l.lastDrawn }
-- 
2.26.2