Files
gio/ui/layout/list.go
T
Elias Naur 252e058766 all: serialize ops
Pros:
- Much less per-frame garbage
- Allow future preprocessing of ops while building it
- Much fewer interface calls and pointer chasing
- Allow future serialization of ops for remote rendering

Cons:
- Slightly clumsier API

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-04-27 22:19:34 +02:00

219 lines
4.1 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 {
size image.Point
block ui.OpBlock
}
type List struct {
Axis Axis
CrossAxisAlignment CrossAxisAlignment
// The distance scrolled since last call to Init.
Distance int
area gesture.Rect
scroll gesture.Scroll
scrollDir int
offset int
first int
ops *ui.Ops
cs Constraints
len int
maxSize int
children []scrollChild
elem func(w Widget)
size image.Point
}
type Interface interface {
Len() int
At(i int) Widget
}
func (l *List) Init(ops *ui.Ops, cs Constraints, len int) (int, bool) {
l.maxSize = 0
l.children = l.children[:0]
l.ops = ops
l.cs = cs
l.len = len
l.elem = nil
if l.first > len {
l.first = len
}
if len == 0 {
return 0, false
}
l.scroll.Op(ops, &l.area)
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() Dimens {
l.area.Size = l.size
return 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) {
l.first--
child := l.add(w)
mainSize := axisMain(l.Axis, child.size)
l.offset += mainSize
l.maxSize += mainSize
l.children = append([]scrollChild{child}, l.children...)
}
func (l *List) forward(w Widget) {
child := l.add(w)
mainSize := axisMain(l.Axis, child.size)
l.maxSize += mainSize
l.children = append(l.children, child)
}
func (l *List) add(w Widget) scrollChild {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
l.ops.Begin()
ui.OpLayer{}.Add(l.ops)
dims := w.Layout(l.ops, subcs)
block := l.ops.End()
return scrollChild{dims.Size, block}
}
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
}
r := image.Rectangle{
Min: axisPoint(l.Axis, min, -ui.Inf),
Max: axisPoint(l.Axis, max, ui.Inf),
}
l.ops.Begin()
draw.OpClip{Path: draw.RectPath(r)}.Add(l.ops)
ui.OpTransform{
Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))),
}.Add(l.ops)
child.block.Add(l.ops)
l.ops.End().Add(l.ops)
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
}