shiny/widget: let a Flow shrink as well as expand.

This will be needed for scrollable Sheets, where the parent (the Sheet)
should be laid out at a smaller size than its child.

Change-Id: Iacf69c90f263f24f9fd149f3864d23737c1593a8
Reviewed-on: https://go-review.googlesource.com/28345
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/example/layout/main.go b/shiny/example/layout/main.go
index fcf1a3c..b5cac9d 100644
--- a/shiny/example/layout/main.go
+++ b/shiny/example/layout/main.go
@@ -46,17 +46,17 @@
 		widget.NewLabel("Cyan:"),
 		widget.WithLayoutData(
 			colorPatch(color.RGBA{0x00, 0x7f, 0x7f, 0xff}, px(0), px(20)),
-			widget.FlowLayoutData{ExpandAlongWeight: 1},
+			widget.FlowLayoutData{AlongWeight: 1, ExpandAlong: true},
 		),
 		widget.NewLabel("Magenta:"),
 		widget.WithLayoutData(
 			colorPatch(color.RGBA{0x7f, 0x00, 0x7f, 0xff}, px(0), px(30)),
-			widget.FlowLayoutData{ExpandAlongWeight: 2},
+			widget.FlowLayoutData{AlongWeight: 2, ExpandAlong: true},
 		),
 		widget.NewLabel("Yellow:"),
 		widget.WithLayoutData(
 			colorPatch(color.RGBA{0x7f, 0x7f, 0x00, 0xff}, px(0), px(40)),
-			widget.FlowLayoutData{ExpandAlongWeight: 3},
+			widget.FlowLayoutData{AlongWeight: 3, ExpandAlong: true},
 		),
 	)
 
diff --git a/shiny/example/textedit/main.go b/shiny/example/textedit/main.go
index de8929a..b47ba17 100644
--- a/shiny/example/textedit/main.go
+++ b/shiny/example/textedit/main.go
@@ -25,10 +25,13 @@
 	"golang.org/x/exp/shiny/widget/theme"
 )
 
-func expand(n node.Node, expandAlongWeight int) node.Node {
+func stretch(n node.Node, alongWeight int) node.Node {
 	return widget.WithLayoutData(n, widget.FlowLayoutData{
-		ExpandAcross:      true,
-		ExpandAlongWeight: expandAlongWeight,
+		AlongWeight:  alongWeight,
+		ExpandAlong:  true,
+		ShrinkAlong:  true,
+		ExpandAcross: true,
+		ShrinkAcross: true,
 	})
 }
 
@@ -39,7 +42,7 @@
 			widget.NewPadder(widget.AxisBoth, unit.Ems(0.5),
 				widget.NewFlow(widget.AxisHorizontal,
 					widget.NewLabel("TODO: status"),
-					expand(widget.NewSpace(), 1),
+					stretch(widget.NewSpace(), 1),
 					widget.NewLabel("TODO: Menu"),
 				),
 			),
@@ -50,10 +53,10 @@
 		body := widget.NewText(prideAndPrejudice)
 
 		w := widget.NewFlow(widget.AxisVertical,
-			expand(widget.NewSheet(header), 0),
-			expand(widget.NewSheet(divider), 0),
+			stretch(widget.NewSheet(header), 0),
+			stretch(widget.NewSheet(divider), 0),
 			// TODO: make the body's sheet scrollable.
-			expand(widget.NewSheet(body), 1),
+			stretch(widget.NewSheet(body), 1),
 		)
 
 		if err := widget.RunWindow(s, w, nil); err != nil {
diff --git a/shiny/widget/flow.go b/shiny/widget/flow.go
index 29c85d1..9dcc5a2 100644
--- a/shiny/widget/flow.go
+++ b/shiny/widget/flow.go
@@ -71,46 +71,73 @@
 		return
 	}
 
-	eaExtra, eaWeight := 0, 0
+	extra, totalExpandWeight, totalShrinkWeight := 0, 0, 0
 	if w.Axis == AxisHorizontal {
-		eaExtra = w.Rect.Dx()
+		extra = w.Rect.Dx()
 	} else {
-		eaExtra = w.Rect.Dy()
+		extra = w.Rect.Dy()
 	}
 	for c := w.FirstChild; c != nil; c = c.NextSibling {
-		if d, ok := c.LayoutData.(FlowLayoutData); ok && d.ExpandAlongWeight > 0 {
-			eaWeight += d.ExpandAlongWeight
+		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 {
-			eaExtra -= c.MeasuredSize.X
+			extra -= c.MeasuredSize.X
 		} else {
-			eaExtra -= c.MeasuredSize.Y
+			extra -= c.MeasuredSize.Y
 		}
 	}
-	if eaExtra < 0 {
-		eaExtra = 0
+	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.ExpandAlongWeight > 0 {
-				delta := eaExtra * d.ExpandAlongWeight / eaWeight
-				eaExtra -= delta
-				eaWeight -= d.ExpandAlongWeight
-				if w.Axis == AxisHorizontal {
-					q.X += delta
-				} else {
-					q.Y += delta
+			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 d.ExpandAcross {
-				if w.Axis == AxisHorizontal {
-					q.Y = max(q.Y, w.Rect.Dy())
-				} else {
-					q.X = max(q.X, w.Rect.Dx())
-				}
+
+			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{
@@ -126,19 +153,55 @@
 	}
 }
 
+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 {
-	// ExpandAlongWeight is the relative weight for distributing any excess
-	// space along the Flow's axis. For example, if an AxisHorizontal Flow's
+	// 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.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
+	// 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
 
-	// 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.
+	// 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
 }
diff --git a/shiny/widget/widget.go b/shiny/widget/widget.go
index d45108d..0dcbf63 100644
--- a/shiny/widget/widget.go
+++ b/shiny/widget/widget.go
@@ -22,13 +22,6 @@
 	"golang.org/x/mobile/event/size"
 )
 
-func max(x, y int) int {
-	if x > y {
-		return x
-	}
-	return y
-}
-
 // Axis is zero, one or both of the horizontal and vertical axes. For example,
 // a widget may be scrollable in one of the four AxisXxx values.
 type Axis uint8