Files
gio/ui/layout/list.go
T
Elias Naur 94a913a371 ui: move macro recording from Ops to MacroOp
Move the Record and Stop methods from Ops to MacroOp itself.

Before this change, Ops.Stop stopped the recording of the most
recent macro, which could be a different macro than intended.
After this change, there is no such confusion.

As a bonus, the Ops API becomes less cluttered.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-07-16 13:21:51 +02:00

248 lines
4.8 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/input"
"gioui.org/ui/pointer"
)
type scrollChild struct {
size image.Point
macro ui.MacroOp
}
type List struct {
Config ui.Config
Inputs input.Queue
Axis Axis
Invert bool
CrossAxisAlignment CrossAxisAlignment
// The distance scrolled since last call to Init.
Distance int
macro ui.MacroOp
child ui.MacroOp
ops *ui.Ops
scroll gesture.Scroll
scrollDir int
offset int
first int
cs Constraints
len int
maxSize int
children []scrollChild
dir iterationDir
// Iterator state.
index int
more bool
}
type iterationDir uint8
const (
iterateNone iterationDir = iota
iterateForward
iterateBackward
)
// Init prepares the list for iterating through its elements with Next.
func (l *List) Init(ops *ui.Ops, cs Constraints, len int) {
if l.more {
panic("unfinished element")
}
l.update()
l.ops = ops
l.dir = iterateNone
l.maxSize = 0
l.children = l.children[:0]
l.cs = cs
l.len = len
l.more = true
if l.first > len {
l.first = len
}
l.macro.Record(ops)
l.Next()
}
func (l *List) Dragging() bool {
return l.scroll.Dragging()
}
func (l *List) update() {
l.Distance = 0
d := l.scroll.Scroll(l.Config, l.Inputs, gesture.Axis(l.Axis))
if l.Invert {
d = -d
}
l.scrollDir = d
l.Distance += d
l.offset += d
}
// Next advances the list to the next element.
func (l *List) Next() {
if !l.more {
panic("end of list reached")
}
i, more := l.next()
l.more = more
if !more {
return
}
if l.Invert {
i = l.len - 1 - i
}
l.index = i
l.child.Record(l.ops)
}
// Index is the current element index.
func (l *List) Index() int {
return l.index
}
// Constraints is the constraints for the current element.
func (l *List) Constraints() Constraints {
return axisConstraints(l.Axis, Constraint{Max: ui.Inf}, axisCrossConstraint(l.Axis, l.cs))
}
func (l *List) More() bool {
return l.more
}
func (l *List) next() (int, bool) {
mainc := axisMainConstraint(l.Axis, l.cs)
if l.offset <= 0 {
if l.first > 0 {
l.dir = iterateBackward
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.dir = iterateForward
return i, true
}
missing := mainc.Max - (l.maxSize - l.offset)
if missing > l.offset {
missing = l.offset
}
l.offset -= missing
}
return 0, false
}
// Elem completes an element.
func (l *List) Elem(dims Dimens) {
l.child.Stop()
child := scrollChild{dims.Size, l.child}
switch l.dir {
case iterateForward:
mainSize := axisMain(l.Axis, child.size)
l.maxSize += mainSize
l.children = append(l.children, child)
case iterateBackward:
l.first--
mainSize := axisMain(l.Axis, child.size)
l.offset += mainSize
l.maxSize += mainSize
l.children = append([]scrollChild{child}, l.children...)
default:
panic("call Next before Elem")
}
l.dir = iterateNone
}
func (l *List) Layout() Dimens {
if l.more {
panic("unfinished element")
}
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
}
}
ops := l.ops
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
}
childSize := axisMain(l.Axis, sz)
max := childSize + pos
if max > mainc.Max {
max = mainc.Max
}
min := pos
if min < 0 {
min = 0
}
transPos := pos
if l.Invert {
transPos = mainc.Max - transPos - childSize
min, max = mainc.Max-max, mainc.Max-min
}
r := image.Rectangle{
Min: axisPoint(l.Axis, min, -ui.Inf),
Max: axisPoint(l.Axis, max, ui.Inf),
}
ui.PushOp{}.Add(ops)
draw.RectClip(r).Add(ops)
ui.TransformOp{
Transform: ui.Offset(toPointF(axisPoint(l.Axis, transPos, cross))),
}.Add(ops)
child.macro.Add(ops)
ui.PopOp{}.Add(ops)
pos += childSize
}
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()
}
dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
l.macro.Stop()
pointer.RectAreaOp{Size: dims}.Add(ops)
l.scroll.Add(ops)
l.macro.Add(ops)
return Dimens{Size: dims}
}