shiny/widget: add FlowLayoutData and Node.LayoutData.
Also fix a bug in FlowClass.Measure.
Change-Id: Ie2ff19b89dbdbbc022e4ef6193f175864c30feca
Reviewed-on: https://go-review.googlesource.com/22078
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/example/layout/main.go b/shiny/example/layout/main.go
index d3945aa..a890fa6 100644
--- a/shiny/example/layout/main.go
+++ b/shiny/example/layout/main.go
@@ -38,17 +38,21 @@
// Make the widget node tree.
hf := widget.NewFlow(widget.AxisHorizontal)
hf.AppendChild(widget.NewLabel("Cyan:").Node)
- hf.AppendChild(widget.NewUniform(color.RGBA{0x00, 0x7f, 0x7f, 0xff}, px(100), px(20)).Node)
+ hf.AppendChild(widget.NewUniform(color.RGBA{0x00, 0x7f, 0x7f, 0xff}, px(0), px(20)).Node)
+ hf.LastChild.LayoutData = widget.FlowLayoutData{ExpandAlongWeight: 1}
hf.AppendChild(widget.NewLabel("Magenta:").Node)
- hf.AppendChild(widget.NewUniform(color.RGBA{0x7f, 0x00, 0x7f, 0xff}, px(200), px(30)).Node)
+ hf.AppendChild(widget.NewUniform(color.RGBA{0x7f, 0x00, 0x7f, 0xff}, px(0), px(30)).Node)
+ hf.LastChild.LayoutData = widget.FlowLayoutData{ExpandAlongWeight: 2}
hf.AppendChild(widget.NewLabel("Yellow:").Node)
- hf.AppendChild(widget.NewUniform(color.RGBA{0x7f, 0x7f, 0x00, 0xff}, px(300), px(40)).Node)
+ hf.AppendChild(widget.NewUniform(color.RGBA{0x7f, 0x7f, 0x00, 0xff}, px(0), px(40)).Node)
+ hf.LastChild.LayoutData = widget.FlowLayoutData{ExpandAlongWeight: 3}
vf := widget.NewFlow(widget.AxisVertical)
vf.AppendChild(widget.NewUniform(color.RGBA{0xff, 0x00, 0x00, 0xff}, px(80), px(40)).Node)
vf.AppendChild(widget.NewUniform(color.RGBA{0x00, 0xff, 0x00, 0xff}, px(50), px(50)).Node)
vf.AppendChild(widget.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff}, px(20), px(60)).Node)
vf.AppendChild(hf.Node)
+ vf.LastChild.LayoutData = widget.FlowLayoutData{ExpandAcross: true}
vf.AppendChild(widget.NewLabel(fmt.Sprintf(
"The black rectangle is 1.5 inches x 1 inch when viewed at %v DPI.", t.GetDPI())).Node)
vf.AppendChild(widget.NewUniform(color.Black, unit.Inches(1.5), unit.Inches(1)).Node)
diff --git a/shiny/widget/flow.go b/shiny/widget/flow.go
index 31d44c1..dd62e29 100644
--- a/shiny/widget/flow.go
+++ b/shiny/widget/flow.go
@@ -11,7 +11,9 @@
// TODO: padding, alignment.
// Flow is a container widget that lays out its children in sequence along an
-// axis, either horizontally or vertically.
+// 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 }
// NewFlow returns a new Flow widget.
@@ -42,10 +44,12 @@
for c := n.FirstChild; c != nil; c = c.NextSibling {
c.Measure(t)
if 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
}
@@ -62,17 +66,74 @@
return
}
- min := image.Point{}
+ eaExtra, eaWeight := 0, 0
+ if axis == AxisHorizontal {
+ eaExtra = n.Rect.Dx()
+ } else {
+ eaExtra = n.Rect.Dy()
+ }
for c := n.FirstChild; c != nil; c = c.NextSibling {
+ if d, ok := c.LayoutData.(FlowLayoutData); ok && d.ExpandAlongWeight > 0 {
+ eaWeight += d.ExpandAlongWeight
+ }
+ if axis == AxisHorizontal {
+ eaExtra -= c.MeasuredSize.X
+ } else {
+ eaExtra -= c.MeasuredSize.Y
+ }
+ }
+ if eaExtra < 0 {
+ eaExtra = 0
+ }
+
+ p := image.Point{}
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ q := p.Add(c.MeasuredSize)
+ if d, ok := c.LayoutData.(FlowLayoutData); ok {
+ if d.ExpandAlongWeight > 0 {
+ delta := eaExtra * d.ExpandAlongWeight / eaWeight
+ eaExtra -= delta
+ eaWeight -= d.ExpandAlongWeight
+ if axis == AxisHorizontal {
+ q.X += delta
+ } else {
+ q.Y += delta
+ }
+ }
+ if d.ExpandAcross {
+ if axis == AxisHorizontal {
+ q.Y = max(q.Y, n.Rect.Dy())
+ } else {
+ q.X = max(q.X, n.Rect.Dx())
+ }
+ }
+ }
c.Rect = image.Rectangle{
- Min: min,
- Max: min.Add(c.MeasuredSize),
+ Min: p,
+ Max: q,
}
c.Layout(t)
if axis == AxisHorizontal {
- min.X += c.MeasuredSize.X
+ p.X = q.X
} else {
- min.Y += c.MeasuredSize.Y
+ p.Y = q.Y
}
}
}
+
+// FlowLayoutData is the Node.LayoutData type for a Flow's children.
+type FlowLayoutData struct {
+ // ExpandAlongWeight is the relative weight for distributing any excess
+ // space 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.ExpandAlongWeight
+ // values 6, 3 and 1, then those children's laid out widths would be larger
+ // than their natural widths by 60, 30 and 10 pixels.
+ ExpandAlongWeight int
+
+ // ExpandAcross is whether the child's laid out size should expand to fill
+ // the Flow's cross-axis. 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
+}
diff --git a/shiny/widget/widget.go b/shiny/widget/widget.go
index 43ffb4f..ef23730 100644
--- a/shiny/widget/widget.go
+++ b/shiny/widget/widget.go
@@ -11,6 +11,13 @@
"image"
)
+func max(x, y int) int {
+ if x > y {
+ return x
+ }
+ return y
+}
+
// Arity is the number of children a class of nodes can have.
type Arity uint8
@@ -152,6 +159,11 @@
// ProgressBarClass may store a numerical percentage.
ClassData interface{}
+ // LayoutData is layout-specific data for this node. Its type is determined
+ // by its parent node's class. For example, each child of a Flow may hold a
+ // FlowLayoutData in this field.
+ LayoutData interface{}
+
// TODO: add commentary about the Measure / Layout / Paint model, and about
// the lifetime of the MeasuredSize and Rect fields, and when user code can
// access and/or modify them. At some point a new cycle begins, a call to