all: rename the gioui.org/ui module to gioui.org

The "ui" is redundant and stutters.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-09-30 12:27:55 +02:00
parent ce74bc0cba
commit 22cd88df9f
102 changed files with 93 additions and 93 deletions
+51
View File
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package layout implements layouts common to GUI programs.
Constraints and dimensions
Constraints and dimensions form the interface between layouts and
interface child elements. This package operates on Widgets, functions
that compute Dimensions from a a set of constraints for acceptable
widths and heights. Both the constraints and dimensions are maintained
in an implicit Context to keep the Widget declaration short.
For example, to add space above a widget:
gtx := &layout.Context{...}
gtx.Reset(...)
// Configure a top inset.
inset := layout.Inset{Top: ui.Dp(8), ...}
// Use the inset to lay out a widget.
inset.Layout(gtx, func() {
// Lay out widget and determine its size given the constraints.
...
dims := layout.Dimensions{...}
gtx.Dimensions = dims
})
Note that the example does not generate any garbage even though the
Inset is transient. Layouts that don't accept user input are designed
to not escape to the heap during their use.
Layout operations are recursive: a child in a layout operation can
itself be another layout. That way, complex user interfaces can
be created from a few generic layouts.
This example both aligns and insets a child:
inset := layout.Inset{...}
inset.Layout(gtx, func() {
align := layout.Align(...)
align.Layout(gtx, func() {
widget.Layout(gtx, ...)
})
})
More complex layouts such as Stack and Flex lay out multiple children,
and stateful layouts such as List accept user input.
*/
package layout
+298
View File
@@ -0,0 +1,298 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/f32"
"gioui.org/ui"
)
// Flex lays out child elements along an axis,
// according to alignment and weights.
type Flex struct {
// Axis is the main axis, either Horizontal or Vertical.
Axis Axis
// Spacing controls the distribution of space left after
// layout.
Spacing Spacing
// Alignment is the alignment in the cross axis.
Alignment Alignment
ctx *Context
macro ui.MacroOp
mode flexMode
size int
rigidSize int
// fraction is the rounding error from a Flexible weighting.
fraction float32
maxCross int
maxBaseline int
}
// FlexChild is the layout result of a call End.
type FlexChild struct {
macro ui.MacroOp
dims Dimensions
}
// Spacing determine the spacing mode for a Flex.
type Spacing uint8
type flexMode uint8
const (
// SpaceEnd leaves space at the end.
SpaceEnd Spacing = iota
// SpaceStart leaves space at the start.
SpaceStart
// SpaceSides shares space between the start and end.
SpaceSides
// SpaceAround distributes space evenly between children,
// with half as much space at the start and end.
SpaceAround
// SpaceBetween distributes space evenly between children,
// leaving no space at the start and end.
SpaceBetween
// SpaceEvenly distributes space evenly between children and
// at the start and end.
SpaceEvenly
)
const (
modeNone flexMode = iota
modeBegun
modeRigid
modeFlex
)
// Init must be called before Rigid or Flexible.
func (f *Flex) Init(gtx *Context) *Flex {
if f.mode > modeBegun {
panic("must End the current child before calling Init again")
}
f.mode = modeBegun
f.ctx = gtx
f.size = 0
f.rigidSize = 0
f.maxCross = 0
f.maxBaseline = 0
return f
}
func (f *Flex) begin(mode flexMode) {
switch {
case f.mode == modeNone:
panic("must Init before adding a child")
case f.mode > modeBegun:
panic("must End before adding a child")
}
f.mode = mode
f.macro.Record(f.ctx.Ops)
}
// Rigid lays out a widget with the main axis constrained to the range
// from 0 to the remaining space.
func (f *Flex) Rigid(w Widget) FlexChild {
f.begin(modeRigid)
cs := f.ctx.Constraints
mainc := axisMainConstraint(f.Axis, cs)
mainMax := mainc.Max - f.size
if mainMax < 0 {
mainMax = 0
}
cs = axisConstraints(f.Axis, Constraint{Max: mainMax}, axisCrossConstraint(f.Axis, cs))
dims := f.ctx.Layout(cs, w)
return f.end(dims)
}
// Flexible is like Rigid, where the main axis size is also constrained to a
// fraction of the space not taken up by Rigid children.
func (f *Flex) Flexible(weight float32, w Widget) FlexChild {
f.begin(modeFlex)
cs := f.ctx.Constraints
mainc := axisMainConstraint(f.Axis, cs)
var flexSize int
if mainc.Max > f.size {
flexSize = mainc.Max - f.rigidSize
// Apply weight and add any leftover fraction from a
// previous Flexible.
size := float32(flexSize)*weight + f.fraction
flexSize = int(size + .5)
f.fraction = size - float32(flexSize)
if max := mainc.Max - f.size; flexSize > max {
flexSize = max
}
}
submainc := Constraint{Max: flexSize}
cs = axisConstraints(f.Axis, submainc, axisCrossConstraint(f.Axis, cs))
dims := f.ctx.Layout(cs, w)
return f.end(dims)
}
// End a child by specifying its dimensions. Pass the returned layout result
// to Layout.
func (f *Flex) end(dims Dimensions) FlexChild {
if f.mode <= modeBegun {
panic("End called without an active child")
}
f.macro.Stop()
sz := axisMain(f.Axis, dims.Size)
f.size += sz
if f.mode == modeRigid {
f.rigidSize += sz
}
f.mode = modeBegun
if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
f.maxCross = c
}
if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b
}
return FlexChild{f.macro, dims}
}
// Layout a list of children. The order of the children determines their laid
// out order.
func (f *Flex) Layout(children ...FlexChild) {
cs := f.ctx.Constraints
mainc := axisMainConstraint(f.Axis, cs)
crossSize := axisCrossConstraint(f.Axis, cs).Constrain(f.maxCross)
var space int
if mainc.Min > f.size {
space = mainc.Min - f.size
}
var mainSize int
var baseline int
switch f.Spacing {
case SpaceSides:
mainSize += space / 2
case SpaceStart:
mainSize += space
case SpaceEvenly:
mainSize += space / (1 + len(children))
case SpaceAround:
mainSize += space / (len(children) * 2)
}
for i, child := range children {
dims := child.dims
b := dims.Baseline
var cross int
switch f.Alignment {
case End:
cross = crossSize - axisCross(f.Axis, dims.Size)
case Middle:
cross = (crossSize - axisCross(f.Axis, dims.Size)) / 2
case Baseline:
if f.Axis == Horizontal {
cross = f.maxBaseline - b
}
}
var stack ui.StackOp
stack.Push(f.ctx.Ops)
ui.TransformOp{}.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))).Add(f.ctx.Ops)
child.macro.Add(f.ctx.Ops)
stack.Pop()
mainSize += axisMain(f.Axis, dims.Size)
if i < len(children)-1 {
switch f.Spacing {
case SpaceEvenly:
mainSize += space / (1 + len(children))
case SpaceAround:
mainSize += space / len(children)
case SpaceBetween:
mainSize += space / (len(children) - 1)
}
}
if b != dims.Size.Y {
baseline = b
}
}
switch f.Spacing {
case SpaceSides:
mainSize += space / 2
case SpaceEnd:
mainSize += space
case SpaceEvenly:
mainSize += space / (1 + len(children))
case SpaceAround:
mainSize += space / (len(children) * 2)
}
sz := axisPoint(f.Axis, mainSize, crossSize)
if baseline == 0 {
baseline = sz.Y
}
f.ctx.Dimensions = Dimensions{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 axisConstraints(a Axis, mainc, crossc Constraint) Constraints {
if a == Horizontal {
return Constraints{Width: mainc, Height: crossc}
} else {
return Constraints{Width: crossc, Height: mainc}
}
}
func toPointF(p image.Point) f32.Point {
return f32.Point{X: float32(p.X), Y: float32(p.Y)}
}
func (s Spacing) String() string {
switch s {
case SpaceEnd:
return "SpaceEnd"
case SpaceStart:
return "SpaceStart"
case SpaceSides:
return "SpaceSides"
case SpaceAround:
return "SpaceAround"
case SpaceBetween:
return "SpaceAround"
case SpaceEvenly:
return "SpaceEvenly"
default:
panic("unreachable")
}
}
+266
View File
@@ -0,0 +1,266 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
)
// Constraints represent a set of acceptable ranges for
// a widget's width and height.
type Constraints struct {
Width Constraint
Height Constraint
}
// Constraint is a range of acceptable sizes in a single
// dimension.
type Constraint struct {
Min, Max int
}
// Dimensions are the resolved size and baseline for a widget.
type Dimensions struct {
Size image.Point
Baseline int
}
// Axis is the Horizontal or Vertical direction.
type Axis uint8
// Alignment is the mutual alignment of a list of widgets.
type Alignment uint8
// Direction is the alignment of widgets relative to a containing
// space.
type Direction uint8
// Widget is a function scope for drawing, processing events and
// computing dimensions for a user interface element.
type Widget func()
// Context carry the state needed by almost all layouts and widgets.
type Context struct {
// Constraints track the constraints for the active widget or
// layout.
Constraints Constraints
// Dimensions track the result of the most recent layout
// operation.
Dimensions Dimensions
ui.Config
ui.Queue
*ui.Ops
}
const (
Start Alignment = iota
End
Middle
Baseline
)
const (
NW Direction = iota
N
NE
E
SE
S
SW
W
Center
)
const (
Horizontal Axis = iota
Vertical
)
// Layout a widget with a set of constraints and return its
// dimensions. The previous constraints are restored after layout.
func (s *Context) Layout(cs Constraints, w Widget) Dimensions {
saved := s.Constraints
s.Constraints = cs
s.Dimensions = Dimensions{}
w()
s.Constraints = saved
return s.Dimensions
}
// Reset the context.
func (c *Context) Reset(cfg ui.Config, cs Constraints) {
c.Constraints = cs
c.Dimensions = Dimensions{}
c.Config = cfg
if c.Ops == nil {
c.Ops = new(ui.Ops)
}
c.Ops.Reset()
}
// Constrain a value to the range [Min; Max].
func (c Constraint) Constrain(v int) int {
if v < c.Min {
return c.Min
} else if v > c.Max {
return c.Max
}
return v
}
// Constrain a size to the Width and Height ranges.
func (c Constraints) Constrain(size image.Point) image.Point {
return image.Point{X: c.Width.Constrain(size.X), Y: c.Height.Constrain(size.Y)}
}
// RigidConstraints returns the constraints that can only be
// satisfied by the given dimensions.
func RigidConstraints(size image.Point) Constraints {
return Constraints{
Width: Constraint{Min: size.X, Max: size.X},
Height: Constraint{Min: size.Y, Max: size.Y},
}
}
// Inset adds space around a widget.
type Inset struct {
Top, Right, Bottom, Left ui.Value
}
// Align aligns a widget in the available space.
type Align Direction
// Layout a widget.
func (in Inset) Layout(gtx *Context, w Widget) {
top := gtx.Px(in.Top)
right := gtx.Px(in.Right)
bottom := gtx.Px(in.Bottom)
left := gtx.Px(in.Left)
mcs := gtx.Constraints
mcs.Width.Min -= left + right
mcs.Width.Max -= left + right
if mcs.Width.Min < 0 {
mcs.Width.Min = 0
}
if mcs.Width.Max < mcs.Width.Min {
mcs.Width.Max = mcs.Width.Min
}
mcs.Height.Min -= top + bottom
mcs.Height.Max -= top + bottom
if mcs.Height.Min < 0 {
mcs.Height.Min = 0
}
if mcs.Height.Max < mcs.Height.Min {
mcs.Height.Max = mcs.Height.Min
}
var stack ui.StackOp
stack.Push(gtx.Ops)
ui.TransformOp{}.Offset(toPointF(image.Point{X: left, Y: top})).Add(gtx.Ops)
dims := gtx.Layout(mcs, w)
stack.Pop()
gtx.Dimensions = Dimensions{
Size: gtx.Constraints.Constrain(dims.Size.Add(image.Point{X: right + left, Y: top + bottom})),
Baseline: dims.Baseline + top,
}
}
// UniformInset returns an Inset with a single inset applied to all
// edges.
func UniformInset(v ui.Value) Inset {
return Inset{Top: v, Right: v, Bottom: v, Left: v}
}
// Layout a widget.
func (a Align) Layout(gtx *Context, w Widget) {
var macro ui.MacroOp
macro.Record(gtx.Ops)
cs := gtx.Constraints
mcs := cs
mcs.Width.Min = 0
mcs.Height.Min = 0
dims := gtx.Layout(mcs, w)
macro.Stop()
sz := dims.Size
if sz.X < cs.Width.Min {
sz.X = cs.Width.Min
}
if sz.Y < cs.Height.Min {
sz.Y = cs.Height.Min
}
var p image.Point
switch Direction(a) {
case N, S, Center:
p.X = (sz.X - dims.Size.X) / 2
case NE, SE, E:
p.X = sz.X - dims.Size.X
}
switch Direction(a) {
case W, Center, E:
p.Y = (sz.Y - dims.Size.Y) / 2
case SW, S, SE:
p.Y = sz.Y - dims.Size.Y
}
var stack ui.StackOp
stack.Push(gtx.Ops)
ui.TransformOp{}.Offset(toPointF(p)).Add(gtx.Ops)
macro.Add(gtx.Ops)
stack.Pop()
gtx.Dimensions = Dimensions{
Size: sz,
Baseline: dims.Baseline,
}
}
func (a Alignment) String() string {
switch a {
case Start:
return "Start"
case End:
return "End"
case Middle:
return "Middle"
case Baseline:
return "Baseline"
default:
panic("unreachable")
}
}
func (a Axis) String() string {
switch a {
case Horizontal:
return "Horizontal"
case Vertical:
return "Vertical"
default:
panic("unreachable")
}
}
func (d Direction) String() string {
switch d {
case NW:
return "NW"
case N:
return "N"
case NE:
return "NE"
case E:
return "E"
case SE:
return "SE"
case S:
return "S"
case SW:
return "SW"
case W:
return "W"
case Center:
return "Center"
default:
panic("unreachable")
}
}
+154
View File
@@ -0,0 +1,154 @@
package layout_test
import (
"fmt"
"image"
"time"
"gioui.org/layout"
"gioui.org/ui"
)
type queue struct{}
type config struct{}
var q queue
var cfg = new(config)
func ExampleInset() {
gtx := &layout.Context{Queue: q}
// Loose constraints with no minimal size.
var cs layout.Constraints
cs.Width.Max = 100
cs.Height.Max = 100
gtx.Reset(cfg, cs)
// Inset all edges by 10.
inset := layout.UniformInset(ui.Dp(10))
inset.Layout(gtx, func() {
// Lay out a 50x50 sized widget.
layoutWidget(gtx, 50, 50)
fmt.Println(gtx.Dimensions.Size)
})
fmt.Println(gtx.Dimensions.Size)
// Output:
// (50,50)
// (70,70)
}
func ExampleAlign() {
gtx := &layout.Context{Queue: q}
// Rigid constraints with both minimum and maximum set.
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100})
gtx.Reset(cfg, cs)
align := layout.Align(layout.Center)
align.Layout(gtx, func() {
// Lay out a 50x50 sized widget.
layoutWidget(gtx, 50, 50)
fmt.Println(gtx.Dimensions.Size)
})
fmt.Println(gtx.Dimensions.Size)
// Output:
// (50,50)
// (100,100)
}
func ExampleFlex() {
gtx := &layout.Context{Queue: q}
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100})
gtx.Reset(cfg, cs)
flex := layout.Flex{}
flex.Init(gtx)
// Rigid 10x10 widget.
child1 := flex.Rigid(func() {
fmt.Printf("Rigid: %v\n", gtx.Constraints.Width)
layoutWidget(gtx, 10, 10)
})
// Child with 50% space allowance.
child2 := flex.Flexible(0.5, func() {
fmt.Printf("50%%: %v\n", gtx.Constraints.Width)
layoutWidget(gtx, 10, 10)
})
flex.Layout(child1, child2)
// Output:
// Rigid: {0 100}
// 50%: {0 45}
}
func ExampleStack() {
gtx := &layout.Context{Queue: q}
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100})
gtx.Reset(cfg, cs)
stack := layout.Stack{}
stack.Init(gtx)
// Rigid 50x50 widget.
child1 := stack.Rigid(func() {
layoutWidget(gtx, 50, 50)
})
// Force widget to the same size as the first.
child2 := stack.Expand(func() {
fmt.Printf("Expand: %v\n", gtx.Constraints)
layoutWidget(gtx, 10, 10)
})
stack.Layout(child1, child2)
// Output:
// Expand: {{50 50} {50 50}}
}
func ExampleList() {
gtx := &layout.Context{Queue: q}
cs := layout.RigidConstraints(image.Point{X: 100, Y: 100})
gtx.Reset(cfg, cs)
// The list is 1e6 elements, but only 5 fit the constraints.
const listLen = 1e6
var list layout.List
count := 0
list.Layout(gtx, listLen, func(i int) {
count++
layoutWidget(gtx, 20, 20)
})
fmt.Println(count)
// Output:
// 5
}
func layoutWidget(ctx *layout.Context, width, height int) {
ctx.Dimensions = layout.Dimensions{
Size: image.Point{
X: width,
Y: height,
},
}
}
func (config) Now() time.Time {
return time.Now()
}
func (config) Px(v ui.Value) int {
return int(v.V + .5)
}
func (queue) Events(k ui.Key) []ui.Event {
return nil
}
+269
View File
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
"gioui.org/gesture"
"gioui.org/paint"
"gioui.org/pointer"
)
type scrollChild struct {
size image.Point
macro ui.MacroOp
}
// List displays a subsection of a potentially infinitely
// large underlying list. List accepts user input to scroll
// the subsection.
type List struct {
Axis Axis
// ScrollToEnd instructs the list to stay scrolled to the far end position
// once reahed. A List with ScrollToEnd enabled also align its content to
// the end.
ScrollToEnd bool
// Alignment is the cross axis alignment of list elements.
Alignment Alignment
// beforeEnd tracks whether the List position is before
// the very end.
beforeEnd bool
ctx *Context
macro ui.MacroOp
child ui.MacroOp
scroll gesture.Scroll
scrollDelta int
// first is the index of the first visible child.
first int
// offset is the signed distance from the top edge
// to the child with index first.
offset int
len int
// maxSize is the total size of visible children.
maxSize int
children []scrollChild
dir iterationDir
}
// ListElement is a function that computes the dimensions of
// a list element.
type ListElement func(index int)
type iterationDir uint8
const (
iterateNone iterationDir = iota
iterateForward
iterateBackward
)
const inf = 1e6
// init prepares the list for iterating through its children with next.
func (l *List) init(gtx *Context, len int) {
if l.more() {
panic("unfinished child")
}
l.ctx = gtx
l.maxSize = 0
l.children = l.children[:0]
l.len = len
l.update()
if l.scrollToEnd() {
l.offset = 0
l.first = len
}
if l.first > len {
l.offset = 0
l.first = len
}
l.macro.Record(gtx.Ops)
l.next()
}
// Layout the List and return its dimensions.
func (l *List) Layout(gtx *Context, len int, w ListElement) {
for l.init(gtx, len); l.more(); l.next() {
cs := axisConstraints(l.Axis, Constraint{Max: inf}, axisCrossConstraint(l.Axis, l.ctx.Constraints))
i := l.index()
l.end(gtx.Layout(cs, func() {
w(i)
}))
}
gtx.Dimensions = l.layout()
}
func (l *List) scrollToEnd() bool {
return l.ScrollToEnd && !l.beforeEnd
}
// Dragging reports whether the List is being dragged.
func (l *List) Dragging() bool {
return l.scroll.State() == gesture.StateDragging
}
func (l *List) update() {
d := l.scroll.Scroll(l.ctx.Config, l.ctx.Queue, gesture.Axis(l.Axis))
l.scrollDelta = d
l.offset += d
}
// next advances to the next child.
func (l *List) next() {
l.dir = l.nextDir()
// The user scroll offset is applied after scrolling to
// list end.
if l.scrollToEnd() && !l.more() && l.scrollDelta < 0 {
l.beforeEnd = true
l.offset += l.scrollDelta
l.dir = l.nextDir()
}
if l.more() {
l.child.Record(l.ctx.Ops)
}
}
// index is current child's position in the underlying list.
func (l *List) index() int {
switch l.dir {
case iterateBackward:
return l.first - 1
case iterateForward:
return l.first + len(l.children)
default:
panic("Index called before Next")
}
}
// more reports whether more children are needed.
func (l *List) more() bool {
return l.dir != iterateNone
}
func (l *List) nextDir() iterationDir {
vsize := axisMainConstraint(l.Axis, l.ctx.Constraints).Max
last := l.first + len(l.children)
// Clamp offset.
if l.maxSize-l.offset < vsize && last == l.len {
l.offset = l.maxSize - vsize
}
if l.offset < 0 && l.first == 0 {
l.offset = 0
}
switch {
case len(l.children) == l.len:
return iterateNone
case l.maxSize-l.offset < vsize:
return iterateForward
case l.offset < 0:
return iterateBackward
}
return iterateNone
}
// End the current child by specifying its dimensions.
func (l *List) end(dims Dimensions) {
l.child.Stop()
child := scrollChild{dims.Size, l.child}
mainSize := axisMain(l.Axis, child.size)
l.maxSize += mainSize
switch l.dir {
case iterateForward:
l.children = append(l.children, child)
case iterateBackward:
l.children = append([]scrollChild{child}, l.children...)
l.first--
l.offset += mainSize
default:
panic("call Next before End")
}
l.dir = iterateNone
}
// Layout the List and return its dimensions.
func (l *List) layout() Dimensions {
if l.more() {
panic("unfinished child")
}
mainc := axisMainConstraint(l.Axis, l.ctx.Constraints)
children := l.children
// Skip invisible children
for len(children) > 0 {
sz := children[0].size
mainSize := axisMain(l.Axis, sz)
if l.offset <= mainSize {
break
}
l.first++
l.offset -= mainSize
children = children[1:]
}
size := -l.offset
var maxCross int
for i, child := range children {
sz := child.size
if c := axisCross(l.Axis, sz); c > maxCross {
maxCross = c
}
size += axisMain(l.Axis, sz)
if size >= mainc.Max {
children = children[:i+1]
break
}
}
ops := l.ctx.Ops
pos := -l.offset
// ScrollToEnd lists lists are end aligned.
if space := mainc.Max - size; l.ScrollToEnd && space > 0 {
pos += space
}
for _, child := range children {
sz := child.size
var cross int
switch l.Alignment {
case End:
cross = maxCross - axisCross(l.Axis, sz)
case Middle:
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
}
r := image.Rectangle{
Min: axisPoint(l.Axis, min, -inf),
Max: axisPoint(l.Axis, max, inf),
}
var stack ui.StackOp
stack.Push(ops)
paint.RectClip(r).Add(ops)
ui.TransformOp{}.Offset(toPointF(axisPoint(l.Axis, pos, cross))).Add(ops)
child.macro.Add(ops)
stack.Pop()
pos += childSize
}
atStart := l.first == 0 && l.offset <= 0
atEnd := l.first+len(children) == l.len && mainc.Max >= pos
if atStart && l.scrollDelta < 0 || atEnd && l.scrollDelta > 0 {
l.scroll.Stop()
}
l.beforeEnd = !atEnd
dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
l.macro.Stop()
pointer.RectAreaOp{Rect: image.Rectangle{Max: dims}}.Add(ops)
l.scroll.Add(ops)
l.macro.Add(ops)
return Dimensions{Size: dims}
}
+116
View File
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: Unlicense OR MIT
package layout
import (
"image"
"gioui.org/ui"
)
// Stack lays out child elements on top of each other,
// according to an alignment direction.
type Stack struct {
// Alignment is the direction to align children
// smaller than the available space.
Alignment Direction
macro ui.MacroOp
constrained bool
ctx *Context
maxSZ image.Point
baseline int
}
// StackChild is the layout result of a call to End.
type StackChild struct {
macro ui.MacroOp
dims Dimensions
}
// Init a stack before calling Rigid or Expand.
func (s *Stack) Init(gtx *Context) *Stack {
s.ctx = gtx
s.constrained = true
s.maxSZ = image.Point{}
s.baseline = 0
return s
}
func (s *Stack) begin() {
if !s.constrained {
panic("must Init before adding a child")
}
s.macro.Record(s.ctx.Ops)
}
// Rigid lays out a widget with the same constraints that were
// passed to Init.
func (s *Stack) Rigid(w Widget) StackChild {
s.begin()
dims := s.ctx.Layout(s.ctx.Constraints, w)
return s.end(dims)
}
// Expand lays out a widget with constraints that exactly match
// the biggest child previously added.
func (s *Stack) Expand(w Widget) StackChild {
s.begin()
cs := Constraints{
Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
}
dims := s.ctx.Layout(cs, w)
return s.end(dims)
}
// End a child by specifying its dimensions.
func (s *Stack) end(dims Dimensions) StackChild {
s.macro.Stop()
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
}
if s.baseline == 0 {
if b := dims.Baseline; b != dims.Size.Y {
s.baseline = b
}
}
return StackChild{s.macro, dims}
}
// Layout a list of children. The order of the children determines their laid
// out order.
func (s *Stack) Layout(children ...StackChild) {
for _, ch := range 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
}
var stack ui.StackOp
stack.Push(s.ctx.Ops)
ui.TransformOp{}.Offset(toPointF(p)).Add(s.ctx.Ops)
ch.macro.Add(s.ctx.Ops)
stack.Pop()
}
b := s.baseline
if b == 0 {
b = s.maxSZ.Y
}
s.ctx.Dimensions = Dimensions{
Size: s.maxSZ,
Baseline: b,
}
}