aboutsummaryrefslogtreecommitdiff
path: root/lib/ui/grid.go
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2018-02-26 22:54:39 -0500
committerDrew DeVault <sir@cmpwn.com>2018-02-26 22:54:39 -0500
commit1418e1b9dc41d8f69bccb8de0fe0f1fb6835ce11 (patch)
tree4ae8b3373fdadb6dd3e7b8c8789cf938522b8f8a /lib/ui/grid.go
parent661e3ec2a4dd97d4a8a8eab4f281b088770a6af2 (diff)
Split UI library and widgets
Diffstat (limited to 'lib/ui/grid.go')
-rw-r--r--lib/ui/grid.go191
1 files changed, 191 insertions, 0 deletions
diff --git a/lib/ui/grid.go b/lib/ui/grid.go
new file mode 100644
index 0000000..ede7d0c
--- /dev/null
+++ b/lib/ui/grid.go
@@ -0,0 +1,191 @@
+package ui
+
+import (
+ "fmt"
+ "math"
+)
+
+type Grid struct {
+ rows []GridSpec
+ rowLayout []gridLayout
+ columns []GridSpec
+ columnLayout []gridLayout
+ 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 GridSpec struct {
+ // One of SIZE_EXACT or SIZE_WEIGHT
+ Strategy int
+ // 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.
+ Size int
+}
+
+// Used to cache layout of each row/column
+type gridLayout struct {
+ Offset int
+ Size int
+}
+
+type GridCell struct {
+ Row int
+ Column int
+ RowSpan int
+ ColSpan int
+ 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
+}
+
+func (grid *Grid) Rows(spec []GridSpec) *Grid {
+ grid.rows = spec
+ return grid
+}
+
+func (grid *Grid) Columns(spec []GridSpec) *Grid {
+ grid.columns = spec
+ return grid
+}
+
+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.Row+cell.RowSpan]
+ cols := grid.columnLayout[cell.Column : cell.Column+cell.ColSpan]
+ 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
+ }
+ 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 *[]GridSpec, layouts *[]gridLayout, extent int) {
+ exact := 0
+ weight := 0
+ nweights := 0
+ for _, spec := range *specs {
+ if spec.Strategy == SIZE_EXACT {
+ exact += spec.Size
+ } else if spec.Strategy == SIZE_WEIGHT {
+ nweights += 1
+ weight += spec.Size
+ }
+ }
+ offset := 0
+ 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))
+ }
+ offset += layout.Size
+ *layouts = append(*layouts, layout)
+ }
+ }
+ flow(&grid.rows, &grid.rowLayout, ctx.Height())
+ flow(&grid.columns, &grid.columnLayout, ctx.Width())
+ grid.invalid = false
+}
+
+func (grid *Grid) invalidateLayout() {
+ grid.invalid = true
+ if grid.onInvalidate != nil {
+ grid.onInvalidate(grid)
+ }
+}
+
+func (grid *Grid) Invalidate() {
+ grid.invalidateLayout()
+ for _, cell := range grid.Cells {
+ cell.Content.Invalidate()
+ }
+}
+
+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,
+ }
+ grid.Cells = append(grid.Cells, cell)
+ cell.Content.OnInvalidate(grid.cellInvalidated)
+ cell.invalid = true
+ grid.invalidateLayout()
+ return cell
+}
+
+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)
+ }
+}