summary refs log blame commit diff stats
path: root/ui/grid.go
blob: 2183a557dc5a7f8323308f559a7dcc57b7012f24 (plain) (tree)
1
2
3
4
5
6
7
8
9





                              
                                
                              
                                

                                     
                         









                                                 
                    


                                                                                   






                                          


                      



                   



                        

























































                                                                                             









                                                               
                               








                                                                                
                               

















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