~eliasnaur/gio-patches

layout: make List scroll position settable v1 PROPOSED

Larry Clapp: 1
 layout: make List scroll position settable

 1 files changed, 43 insertions(+), 36 deletions(-)
> That's exactly what Position is :) You're free to improve my
> description, of course.
I went with your description after all. :)
Next
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/9145/mbox | git am -3
Learn more about email & git

[PATCH] layout: make List scroll position settable Export this patch

- Added List.LayoutPos, which displays the list starting at the given
  Position, and returns the list's Position after layout (including any
  scrolling).
- Make List.Layout call LayoutPos, for the common case where you don't
  care about setting the list position.

Signed-off-by: Larry Clapp <larry@theclapp.org>
---
 layout/list.go | 79 +++++++++++++++++++++++++++-----------------------
 1 file changed, 43 insertions(+), 36 deletions(-)

diff --git a/layout/list.go b/layout/list.go
index bd4d08a..119e308 100644
--- a/layout/list.go
+++ b/layout/list.go
@@ -23,7 +23,7 @@ type scrollChild struct {
 type List struct {
 	Axis Axis
 	// ScrollToEnd instructs the list to stay scrolled to the far end position
-	// once reahed. A List with ScrollToEnd enabled also align its content to
+	// once reached. A List with ScrollToEnd enabled also align its content to
 	// the end.
 	ScrollToEnd bool
 	// Alignment is the cross axis alignment of list elements.
@@ -39,12 +39,7 @@ type List struct {
 	scroll      gesture.Scroll
 	scrollDelta int
 
-	// first is the index of the first visible child.
-	first int
-	// offset is the signed distance from the top edge
-	// to the child with index first.
-	offset int
-
+	pos Position
 	len int
 
 	// maxSize is the total size of visible children.
@@ -59,6 +54,12 @@ type ListElement func(index int)
 
 type iterationDir uint8
 
+// First is the index of the first visible child.  Offset is the signed
+// distance from the top edge to the child with the index First.
+type Position struct {
+	First, Offset int
+}
+
 const (
 	iterateNone iterationDir = iota
 	iterateForward
@@ -68,7 +69,7 @@ const (
 const inf = 1e6
 
 // init prepares the list for iterating through its children with next.
-func (l *List) init(gtx *Context, len int) {
+func (l *List) init(gtx *Context, len int, p Position) {
 	if l.more() {
 		panic("unfinished child")
 	}
@@ -76,14 +77,12 @@ func (l *List) init(gtx *Context, len int) {
 	l.maxSize = 0
 	l.children = l.children[:0]
 	l.len = len
-	l.update()
-	if l.scrollToEnd() {
-		l.offset = 0
-		l.first = len
+	if p.First >= 0 && p.Offset >= 0 {
+		l.pos = p
 	}
-	if l.first > len {
-		l.offset = 0
-		l.first = len
+	l.update()
+	if l.scrollToEnd() || l.pos.First > len {
+		l.pos = Position{First: len}
 	}
 	l.macro.Record(gtx.Ops)
 	l.next()
@@ -91,7 +90,14 @@ func (l *List) init(gtx *Context, len int) {
 
 // Layout the List.
 func (l *List) Layout(gtx *Context, len int, w ListElement) {
-	for l.init(gtx, len); l.more(); l.next() {
+	l.LayoutPos(gtx, len, l.pos, w)
+}
+
+// Layout the list, at position p.  Position{} is the top of the list.
+// Negative p.First or p.Offset are ignored.  The list's position is returned,
+// for use in the next call to Layout.
+func (l *List) LayoutPos(gtx *Context, len int, p Position, w ListElement) Position {
+	for l.init(gtx, len, p); l.more(); l.next() {
 		cs := axisConstraints(l.Axis, Constraint{Max: inf}, axisCrossConstraint(l.Axis, l.ctx.Constraints))
 		i := l.index()
 		l.end(ctxLayout(gtx, cs, func() {
@@ -99,6 +105,7 @@ func (l *List) Layout(gtx *Context, len int, w ListElement) {
 		}))
 	}
 	gtx.Dimensions = l.layout()
+	return l.pos
 }
 
 func (l *List) scrollToEnd() bool {
@@ -113,7 +120,7 @@ func (l *List) Dragging() bool {
 func (l *List) update() {
 	d := l.scroll.Scroll(l.ctx.Config, l.ctx.Queue, l.ctx.Now(), gesture.Axis(l.Axis))
 	l.scrollDelta = d
-	l.offset += d
+	l.pos.Offset += d
 }
 
 // next advances to the next child.
@@ -123,7 +130,7 @@ func (l *List) next() {
 	// list end.
 	if l.scrollToEnd() && !l.more() && l.scrollDelta < 0 {
 		l.beforeEnd = true
-		l.offset += l.scrollDelta
+		l.pos.Offset += l.scrollDelta
 		l.dir = l.nextDir()
 	}
 	if l.more() {
@@ -135,9 +142,9 @@ func (l *List) next() {
 func (l *List) index() int {
 	switch l.dir {
 	case iterateBackward:
-		return l.first - 1
+		return l.pos.First - 1
 	case iterateForward:
-		return l.first + len(l.children)
+		return l.pos.First + len(l.children)
 	default:
 		panic("Index called before Next")
 	}
@@ -150,20 +157,20 @@ func (l *List) more() bool {
 
 func (l *List) nextDir() iterationDir {
 	vsize := axisMainConstraint(l.Axis, l.ctx.Constraints).Max
-	last := l.first + len(l.children)
+	last := l.pos.First + len(l.children)
 	// Clamp offset.
-	if l.maxSize-l.offset < vsize && last == l.len {
-		l.offset = l.maxSize - vsize
+	if l.maxSize-l.pos.Offset < vsize && last == l.len {
+		l.pos.Offset = l.maxSize - vsize
 	}
-	if l.offset < 0 && l.first == 0 {
-		l.offset = 0
+	if l.pos.Offset < 0 && l.pos.First == 0 {
+		l.pos.Offset = 0
 	}
 	switch {
 	case len(l.children) == l.len:
 		return iterateNone
-	case l.maxSize-l.offset < vsize:
+	case l.maxSize-l.pos.Offset < vsize:
 		return iterateForward
-	case l.offset < 0:
+	case l.pos.Offset < 0:
 		return iterateBackward
 	}
 	return iterateNone
@@ -180,8 +187,8 @@ func (l *List) end(dims Dimensions) {
 		l.children = append(l.children, child)
 	case iterateBackward:
 		l.children = append([]scrollChild{child}, l.children...)
-		l.first--
-		l.offset += mainSize
+		l.pos.First--
+		l.pos.Offset += mainSize
 	default:
 		panic("call Next before End")
 	}
@@ -199,14 +206,14 @@ func (l *List) layout() Dimensions {
 	for len(children) > 0 {
 		sz := children[0].size
 		mainSize := axisMain(l.Axis, sz)
-		if l.offset <= mainSize {
+		if l.pos.Offset <= mainSize {
 			break
 		}
-		l.first++
-		l.offset -= mainSize
+		l.pos.First++
+		l.pos.Offset -= mainSize
 		children = children[1:]
 	}
-	size := -l.offset
+	size := -l.pos.Offset
 	var maxCross int
 	for i, child := range children {
 		sz := child.size
@@ -220,7 +227,7 @@ func (l *List) layout() Dimensions {
 		}
 	}
 	ops := l.ctx.Ops
-	pos := -l.offset
+	pos := -l.pos.Offset
 	// ScrollToEnd lists lists are end aligned.
 	if space := mainc.Max - size; l.ScrollToEnd && space > 0 {
 		pos += space
@@ -255,8 +262,8 @@ func (l *List) layout() Dimensions {
 		stack.Pop()
 		pos += childSize
 	}
-	atStart := l.first == 0 && l.offset <= 0
-	atEnd := l.first+len(children) == l.len && mainc.Max >= pos
+	atStart := l.pos.First == 0 && l.pos.Offset <= 0
+	atEnd := l.pos.First+len(children) == l.len && mainc.Max >= pos
 	if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
 		l.scroll.Stop()
 	}
-- 
2.23.0
Nice.

On Sun Nov 24, 2019 at 10:32 PM Larry Clapp wrote:
View this thread in the archives