forked from icza/gowut
-
Notifications
You must be signed in to change notification settings - Fork 0
/
panel.go
390 lines (322 loc) · 9.49 KB
/
panel.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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
// Copyright (C) 2013 Andras Belicza. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Panel component interface and implementation.
package gwu
import (
"bytes"
)
// Layout strategy type.
type Layout int
// Layout strategies.
const (
LayoutNatural Layout = iota // Natural layout: elements are displayed in their natural order.
LayoutVertical // Vertical layout: elements are laid out vertically.
LayoutHorizontal // Horizontal layout: elements are laid out horizontally.
)
// PanelView interface defines a container which stores child components
// sequentially (one dimensional, associated with an index), and lays out
// its children in a row or column using TableView based on a layout strategy,
// but does not define the way how child components can be added.
//
// Default style class: "gwu-Panel"
type PanelView interface {
// PanelView is a TableView.
TableView
// Layout returns the layout strategy used to lay out components when rendering.
Layout() Layout
// SetLayout sets the layout strategy used to lay out components when rendering.
SetLayout(layout Layout)
// CompsCount returns the number of components added to the panel.
CompsCount() int
// CompAt returns the component at the specified index.
// Returns nil if idx<0 or idx>=CompsCount().
CompAt(idx int) Comp
// CompIdx returns the index of the specified component in the panel.
// -1 is returned if the component is not added to the panel.
CompIdx(c Comp) int
// CellFmt returns the cell formatter of the specified child component.
// If the specified component is not a child, nil is returned.
// Cell formatting has no effect if layout is LayoutNatural.
CellFmt(c Comp) CellFmt
}
// Panel interface defines a container which stores child components
// associated with an index, and lays out its children based on a layout
// strategy.
// Default style class: "gwu-Panel"
type Panel interface {
// Panel is a PanelView.
PanelView
// Add adds a component to the panel.
Add(c Comp)
// Insert inserts a component at the specified index.
// Returns true if the index was valid and the component is inserted
// successfully, false otherwise. idx=CompsCount() is also allowed
// in which case comp will be the last component.
Insert(c Comp, idx int) bool
// AddHSpace adds and returns a fixed-width horizontal space consumer.
// Useful when layout is LayoutHorizontal.
AddHSpace(width int) Comp
// AddVSpace adds and returns a fixed-height vertical space consumer.
// Useful when layout is LayoutVertical.
AddVSpace(height int) Comp
// AddSpace adds and returns a fixed-size space consumer.
AddSpace(width, height int) Comp
// AddHConsumer adds and returns a horizontal (free) space consumer.
// Useful when layout is LayoutHorizontal.
//
// Tip: When adding a horizontal space consumer, you may set the
// white space style attribute of other components in the the panel
// to WhiteSpaceNowrap to avoid texts getting wrapped to multiple lines.
AddHConsumer() Comp
// AddVConsumer adds and returns a vertical (free) space consumer.
// Useful when layout is LayoutVertical.
AddVConsumer() Comp
}
// Panel implementation.
type panelImpl struct {
tableViewImpl // TableView implementation
layout Layout // Layout strategy
comps []Comp // Components added to this panel
cellFmts map[ID]*cellFmtImpl // Lazily initialized cell formatters of the child components
}
// NewPanel creates a new Panel.
// Default layout strategy is LayoutVertical,
// default horizontal alignment is HADefault,
// default vertical alignment is VADefault.
func NewPanel() Panel {
c := newPanelImpl()
c.Style().AddClass("gwu-Panel")
return &c
}
// NewNaturalPanel creates a new Panel initialized with
// LayoutNatural layout.
// Default horizontal alignment is HADefault,
// default vertical alignment is VADefault.
func NewNaturalPanel() Panel {
p := NewPanel()
p.SetLayout(LayoutNatural)
return p
}
// NewHorizontalPanel creates a new Panel initialized with
// LayoutHorizontal layout.
// Default horizontal alignment is HADefault,
// default vertical alignment is VADefault.
func NewHorizontalPanel() Panel {
p := NewPanel()
p.SetLayout(LayoutHorizontal)
return p
}
// NewVerticalPanel creates a new Panel initialized with
// LayoutVertical layout.
// Default horizontal alignment is HADefault,
// default vertical alignment is VADefault.
func NewVerticalPanel() Panel {
return NewPanel()
}
// newPanelImpl creates a new panelImpl.
func newPanelImpl() panelImpl {
return panelImpl{tableViewImpl: newTableViewImpl(), layout: LayoutVertical, comps: make([]Comp, 0, 2)}
}
func (c *panelImpl) Remove(c2 Comp) bool {
i := c.CompIdx(c2)
if i < 0 {
return false
}
// Remove associated cell formatter
if c.cellFmts != nil {
delete(c.cellFmts, c2.ID())
}
c2.setParent(nil)
// When removing, also reference must be cleared to allow the comp being gc'ed, also to prevent memory leak.
oldComps := c.comps
// Copy the part after the removable comp, backward by 1:
c.comps = append(oldComps[:i], oldComps[i+1:]...)
// Clear the reference that becomes unused:
oldComps[len(oldComps)-1] = nil
return true
}
func (c *panelImpl) ByID(id ID) Comp {
if c.id == id {
return c
}
for _, c2 := range c.comps {
if c2.ID() == id {
return c2
}
if c3, isContainer := c2.(Container); isContainer {
if c4 := c3.ByID(id); c4 != nil {
return c4
}
}
}
return nil
}
func (c *panelImpl) Clear() {
// Clear cell formatters
if c.cellFmts != nil {
c.cellFmts = nil
}
for _, c2 := range c.comps {
c2.setParent(nil)
}
c.comps = nil
}
func (c *panelImpl) Layout() Layout {
return c.layout
}
func (c *panelImpl) SetLayout(layout Layout) {
c.layout = layout
}
func (c *panelImpl) CompsCount() int {
return len(c.comps)
}
func (c *panelImpl) CompAt(idx int) Comp {
if idx < 0 || idx >= len(c.comps) {
return nil
}
return c.comps[idx]
}
func (c *panelImpl) CompIdx(c2 Comp) int {
for i, c3 := range c.comps {
if c2.Equals(c3) {
return i
}
}
return -1
}
func (c *panelImpl) CellFmt(c2 Comp) CellFmt {
if c.CompIdx(c2) < 0 {
return nil
}
if c.cellFmts == nil {
c.cellFmts = make(map[ID]*cellFmtImpl)
}
cf := c.cellFmts[c2.ID()]
if cf == nil {
cf = newCellFmtImpl()
c.cellFmts[c2.ID()] = cf
}
return cf
}
func (c *panelImpl) Add(c2 Comp) {
c2.makeOrphan()
c.comps = append(c.comps, c2)
c2.setParent(c)
}
func (c *panelImpl) Insert(c2 Comp, idx int) bool {
if idx < 0 || idx > len(c.comps) {
return false
}
c2.makeOrphan()
// Make sure we have room for the extra component:
c.comps = append(c.comps, nil)
copy(c.comps[idx+1:], c.comps[idx:len(c.comps)-1])
c.comps[idx] = c2
c2.setParent(c)
return true
}
func (c *panelImpl) AddHSpace(width int) Comp {
l := NewLabel("")
l.Style().SetDisplay(DisplayBlock).SetWidthPx(width)
c.Add(l)
return l
}
func (c *panelImpl) AddVSpace(height int) Comp {
l := NewLabel("")
l.Style().SetDisplay(DisplayBlock).SetHeightPx(height)
c.Add(l)
return l
}
func (c *panelImpl) AddSpace(width, height int) Comp {
l := NewLabel("")
l.Style().SetDisplay(DisplayBlock).SetSizePx(width, height)
c.Add(l)
return l
}
func (c *panelImpl) AddHConsumer() Comp {
l := NewLabel("")
c.Add(l)
c.CellFmt(l).Style().SetFullWidth()
return l
}
func (c *panelImpl) AddVConsumer() Comp {
l := NewLabel("")
c.Add(l)
c.CellFmt(l).Style().SetFullHeight()
return l
}
func (c *panelImpl) Render(w Writer) {
switch c.layout {
case LayoutNatural:
c.layoutNatural(w)
case LayoutHorizontal:
c.layoutHorizontal(w)
case LayoutVertical:
c.layoutVertical(w)
}
}
// layoutNatural renders the panel and the child components
// using the natural layout strategy.
func (c *panelImpl) layoutNatural(w Writer) {
// No wrapper table but we still need a wrapper tag for attributes...
w.Write(strSpanOp)
c.renderAttrsAndStyle(w)
c.renderEHandlers(w)
w.Write(strGT)
for _, c2 := range c.comps {
c2.Render(w)
}
w.Write(strSpanCl)
}
// layoutHorizontal renders the panel and the child components
// using the horizontal layout strategy.
func (c *panelImpl) layoutHorizontal(w Writer) {
w.Write(strTableOp)
c.renderAttrsAndStyle(w)
c.renderEHandlers(w)
w.Write(strGT)
c.renderTr(w)
for _, c2 := range c.comps {
c.renderTd(c2, w)
c2.Render(w)
}
w.Write(strTableCl)
}
// layoutVertical renders the panel and the child components
// using the vertical layout strategy.
func (c *panelImpl) layoutVertical(w Writer) {
w.Write(strTableOp)
c.renderAttrsAndStyle(w)
c.renderEHandlers(w)
w.Write(strGT)
// There is the same TR tag for each cell:
trWriter := bytes.NewBuffer(nil)
c.renderTr(NewWriter(trWriter))
tr := trWriter.Bytes()
for _, c2 := range c.comps {
w.Write(tr)
c.renderTd(c2, w)
c2.Render(w)
}
w.Write(strTableCl)
}
// renderTd renders the formatted HTML TD tag for the specified child component.
func (c *panelImpl) renderTd(c2 Comp, w Writer) {
if cf := c.cellFmts[c2.ID()]; cf == nil {
w.Write(strTD)
} else {
cf.render(strTDOp, w)
}
}