blob: 4007c32d7e3a2f05b659563e59438bb837878c5c [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flex
import (
"bytes"
"fmt"
"image"
"image/color"
"testing"
"golang.org/x/exp/shiny/unit"
"golang.org/x/exp/shiny/widget"
"golang.org/x/exp/shiny/widget/node"
"golang.org/x/exp/shiny/widget/theme"
)
type layoutTest struct {
desc string
direction Direction
wrap FlexWrap
alignContent AlignContent
justify Justify
size image.Point // size of container
measured [][2]float64 // MeasuredSize of child elements
layoutData []LayoutData // LayoutData of child elements
want []image.Rectangle // final Rect of child elements
}
func (t *layoutTest) html() string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, `<style>
#container {
display: flex;
width: %dpx;
height: %dpx;
`, t.size.X, t.size.Y)
switch t.direction {
case Row:
case RowReverse:
fmt.Fprintf(buf, "\tflex-direction: row-reverse;\n")
case Column:
fmt.Fprintf(buf, "\tflex-direction: column;\n")
case ColumnReverse:
fmt.Fprintf(buf, "\tflex-direction: column-reverse;\n")
}
switch t.wrap {
case NoWrap:
case Wrap:
fmt.Fprintf(buf, "\tflex-wrap: wrap;\n")
case WrapReverse:
fmt.Fprintf(buf, "\tflex-wrap: wrap-reverse;\n")
}
switch t.alignContent {
case AlignContentStart:
case AlignContentEnd:
fmt.Fprintf(buf, "\talign-content: flex-end;\n")
case AlignContentCenter:
fmt.Fprintf(buf, "\talign-content: center;\n")
case AlignContentSpaceBetween:
fmt.Fprintf(buf, "\talign-content: space-between;\n")
case AlignContentSpaceAround:
fmt.Fprintf(buf, "\talign-content: space-around;\n")
case AlignContentStretch:
fmt.Fprintf(buf, "\talign-content: stretch;\n")
}
switch t.justify {
case JustifyStart:
case JustifyEnd:
fmt.Fprintf(buf, "\tjustify-content: flex-end;\n")
case JustifyCenter:
fmt.Fprintf(buf, "\tjustify-content: center;\n")
case JustifySpaceBetween:
fmt.Fprintf(buf, "\tjustify-content: space-between;\n")
case JustifySpaceAround:
fmt.Fprintf(buf, "\tjustify-content: space-around;\n")
}
fmt.Fprintf(buf, "}\n")
for i, m := range t.measured {
fmt.Fprintf(buf, `#child%d {
width: %.2fpx;
height: %.2fpx;
`, i, m[0], m[1])
c := colors[i%len(colors)]
fmt.Fprintf(buf, "\tbackground-color: rgb(%d, %d, %d);\n", c.R, c.G, c.B)
if t.layoutData != nil {
d := t.layoutData[i]
if d.MinSize.X != 0 {
fmt.Fprintf(buf, "\tmin-width: %dpx;\n", d.MinSize.X)
}
if d.MinSize.Y != 0 {
fmt.Fprintf(buf, "\tmin-height: %dpx;\n", d.MinSize.Y)
}
if d.MaxSize != nil {
fmt.Fprintf(buf, "\tmax-width: %dpx;\n", d.MaxSize.X)
fmt.Fprintf(buf, "\tmax-height: %dpx;\n", d.MaxSize.Y)
}
if d.Grow != 0 {
fmt.Fprintf(buf, "\tflex-grow: %f;\n", d.Grow)
}
if d.Shrink != nil {
fmt.Fprintf(buf, "\tflex-shrink: %f;\n", *d.Shrink)
}
// TODO: Basis, Align, BreakAfter
}
fmt.Fprintf(buf, "}\n")
}
fmt.Fprintf(buf, `</style>
<div id="container">
`)
for i := range t.measured {
fmt.Fprintf(buf, "\t<div id=\"child%d\"></div>\n", i)
}
fmt.Fprintf(buf, `</div>
<pre id="out"></pre>
<script>
var out = document.getElementById("out");
var container = document.getElementById("container");
for (var i = 0; i < container.children.length; i++) {
var c = container.children[i];
var ctop = c.offsetTop - container.offsetTop;
var cleft = c.offsetLeft - container.offsetLeft;
var cbottom = ctop + c.offsetHeight;
var cright = cleft + c.offsetWidth;
out.innerHTML += "\timage.Rect(" + cleft + ", " + ctop + ", " + cright + ", " + cbottom + "),\n";
}
</script>
`)
return buf.String()
}
var colors = []color.RGBA{
{0x00, 0x7f, 0x7f, 0xff}, // Cyan
{0x7f, 0x00, 0x7f, 0xff}, // Magenta
{0x7f, 0x7f, 0x00, 0xff}, // Yellow
{0xff, 0x00, 0x00, 0xff}, // Red
{0x00, 0xff, 0x00, 0xff}, // Green
{0x00, 0x00, 0xff, 0xff}, // Blue
}
var layoutTests = []layoutTest{{
desc: "no children",
}, {
desc: "no children wrapped",
wrap: Wrap,
}, {
desc: "single unflexed child",
size: image.Point{100, 100},
measured: [][2]float64{{100, 100}},
want: []image.Rectangle{
image.Rect(0, 0, 100, 100),
},
}, {
desc: "unflexed children",
size: image.Point{350, 100},
measured: [][2]float64{{100, 100}, {100, 100}, {100, 100}},
want: []image.Rectangle{
image.Rect(0, 0, 100, 100),
image.Rect(100, 0, 200, 100),
image.Rect(200, 0, 300, 100),
},
}, {
desc: "final child that grows",
size: image.Point{300, 100},
measured: [][2]float64{{100, 100}, {100, 100}},
want: []image.Rectangle{
image.Rect(0, 0, 100, 100),
image.Rect(100, 0, 300, 100),
},
layoutData: []LayoutData{{}, {Grow: 1}},
}, {
desc: "share growth equally",
size: image.Point{300, 100},
measured: [][2]float64{{50, 50}, {100, 100}, {100, 100}},
want: []image.Rectangle{
image.Rect(0, 0, 50, 50),
image.Rect(50, 0, 175, 100),
image.Rect(175, 0, 300, 100),
},
layoutData: []LayoutData{{}, {Grow: 1}, {Grow: 1}},
}, {
desc: "share growth inequally",
size: image.Point{300, 100},
measured: [][2]float64{{20, 100}, {20, 100}, {20, 100}},
want: []image.Rectangle{
image.Rect(0, 0, 30, 100),
image.Rect(30, 0, 130, 100),
image.Rect(130, 0, 300, 100),
},
layoutData: []LayoutData{
{MaxSize: &image.Point{30, 100}, Grow: 1},
{MinSize: image.Point{100, 0}, Grow: 1},
{Grow: 4},
},
}, {
desc: "wrap",
size: image.Point{300, 200},
wrap: Wrap,
measured: [][2]float64{{150, 100}, {280, 100}, {20, 100}},
want: []image.Rectangle{
image.Rect(0, 0, 30, 100),
image.Rect(0, 100, 280, 200),
image.Rect(280, 100, 300, 200),
},
layoutData: []LayoutData{
{MaxSize: &image.Point{30, 100}, Grow: 1},
{MinSize: image.Point{100, 0}, Grow: 1},
{Grow: 1},
},
}, {
desc: "align-content default",
size: image.Point{300, 200},
direction: Column,
wrap: Wrap,
measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
want: []image.Rectangle{
image.Rect(0, 0, 30, 100),
image.Rect(0, 100, 160, 200),
image.Rect(220, 0, 240, 195),
image.Rect(220, 195, 225, 200),
},
layoutData: []LayoutData{
{MaxSize: &image.Point{30, 100}, Grow: 1},
{MinSize: image.Point{100, 0}, Grow: 1},
{Grow: 1},
{MaxSize: &image.Point{5, 5}},
},
}, {
desc: "align-content: space-around",
size: image.Point{300, 200},
direction: Column,
wrap: Wrap,
alignContent: AlignContentSpaceAround,
measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
want: []image.Rectangle{
image.Rect(30, 0, 60, 100),
image.Rect(30, 100, 190, 200),
image.Rect(250, 0, 270, 195),
image.Rect(250, 195, 255, 200),
},
layoutData: []LayoutData{
{MaxSize: &image.Point{30, 100}, Grow: 1},
{MinSize: image.Point{100, 0}, Grow: 1},
{Grow: 1},
{MaxSize: &image.Point{5, 5}},
},
}, {
desc: "align-content: space-between",
size: image.Point{300, 200},
direction: Column,
wrap: Wrap,
alignContent: AlignContentSpaceBetween,
measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
want: []image.Rectangle{
image.Rect(0, 0, 30, 100),
image.Rect(0, 100, 160, 200),
image.Rect(280, 0, 300, 195),
image.Rect(280, 195, 285, 200),
},
layoutData: []LayoutData{
{MaxSize: &image.Point{30, 100}, Grow: 1},
{MinSize: image.Point{100, 0}, Grow: 1},
{Grow: 1},
{MaxSize: &image.Point{5, 5}},
},
}, {
desc: "align-content: end",
size: image.Point{300, 200},
direction: Column,
wrap: Wrap,
alignContent: AlignContentEnd,
measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
want: []image.Rectangle{
image.Rect(120, 0, 150, 100),
image.Rect(120, 100, 280, 200),
image.Rect(280, 0, 300, 195),
image.Rect(280, 195, 285, 200),
},
layoutData: []LayoutData{
{MaxSize: &image.Point{30, 100}, Grow: 1},
{MinSize: image.Point{100, 0}, Grow: 1},
{Grow: 1},
{MaxSize: &image.Point{5, 5}},
},
}, {
desc: "align-content: center",
size: image.Point{300, 200},
direction: Column,
wrap: Wrap,
alignContent: AlignContentCenter,
measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
want: []image.Rectangle{
image.Rect(60, 0, 90, 100),
image.Rect(60, 100, 220, 200),
image.Rect(220, 0, 240, 195),
image.Rect(220, 195, 225, 200),
},
layoutData: []LayoutData{
{MaxSize: &image.Point{30, 100}, Grow: 1},
{MinSize: image.Point{100, 0}, Grow: 1},
{Grow: 1},
{MaxSize: &image.Point{5, 5}},
},
}, {
desc: "column-reverse",
size: image.Point{300, 60},
direction: ColumnReverse,
wrap: Wrap,
measured: [][2]float64{{25, 25}, {25, 25}, {25, 25}, {25, 25}, {25, 25}},
want: []image.Rectangle{
image.Rect(0, 35, 25, 60),
image.Rect(0, 0, 25, 35),
image.Rect(100, 35, 125, 60),
image.Rect(100, 10, 125, 35),
image.Rect(200, 0, 225, 60),
},
layoutData: []LayoutData{
{},
{Grow: 1},
{},
{},
{Grow: 1},
},
}, {
desc: "justify-content: flex-start",
size: image.Point{90, 90},
measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
justify: JustifyStart,
want: []image.Rectangle{
image.Rect(0, 0, 5, 10),
image.Rect(5, 0, 10, 10),
image.Rect(10, 0, 20, 10),
},
}, {
desc: "justify-content: flex-end",
size: image.Point{90, 90},
measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
justify: JustifyEnd,
want: []image.Rectangle{
image.Rect(70, 0, 75, 10),
image.Rect(75, 0, 80, 10),
image.Rect(80, 0, 90, 10),
},
}, {
desc: "justify-content: center",
size: image.Point{90, 90},
measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
justify: JustifyCenter,
want: []image.Rectangle{
image.Rect(35, 0, 40, 10),
image.Rect(40, 0, 45, 10),
image.Rect(45, 0, 55, 10),
},
}, {
desc: "justify-content: space-between",
size: image.Point{90, 90},
measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
justify: JustifySpaceBetween,
want: []image.Rectangle{
image.Rect(0, 0, 5, 10),
image.Rect(40, 0, 45, 10),
image.Rect(80, 0, 90, 10),
},
}, {
desc: "justify-content: space-around",
size: image.Point{90, 90},
measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
justify: JustifySpaceAround,
want: []image.Rectangle{
image.Rect(12, 0, 17, 10),
image.Rect(40, 0, 45, 10),
image.Rect(68, 0, 78, 10),
},
}}
func TestLayout(t *testing.T) {
for testNum, test := range layoutTests {
var children []node.Node
for i, sz := range test.measured {
u := widget.NewUniform(theme.StaticColor(colors[i]), nil)
n := widget.NewSizer(unit.Pixels(sz[0]), unit.Pixels(sz[1]), u)
if test.layoutData != nil {
n.LayoutData = test.layoutData[i]
}
children = append(children, n)
}
w := NewFlex(children...)
w.Direction = test.direction
w.Wrap = test.wrap
w.AlignContent = test.alignContent
w.Justify = test.justify
w.Measure(nil, node.NoHint, node.NoHint)
w.Rect = image.Rectangle{Max: test.size}
w.Layout(nil)
bad := false
for i, n := range children {
if n.Wrappee().Rect != test.want[i] {
bad = true
break
}
}
if bad {
t.Logf("Bad test %d, %q:\n%s", testNum, test.desc, test.html())
}
for i, n := range children {
if got, want := n.Wrappee().Rect, test.want[i]; got != want {
t.Errorf("[%d].Rect=%v, want %v", i, got, want)
}
}
}
}