Skip to content

Commit ba044e6

Browse files
committed
Add RowWrap layout
1 parent 669edf7 commit ba044e6

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

layout/rowwrap.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package layout
2+
3+
import (
4+
"fyne.io/fyne/v2"
5+
"fyne.io/fyne/v2/theme"
6+
)
7+
8+
type rowWrapLayout struct {
9+
rowCount int
10+
}
11+
12+
// NewRowWrapLayout returns a layout that dynamically arranges objects
13+
// with the same height in rows and wraps them as necessary.
14+
//
15+
// Object visibility is supported.
16+
//
17+
// Since: 2.7
18+
func NewRowWrapLayout() *rowWrapLayout {
19+
return &rowWrapLayout{}
20+
}
21+
22+
var _ fyne.Layout = (*rowWrapLayout)(nil)
23+
24+
func (l *rowWrapLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
25+
if len(objects) == 0 {
26+
return fyne.NewSize(0, 0)
27+
}
28+
rows := l.rowCount
29+
if rows == 0 {
30+
rows = 1
31+
}
32+
rowHeight := objects[0].MinSize().Height
33+
var w float32
34+
for _, o := range objects {
35+
size := o.MinSize()
36+
w = max(w, size.Width)
37+
}
38+
s := fyne.NewSize(w, rowHeight*float32(rows)+theme.Padding()*float32(rows-1))
39+
return s
40+
}
41+
42+
func (l *rowWrapLayout) Layout(objects []fyne.CanvasObject, containerSize fyne.Size) {
43+
if len(objects) == 0 {
44+
return
45+
}
46+
padding := theme.Padding()
47+
rowHeight := objects[0].MinSize().Height
48+
pos := fyne.NewPos(0, 0)
49+
rows := 1
50+
for _, o := range objects {
51+
if !o.Visible() {
52+
continue
53+
}
54+
size := o.MinSize()
55+
o.Resize(size)
56+
w := size.Width + padding
57+
if pos.X+w > containerSize.Width {
58+
pos = fyne.NewPos(0, float32(rows)*(rowHeight+padding))
59+
rows++
60+
}
61+
o.Move(pos)
62+
pos = pos.Add(fyne.NewPos(w, 0))
63+
}
64+
l.rowCount = rows
65+
}
66+
67+
func max(a, b float32) float32 {
68+
if a < b {
69+
return b
70+
}
71+
return a
72+
}

