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)?