initial commit
This commit is contained in:
247
table.go
Normal file
247
table.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package gel
|
||||
|
||||
import (
|
||||
"image"
|
||||
"sort"
|
||||
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
)
|
||||
|
||||
type Cell struct {
|
||||
l.Widget
|
||||
dims l.Dimensions
|
||||
computed bool
|
||||
// priority only has meaning for the header row in defining an order of eliminating elements to fit a width.
|
||||
// When trimming size to fit width add from highest to lowest priority and stop when dimensions exceed the target.
|
||||
Priority int
|
||||
}
|
||||
|
||||
func (c *Cell) getWidgetDimensions(gtx l.Context) {
|
||||
if c.Widget == nil {
|
||||
// this happens when new items are added if a frame reads the cell, it just - can't - be rendered!
|
||||
return
|
||||
}
|
||||
if c.computed {
|
||||
return
|
||||
}
|
||||
// gather the dimensions of the list elements
|
||||
gtx.Ops.Reset()
|
||||
child := op.Record(gtx.Ops)
|
||||
c.dims = c.Widget(gtx)
|
||||
c.computed = true
|
||||
_ = child.Stop()
|
||||
return
|
||||
}
|
||||
|
||||
type CellRow []Cell
|
||||
|
||||
func (c CellRow) GetPriority() (out CellPriorities) {
|
||||
for i := range c {
|
||||
var cp CellPriority
|
||||
cp.Priority = c[i].Priority
|
||||
cp.Column = i
|
||||
out = append(out, cp)
|
||||
}
|
||||
sort.Sort(out)
|
||||
return
|
||||
}
|
||||
|
||||
type CellPriority struct {
|
||||
Column int
|
||||
Priority int
|
||||
}
|
||||
|
||||
type CellPriorities []CellPriority
|
||||
|
||||
// Len sorts a cell row by priority
|
||||
func (c CellPriorities) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
func (c CellPriorities) Less(i, j int) bool {
|
||||
return c[i].Priority < c[j].Priority
|
||||
}
|
||||
func (c CellPriorities) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
type CellGrid []CellRow
|
||||
|
||||
// Table is a super simple table widget that finds the dimensions of all cells, sets all to max of each axis, and then
|
||||
// scales the remaining space evenly
|
||||
type Table struct {
|
||||
*Window
|
||||
header CellRow
|
||||
body CellGrid
|
||||
list *List
|
||||
Y, X []int
|
||||
headerBackground string
|
||||
cellBackground string
|
||||
reverse bool
|
||||
}
|
||||
|
||||
func (w *Window) Table() *Table {
|
||||
return &Table{
|
||||
Window: w,
|
||||
list: w.List(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) SetReverse(color string) *Table {
|
||||
t.reverse = true
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Table) HeaderBackground(color string) *Table {
|
||||
t.headerBackground = color
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Table) CellBackground(color string) *Table {
|
||||
t.cellBackground = color
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Table) Header(h CellRow) *Table {
|
||||
t.header = h
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Table) Body(g CellGrid) *Table {
|
||||
t.body = g
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Table) Fn(gtx l.Context) l.Dimensions {
|
||||
if len(t.header) == 0 {
|
||||
return l.Dimensions{}
|
||||
}
|
||||
for i := range t.body {
|
||||
if len(t.header) != len(t.body[i]) {
|
||||
// Row has mismatched column count - skip rendering
|
||||
D.Ln("table row", i, "has", len(t.body[i]), "cells but header has", len(t.header))
|
||||
return l.Dimensions{}
|
||||
}
|
||||
}
|
||||
gtx1 := CopyContextDimensionsWithMaxAxis(gtx, l.Vertical)
|
||||
gtx1.Constraints.Max = image.Point{X: Inf, Y: Inf}
|
||||
// gather the dimensions from all cells
|
||||
for i := range t.header {
|
||||
t.header[i].getWidgetDimensions(gtx1)
|
||||
}
|
||||
for i := range t.body {
|
||||
for j := range t.body[i] {
|
||||
t.body[i][j].getWidgetDimensions(gtx1)
|
||||
}
|
||||
}
|
||||
|
||||
// find the max of each row and column
|
||||
var table CellGrid
|
||||
table = append(table, t.header)
|
||||
table = append(table, t.body...)
|
||||
t.Y = make([]int, len(table))
|
||||
t.X = make([]int, len(table[0]))
|
||||
for i := range table {
|
||||
for j := range table[i] {
|
||||
y := table[i][j].dims.Size.Y
|
||||
if y > t.Y[i] {
|
||||
t.Y[i] = y
|
||||
}
|
||||
x := table[i][j].dims.Size.X
|
||||
if x > t.X[j] {
|
||||
t.X[j] = x
|
||||
}
|
||||
}
|
||||
}
|
||||
var total int
|
||||
for i := range t.X {
|
||||
total += t.X[i]
|
||||
}
|
||||
maxWidth := gtx.Constraints.Max.X
|
||||
for i := range t.X {
|
||||
t.X[i] = int(float32(t.X[i]) * float32(maxWidth) / float32(total))
|
||||
}
|
||||
header := t.Theme.Flex() // .SpaceEvenly()
|
||||
for x, oi := range t.header {
|
||||
i := x
|
||||
// header is not in the list but drawn above it
|
||||
oie := oi
|
||||
txi := t.X[i]
|
||||
tyi := t.Y[0]
|
||||
header.Rigid(func(gtx l.Context) l.Dimensions {
|
||||
cs := gtx.Constraints
|
||||
cs.Max.X = txi
|
||||
cs.Min.X = gtx.Constraints.Max.X
|
||||
cs.Max.Y = tyi
|
||||
cs.Min.Y = gtx.Constraints.Max.Y
|
||||
// gtx.Constraints.Constrain(image.Point{X: txi, Y: tyi})
|
||||
dims := t.Fill(t.headerBackground, l.Center, float32(t.TextSize), 0, EmptySpace(txi, tyi)).Fn(gtx)
|
||||
oie.Widget(gtx)
|
||||
return dims
|
||||
})
|
||||
}
|
||||
|
||||
var out CellGrid
|
||||
out = CellGrid{t.header}
|
||||
if t.reverse {
|
||||
// append the body elements in reverse order stored
|
||||
lb := len(t.body) - 1
|
||||
for i := range t.body {
|
||||
out = append(out, t.body[lb-i])
|
||||
}
|
||||
} else {
|
||||
out = append(out, t.body...)
|
||||
}
|
||||
le := func(gtx l.Context, index int) l.Dimensions {
|
||||
f := t.Theme.Flex() // .SpaceEvenly()
|
||||
oi := out[index]
|
||||
for x, oiee := range oi {
|
||||
i := x
|
||||
if index == 0 {
|
||||
// we skip the header, not implemented but the header could be part of the scrollable area if need
|
||||
// arises later, unwrap this block on a flag
|
||||
} else {
|
||||
if index >= len(t.Y) {
|
||||
break
|
||||
}
|
||||
oie := oiee
|
||||
txi := t.X[i]
|
||||
tyi := t.Y[index]
|
||||
f.Rigid(t.Fill(t.cellBackground, l.Center, float32(t.TextSize), 0, func(gtx l.Context) l.Dimensions {
|
||||
cs := gtx.Constraints
|
||||
cs.Max.X = txi
|
||||
cs.Min.X = gtx.Constraints.Max.X
|
||||
cs.Max.Y = tyi
|
||||
cs.Min.Y = gtx.Constraints.Max.Y // gtx.Constraints.Constrain(image.Point{
|
||||
// X: t.X[i],
|
||||
// Y: t.Y[index],
|
||||
// })
|
||||
gtx.Constraints.Max.X = txi
|
||||
// gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||
gtx.Constraints.Max.Y = tyi
|
||||
// gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
||||
dims := EmptySpace(txi, tyi)(gtx)
|
||||
// dims
|
||||
oie.Widget(gtx)
|
||||
return dims
|
||||
}).Fn)
|
||||
}
|
||||
}
|
||||
return f.Fn(gtx)
|
||||
}
|
||||
return t.Theme.VFlex().
|
||||
Rigid(func(gtx l.Context) l.Dimensions {
|
||||
// header is fixed to the top of the widget
|
||||
return t.Fill(t.headerBackground, l.Center, float32(t.TextSize), 0, header.Fn).Fn(gtx)
|
||||
}).
|
||||
Flexed(1,
|
||||
t.Fill(t.cellBackground, l.Center, float32(t.TextSize), 0, func(gtx l.Context) l.Dimensions {
|
||||
return t.list.Vertical().
|
||||
Length(len(out)).
|
||||
Background(t.cellBackground).
|
||||
ListElement(le).
|
||||
Fn(gtx)
|
||||
}).Fn,
|
||||
).
|
||||
Fn(gtx)
|
||||
}
|
||||
Reference in New Issue
Block a user