mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 02:15:34 +00:00
layout: make list scroll position settable
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>
This commit is contained in:
+53
-42
@@ -23,27 +23,24 @@ type scrollChild struct {
|
|||||||
type List struct {
|
type List struct {
|
||||||
Axis Axis
|
Axis Axis
|
||||||
// ScrollToEnd instructs the list to stay scrolled to the far end position
|
// 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 == true and Position.BeforeEnd ==
|
||||||
// the end.
|
// false draws its content with the last item at the bottom of the list
|
||||||
|
// area.
|
||||||
ScrollToEnd bool
|
ScrollToEnd bool
|
||||||
// Alignment is the cross axis alignment of list elements.
|
// Alignment is the cross axis alignment of list elements.
|
||||||
Alignment Alignment
|
Alignment Alignment
|
||||||
|
|
||||||
// beforeEnd tracks whether the List position is before
|
|
||||||
// the very end.
|
|
||||||
beforeEnd bool
|
|
||||||
|
|
||||||
ctx *Context
|
ctx *Context
|
||||||
macro op.MacroOp
|
macro op.MacroOp
|
||||||
child op.MacroOp
|
child op.MacroOp
|
||||||
scroll gesture.Scroll
|
scroll gesture.Scroll
|
||||||
scrollDelta int
|
scrollDelta int
|
||||||
|
|
||||||
// first is the index of the first visible child.
|
// Position is updated during Layout. To save the list scroll position,
|
||||||
first int
|
// just save Position after Layout finishes. To scroll the list
|
||||||
// offset is the signed distance from the top edge
|
// programatically, update Position (e.g. restore it from a saved value)
|
||||||
// to the child with index first.
|
// before calling Layout.
|
||||||
offset int
|
Position Position
|
||||||
|
|
||||||
len int
|
len int
|
||||||
|
|
||||||
@@ -59,6 +56,24 @@ type ListElement func(index int)
|
|||||||
|
|
||||||
type iterationDir uint8
|
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 (
|
const (
|
||||||
iterateNone iterationDir = iota
|
iterateNone iterationDir = iota
|
||||||
iterateForward
|
iterateForward
|
||||||
@@ -77,13 +92,9 @@ func (l *List) init(gtx *Context, len int) {
|
|||||||
l.children = l.children[:0]
|
l.children = l.children[:0]
|
||||||
l.len = len
|
l.len = len
|
||||||
l.update()
|
l.update()
|
||||||
if l.scrollToEnd() {
|
if l.scrollToEnd() || l.Position.First > len {
|
||||||
l.offset = 0
|
l.Position.Offset = 0
|
||||||
l.first = len
|
l.Position.First = len
|
||||||
}
|
|
||||||
if l.first > len {
|
|
||||||
l.offset = 0
|
|
||||||
l.first = len
|
|
||||||
}
|
}
|
||||||
l.macro.Record(gtx.Ops)
|
l.macro.Record(gtx.Ops)
|
||||||
l.next()
|
l.next()
|
||||||
@@ -102,7 +113,7 @@ func (l *List) Layout(gtx *Context, len int, w ListElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *List) scrollToEnd() bool {
|
func (l *List) scrollToEnd() bool {
|
||||||
return l.ScrollToEnd && !l.beforeEnd
|
return l.ScrollToEnd && !l.Position.BeforeEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dragging reports whether the List is being dragged.
|
// Dragging reports whether the List is being dragged.
|
||||||
@@ -113,7 +124,7 @@ func (l *List) Dragging() bool {
|
|||||||
func (l *List) update() {
|
func (l *List) update() {
|
||||||
d := l.scroll.Scroll(l.ctx.Config, l.ctx.Queue, l.ctx.Now(), gesture.Axis(l.Axis))
|
d := l.scroll.Scroll(l.ctx.Config, l.ctx.Queue, l.ctx.Now(), gesture.Axis(l.Axis))
|
||||||
l.scrollDelta = d
|
l.scrollDelta = d
|
||||||
l.offset += d
|
l.Position.Offset += d
|
||||||
}
|
}
|
||||||
|
|
||||||
// next advances to the next child.
|
// next advances to the next child.
|
||||||
@@ -122,8 +133,8 @@ func (l *List) next() {
|
|||||||
// The user scroll offset is applied after scrolling to
|
// The user scroll offset is applied after scrolling to
|
||||||
// list end.
|
// list end.
|
||||||
if l.scrollToEnd() && !l.more() && l.scrollDelta < 0 {
|
if l.scrollToEnd() && !l.more() && l.scrollDelta < 0 {
|
||||||
l.beforeEnd = true
|
l.Position.BeforeEnd = true
|
||||||
l.offset += l.scrollDelta
|
l.Position.Offset += l.scrollDelta
|
||||||
l.dir = l.nextDir()
|
l.dir = l.nextDir()
|
||||||
}
|
}
|
||||||
if l.more() {
|
if l.more() {
|
||||||
@@ -135,9 +146,9 @@ func (l *List) next() {
|
|||||||
func (l *List) index() int {
|
func (l *List) index() int {
|
||||||
switch l.dir {
|
switch l.dir {
|
||||||
case iterateBackward:
|
case iterateBackward:
|
||||||
return l.first - 1
|
return l.Position.First - 1
|
||||||
case iterateForward:
|
case iterateForward:
|
||||||
return l.first + len(l.children)
|
return l.Position.First + len(l.children)
|
||||||
default:
|
default:
|
||||||
panic("Index called before Next")
|
panic("Index called before Next")
|
||||||
}
|
}
|
||||||
@@ -150,20 +161,20 @@ func (l *List) more() bool {
|
|||||||
|
|
||||||
func (l *List) nextDir() iterationDir {
|
func (l *List) nextDir() iterationDir {
|
||||||
vsize := axisMainConstraint(l.Axis, l.ctx.Constraints).Max
|
vsize := axisMainConstraint(l.Axis, l.ctx.Constraints).Max
|
||||||
last := l.first + len(l.children)
|
last := l.Position.First + len(l.children)
|
||||||
// Clamp offset.
|
// Clamp offset.
|
||||||
if l.maxSize-l.offset < vsize && last == l.len {
|
if l.maxSize-l.Position.Offset < vsize && last == l.len {
|
||||||
l.offset = l.maxSize - vsize
|
l.Position.Offset = l.maxSize - vsize
|
||||||
}
|
}
|
||||||
if l.offset < 0 && l.first == 0 {
|
if l.Position.Offset < 0 && l.Position.First == 0 {
|
||||||
l.offset = 0
|
l.Position.Offset = 0
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case len(l.children) == l.len:
|
case len(l.children) == l.len:
|
||||||
return iterateNone
|
return iterateNone
|
||||||
case l.maxSize-l.offset < vsize:
|
case l.maxSize-l.Position.Offset < vsize:
|
||||||
return iterateForward
|
return iterateForward
|
||||||
case l.offset < 0:
|
case l.Position.Offset < 0:
|
||||||
return iterateBackward
|
return iterateBackward
|
||||||
}
|
}
|
||||||
return iterateNone
|
return iterateNone
|
||||||
@@ -180,8 +191,8 @@ func (l *List) end(dims Dimensions) {
|
|||||||
l.children = append(l.children, child)
|
l.children = append(l.children, child)
|
||||||
case iterateBackward:
|
case iterateBackward:
|
||||||
l.children = append([]scrollChild{child}, l.children...)
|
l.children = append([]scrollChild{child}, l.children...)
|
||||||
l.first--
|
l.Position.First--
|
||||||
l.offset += mainSize
|
l.Position.Offset += mainSize
|
||||||
default:
|
default:
|
||||||
panic("call Next before End")
|
panic("call Next before End")
|
||||||
}
|
}
|
||||||
@@ -199,14 +210,14 @@ func (l *List) layout() Dimensions {
|
|||||||
for len(children) > 0 {
|
for len(children) > 0 {
|
||||||
sz := children[0].size
|
sz := children[0].size
|
||||||
mainSize := axisMain(l.Axis, sz)
|
mainSize := axisMain(l.Axis, sz)
|
||||||
if l.offset <= mainSize {
|
if l.Position.Offset <= mainSize {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
l.first++
|
l.Position.First++
|
||||||
l.offset -= mainSize
|
l.Position.Offset -= mainSize
|
||||||
children = children[1:]
|
children = children[1:]
|
||||||
}
|
}
|
||||||
size := -l.offset
|
size := -l.Position.Offset
|
||||||
var maxCross int
|
var maxCross int
|
||||||
for i, child := range children {
|
for i, child := range children {
|
||||||
sz := child.size
|
sz := child.size
|
||||||
@@ -220,8 +231,8 @@ func (l *List) layout() Dimensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ops := l.ctx.Ops
|
ops := l.ctx.Ops
|
||||||
pos := -l.offset
|
pos := -l.Position.Offset
|
||||||
// ScrollToEnd lists lists are end aligned.
|
// ScrollToEnd lists are end aligned.
|
||||||
if space := mainc.Max - size; l.ScrollToEnd && space > 0 {
|
if space := mainc.Max - size; l.ScrollToEnd && space > 0 {
|
||||||
pos += space
|
pos += space
|
||||||
}
|
}
|
||||||
@@ -255,12 +266,12 @@ func (l *List) layout() Dimensions {
|
|||||||
stack.Pop()
|
stack.Pop()
|
||||||
pos += childSize
|
pos += childSize
|
||||||
}
|
}
|
||||||
atStart := l.first == 0 && l.offset <= 0
|
atStart := l.Position.First == 0 && l.Position.Offset <= 0
|
||||||
atEnd := l.first+len(children) == l.len && mainc.Max >= pos
|
atEnd := l.Position.First+len(children) == l.len && mainc.Max >= pos
|
||||||
if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
|
if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
|
||||||
l.scroll.Stop()
|
l.scroll.Stop()
|
||||||
}
|
}
|
||||||
l.beforeEnd = !atEnd
|
l.Position.BeforeEnd = !atEnd
|
||||||
dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
|
dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
|
||||||
l.macro.Stop()
|
l.macro.Stop()
|
||||||
pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
|
pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
|
||||||
|
|||||||
Reference in New Issue
Block a user