mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 18:35:34 +00:00
@@ -0,0 +1,254 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package layout
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"gioui.org/ui/f32"
|
||||
"gioui.org/ui"
|
||||
)
|
||||
|
||||
type Flex struct {
|
||||
Axis Axis
|
||||
MainAxisAlignment MainAxisAlignment
|
||||
CrossAxisAlignment CrossAxisAlignment
|
||||
MainAxisSize MainAxisSize
|
||||
|
||||
cs Constraints
|
||||
|
||||
children []flexChild
|
||||
taken int
|
||||
maxCross int
|
||||
maxBaseline int
|
||||
|
||||
ccache [10]flexChild
|
||||
opCache [10]ui.Op
|
||||
}
|
||||
|
||||
type flexChild struct {
|
||||
op ui.Op
|
||||
dims Dimens
|
||||
}
|
||||
|
||||
type MainAxisSize uint8
|
||||
|
||||
type FlexMode uint8
|
||||
type MainAxisAlignment uint8
|
||||
type CrossAxisAlignment uint8
|
||||
|
||||
const (
|
||||
Loose FlexMode = iota
|
||||
Fit
|
||||
)
|
||||
|
||||
const (
|
||||
Max MainAxisSize = iota
|
||||
Min
|
||||
)
|
||||
|
||||
const (
|
||||
Start = 100 + iota
|
||||
End
|
||||
Center
|
||||
|
||||
SpaceAround MainAxisAlignment = iota
|
||||
SpaceBetween
|
||||
SpaceEvenly
|
||||
|
||||
Baseline CrossAxisAlignment = iota
|
||||
Stretch
|
||||
)
|
||||
|
||||
func (f *Flex) Init(cs Constraints) *Flex {
|
||||
f.cs = cs
|
||||
if f.children == nil {
|
||||
f.children = f.ccache[:0]
|
||||
}
|
||||
f.children = f.children[:0]
|
||||
f.maxCross = 0
|
||||
f.maxBaseline = 0
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Flex) Rigid(w Widget) *Flex {
|
||||
mainc := axisMainConstraint(f.Axis, f.cs)
|
||||
mainMax := mainc.Max
|
||||
if mainc.Max != ui.Inf {
|
||||
mainMax -= f.taken
|
||||
}
|
||||
cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs))
|
||||
op, dims := w.Layout(cs)
|
||||
f.taken += axisMain(f.Axis, dims.Size)
|
||||
if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
|
||||
f.maxCross = c
|
||||
}
|
||||
if b := dims.Baseline; b > f.maxBaseline {
|
||||
f.maxBaseline = b
|
||||
}
|
||||
f.children = append(f.children, flexChild{op, dims})
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex {
|
||||
mainc := axisMainConstraint(f.Axis, f.cs)
|
||||
var flexSize int
|
||||
if mainc.Max != ui.Inf && mainc.Max > f.taken {
|
||||
flexSize = mainc.Max - f.taken
|
||||
}
|
||||
submainc := Constraint{Max: int(float32(flexSize) * flex)}
|
||||
if mode == Fit {
|
||||
submainc.Min = submainc.Max
|
||||
}
|
||||
cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs))
|
||||
op, dims := w.Layout(cs)
|
||||
f.taken += axisMain(f.Axis, dims.Size)
|
||||
if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
|
||||
f.maxCross = c
|
||||
}
|
||||
if b := dims.Baseline; b > f.maxBaseline {
|
||||
f.maxBaseline = b
|
||||
}
|
||||
f.children = append(f.children, flexChild{op, dims})
|
||||
if idx < 0 {
|
||||
idx += len(f.children)
|
||||
}
|
||||
f.children[idx], f.children[len(f.children)-1] = f.children[len(f.children)-1], f.children[idx]
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Flex) Layout() (ui.Op, Dimens) {
|
||||
mainc := axisMainConstraint(f.Axis, f.cs)
|
||||
crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross)
|
||||
var space int
|
||||
if mainc.Max != ui.Inf && f.MainAxisSize == Max {
|
||||
if mainc.Max > f.taken {
|
||||
space = mainc.Max - f.taken
|
||||
}
|
||||
} else if mainc.Min > f.taken {
|
||||
space = mainc.Min - f.taken
|
||||
}
|
||||
var mainSize int
|
||||
var baseline int
|
||||
switch f.MainAxisAlignment {
|
||||
case Center:
|
||||
mainSize += space / 2
|
||||
case End:
|
||||
mainSize += space
|
||||
case SpaceEvenly:
|
||||
mainSize += space / (1 + len(f.children))
|
||||
case SpaceAround:
|
||||
mainSize += space / (len(f.children) * 2)
|
||||
}
|
||||
var ops ui.Ops
|
||||
if len(f.children) > len(f.opCache) {
|
||||
ops = make([]ui.Op, len(f.children))
|
||||
} else {
|
||||
ops = f.opCache[:len(f.children)]
|
||||
}
|
||||
for i, child := range f.children {
|
||||
dims := child.dims
|
||||
b := dims.Baseline
|
||||
var cross int
|
||||
switch f.CrossAxisAlignment {
|
||||
case End:
|
||||
cross = crossSize - axisCross(f.Axis, dims.Size)
|
||||
case Center:
|
||||
cross = (crossSize - axisCross(f.Axis, dims.Size)) / 2
|
||||
case Baseline:
|
||||
if f.Axis == Horizontal {
|
||||
cross = f.maxBaseline - b
|
||||
}
|
||||
}
|
||||
off := ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross)))
|
||||
ops[i] = ui.OpLayer{Op: ui.OpTransform{Transform: off, Op: child.op}}
|
||||
mainSize += axisMain(f.Axis, dims.Size)
|
||||
switch f.MainAxisAlignment {
|
||||
case SpaceEvenly:
|
||||
mainSize += space / (1 + len(f.children))
|
||||
case SpaceAround:
|
||||
mainSize += space / len(f.children)
|
||||
case SpaceBetween:
|
||||
mainSize += space / (len(f.children) - 1)
|
||||
}
|
||||
if b != dims.Size.Y {
|
||||
baseline = b
|
||||
}
|
||||
}
|
||||
switch f.MainAxisAlignment {
|
||||
case Start:
|
||||
mainSize += space
|
||||
case SpaceEvenly:
|
||||
mainSize += space / (1 + len(f.children))
|
||||
case SpaceAround:
|
||||
mainSize += space / (len(f.children) * 2)
|
||||
}
|
||||
sz := axisPoint(f.Axis, mainSize, crossSize)
|
||||
if baseline == 0 {
|
||||
baseline = sz.Y
|
||||
}
|
||||
return ops, Dimens{Size: sz, Baseline: baseline}
|
||||
}
|
||||
|
||||
func axisPoint(a Axis, main, cross int) image.Point {
|
||||
if a == Horizontal {
|
||||
return image.Point{main, cross}
|
||||
} else {
|
||||
return image.Point{cross, main}
|
||||
}
|
||||
}
|
||||
|
||||
func axisMain(a Axis, sz image.Point) int {
|
||||
if a == Horizontal {
|
||||
return sz.X
|
||||
} else {
|
||||
return sz.Y
|
||||
}
|
||||
}
|
||||
|
||||
func axisCross(a Axis, sz image.Point) int {
|
||||
if a == Horizontal {
|
||||
return sz.Y
|
||||
} else {
|
||||
return sz.X
|
||||
}
|
||||
}
|
||||
|
||||
func axisMainConstraint(a Axis, cs Constraints) Constraint {
|
||||
if a == Horizontal {
|
||||
return cs.Width
|
||||
} else {
|
||||
return cs.Height
|
||||
}
|
||||
}
|
||||
|
||||
func axisCrossConstraint(a Axis, cs Constraints) Constraint {
|
||||
if a == Horizontal {
|
||||
return cs.Height
|
||||
} else {
|
||||
return cs.Width
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flex) crossConstraintChild(cs Constraints) Constraint {
|
||||
c := axisCrossConstraint(f.Axis, cs)
|
||||
switch f.CrossAxisAlignment {
|
||||
case Stretch:
|
||||
c.Min = c.Max
|
||||
default:
|
||||
c.Min = 0
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func axisConstraints(a Axis, mainc, crossc Constraint) Constraints {
|
||||
if a == Horizontal {
|
||||
return Constraints{mainc, crossc}
|
||||
} else {
|
||||
return Constraints{crossc, mainc}
|
||||
}
|
||||
}
|
||||
|
||||
func toPointF(p image.Point) f32.Point {
|
||||
return f32.Point{X: float32(p.X), Y: float32(p.Y)}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package layout
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"gioui.org/ui"
|
||||
)
|
||||
|
||||
type Widget interface {
|
||||
Layout(cs Constraints) (ui.Op, Dimens)
|
||||
}
|
||||
|
||||
type Constraints struct {
|
||||
Width Constraint
|
||||
Height Constraint
|
||||
}
|
||||
|
||||
type Constraint struct {
|
||||
Min, Max int
|
||||
}
|
||||
|
||||
type Dimens struct {
|
||||
Size image.Point
|
||||
Baseline int
|
||||
}
|
||||
|
||||
type Axis uint8
|
||||
|
||||
type F func(cs Constraints) (ui.Op, Dimens)
|
||||
|
||||
const (
|
||||
Horizontal Axis = iota
|
||||
Vertical
|
||||
)
|
||||
|
||||
func (c Constraint) Constrain(v int) int {
|
||||
if v < c.Min {
|
||||
return c.Min
|
||||
} else if v > c.Max {
|
||||
return c.Max
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (c Constraints) Constrain(p image.Point) image.Point {
|
||||
return image.Point{X: c.Width.Constrain(p.X), Y: c.Height.Constrain(p.Y)}
|
||||
}
|
||||
|
||||
func (c Constraints) Expand() Constraints {
|
||||
return Constraints{Width: c.Width.Expand(), Height: c.Height.Expand()}
|
||||
}
|
||||
|
||||
func (c Constraint) Expand() Constraint {
|
||||
return Constraint{Min: c.Max, Max: c.Max}
|
||||
}
|
||||
func (c Constraints) Loose() Constraints {
|
||||
return Constraints{Width: c.Width.Loose(), Height: c.Height.Loose()}
|
||||
}
|
||||
|
||||
func (c Constraint) Loose() Constraint {
|
||||
return Constraint{Max: c.Max}
|
||||
}
|
||||
|
||||
// ExactConstraints returns the constraints that exactly represents the
|
||||
// given dimensions.
|
||||
func ExactConstraints(size image.Point) Constraints {
|
||||
return Constraints{
|
||||
Width: Constraint{Min: size.X, Max: size.X},
|
||||
Height: Constraint{Min: size.Y, Max: size.Y},
|
||||
}
|
||||
}
|
||||
|
||||
func (f F) Layout(cs Constraints) (ui.Op, Dimens) {
|
||||
return f(cs)
|
||||
}
|
||||
|
||||
type Margins struct {
|
||||
Top, Right, Bottom, Left ui.Value
|
||||
}
|
||||
|
||||
func Margin(c *ui.Config, m Margins, w Widget) Widget {
|
||||
return F(func(cs Constraints) (ui.Op, Dimens) {
|
||||
mcs := cs
|
||||
t, r, b, l := int(c.Pixels(m.Top)+0.5), int(c.Pixels(m.Right)+0.5), int(c.Pixels(m.Bottom)+0.5), int(c.Pixels(m.Left)+0.5)
|
||||
if mcs.Width.Max != ui.Inf {
|
||||
mcs.Width.Min -= l + r
|
||||
mcs.Width.Max -= l + r
|
||||
if mcs.Width.Min < 0 {
|
||||
mcs.Width.Min = 0
|
||||
}
|
||||
if mcs.Width.Max < mcs.Width.Min {
|
||||
mcs.Width.Max = mcs.Width.Min
|
||||
}
|
||||
}
|
||||
if mcs.Height.Max != ui.Inf {
|
||||
mcs.Height.Min -= t + b
|
||||
mcs.Height.Max -= t + b
|
||||
if mcs.Height.Min < 0 {
|
||||
mcs.Height.Min = 0
|
||||
}
|
||||
if mcs.Height.Max < mcs.Height.Min {
|
||||
mcs.Height.Max = mcs.Height.Min
|
||||
}
|
||||
}
|
||||
|
||||
op, dims := w.Layout(mcs)
|
||||
op = ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t})), Op: op}
|
||||
return op, Dimens{
|
||||
Size: cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})),
|
||||
Baseline: dims.Baseline + t,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func EqualMargins(v ui.Value) Margins {
|
||||
return Margins{Top: v, Right: v, Bottom: v, Left: v}
|
||||
}
|
||||
|
||||
func isInf(v ui.Value) bool {
|
||||
return math.IsInf(float64(v.V), 1)
|
||||
}
|
||||
|
||||
func Capped(c *ui.Config, maxWidth, maxHeight ui.Value, wt Widget) Widget {
|
||||
return F(func(cs Constraints) (ui.Op, Dimens) {
|
||||
if !isInf(maxWidth) {
|
||||
mw := int(c.Pixels(maxWidth) + .5)
|
||||
if mw < cs.Width.Min {
|
||||
mw = cs.Width.Min
|
||||
}
|
||||
if mw < cs.Width.Max {
|
||||
cs.Width.Max = mw
|
||||
}
|
||||
}
|
||||
if !isInf(maxHeight) {
|
||||
mh := int(c.Pixels(maxHeight) + 0.5)
|
||||
if mh < cs.Height.Min {
|
||||
mh = cs.Height.Min
|
||||
}
|
||||
if mh < cs.Height.Max {
|
||||
cs.Height.Max = mh
|
||||
}
|
||||
}
|
||||
return wt.Layout(cs)
|
||||
})
|
||||
}
|
||||
|
||||
func Sized(c *ui.Config, width, height ui.Value, wt Widget) Widget {
|
||||
return F(func(cs Constraints) (ui.Op, Dimens) {
|
||||
if h := int(c.Pixels(height) + 0.5); h != 0 {
|
||||
if cs.Height.Min < h {
|
||||
cs.Height.Min = h
|
||||
}
|
||||
if h < cs.Height.Max {
|
||||
cs.Height.Max = h
|
||||
}
|
||||
}
|
||||
if w := int(c.Pixels(width) + .5); w != 0 {
|
||||
if cs.Width.Min < w {
|
||||
cs.Width.Min = w
|
||||
}
|
||||
if w < cs.Width.Max {
|
||||
cs.Width.Max = w
|
||||
}
|
||||
}
|
||||
return wt.Layout(cs)
|
||||
})
|
||||
}
|
||||
|
||||
func Expand(w Widget) Widget {
|
||||
return F(func(cs Constraints) (ui.Op, Dimens) {
|
||||
if cs.Height.Max != ui.Inf {
|
||||
cs.Height.Min = cs.Height.Max
|
||||
}
|
||||
if cs.Width.Max != ui.Inf {
|
||||
cs.Width.Min = cs.Width.Max
|
||||
}
|
||||
return w.Layout(cs)
|
||||
})
|
||||
}
|
||||
|
||||
func Align(alignment Direction, w Widget) Widget {
|
||||
return F(func(cs Constraints) (ui.Op, Dimens) {
|
||||
op, dims := w.Layout(cs.Loose())
|
||||
sz := dims.Size
|
||||
if cs.Width.Max != ui.Inf {
|
||||
sz.X = cs.Width.Max
|
||||
}
|
||||
if cs.Height.Max != ui.Inf {
|
||||
sz.Y = cs.Height.Max
|
||||
}
|
||||
var p image.Point
|
||||
switch alignment {
|
||||
case N, S, Center:
|
||||
p.X = (sz.X - dims.Size.X) / 2
|
||||
case NE, SE, E:
|
||||
p.X = sz.X - dims.Size.X
|
||||
}
|
||||
switch alignment {
|
||||
case W, Center, E:
|
||||
p.Y = (sz.Y - dims.Size.Y) / 2
|
||||
case SW, S, SE:
|
||||
p.Y = sz.Y - dims.Size.Y
|
||||
}
|
||||
op = ui.OpTransform{Transform: ui.Offset(toPointF(p)), Op: op}
|
||||
return op, Dimens{
|
||||
Size: sz,
|
||||
Baseline: dims.Baseline,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package layout
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"gioui.org/ui"
|
||||
)
|
||||
|
||||
type Stack struct {
|
||||
Alignment Direction
|
||||
|
||||
cs Constraints
|
||||
children []stackChild
|
||||
maxSZ image.Point
|
||||
baseline int
|
||||
|
||||
ccache [10]stackChild
|
||||
opCache [10]ui.Op
|
||||
}
|
||||
|
||||
type stackChild struct {
|
||||
op ui.Op
|
||||
dims Dimens
|
||||
}
|
||||
|
||||
type Direction uint8
|
||||
|
||||
const (
|
||||
NW Direction = iota
|
||||
N
|
||||
NE
|
||||
E
|
||||
SE
|
||||
S
|
||||
SW
|
||||
W
|
||||
)
|
||||
|
||||
func (s *Stack) Init(cs Constraints) *Stack {
|
||||
if s.children == nil {
|
||||
s.children = s.ccache[:0]
|
||||
}
|
||||
s.children = s.children[:0]
|
||||
s.maxSZ = image.Point{}
|
||||
s.baseline = 0
|
||||
s.cs = cs
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Stack) Rigid(w Widget) *Stack {
|
||||
op, dims := w.Layout(s.cs)
|
||||
if w := dims.Size.X; w > s.maxSZ.X {
|
||||
s.maxSZ.X = w
|
||||
}
|
||||
if h := dims.Size.Y; h > s.maxSZ.Y {
|
||||
s.maxSZ.Y = h
|
||||
}
|
||||
s.add(op, dims)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Stack) Expand(idx int, w Widget) *Stack {
|
||||
cs := Constraints{
|
||||
Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
|
||||
Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
|
||||
}
|
||||
s.add(w.Layout(cs))
|
||||
if idx < 0 {
|
||||
idx += len(s.children)
|
||||
}
|
||||
s.children[idx], s.children[len(s.children)-1] = s.children[len(s.children)-1], s.children[idx]
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Stack) add(op ui.Op, dims Dimens) {
|
||||
s.children = append(s.children, stackChild{op, dims})
|
||||
if s.baseline == 0 {
|
||||
if b := dims.Baseline; b != dims.Size.Y {
|
||||
s.baseline = b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stack) Layout() (ui.Op, Dimens) {
|
||||
var ops ui.Ops
|
||||
if len(s.children) > len(s.opCache) {
|
||||
ops = make([]ui.Op, len(s.children))
|
||||
} else {
|
||||
ops = s.opCache[:len(s.children)]
|
||||
}
|
||||
for i, ch := range s.children {
|
||||
sz := ch.dims.Size
|
||||
var p image.Point
|
||||
switch s.Alignment {
|
||||
case N, S, Center:
|
||||
p.X = (s.maxSZ.X - sz.X) / 2
|
||||
case NE, SE, E:
|
||||
p.X = s.maxSZ.X - sz.X
|
||||
}
|
||||
switch s.Alignment {
|
||||
case W, Center, E:
|
||||
p.Y = (s.maxSZ.Y - sz.Y) / 2
|
||||
case SW, S, SE:
|
||||
p.Y = s.maxSZ.Y - sz.Y
|
||||
}
|
||||
ops[i] = ui.OpLayer{Op: ui.OpTransform{Transform: ui.Offset(toPointF(p)), Op: ch.op}}
|
||||
}
|
||||
b := s.baseline
|
||||
if b == 0 {
|
||||
b = s.maxSZ.Y
|
||||
}
|
||||
return ops, Dimens{
|
||||
Size: s.maxSZ,
|
||||
Baseline: b,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user