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