// 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 }