shiny/widget: add width and height hints to Measure.

Change-Id: I6851e14709a3249e3b9281d5c3b8008c0ca50f6f
Reviewed-on: https://go-review.googlesource.com/26910
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/example/layout/main.go b/shiny/example/layout/main.go
index 8b6d8a6..fcf1a3c 100644
--- a/shiny/example/layout/main.go
+++ b/shiny/example/layout/main.go
@@ -76,11 +76,12 @@
 	)
 
 	// Make the RGBA image.
-	rgba := image.NewRGBA(image.Rect(0, 0, 640, 480))
+	const width, height = 640, 480
+	rgba := image.NewRGBA(image.Rect(0, 0, width, height))
 	draw.Draw(rgba, rgba.Bounds(), t.GetPalette().Neutral(), image.Point{}, draw.Src)
 
 	// Measure, layout and paint.
-	vf.Measure(t)
+	vf.Measure(t, width, height)
 	vf.Rect = rgba.Bounds()
 	vf.Layout(t)
 	vf.PaintBase(&node.PaintBaseContext{
diff --git a/shiny/widget/flex/flex.go b/shiny/widget/flex/flex.go
index 7ecfa28..22bd019 100644
--- a/shiny/widget/flex/flex.go
+++ b/shiny/widget/flex/flex.go
@@ -130,12 +130,13 @@
 	return w
 }
 
-func (w *Flex) Measure(t *theme.Theme) {
+func (w *Flex) Measure(t *theme.Theme, widthHint, heightHint int) {
 	// As Measure is a bottom-up calculation of natural size, we have no
 	// hint yet as to how we should flex. So we ignore Wrap, Justify,
 	// AlignItem, AlignContent.
 	for c := w.FirstChild; c != nil; c = c.NextSibling {
-		c.Wrapper.Measure(t)
+		// TODO: pass down width/height hints?
+		c.Wrapper.Measure(t, node.NoHint, node.NoHint)
 		if d, ok := c.LayoutData.(LayoutData); ok {
 			_ = d
 			// TODO Measure
diff --git a/shiny/widget/flex/flex_test.go b/shiny/widget/flex/flex_test.go
index 247c7a7..4007c32 100644
--- a/shiny/widget/flex/flex_test.go
+++ b/shiny/widget/flex/flex_test.go
@@ -397,7 +397,7 @@
 		w.AlignContent = test.alignContent
 		w.Justify = test.justify
 
-		w.Measure(nil)
+		w.Measure(nil, node.NoHint, node.NoHint)
 		w.Rect = image.Rectangle{Max: test.size}
 		w.Layout(nil)
 
diff --git a/shiny/widget/flow.go b/shiny/widget/flow.go
index e6328fd..29c85d1 100644
--- a/shiny/widget/flow.go
+++ b/shiny/widget/flow.go
@@ -34,15 +34,22 @@
 	return w
 }
 
-func (w *Flow) Measure(t *theme.Theme) {
+func (w *Flow) Measure(t *theme.Theme, widthHint, heightHint int) {
 	if w.Axis != AxisHorizontal && w.Axis != AxisVertical {
-		w.ContainerEmbed.Measure(t)
+		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)
+		c.Wrapper.Measure(t, widthHint, heightHint)
 		if w.Axis == AxisHorizontal {
 			mSize.X += c.MeasuredSize.X
 			if mSize.Y < c.MeasuredSize.Y {
diff --git a/shiny/widget/image.go b/shiny/widget/image.go
index 8a085ff..b705edd 100644
--- a/shiny/widget/image.go
+++ b/shiny/widget/image.go
@@ -39,7 +39,7 @@
 	return w
 }
 
-func (w *Image) Measure(t *theme.Theme) {
+func (w *Image) Measure(t *theme.Theme, widthHint, heightHint int) {
 	w.MeasuredSize = w.SrcRect.Size()
 }
 
diff --git a/shiny/widget/label.go b/shiny/widget/label.go
index 5fca977..120f6ac 100644
--- a/shiny/widget/label.go
+++ b/shiny/widget/label.go
@@ -29,7 +29,7 @@
 	return w
 }
 
-func (w *Label) Measure(t *theme.Theme) {
+func (w *Label) Measure(t *theme.Theme, widthHint, heightHint int) {
 	face := t.AcquireFontFace(theme.FontFaceOptions{})
 	defer t.ReleaseFontFace(theme.FontFaceOptions{}, face)
 	m := face.Metrics()
diff --git a/shiny/widget/node/node.go b/shiny/widget/node/node.go
index bc19870..d995f60 100644
--- a/shiny/widget/node/node.go
+++ b/shiny/widget/node/node.go
@@ -31,7 +31,7 @@
 // write subtly incorrect code like:
 //
 //	for c := w.FirstChild; c != nil; c = c.NextSibling {
-//		c.Measure(t) // This should instead be c.Wrapper.Measure(t).
+//		c.Measure(etc) // This should instead be c.Wrapper.Measure(etc).
 //	}
 //
 // In any case, most programmers that want to construct a widget tree should
@@ -62,6 +62,9 @@
 	Handled    = EventHandled(true)
 )
 
+// NoHint means that there is no width or height hint in a Measure call.
+const NoHint = -1
+
 // Node is a node in the widget tree.
 type Node interface {
 	// Wrappee returns the inner (embedded) type that is wrapped by this type.
@@ -83,8 +86,13 @@
 	// Measure sets this node's Embed.MeasuredSize to its natural size, taking
 	// its children into account.
 	//
-	// TODO: include width / height hints, for width-in-height-out layout?
-	Measure(t *theme.Theme)
+	// Some nodes' natural height might depend on their imposed width, such as
+	// a text widget word-wrapping its contents. The caller may provide hints
+	// that the parent can override the child's natural size in the width,
+	// height or both directions. A negative value means that there is no hint.
+	// For example, a container might lay out its children to all have the same
+	// width, and could pass that width as the widthHint argument.
+	Measure(t *theme.Theme, widthHint, heightHint int)
 
 	// Layout lays out this node (and its children), setting the Embed.Rect
 	// fields of each child. This node's Embed.Rect field should have
@@ -191,7 +199,7 @@
 
 func (m *LeafEmbed) Remove(c Node) { m.remove(c) }
 
-func (m *LeafEmbed) Measure(t *theme.Theme) { m.MeasuredSize = image.Point{} }
+func (m *LeafEmbed) Measure(t *theme.Theme, widthHint, heightHint int) { m.MeasuredSize = image.Point{} }
 
 func (m *LeafEmbed) Layout(t *theme.Theme) {}
 
@@ -224,9 +232,9 @@
 
 func (m *ShellEmbed) Remove(c Node) { m.remove(c) }
 
-func (m *ShellEmbed) Measure(t *theme.Theme) {
+func (m *ShellEmbed) Measure(t *theme.Theme, widthHint, heightHint int) {
 	if c := m.FirstChild; c != nil {
-		c.Wrapper.Measure(t)
+		c.Wrapper.Measure(t, widthHint, heightHint)
 		m.MeasuredSize = c.MeasuredSize
 	} else {
 		m.MeasuredSize = image.Point{}
@@ -281,10 +289,10 @@
 
 func (m *ContainerEmbed) Remove(c Node) { m.remove(c) }
 
-func (m *ContainerEmbed) Measure(t *theme.Theme) {
+func (m *ContainerEmbed) Measure(t *theme.Theme, widthHint, heightHint int) {
 	mSize := image.Point{}
 	for c := m.FirstChild; c != nil; c = c.NextSibling {
-		c.Wrapper.Measure(t)
+		c.Wrapper.Measure(t, NoHint, NoHint)
 		if mSize.X < c.MeasuredSize.X {
 			mSize.X = c.MeasuredSize.X
 		}
diff --git a/shiny/widget/padder.go b/shiny/widget/padder.go
index 63f3dbf..dcc7738 100644
--- a/shiny/widget/padder.go
+++ b/shiny/widget/padder.go
@@ -36,9 +36,21 @@
 	return w
 }
 
-func (w *Padder) Measure(t *theme.Theme) {
+func (w *Padder) Measure(t *theme.Theme, widthHint, heightHint int) {
 	margin2 := t.Pixels(w.Margin).Round() * 2
-	w.ShellEmbed.Measure(t)
+	if w.Axis.Horizontal() && widthHint >= 0 {
+		widthHint -= margin2
+		if widthHint < 0 {
+			widthHint = 0
+		}
+	}
+	if w.Axis.Vertical() && heightHint >= 0 {
+		heightHint -= margin2
+		if heightHint < 0 {
+			heightHint = 0
+		}
+	}
+	w.ShellEmbed.Measure(t, widthHint, heightHint)
 	if w.Axis.Horizontal() {
 		w.MeasuredSize.X += margin2
 	}
diff --git a/shiny/widget/sizer.go b/shiny/widget/sizer.go
index ffac8fc..69e430a 100644
--- a/shiny/widget/sizer.go
+++ b/shiny/widget/sizer.go
@@ -32,10 +32,10 @@
 	return w
 }
 
-func (w *Sizer) Measure(t *theme.Theme) {
+func (w *Sizer) Measure(t *theme.Theme, widthHint, heightHint int) {
 	w.MeasuredSize.X = t.Pixels(w.NaturalWidth).Round()
 	w.MeasuredSize.Y = t.Pixels(w.NaturalHeight).Round()
 	if c := w.FirstChild; c != nil {
-		c.Wrapper.Measure(t)
+		c.Wrapper.Measure(t, w.MeasuredSize.X, w.MeasuredSize.Y)
 	}
 }
diff --git a/shiny/widget/text.go b/shiny/widget/text.go
index 9b87053..6183ae3 100644
--- a/shiny/widget/text.go
+++ b/shiny/widget/text.go
@@ -38,12 +38,7 @@
 	return w
 }
 
-func (w *Text) Measure(t *theme.Theme) {
-	// TODO: implement. Should the Measure method include a width hint?
-	w.MeasuredSize = image.Point{}
-}
-
-func (w *Text) Layout(t *theme.Theme) {
+func (w *Text) setFace(t *theme.Theme) {
 	// TODO: can a theme change at runtime, or can it be set only once, at
 	// start-up?
 	if !w.faceSet {
@@ -56,12 +51,48 @@
 		face := t.AcquireFontFace(theme.FontFaceOptions{})
 		w.frame.SetFace(face)
 	}
+}
 
-	// TODO: should padding (and/or margin and border) be a universal concept
-	// and part of the node.Embed type instead of having each widget implement
-	// its own?
-	padding := t.Pixels(unit.Ems(0.5)).Ceil()
-	w.frame.SetMaxWidth(fixed.I(w.Rect.Dx() - 2*padding))
+// TODO: should padding (and/or margin and border) be a universal concept and
+// part of the node.Embed type instead of having each widget implement its own?
+
+func (w *Text) padding(t *theme.Theme) int {
+	return t.Pixels(unit.Ems(0.5)).Ceil()
+}
+
+func (w *Text) Measure(t *theme.Theme, widthHint, heightHint int) {
+	w.setFace(t)
+	padding := w.padding(t)
+
+	if widthHint < 0 {
+		w.frame.SetMaxWidth(0)
+		w.MeasuredSize = image.Point{
+			0, // TODO: this isn't right.
+			w.frame.Height() + 2*padding,
+		}
+		return
+	}
+
+	maxWidth := fixed.I(widthHint - 2*padding)
+	if maxWidth <= 1 {
+		maxWidth = 1
+	}
+	w.frame.SetMaxWidth(maxWidth)
+
+	w.MeasuredSize = image.Point{
+		widthHint,
+		w.frame.Height() + 2*padding,
+	}
+}
+
+func (w *Text) Layout(t *theme.Theme) {
+	w.setFace(t)
+	padding := w.padding(t)
+	maxWidth := fixed.I(w.Rect.Dx() - 2*padding)
+	if maxWidth <= 1 {
+		maxWidth = 1
+	}
+	w.frame.SetMaxWidth(maxWidth)
 }
 
 func (w *Text) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
@@ -78,7 +109,7 @@
 	descent := m.Descent.Ceil()
 	height := m.Height.Ceil()
 
-	padding := ctx.Theme.Pixels(unit.Ems(0.5)).Ceil()
+	padding := w.padding(ctx.Theme)
 
 	draw.Draw(dst, dst.Bounds(), ctx.Theme.GetPalette().Background(), image.Point{}, draw.Src)
 
diff --git a/shiny/widget/widget.go b/shiny/widget/widget.go
index 1e35e5e..d45108d 100644
--- a/shiny/widget/widget.go
+++ b/shiny/widget/widget.go
@@ -143,7 +143,8 @@
 				t = newT
 			}
 
-			root.Measure(t)
+			size := e.Size()
+			root.Measure(t, size.X, size.Y)
 			root.Wrappee().Rect = e.Bounds()
 			root.Layout(t)
 			// TODO: call Mark(node.MarkNeedsPaint)?