mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
a8bb3c2f14
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>
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"
|
|
"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
|
|
}
|