mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
0f05231c35
Signed-off-by: Elias Naur <mail@eliasnaur.com>
207 lines
4.0 KiB
Go
207 lines
4.0 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package layout
|
|
|
|
import (
|
|
"image"
|
|
|
|
"gioui.org/ui/draw"
|
|
"gioui.org/ui/gesture"
|
|
"gioui.org/ui/pointer"
|
|
"gioui.org/ui"
|
|
)
|
|
|
|
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
|
|
}
|