table.go raw
1 package gel
2
3 import (
4 "image"
5 "sort"
6
7 l "github.com/p9c/gio/layout"
8 "github.com/p9c/gio/op"
9 )
10
11 type Cell struct {
12 l.Widget
13 dims l.Dimensions
14 computed bool
15 // priority only has meaning for the header row in defining an order of eliminating elements to fit a width.
16 // When trimming size to fit width add from highest to lowest priority and stop when dimensions exceed the target.
17 Priority int
18 }
19
20 func (c *Cell) getWidgetDimensions(gtx l.Context) {
21 if c.Widget == nil {
22 // this happens when new items are added if a frame reads the cell, it just - can't - be rendered!
23 return
24 }
25 if c.computed {
26 return
27 }
28 // gather the dimensions of the list elements
29 gtx.Ops.Reset()
30 child := op.Record(gtx.Ops)
31 c.dims = c.Widget(gtx)
32 c.computed = true
33 _ = child.Stop()
34 return
35 }
36
37 type CellRow []Cell
38
39 func (c CellRow) GetPriority() (out CellPriorities) {
40 for i := range c {
41 var cp CellPriority
42 cp.Priority = c[i].Priority
43 cp.Column = i
44 out = append(out, cp)
45 }
46 sort.Sort(out)
47 return
48 }
49
50 type CellPriority struct {
51 Column int
52 Priority int
53 }
54
55 type CellPriorities []CellPriority
56
57 // Len sorts a cell row by priority
58 func (c CellPriorities) Len() int {
59 return len(c)
60 }
61 func (c CellPriorities) Less(i, j int) bool {
62 return c[i].Priority < c[j].Priority
63 }
64 func (c CellPriorities) Swap(i, j int) {
65 c[i], c[j] = c[j], c[i]
66 }
67
68 type CellGrid []CellRow
69
70 // Table is a super simple table widget that finds the dimensions of all cells, sets all to max of each axis, and then
71 // scales the remaining space evenly
72 type Table struct {
73 *Window
74 header CellRow
75 body CellGrid
76 list *List
77 Y, X []int
78 headerBackground string
79 cellBackground string
80 reverse bool
81 }
82
83 func (w *Window) Table() *Table {
84 return &Table{
85 Window: w,
86 list: w.List(),
87 }
88 }
89
90 func (t *Table) SetReverse(color string) *Table {
91 t.reverse = true
92 return t
93 }
94
95 func (t *Table) HeaderBackground(color string) *Table {
96 t.headerBackground = color
97 return t
98 }
99
100 func (t *Table) CellBackground(color string) *Table {
101 t.cellBackground = color
102 return t
103 }
104
105 func (t *Table) Header(h CellRow) *Table {
106 t.header = h
107 return t
108 }
109
110 func (t *Table) Body(g CellGrid) *Table {
111 t.body = g
112 return t
113 }
114
115 func (t *Table) Fn(gtx l.Context) l.Dimensions {
116 // D.Ln(len(t.body), len(t.header))
117 if len(t.header) == 0 {
118 return l.Dimensions{}
119 }
120 // if len(t.body) == 0 || len(t.header) == 0 {
121 // return l.Dimensions{}
122 // }
123
124 for i := range t.body {
125 if len(t.header) != len(t.body[i]) {
126 // this should never happen hence panic
127 panic("not all rows are equal number of cells")
128 }
129 }
130 gtx1 := CopyContextDimensionsWithMaxAxis(gtx, l.Vertical)
131 gtx1.Constraints.Max = image.Point{X: Inf, Y: Inf}
132 // gather the dimensions from all cells
133 for i := range t.header {
134 t.header[i].getWidgetDimensions(gtx1)
135 }
136 // D.S(t.header)
137 for i := range t.body {
138 for j := range t.body[i] {
139 t.body[i][j].getWidgetDimensions(gtx1)
140 }
141 }
142 // D.S(t.body)
143
144 // find the max of each row and column
145 var table CellGrid
146 table = append(table, t.header)
147 table = append(table, t.body...)
148 t.Y = make([]int, len(table))
149 t.X = make([]int, len(table[0]))
150 for i := range table {
151 for j := range table[i] {
152 y := table[i][j].dims.Size.Y
153 if y > t.Y[i] {
154 t.Y[i] = y
155 }
156 x := table[i][j].dims.Size.X
157 if x > t.X[j] {
158 t.X[j] = x
159 }
160 }
161 }
162 // // D.S(t.Y)
163 // D.S(t.X)
164 var total int
165 for i := range t.X {
166 total += t.X[i]
167 }
168 // D.S(t.X)
169 // D.Ln(total)
170 maxWidth := gtx.Constraints.Max.X
171 for i := range t.X {
172 t.X[i] = int(float32(t.X[i]) * float32(maxWidth) / float32(total))
173 }
174 // D.S(t.X)
175 // D.Ln(maxWidth)
176 // // find the columns that will be rendered into the existing width
177 // // D.S(t.header)
178 // priorities := t.header.GetPriority()
179 // // D.S(priorities)
180 // var runningTotal, prev int
181 // columnsToRender := make([]int, 0)
182 // for i := range priorities {
183 // prev = runningTotal
184 // x := t.header[priorities[i].Column].dims.Size.X
185 // // D.Ln(priorities[i], x)
186 // runningTotal += x
187 //
188 // if runningTotal > maxWidth {
189 // // D.Ln(runningTotal, prev, maxWidth)
190 // break
191 // }
192 // columnsToRender = append(columnsToRender, priorities[i].Column)
193 // }
194 // // txsort the columns to render into their original order
195 // txsort.Ints(columnsToRender)
196 // // D.S(columnsToRender)
197 // // D.Ln(len(columnsToRender))
198 // // All fields will be expanded by the following ratio to reach the target width
199 // expansionFactor := float32(maxWidth) / float32(prev)
200 // outColWidths := make([]int, len(columnsToRender))
201 // for i := range columnsToRender {
202 // outColWidths[i] = int(float32(t.X[columnsToRender[i]]) * expansionFactor)
203 // }
204 // // D.Ln(outColWidths)
205 // // assemble the grid to be rendered as a two dimensional slice
206 // grid := make([][]l.Widget, len(t.body)+1)
207 // for i := 0; i < len(columnsToRender); i++ {
208 // grid[0] = append(grid[0], t.header[columnsToRender[i]].Widget)
209 // }
210 // // for i := 0; i < len(columnsToRender); i++ {
211 // // for j := range t.body[i] {
212 // // grid[i+1] = append(grid[i+1], t.body[i][j].Widget)
213 // // }
214 // // }
215 // // D.S(grid)
216 // // assemble each row into a flex
217 // out := make([]l.Widget, len(grid))
218 // for i := range grid {
219 // outFlex := t.Theme.Flex()
220 // for jj, j := range grid[i] {
221 // x := j
222 // _ = jj
223 // // outFlex.Rigid(x)
224 // outFlex.Rigid(func(gtx l.Context) l.Dimensions {
225 // // lock the cell to the calculated width.
226 // gtx.Constraints.Max.X = outColWidths[jj]
227 // gtx.Constraints.Min.X = gtx.Constraints.Max.X
228 // return x(gtx)
229 // })
230 // }
231 // out[i] = outFlex.Fn
232 // }
233 header := t.Theme.Flex() // .SpaceEvenly()
234 for x, oi := range t.header {
235 i := x
236 // header is not in the list but drawn above it
237 oie := oi
238 txi := t.X[i]
239 tyi := t.Y[0]
240 header.Rigid(func(gtx l.Context) l.Dimensions {
241 cs := gtx.Constraints
242 cs.Max.X = txi
243 cs.Min.X = gtx.Constraints.Max.X
244 cs.Max.Y = tyi
245 cs.Min.Y = gtx.Constraints.Max.Y
246 // gtx.Constraints.Constrain(image.Point{X: txi, Y: tyi})
247 dims := t.Fill(t.headerBackground, l.Center, t.TextSize.V, 0, EmptySpace(txi, tyi)).Fn(gtx)
248 oie.Widget(gtx)
249 return dims
250 })
251 }
252
253 var out CellGrid
254 out = CellGrid{t.header}
255 if t.reverse {
256 // append the body elements in reverse order stored
257 lb := len(t.body) - 1
258 for i := range t.body {
259 out = append(out, t.body[lb-i])
260 }
261 } else {
262 out = append(out, t.body...)
263 }
264 le := func(gtx l.Context, index int) l.Dimensions {
265 f := t.Theme.Flex() // .SpaceEvenly()
266 oi := out[index]
267 for x, oiee := range oi {
268 i := x
269 if index == 0 {
270 // we skip the header, not implemented but the header could be part of the scrollable area if need
271 // arises later, unwrap this block on a flag
272 } else {
273 if index >= len(t.Y) {
274 break
275 }
276 oie := oiee
277 txi := t.X[i]
278 tyi := t.Y[index]
279 f.Rigid(t.Fill(t.cellBackground, l.Center, t.TextSize.V, 0, func(gtx l.Context) l.Dimensions {
280 cs := gtx.Constraints
281 cs.Max.X = txi
282 cs.Min.X = gtx.Constraints.Max.X
283 cs.Max.Y = tyi
284 cs.Min.Y = gtx.Constraints.Max.Y // gtx.Constraints.Constrain(image.Point{
285 // X: t.X[i],
286 // Y: t.Y[index],
287 // })
288 gtx.Constraints.Max.X = txi
289 // gtx.Constraints.Min.X = gtx.Constraints.Max.X
290 gtx.Constraints.Max.Y = tyi
291 // gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
292 dims := EmptySpace(txi, tyi)(gtx)
293 // dims
294 oie.Widget(gtx)
295 return dims
296 }).Fn)
297 }
298 }
299 return f.Fn(gtx)
300 }
301 return t.Theme.VFlex().
302 Rigid(func(gtx l.Context) l.Dimensions {
303 // header is fixed to the top of the widget
304 return t.Fill(t.headerBackground, l.Center, t.TextSize.V, 0, header.Fn).Fn(gtx)
305 }).
306 Flexed(1,
307 t.Fill(t.cellBackground, l.Center, t.TextSize.V, 0, func(gtx l.Context) l.Dimensions {
308 return t.list.Vertical().
309 Length(len(out)).
310 Background(t.cellBackground).
311 ListElement(le).
312 Fn(gtx)
313 }).Fn,
314 ).
315 Fn(gtx)
316 }
317