blob: 9dcc5a2a26e87f17d730c31f34a6b7b05826a508 [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package widget
import (
"image"
"golang.org/x/exp/shiny/widget/node"
"golang.org/x/exp/shiny/widget/theme"
)
// TODO: padding, alignment.
// Flow is a container widget that lays out its children in sequence along an
// axis, either horizontally or vertically. The children's laid out size may
// differ from their natural size, along or across that axis, if a child's
// LayoutData is a FlowLayoutData.
type Flow struct {
node.ContainerEmbed
Axis Axis
}
// NewFlow returns a new Flow widget containing the given children.
func NewFlow(a Axis, children ...node.Node) *Flow {
w := &Flow{
Axis: a,
}
w.Wrapper = w
for _, c := range children {
w.Insert(c, nil)
}
return w
}
func (w *Flow) Measure(t *theme.Theme, widthHint, heightHint int) {
if w.Axis != AxisHorizontal && w.Axis != AxisVertical {
w.ContainerEmbed.Measure(t, widthHint, heightHint)
return
}
if w.Axis == AxisHorizontal {
widthHint = node.NoHint
}
if w.Axis == AxisVertical {
heightHint = node.NoHint
}
mSize := image.Point{}
for c := w.FirstChild; c != nil; c = c.NextSibling {
c.Wrapper.Measure(t, widthHint, heightHint)
if w.Axis == AxisHorizontal {
mSize.X += c.MeasuredSize.X
if mSize.Y < c.MeasuredSize.Y {
mSize.Y = c.MeasuredSize.Y
}
} else {
mSize.Y += c.MeasuredSize.Y
if mSize.X < c.MeasuredSize.X {
mSize.X = c.MeasuredSize.X
}
}
}
w.MeasuredSize = mSize
}
func (w *Flow) Layout(t *theme.Theme) {
if w.Axis != AxisHorizontal && w.Axis != AxisVertical {
w.ContainerEmbed.Layout(t)
return
}
extra, totalExpandWeight, totalShrinkWeight := 0, 0, 0
if w.Axis == AxisHorizontal {
extra = w.Rect.Dx()
} else {
extra = w.Rect.Dy()
}
for c := w.FirstChild; c != nil; c = c.NextSibling {
if d, ok := c.LayoutData.(FlowLayoutData); ok && d.AlongWeight > 0 {
if d.AlongWeight <= 0 {
continue
}
if d.ExpandAlong {
totalExpandWeight += d.AlongWeight
}
if d.ShrinkAlong {
totalShrinkWeight += d.AlongWeight
}
}
if w.Axis == AxisHorizontal {
extra -= c.MeasuredSize.X
} else {
extra -= c.MeasuredSize.Y
}
}
expand, shrink, totalWeight := extra > 0, extra < 0, 0
if expand {
if totalExpandWeight == 0 {
expand = false
} else {
totalWeight = totalExpandWeight
}
}
if shrink {
if totalShrinkWeight == 0 {
shrink = false
} else {
totalWeight = totalShrinkWeight
}
}
p := image.Point{}
for c := w.FirstChild; c != nil; c = c.NextSibling {
q := p.Add(c.MeasuredSize)
if d, ok := c.LayoutData.(FlowLayoutData); ok {
if d.AlongWeight > 0 {
if (expand && d.ExpandAlong) || (shrink && d.ShrinkAlong) {
delta := extra * d.AlongWeight / totalWeight
extra -= delta
totalWeight -= d.AlongWeight
if w.Axis == AxisHorizontal {
q.X += delta
if q.X < p.X {
q.X = p.X
}
} else {
q.Y += delta
if q.Y < p.Y {
q.Y = p.Y
}
}
}
}
if w.Axis == AxisHorizontal {
q.Y = stretchAcross(q.Y, w.Rect.Dy(), d.ExpandAcross, d.ShrinkAcross)
} else {
q.X = stretchAcross(q.X, w.Rect.Dx(), d.ExpandAcross, d.ShrinkAcross)
}
}
c.Rect = image.Rectangle{
Min: p,
Max: q,
}
c.Wrapper.Layout(t)
if w.Axis == AxisHorizontal {
p.X = q.X
} else {
p.Y = q.Y
}
}
}
func stretchAcross(child, parent int, expand, shrink bool) int {
if (expand && child < parent) || (shrink && child > parent) {
return parent
}
return child
}
// FlowLayoutData is the node LayoutData type for a Flow's children.
type FlowLayoutData struct {
// AlongWeight is the relative weight for distributing any space surplus or
// deficit along the Flow's axis. For example, if an AxisHorizontal Flow's
// Rect width was 100 pixels greater than the sum of its children's natural
// widths, and three children had non-zero FlowLayoutData.AlongWeight
// values 6, 3 and 1 (and their FlowLayoutData.ExpandAlong values were
// true) then those children's laid out widths would be larger than their
// natural widths by 60, 30 and 10 pixels.
//
// A negative AlongWeight is equivalent to zero.
AlongWeight int
// ExpandAlong is whether the child's laid out size should increase along
// the Flow's axis, based on AlongWeight, if there is a space surplus (the
// children's measured size total less than the parent's size). To allow
// size decreases as well as increases, set ShrinkAlong.
ExpandAlong bool
// ShrinkAlong is whether the child's laid out size should decrease along
// the Flow's axis, based on AlongWeight, if there is a space deficit (the
// children's measured size total more than the parent's size). To allow
// size increases as well as decreases, set ExpandAlong.
ShrinkAlong bool
// ExpandAcross is whether the child's laid out size should increase along
// the Flow's cross-axis if there is a space surplus (the child's measured
// size is less than the parent's size). To allow size decreases as well as
// increases, set ShrinkAcross.
//
// For example, if an AxisHorizontal Flow's Rect height was 80 pixels, any
// child whose FlowLayoutData.ExpandAcross was true would also be laid out
// with at least an 80 pixel height.
ExpandAcross bool
// ShrinkAcross is whether the child's laid out size should decrease along
// the Flow's cross-axis if there is a space deficit (the child's measured
// size is more than the parent's size). To allow size increases as well as
// decreases, set ExpandAcross.
//
// For example, if an AxisHorizontal Flow's Rect height was 80 pixels, any
// child whose FlowLayoutData.ShrinkAcross was true would also be laid out
// with at most an 80 pixel height.
ShrinkAcross bool
}