mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-08 19:05:35 +00:00
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:
+38
-5
@@ -181,12 +181,25 @@ func (l *List) nextDir() iterationDir {
|
|||||||
if l.Position.Offset < 0 && l.Position.First == 0 {
|
if l.Position.Offset < 0 && l.Position.First == 0 {
|
||||||
l.Position.Offset = 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 {
|
switch {
|
||||||
case len(l.children) == l.len:
|
case len(l.children) == l.len:
|
||||||
return iterateNone
|
return iterateNone
|
||||||
case l.maxSize-l.Position.Offset < vsize:
|
case l.maxSize-l.Position.Offset-lastSize < vsize:
|
||||||
return iterateForward
|
return iterateForward
|
||||||
case l.Position.Offset < 0:
|
case l.Position.Offset-firstSize < 0:
|
||||||
return iterateBackward
|
return iterateBackward
|
||||||
}
|
}
|
||||||
return iterateNone
|
return iterateNone
|
||||||
@@ -219,9 +232,11 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
|||||||
}
|
}
|
||||||
mainMin, mainMax := l.Axis.mainConstraint(l.cs)
|
mainMin, mainMax := l.Axis.mainConstraint(l.cs)
|
||||||
children := l.children
|
children := l.children
|
||||||
// Skip invisible children
|
var first scrollChild
|
||||||
|
// Skip invisible children.
|
||||||
for len(children) > 0 {
|
for len(children) > 0 {
|
||||||
sz := children[0].size
|
child := children[0]
|
||||||
|
sz := child.size
|
||||||
mainSize := l.Axis.Convert(sz).X
|
mainSize := l.Axis.Convert(sz).X
|
||||||
if l.Position.Offset < mainSize {
|
if l.Position.Offset < mainSize {
|
||||||
// First child is partially visible.
|
// 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.First++
|
||||||
l.Position.Offset -= mainSize
|
l.Position.Offset -= mainSize
|
||||||
|
first = child
|
||||||
children = children[1:]
|
children = children[1:]
|
||||||
}
|
}
|
||||||
size := -l.Position.Offset
|
size := -l.Position.Offset
|
||||||
var maxCross int
|
var maxCross int
|
||||||
|
var last scrollChild
|
||||||
for i, child := range children {
|
for i, child := range children {
|
||||||
sz := l.Axis.Convert(child.size)
|
sz := l.Axis.Convert(child.size)
|
||||||
if c := sz.Y; c > maxCross {
|
if c := sz.Y; c > maxCross {
|
||||||
@@ -240,6 +257,9 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
|||||||
}
|
}
|
||||||
size += sz.X
|
size += sz.X
|
||||||
if size >= mainMax {
|
if size >= mainMax {
|
||||||
|
if i < len(children)-1 {
|
||||||
|
last = children[i+1]
|
||||||
|
}
|
||||||
children = children[:i+1]
|
children = children[:i+1]
|
||||||
break
|
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 {
|
if space := l.Position.OffsetLast; l.ScrollToEnd && space > 0 {
|
||||||
pos += space
|
pos += space
|
||||||
}
|
}
|
||||||
for _, child := range children {
|
layout := func(child scrollChild) {
|
||||||
sz := l.Axis.Convert(child.size)
|
sz := l.Axis.Convert(child.size)
|
||||||
var cross int
|
var cross int
|
||||||
switch l.Alignment {
|
switch l.Alignment {
|
||||||
@@ -281,6 +301,19 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
|
|||||||
cl.Pop()
|
cl.Pop()
|
||||||
pos += childSize
|
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
|
atStart := l.Position.First == 0 && l.Position.Offset <= 0
|
||||||
atEnd := l.Position.First+len(children) == l.len && mainMax >= pos
|
atEnd := l.Position.First+len(children) == l.len && mainMax >= pos
|
||||||
if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
|
if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user