aerc/ui/grid.go
2018-02-17 15:21:31 -05:00

141 lines
3 KiB
Go

package ui
import "fmt"
type Grid struct {
Rows []DimSpec
rowLayout []dimLayout
Columns []DimSpec
columnLayout []dimLayout
Cells []*GridCell
onInvalidate func(d Drawable)
invalid bool
}
const (
SIZE_EXACT = iota
SIZE_WEIGHT = iota
)
// Specifies the layout of a single row or column
type DimSpec struct {
// One of SIZE_EXACT or SIZE_WEIGHT
Strategy int
// If Strategy = SIZE_EXACT, this is the number of cells this dim shall
// occupy. If SIZE_WEIGHT, the space left after all exact dims are measured
// is distributed amonst the remaining dims weighted by this value.
Size int
}
// Used to cache layout of each row/column
type dimLayout struct {
Offset int
Size int
}
type GridCell struct {
Row int
Column int
RowSpan int
ColSpan int
Content Drawable
invalid bool
}
func (grid *Grid) Draw(ctx *Context) {
invalid := grid.invalid
if invalid {
grid.reflow(ctx)
}
for _, cell := range grid.Cells {
if !cell.invalid && !invalid {
continue
}
rows := grid.rowLayout[cell.Row:cell.RowSpan]
cols := grid.columnLayout[cell.Column:cell.ColSpan]
x := cols[0].Offset
y := rows[0].Offset
width := 0
height := 0
for _, row := range rows {
width += row.Size
}
for _, col := range cols {
height += col.Size
}
subctx := ctx.Subcontext(x, y, width, height)
cell.Content.Draw(subctx)
}
}
func (grid *Grid) reflow(ctx *Context) {
grid.rowLayout = nil
grid.columnLayout = nil
flow := func(specs *[]DimSpec, layouts *[]dimLayout, extent int) {
exact := 0
weight := 0
for _, dim := range *specs {
if dim.Strategy == SIZE_EXACT {
exact += dim.Size
} else if dim.Strategy == SIZE_WEIGHT {
weight += dim.Size
}
}
offset := 0
for _, dim := range *specs {
layout := dimLayout{Offset: offset}
if dim.Strategy == SIZE_EXACT {
layout.Size = dim.Size
} else if dim.Strategy == SIZE_WEIGHT {
size := float64(dim.Size) / float64(weight) * float64(extent)
layout.Size = int(size)
}
*layouts = append(*layouts, layout)
}
}
flow(&grid.Rows, &grid.rowLayout, ctx.Width())
flow(&grid.Columns, &grid.columnLayout, ctx.Height())
grid.invalid = false
}
func (grid *Grid) InvalidateLayout() {
grid.invalid = true
}
func (grid *Grid) OnInvalidate(onInvalidate func(d Drawable)) {
grid.onInvalidate = onInvalidate
}
func (grid *Grid) AddChild(cell *GridCell) {
grid.Cells = append(grid.Cells, cell)
cell.Content.OnInvalidate(grid.cellInvalidated)
cell.invalid = true
grid.InvalidateLayout()
}
func (grid *Grid) RemoveChild(cell *GridCell) {
for i, _cell := range grid.Cells {
if _cell == cell {
grid.Cells = append(grid.Cells[:i], grid.Cells[i+1:]...)
break
}
}
grid.InvalidateLayout()
}
func (grid *Grid) cellInvalidated(drawable Drawable) {
var cell *GridCell
for _, cell = range grid.Cells {
if cell.Content == drawable {
break
}
cell = nil
}
if cell == nil {
panic(fmt.Errorf("Attempted to invalidate unknown cell"))
}
cell.invalid = true
if grid.onInvalidate != nil {
grid.onInvalidate(grid)
}
}