Files
gio/ui/layout/list.go
T
Elias Naur a8bb3c2f14 all: make pointer.Area an interface
With an interface instead of anonymous functions, amending an
area's parameters can be done even after adding it to an OpHandler.

This will be useful when we switch to serialized op lists.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-04-25 10:02:14 +02:00

207 lines
4.0 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
"gioui.org/ui/draw"
"gioui.org/ui/gesture"
"gioui.org/ui/pointer"
)
type scrollChild struct {
op ui.Op
size image.Point
}
type List struct {
Axis Axis
CrossAxisAlignment CrossAxisAlignment
// The distance scrolled since last call to Init.
Distance int
scroll gesture.Scroll
scrollDir int
offset int
first int
cs Constraints
len int
maxSize int
children []scrollChild
elem func(w Widget)
size image.Point
ops ui.Ops
}
type Interface interface {
Len() int
At(i int) Widget
}
func (l *List) Init(cs Constraints, len int) (int, bool) {
l.maxSize = 0
l.children = l.children[:0]
l.cs = cs
l.len = len
l.elem = nil
if l.first > len {
l.first = len
}
l.ops = l.ops[:0]
if len == 0 {
return 0, false
}
return l.Index()
}
func (l *List) Dragging() bool {
return l.scroll.Dragging()
}
func (l *List) Scroll(c *ui.Config, q pointer.Events) {
l.Distance = 0
d := l.scroll.Scroll(c, q, gesture.Axis(l.Axis))
l.scrollDir = d
l.Distance += d
l.offset += d
}
func (l *List) Index() (int, bool) {
i, ok := l.next()
if !ok {
l.draw()
}
return i, ok
}
func (l *List) Layout() (ui.Op, Dimens) {
ops := append(ui.Ops{l.scroll.Op(&gesture.Rect{l.size})}, l.ops...)
return ops, Dimens{Size: l.size}
}
func (l *List) next() (int, bool) {
mainc := axisMainConstraint(l.Axis, l.cs)
if l.offset <= 0 {
if l.first > 0 {
l.elem = l.backward
return l.first - 1, true
}
l.offset = 0
}
if l.maxSize-l.offset < mainc.Max {
i := l.first + len(l.children)
if i < l.len {
l.elem = l.forward
return i, true
}
missing := mainc.Max - (l.maxSize - l.offset)
if missing > l.offset {
missing = l.offset
}
l.offset -= missing
}
return 0, false
}
func (l *List) Elem(w Widget) {
l.elem(w)
}
func (l *List) backward(w Widget) {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
l.first--
op, dims := w.Layout(subcs)
mainSize := axisMain(l.Axis, dims.Size)
l.offset += mainSize
l.maxSize += mainSize
l.children = append([]scrollChild{{op, dims.Size}}, l.children...)
}
func (l *List) forward(w Widget) {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
op, dims := w.Layout(subcs)
mainSize := axisMain(l.Axis, dims.Size)
l.maxSize += mainSize
l.children = append(l.children, scrollChild{op, dims.Size})
}
func (l *List) draw() {
mainc := axisMainConstraint(l.Axis, l.cs)
for len(l.children) > 0 {
sz := l.children[0].size
mainSize := axisMain(l.Axis, sz)
if l.offset <= mainSize {
break
}
l.first++
l.offset -= mainSize
l.children = l.children[1:]
}
size := -l.offset
var maxCross int
for i, child := range l.children {
sz := child.size
if c := axisCross(l.Axis, sz); c > maxCross {
maxCross = c
}
size += axisMain(l.Axis, sz)
if size >= mainc.Max {
l.children = l.children[:i+1]
break
}
}
pos := -l.offset
for _, child := range l.children {
sz := child.size
var cross int
switch l.CrossAxisAlignment {
case End:
cross = maxCross - axisCross(l.Axis, sz)
case Center:
cross = (maxCross - axisCross(l.Axis, sz)) / 2
}
max := axisMain(l.Axis, sz) + pos
if max > mainc.Max {
max = mainc.Max
}
min := pos
if min < 0 {
min = 0
}
op := draw.ClipRect(
image.Rectangle{
Min: axisPoint(l.Axis, min, -ui.Inf),
Max: axisPoint(l.Axis, max, ui.Inf),
},
ui.OpTransform{Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))), Op: child.op},
)
l.ops = append(l.ops, ui.OpLayer{Op: op})
pos += axisMain(l.Axis, sz)
}
atStart := l.first == 0 && l.offset <= 0
atEnd := l.first+len(l.children) == l.len && mainc.Max >= pos
if atStart && l.scrollDir < 0 || atEnd && l.scrollDir > 0 {
l.scroll.Stop()
}
l.size = axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
}
func (l *List) crossConstraintChild(cs Constraints) Constraint {
c := axisCrossConstraint(l.Axis, cs)
switch l.CrossAxisAlignment {
case Stretch:
c.Min = c.Max
default:
c.Min = 0
}
return c
}