248 lines
5.7 KiB
Go
248 lines
5.7 KiB
Go
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)
|
|
}
|