layout: layout one invisible child at each end of a List

A recent change added automatic scrolling to move focused widgets
into view. This change modifies List to layout an extra child at
each of its ends, to enable focus to move to them and trigger
automatic scrolling of the list.

For https://github.com/tailscale/tailscale/issues/4278.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2022-03-30 19:21:29 +02:00
parent a699fb89ac
commit 508330e818
2 changed files with 56 additions and 5 deletions
+38 -5
View File
@@ -181,12 +181,25 @@ func (l *List) nextDir() iterationDir {
if l.Position.Offset < 0 && l.Position.First == 0 {
l.Position.Offset = 0
}
// Lay out an extra (invisible) child at each end to enable focus to
// move to them, triggering automatic scroll.
firstSize, lastSize := 0, 0
if len(l.children) > 0 {
if l.Position.First > 0 {
firstChild := l.children[0]
firstSize = l.Axis.Convert(firstChild.size).X
}
if last < l.len {
lastChild := l.children[len(l.children)-1]
lastSize = l.Axis.Convert(lastChild.size).X
}
}
switch {
case len(l.children) == l.len:
return iterateNone
case l.maxSize-l.Position.Offset < vsize:
case l.maxSize-l.Position.Offset-lastSize < vsize:
return iterateForward
case l.Position.Offset < 0:
case l.Position.Offset-firstSize < 0:
return iterateBackward
}
return iterateNone
@@ -219,9 +232,11 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
}
mainMin, mainMax := l.Axis.mainConstraint(l.cs)
children := l.children
// Skip invisible children
var first scrollChild
// Skip invisible children.
for len(children) > 0 {
sz := children[0].size
child := children[0]
sz := child.size
mainSize := l.Axis.Convert(sz).X
if l.Position.Offset < mainSize {
// First child is partially visible.
@@ -229,10 +244,12 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
}
l.Position.First++
l.Position.Offset -= mainSize
first = child
children = children[1:]
}
size := -l.Position.Offset
var maxCross int
var last scrollChild
for i, child := range children {
sz := l.Axis.Convert(child.size)
if c := sz.Y; c > maxCross {
@@ -240,6 +257,9 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
}
size += sz.X
if size >= mainMax {
if i < len(children)-1 {
last = children[i+1]
}
children = children[:i+1]
break
}
@@ -251,7 +271,7 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
if space := l.Position.OffsetLast; l.ScrollToEnd && space > 0 {
pos += space
}
for _, child := range children {
layout := func(child scrollChild) {
sz := l.Axis.Convert(child.size)
var cross int
switch l.Alignment {
@@ -281,6 +301,19 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
cl.Pop()
pos += childSize
}
// Lay out leading invisible child.
if first != (scrollChild{}) {
sz := l.Axis.Convert(first.size)
pos -= sz.X
layout(first)
}
for _, child := range children {
layout(child)
}
// Lay out trailing invisible child.
if last != (scrollChild{}) {
layout(last)
}
atStart := l.Position.First == 0 && l.Position.Offset <= 0
atEnd := l.Position.First+len(children) == l.len && mainMax >= pos
if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
+18
View File
@@ -140,3 +140,21 @@ func TestListPosition(t *testing.T) {
})
}
}
func TestExtraChildren(t *testing.T) {
var l List
l.Position.First = 1
gtx := Context{
Ops: new(op.Ops),
Constraints: Exact(image.Pt(10, 10)),
}
count := 0
const all = 3
l.Layout(gtx, all, func(gtx Context, idx int) Dimensions {
count++
return Dimensions{Size: image.Pt(10, 10)}
})
if count != all {
t.Errorf("laid out %d of %d children", count, all)
}
}