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) }