layout/rowwrap_test.go

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package layout_test
2+
3+
import (
4+
"image/color"
5+
"testing"
6+
7+
"fyne.io/fyne/v2"
8+
"fyne.io/fyne/v2/canvas"
9+
"fyne.io/fyne/v2/container"
10+
"fyne.io/fyne/v2/layout"
11+
"fyne.io/fyne/v2/theme"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestRowWrapLayout_Layout(t *testing.T) {
16+
t.Run("should arrange objects in a row and wrap overflow objects into next row", func(t *testing.T) {
17+
// given
18+
h := float32(10)
19+
o1 := canvas.NewRectangle(color.Opaque)
20+
o1.SetMinSize(fyne.NewSize(30, h))
21+
o2 := canvas.NewRectangle(color.Opaque)
22+
o2.SetMinSize(fyne.NewSize(80, h))
23+
o3 := canvas.NewRectangle(color.Opaque)
24+
o3.SetMinSize(fyne.NewSize(50, h))
25+
26+
containerSize := fyne.NewSize(125, 125)
27+
container := &fyne.Container{
28+
Objects: []fyne.CanvasObject{o1, o2, o3},
29+
}
30+
container.Resize(containerSize)
31+
32+
// when
33+
layout.NewRowWrapLayout().Layout(container.Objects, containerSize)
34+
35+
// then
36+
p := theme.Padding()
37+
assert.Equal(t, fyne.NewPos(0, 0), o1.Position())
38+
assert.Equal(t, fyne.NewPos(o1.Size().Width+p, 0), o2.Position())
39+
assert.Equal(t, fyne.NewPos(0, o1.Size().Height+p), o3.Position())
40+
})
41+
t.Run("should do nothing when container is empty", func(t *testing.T) {
42+
containerSize := fyne.NewSize(125, 125)
43+
container := &fyne.Container{}
44+
container.Resize(containerSize)
45+
46+
// when
47+
layout.NewRowWrapLayout().Layout(container.Objects, containerSize)
48+
})
49+
t.Run("should ignore hidden objects", func(t *testing.T) {
50+
// given
51+
h := float32(10)
52+
o1 := canvas.NewRectangle(color.Opaque)
53+
o1.SetMinSize(fyne.NewSize(30, h))
54+
o2 := canvas.NewRectangle(color.Opaque)
55+
o2.SetMinSize(fyne.NewSize(80, h))
56+
o2.Hide()
57+
o3 := canvas.NewRectangle(color.Opaque)
58+
o3.SetMinSize(fyne.NewSize(50, h))
59+
60+
containerSize := fyne.NewSize(125, 125)
61+
container := &fyne.Container{
62+
Objects: []fyne.CanvasObject{o1, o2, o3},
63+
}
64+
container.Resize(containerSize)
65+
66+
// when
67+
layout.NewRowWrapLayout().Layout(container.Objects, containerSize)
68+
69+
// then
70+
p := theme.Padding()
71+
assert.Equal(t, fyne.NewPos(0, 0), o1.Position())
72+
assert.Equal(t, fyne.NewPos(o1.Size().Width+p, 0), o3.Position())
73+
})
74+
}
75+
76+
func TestRowWrapLayout_MinSize(t *testing.T) {
77+
t.Run("should return min size of single object when container has only one", func(t *testing.T) {
78+
// given
79+
o := canvas.NewRectangle(color.Opaque)
80+
o.SetMinSize(fyne.NewSize(10, 10))
81+
container := container.NewWithoutLayout(o)
82+
layout := layout.NewRowWrapLayout()
83+
84+
// when/then
85+
minSize := layout.MinSize(container.Objects)
86+
87+
// then
88+
assert.Equal(t, o.MinSize(), minSize)
89+
})
90+
t.Run("should return size 0 when container is empty", func(t *testing.T) {
91+
// given
92+
container := container.NewWithoutLayout()
93+
layout := layout.NewRowWrapLayout()
94+
95+
// when/then
96+
minSize := layout.MinSize(container.Objects)
97+
98+
// then
99+
assert.Equal(t, fyne.NewSize(0, 0), minSize)
100+
})
101+
t.Run("should initially return height of first object and width of widest object", func(t *testing.T) {
102+
// given
103+
h := float32(10)
104+
o1 := canvas.NewRectangle(color.Opaque)
105+
o1.SetMinSize(fyne.NewSize(10, h))
106+
o2 := canvas.NewRectangle(color.Opaque)
107+
o2.SetMinSize(fyne.NewSize(20, h))
108+
container := container.NewWithoutLayout(o1, o2)
109+
layout := layout.NewRowWrapLayout()
110+
111+
// when/then
112+
minSize := layout.MinSize(container.Objects)
113+
114+
// then
115+
assert.Equal(t, fyne.NewSize(20, h), minSize)
116+
})
117+
t.Run("should return height of arranged objects after layout was calculated", func(t *testing.T) {
118+
// given
119+
h := float32(10)
120+
o1 := canvas.NewRectangle(color.Opaque)
121+
o1.SetMinSize(fyne.NewSize(10, h))
122+
o2 := canvas.NewRectangle(color.Opaque)
123+
o2.SetMinSize(fyne.NewSize(20, h))
124+
container := container.New(layout.NewRowWrapLayout(), o1, o2)
125+
container.Resize(fyne.NewSize(15, 50))
126+
127+
// when/then
128+
minSize := container.MinSize()
129+
130+
// then
131+
assert.Equal(t, fyne.NewSize(o2.Size().Width, (o1.Size().Height*2)+theme.Padding()), minSize)
132+
})
133+
}

0 commit comments

Comments
 (0)