aerc/lib/ui/grid.go

193 lines
3.9 KiB
Go
Raw Normal View History

2018-02-16 05:05:07 +00:00
package ui
import (
"fmt"
"math"
)
2018-02-16 05:05:07 +00:00
// A container which arranges its children on a grid.
2018-02-16 05:05:07 +00:00
type Grid struct {
2018-02-18 01:11:58 +00:00
rows []GridSpec
rowLayout []gridLayout
columns []GridSpec
columnLayout []gridLayout
2018-02-28 00:30:59 +00:00
cells []*GridCell
2018-02-16 05:05:07 +00:00
onInvalidate func(d Drawable)
2018-02-17 20:21:22 +00:00
invalid bool
2018-02-16 05:05:07 +00:00
}
const (
SIZE_EXACT = iota
SIZE_WEIGHT = iota
)
// Specifies the layout of a single row or column
2018-02-18 01:11:58 +00:00
type GridSpec struct {
2018-02-16 05:05:07 +00:00
// One of SIZE_EXACT or SIZE_WEIGHT
2018-02-17 20:21:22 +00:00
Strategy int
2018-02-18 01:11:58 +00:00
// If Strategy = SIZE_EXACT, this is the number of cells this row/col shall
// occupy. If SIZE_WEIGHT, the space left after all exact rows/cols are
// measured is distributed amonst the remainder weighted by this value.
2018-02-17 20:21:22 +00:00
Size int
}
// Used to cache layout of each row/column
2018-02-18 01:11:58 +00:00
type gridLayout struct {
2018-02-17 20:21:22 +00:00
Offset int
Size int
2018-02-16 05:05:07 +00:00
}
type GridCell struct {
2018-02-17 20:21:22 +00:00
Row int
Column int
RowSpan int
ColSpan int
2018-02-16 05:05:07 +00:00
Content Drawable
invalid bool
}
func NewGrid() *Grid {
return &Grid{invalid: true}
}
func (cell *GridCell) At(row, col int) *GridCell {
cell.Row = row
cell.Column = col
return cell
}
func (cell *GridCell) Span(rows, cols int) *GridCell {
cell.RowSpan = rows
cell.ColSpan = cols
return cell
}
2018-02-18 01:11:58 +00:00
func (grid *Grid) Rows(spec []GridSpec) *Grid {
grid.rows = spec
return grid
}
func (grid *Grid) Columns(spec []GridSpec) *Grid {
grid.columns = spec
return grid
}
2018-02-17 20:21:22 +00:00
func (grid *Grid) Draw(ctx *Context) {
invalid := grid.invalid
if invalid {
grid.reflow(ctx)
}
2018-02-28 00:30:59 +00:00
for _, cell := range grid.cells {
2018-02-17 20:21:22 +00:00
if !cell.invalid && !invalid {
continue
}
rows := grid.rowLayout[cell.Row : cell.Row+cell.RowSpan]
cols := grid.columnLayout[cell.Column : cell.Column+cell.ColSpan]
2018-02-17 20:21:22 +00:00
x := cols[0].Offset
y := rows[0].Offset
width := 0
height := 0
for _, col := range cols {
width += col.Size
}
for _, row := range rows {
height += row.Size
2018-02-17 20:21:22 +00:00
}
subctx := ctx.Subcontext(x, y, width, height)
cell.Content.Draw(subctx)
}
}
func (grid *Grid) reflow(ctx *Context) {
grid.rowLayout = nil
grid.columnLayout = nil
2018-02-18 01:11:58 +00:00
flow := func(specs *[]GridSpec, layouts *[]gridLayout, extent int) {
2018-02-17 20:21:22 +00:00
exact := 0
weight := 0
nweights := 0
2018-02-18 01:11:58 +00:00
for _, spec := range *specs {
if spec.Strategy == SIZE_EXACT {
exact += spec.Size
} else if spec.Strategy == SIZE_WEIGHT {
nweights += 1
2018-02-18 01:11:58 +00:00
weight += spec.Size
2018-02-17 20:21:22 +00:00
}
}
offset := 0
2018-02-18 01:11:58 +00:00
for _, spec := range *specs {
layout := gridLayout{Offset: offset}
if spec.Strategy == SIZE_EXACT {
layout.Size = spec.Size
} else if spec.Strategy == SIZE_WEIGHT {
size := float64(spec.Size) / float64(weight)
size *= float64(extent - exact)
layout.Size = int(math.Floor(size))
2018-02-17 20:21:22 +00:00
}
offset += layout.Size
2018-02-17 20:21:22 +00:00
*layouts = append(*layouts, layout)
}
}
2018-02-18 01:11:58 +00:00
flow(&grid.rows, &grid.rowLayout, ctx.Height())
flow(&grid.columns, &grid.columnLayout, ctx.Width())
2018-02-17 20:21:22 +00:00
grid.invalid = false
}
func (grid *Grid) invalidateLayout() {
2018-02-17 20:21:22 +00:00
grid.invalid = true
if grid.onInvalidate != nil {
grid.onInvalidate(grid)
}
}
func (grid *Grid) Invalidate() {
grid.invalidateLayout()
2018-02-28 00:30:59 +00:00
for _, cell := range grid.cells {
cell.Content.Invalidate()
}
2018-02-16 05:05:07 +00:00
}
func (grid *Grid) OnInvalidate(onInvalidate func(d Drawable)) {
grid.onInvalidate = onInvalidate
}
func (grid *Grid) AddChild(content Drawable) *GridCell {
cell := &GridCell{
RowSpan: 1,
ColSpan: 1,
Content: content,
invalid: true,
}
2018-02-28 00:30:59 +00:00
grid.cells = append(grid.cells, cell)
2018-02-16 05:05:07 +00:00
cell.Content.OnInvalidate(grid.cellInvalidated)
cell.invalid = true
grid.invalidateLayout()
return cell
2018-02-16 05:05:07 +00:00
}
func (grid *Grid) RemoveChild(cell *GridCell) {
2018-02-28 00:30:59 +00:00
for i, _cell := range grid.cells {
2018-02-16 05:05:07 +00:00
if _cell == cell {
2018-02-28 00:30:59 +00:00
grid.cells = append(grid.cells[:i], grid.cells[i+1:]...)
2018-02-16 05:05:07 +00:00
break
}
}
grid.invalidateLayout()
2018-02-16 05:05:07 +00:00
}
func (grid *Grid) cellInvalidated(drawable Drawable) {
var cell *GridCell
2018-02-28 00:30:59 +00:00
for _, cell = range grid.cells {
2018-02-16 05:05:07 +00:00
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)
}
}