~eliasnaur/gio-patches

layout: make list scroll position settable v2 PROPOSED

Larry Clapp: 1
 layout: make list scroll position settable

 1 files changed, 53 insertions(+), 42 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/9157/mbox | git am -3
Learn more about email & git

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

Put List.{first|offset|beforeEnd} into a new exported Position slot, and
also export each individually.

Have to put BeforeEnd into the Position slot to support ScrollToEnd
lists.

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

Commentary:

Take two!

I went with your description of the Position type.

diff --git a/layout/list.go b/layout/list.go
index bd4d08a..6af3da0 100644
--- a/layout/list.go
+++ b/layout/list.go
@@ -23,27 +23,24 @@ 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
-	// the end.
+	// 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.
 	ScrollToEnd bool
 	// Alignment is the cross axis alignment of list elements.
 	Alignment Alignment
 
-	// beforeEnd tracks whether the List position is before
-	// the very end.
-	beforeEnd bool
-
 	ctx         *Context
 	macro       op.MacroOp
 	child       op.MacroOp
 	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
+	// Position is updated during Layout. To save the list scroll position,
+	// just save Position after Layout finishes. To scroll the list
+	// programatically, update Position (e.g. restore it from a saved value)
+	// before calling Layout.
+	Position Position
 
 	len int
 
@@ -59,6 +56,24 @@ type ListElement func(index int)
 
 type iterationDir uint8
 
+// Position is a List scroll offset represented as an offset from the top edge
+// of a child element.
+type Position struct {
+	// BeforeEnd tracks whether the List position is before the very end. We
+	// use "before end" instead of "at end" so that the zero value of a
+	// Position struct is useful.
+	//
+	// When laying out a list, if ScrollToEnd is true and BeforeEnd is false,
+	// then First and Offset are ignored, and the list is drawn with the last
+	// item at the bottom. If ScrollToEnd is false then BeforeEnd is ignored.
+	BeforeEnd bool
+	// 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.
+	Offset int
+}
+
 const (
 	iterateNone iterationDir = iota
 	iterateForward
@@ -77,13 +92,9 @@ func (l *List) init(gtx *Context, len int) {
 	l.children = l.children[:0]
 	l.len = len
 	l.update()
-	if l.scrollToEnd() {
-		l.offset = 0
-		l.first = len
-	}
-	if l.first > len {
-		l.offset = 0
-		l.first = len
+	if l.scrollToEnd() || l.Position.First > len {
+		l.Position.Offset = 0
+		l.Position.First = len
 	}
 	l.macro.Record(gtx.Ops)
 	l.next()
@@ -102,7 +113,7 @@ func (l *List) Layout(gtx *Context, len int, w ListElement) {
 }
 
 func (l *List) scrollToEnd() bool {
-	return l.ScrollToEnd && !l.beforeEnd
+	return l.ScrollToEnd && !l.Position.BeforeEnd
 }
 
 // Dragging reports whether the List is being dragged.
@@ -113,7 +124,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.Position.Offset += d
 }
 
 // next advances to the next child.
@@ -122,8 +133,8 @@ func (l *List) next() {
 	// The user scroll offset is applied after scrolling to
 	// list end.
 	if l.scrollToEnd() && !l.more() && l.scrollDelta < 0 {
-		l.beforeEnd = true
-		l.offset += l.scrollDelta
+		l.Position.BeforeEnd = true
+		l.Position.Offset += l.scrollDelta
 		l.dir = l.nextDir()
 	}
 	if l.more() {
@@ -135,9 +146,9 @@ func (l *List) next() {
 func (l *List) index() int {
 	switch l.dir {
 	case iterateBackward:
-		return l.first - 1
+		return l.Position.First - 1
 	case iterateForward:
-		return l.first + len(l.children)
+		return l.Position.First + len(l.children)
 	default:
 		panic("Index called before Next")
 	}
@@ -150,20 +161,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.Position.First + len(l.children)
 	// Clamp offset.
-	if l.maxSize-l.offset < vsize && last == l.len {
-		l.offset = l.maxSize - vsize
+	if l.maxSize-l.Position.Offset < vsize && last == l.len {
+		l.Position.Offset = l.maxSize - vsize
 	}
-	if l.offset < 0 && l.first == 0 {
-		l.offset = 0
+	if l.Position.Offset < 0 && l.Position.First == 0 {
+		l.Position.Offset = 0
 	}
 	switch {
 	case len(l.children) == l.len:
 		return iterateNone
-	case l.maxSize-l.offset < vsize:
+	case l.maxSize-l.Position.Offset < vsize:
 		return iterateForward
-	case l.offset < 0:
+	case l.Position.Offset < 0:
 		return iterateBackward
 	}
 	return iterateNone
@@ -180,8 +191,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.Position.First--
+		l.Position.Offset += mainSize
 	default:
 		panic("call Next before End")
 	}
@@ -199,14 +210,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.Position.Offset <= mainSize {
 			break
 		}
-		l.first++
-		l.offset -= mainSize
+		l.Position.First++
+		l.Position.Offset -= mainSize
 		children = children[1:]
 	}
-	size := -l.offset
+	size := -l.Position.Offset
 	var maxCross int
 	for i, child := range children {
 		sz := child.size
@@ -220,8 +231,8 @@ func (l *List) layout() Dimensions {
 		}
 	}
 	ops := l.ctx.Ops
-	pos := -l.offset
-	// ScrollToEnd lists lists are end aligned.
+	pos := -l.Position.Offset
+	// ScrollToEnd lists are end aligned.
 	if space := mainc.Max - size; l.ScrollToEnd && space > 0 {
 		pos += space
 	}
@@ -255,12 +266,12 @@ 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.Position.First == 0 && l.Position.Offset <= 0
+	atEnd := l.Position.First+len(children) == l.len && mainc.Max >= pos
 	if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
 		l.scroll.Stop()
 	}
-	l.beforeEnd = !atEnd
+	l.Position.BeforeEnd = !atEnd
 	dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
 	l.macro.Stop()
 	pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
-- 
2.23.0
Thanks! Applied.
View this thread in the archives