forked from joejulian/gio
3af01a3f43
Change the definition of Widget from the implicit
type Widget func()
to the explicit functional
type Widget func(gtx layout.Context) layout.Dimensions
The advantages are numerous:
- Clearer connection between the incoming context and the output dimensions.
- Returning the Dimensions are impossible to omit.
- Contexts passed by value, so its fields can be exported
and freely mutated by the program.
The only disadvantage is the longer function literals and the many "returns".
What tipped the scales in favour of the explicit Widget variant is that type
aliases can dramatically shorten the literals:
type (
C = layout.Context
D = layout.Dimensions
)
widget := func(gtx C) D {
...
}
Note that the aliases are not part of the Gio API and it is up to each user
whether they want to use them.
Finally the Go proposal for lightweight function literals,
https://github.com/golang/go/issues/21498, may remove the disadvantage
completely in future.
Context becomes a plain struct with only public fields, and its Reset is
replaced by a NewContext convenience constructor.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
245 lines
4.6 KiB
Go
245 lines
4.6 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package layout
|
|
|
|
import (
|
|
"image"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/op"
|
|
"gioui.org/unit"
|
|
)
|
|
|
|
// Constraints represent the minimum and maximum size of a widget.
|
|
//
|
|
// A widget does not have to treat its constraints as "hard". For
|
|
// example, if it's passed a constraint with a minimum size that's
|
|
// smaller than its actual minimum size, it should return its minimum
|
|
// size dimensions instead. Parent widgets should deal appropriately
|
|
// with child widgets that return dimensions that do not fit their
|
|
// constraints (for example, by clipping).
|
|
type Constraints struct {
|
|
Min, Max image.Point
|
|
}
|
|
|
|
// 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(gtx Context) Dimensions
|
|
|
|
const (
|
|
Start Alignment = iota
|
|
End
|
|
Middle
|
|
Baseline
|
|
)
|
|
|
|
const (
|
|
NW Direction = iota
|
|
N
|
|
NE
|
|
E
|
|
SE
|
|
S
|
|
SW
|
|
W
|
|
Center
|
|
)
|
|
|
|
const (
|
|
Horizontal Axis = iota
|
|
Vertical
|
|
)
|
|
|
|
// Exact returns the Constraints with the minimum and maximum size
|
|
// set to size.
|
|
func Exact(size image.Point) Constraints {
|
|
return Constraints{
|
|
Min: size, Max: size,
|
|
}
|
|
}
|
|
|
|
// FPt converts an point to a f32.Point.
|
|
func FPt(p image.Point) f32.Point {
|
|
return f32.Point{
|
|
X: float32(p.X), Y: float32(p.Y),
|
|
}
|
|
}
|
|
|
|
// FRect converts a rectangle to a f32.Rectangle.
|
|
func FRect(r image.Rectangle) f32.Rectangle {
|
|
return f32.Rectangle{
|
|
Min: FPt(r.Min), Max: FPt(r.Max),
|
|
}
|
|
}
|
|
|
|
// Constrain a size so each dimension is in the range [min;max].
|
|
func (c Constraints) Constrain(size image.Point) image.Point {
|
|
if min := c.Min.X; size.X < min {
|
|
size.X = min
|
|
}
|
|
if min := c.Min.Y; size.Y < min {
|
|
size.Y = min
|
|
}
|
|
if max := c.Max.X; size.X > max {
|
|
size.X = max
|
|
}
|
|
if max := c.Max.Y; size.Y > max {
|
|
size.Y = max
|
|
}
|
|
return size
|
|
}
|
|
|
|
// Inset adds space around a widget.
|
|
type Inset struct {
|
|
Top, Right, Bottom, Left unit.Value
|
|
}
|
|
|
|
// Layout a widget.
|
|
func (in Inset) Layout(gtx Context, w Widget) Dimensions {
|
|
top := gtx.Px(in.Top)
|
|
right := gtx.Px(in.Right)
|
|
bottom := gtx.Px(in.Bottom)
|
|
left := gtx.Px(in.Left)
|
|
mcs := gtx.Constraints
|
|
mcs.Max.X -= left + right
|
|
if mcs.Max.X < 0 {
|
|
left = 0
|
|
right = 0
|
|
mcs.Max.X = 0
|
|
}
|
|
if mcs.Min.X > mcs.Max.X {
|
|
mcs.Min.X = mcs.Max.X
|
|
}
|
|
mcs.Max.Y -= top + bottom
|
|
if mcs.Max.Y < 0 {
|
|
bottom = 0
|
|
top = 0
|
|
mcs.Max.Y = 0
|
|
}
|
|
if mcs.Min.Y > mcs.Max.Y {
|
|
mcs.Min.Y = mcs.Max.Y
|
|
}
|
|
var stack op.StackOp
|
|
stack.Push(gtx.Ops)
|
|
op.TransformOp{}.Offset(FPt(image.Point{X: left, Y: top})).Add(gtx.Ops)
|
|
gtx.Constraints = mcs
|
|
dims := w(gtx)
|
|
stack.Pop()
|
|
return Dimensions{
|
|
Size: dims.Size.Add(image.Point{X: right + left, Y: top + bottom}),
|
|
Baseline: dims.Baseline + bottom,
|
|
}
|
|
}
|
|
|
|
// UniformInset returns an Inset with a single inset applied to all
|
|
// edges.
|
|
func UniformInset(v unit.Value) Inset {
|
|
return Inset{Top: v, Right: v, Bottom: v, Left: v}
|
|
}
|
|
|
|
// Layout a widget according to the direction.
|
|
func (a Direction) Layout(gtx Context, w Widget) Dimensions {
|
|
var macro op.MacroOp
|
|
macro.Record(gtx.Ops)
|
|
cs := gtx.Constraints
|
|
gtx.Constraints.Min = image.Point{}
|
|
dims := w(gtx)
|
|
macro.Stop()
|
|
sz := dims.Size
|
|
if sz.X < cs.Min.X {
|
|
sz.X = cs.Min.X
|
|
}
|
|
if sz.Y < cs.Min.Y {
|
|
sz.Y = cs.Min.Y
|
|
}
|
|
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 op.StackOp
|
|
stack.Push(gtx.Ops)
|
|
op.TransformOp{}.Offset(FPt(p)).Add(gtx.Ops)
|
|
macro.Add()
|
|
stack.Pop()
|
|
return Dimensions{
|
|
Size: sz,
|
|
Baseline: dims.Baseline + sz.Y - dims.Size.Y - p.Y,
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|