forked from g3n/engine
/
gridlayout.go
311 lines (283 loc) · 8.53 KB
/
gridlayout.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
// Copyright 2016 The G3N 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 gui
// GridLayout is a panel layout which arranges its children in a rectangular grid.
// It is necessary to set the number of columns of the grid when the layout is created.
// The panel's child elements are positioned in the grid cells accordingly to the
// order they were added to the panel.
// The height of each row is determined by the height of the heightest child in the row.
// The width of each column is determined by the width of the widest child in the column
type GridLayout struct {
pan IPanel // parent panel
columns []colInfo // columns alignment info
alignh Align // global cell horizontal alignment
alignv Align // global cell vertical alignment
expandh bool // expand horizontally flag
expandv bool // expand vertically flag
}
// GridLayoutParams describes layout parameter for an specific child
type GridLayoutParams struct {
ColSpan int // Number of additional columns to ocuppy to the right
AlignH Align // Vertical alignment
AlignV Align // Horizontal alignment
}
// colInfo keeps information about each grid column
type colInfo struct {
alignh *Align // optional column horizontal alignment
alignv *Align // optional column vertical alignment
}
// NewGridLayout creates and returns a pointer of a new grid layout
func NewGridLayout(ncols int) *GridLayout {
if ncols <= 0 {
panic("Invalid number of columns")
}
gl := new(GridLayout)
gl.columns = make([]colInfo, ncols)
return gl
}
// SetAlignV sets the vertical alignment for all the grid cells
// The alignment of an individual cell can be set by settings its layout parameters.
func (g *GridLayout) SetAlignV(align Align) {
g.alignv = align
g.Recalc(g.pan)
}
// SetAlignH sets the horizontal alignment for all the grid cells
// The alignment of an individual cell can be set by settings its layout parameters.
func (g *GridLayout) SetAlignH(align Align) {
g.alignh = align
g.Recalc(g.pan)
}
// SetExpandH sets it the columns should expand horizontally if possible
func (g *GridLayout) SetExpandH(expand bool) {
g.expandh = expand
g.Recalc(g.pan)
}
// SetExpandV sets it the rowss should expand vertically if possible
func (g *GridLayout) SetExpandV(expand bool) {
g.expandv = expand
g.Recalc(g.pan)
}
// SetColAlignV sets the vertical alignment for all the cells of
// the specified column. The function panics if the supplied column is invalid
func (g *GridLayout) SetColAlignV(col int, align Align) {
if col < 0 || col >= len(g.columns) {
panic("Invalid column")
}
if g.columns[col].alignv == nil {
g.columns[col].alignv = new(Align)
}
*g.columns[col].alignv = align
g.Recalc(g.pan)
}
// SetColAlignH sets the horizontal alignment for all the cells of
// the specified column. The function panics if the supplied column is invalid.
func (g *GridLayout) SetColAlignH(col int, align Align) {
if col < 0 || col >= len(g.columns) {
panic("Invalid column")
}
if g.columns[col].alignh == nil {
g.columns[col].alignh = new(Align)
}
*g.columns[col].alignh = align
g.Recalc(g.pan)
}
// Recalc sets the position and sizes of all of the panel's children.
// It is normally called by the parent panel when its size changes or
// a child is added or removed.
func (g *GridLayout) Recalc(ipan IPanel) {
type cell struct {
panel *Panel // pointer to cell panel
params GridLayoutParams // copy of params or default
paramsDef bool // true if parameters are default
}
type row struct {
cells []*cell // array of row cells
height float32 // row height
}
// Saves the received panel
g.pan = ipan
if g.pan == nil {
return
}
// Builds array of child rows
pan := ipan.GetPanel()
var irow int
var icol int
rows := []row{}
for _, node := range pan.Children() {
// Ignore invisible child
child := node.(IPanel).GetPanel()
if !child.Visible() {
continue
}
// Checks child layout params, if supplied
ip := child.layoutParams
var params *GridLayoutParams
var ok bool
var paramsDef bool
if ip != nil {
params, ok = child.layoutParams.(*GridLayoutParams)
if !ok {
panic("layoutParams is not GridLayoutParams")
}
paramsDef = false
} else {
params = &GridLayoutParams{}
paramsDef = true
}
// If first column, creates row and appends to rows
if icol == 0 {
var r row
r.cells = make([]*cell, len(g.columns))
rows = append(rows, r)
}
// Set current child panel to current cells
rows[irow].cells[icol] = &cell{child, *params, paramsDef}
// Updates next cell column and row
icol += 1 + params.ColSpan
if icol >= len(g.columns) {
irow++
icol = 0
}
}
// Sets the height of each row to the height of the heightest child
// Sets the width of each column to the width of the widest column
colWidths := make([]float32, len(g.columns))
spanWidths := make([]float32, len(g.columns))
for i := 0; i < len(rows); i++ {
r := &rows[i]
r.height = 0
for ci, cell := range r.cells {
if cell == nil {
continue
}
if cell.panel.Height() > r.height {
r.height = cell.panel.Height()
}
// If this cell span columns compare with other span cell widths
if cell.params.ColSpan > 0 {
if cell.panel.Width() > spanWidths[ci] {
spanWidths[ci] = cell.panel.Width()
}
} else {
if cell.panel.Width() > colWidths[ci] {
colWidths[ci] = cell.panel.Width()
}
}
}
}
// The final width for each column is the maximum no span column width
// but if it is zero, is the maximum span column width
for i := 0; i < len(colWidths); i++ {
if colWidths[i] == 0 {
colWidths[i] = spanWidths[i]
}
}
// If expand horizontally set, distribute available space between all columns
if g.expandh {
var twidth float32
for i := 0; i < len(colWidths); i++ {
twidth += colWidths[i]
}
space := pan.ContentWidth() - twidth
if space > 0 {
colspace := space / float32(len(colWidths))
for i := 0; i < len(colWidths); i++ {
colWidths[i] += colspace
}
}
}
// If expand vertically set, distribute available space between all rows
if g.expandv {
// Calculates the sum of all row heights
var theight float32
for _, r := range rows {
theight += r.height
}
// If space available distribute between all rows
space := pan.ContentHeight() - theight
if space > 0 {
rowspace := space / float32(len(rows))
for i := 0; i < len(rows); i++ {
rows[i].height += rowspace
}
}
}
// Position each child panel in the parent panel
var celly float32
for _, r := range rows {
var cellx float32
for ci, cell := range r.cells {
if cell == nil {
continue
}
colspan := 0
// Default grid cell alignment
alignv := g.alignv
alignh := g.alignh
// If column has alignment, use them
if g.columns[ci].alignv != nil {
alignv = *g.columns[ci].alignv
}
if g.columns[ci].alignh != nil {
alignh = *g.columns[ci].alignh
}
// If cell has layout parameters, use them
if !cell.paramsDef {
alignh = cell.params.AlignH
alignv = cell.params.AlignV
colspan = cell.params.ColSpan
}
// Calculates the available width for the cell considering colspan
var cellWidth float32
for i := ci; i < ci+colspan+1; i++ {
if i >= len(colWidths) {
break
}
cellWidth += colWidths[i]
}
// Determines child panel horizontal position
px := cellx
switch alignh {
case AlignNone:
case AlignLeft:
case AlignRight:
space := cellWidth - cell.panel.Width()
if space > 0 {
px += space
}
case AlignCenter:
space := (cellWidth - cell.panel.Width()) / 2
if space > 0 {
px += space
}
default:
panic("Invalid horizontal alignment")
}
// Determines child panel vertical position
py := celly
switch alignv {
case AlignNone:
case AlignTop:
case AlignBottom:
space := r.height - cell.panel.Height()
if space > 0 {
py += space
}
case AlignCenter:
space := (r.height - cell.panel.Height()) / 2
if space > 0 {
py += space
}
default:
panic("Invalid vertical alignment")
}
// Sets child panel position
cell.panel.SetPosition(px, py)
// Advances to next row cell considering colspan
cellx += cellWidth
}
celly += r.height
}
}