2014-02-25 17:21:58 -06:00
// 25 february 2014
package ui
import (
2014-02-25 17:40:36 -06:00
"fmt"
2014-02-25 17:21:58 -06:00
"sync"
)
// A Grid arranges Controls in a two-dimensional grid.
// The height of each row and the width of each column is the maximum preferred height and width (respectively) of all the controls in that row or column (respectively).
2014-02-25 18:05:01 -06:00
// Controls are aligned to the top left corner of each cell.
2014-02-25 20:54:13 -06:00
// All Controls in a Grid maintain their preferred sizes by default; if a Control is marked as being "filling", it will be sized to fill its cell.
// Even if a Control is marked as filling, its preferred size is used to calculate cell sizes.
2014-02-26 06:01:02 -06:00
// One Control can be marked as "stretchy": when the Window containing the Grid is resized, the cell containing that Control resizes to take any remaining space; its row and column are adjusted accordingly (so other filling controls in the same row and column will fill to the new height and width, respectively).
// A stretchy Control implicitly fills its cell.
2014-02-25 20:54:13 -06:00
// All cooridnates in a Grid are given in (row,column) form with (0,0) being the top-left cell.
2014-02-25 18:10:09 -06:00
// Unlike other UI toolkit Grids, this Grid does not (yet? TODO) allow Controls to span multiple rows or columns.
2014-02-25 18:05:01 -06:00
// TODO differnet row/column control alignment; stretchy controls or other resizing options
2014-02-25 17:21:58 -06:00
type Grid struct {
lock sync . Mutex
created bool
controls [ ] [ ] Control
2014-02-25 20:54:13 -06:00
filling [ ] [ ] bool
2014-02-26 06:01:02 -06:00
stretchyrow , stretchycol int
2014-02-25 17:40:36 -06:00
widths , heights [ ] [ ] int // caches to avoid reallocating each time
2014-02-25 17:21:58 -06:00
rowheights , colwidths [ ] int
}
// NewGrid creates a new Grid with the given Controls.
// NewGrid needs to know the number of Controls in a row (alternatively, the number of columns); it will determine the number in a column from the number of Controls given.
// NewGrid panics if not given a full grid of Controls.
// Example:
// grid := NewGrid(3,
// control00, control01, control02,
// control10, control11, control12,
// control20, control21, control22)
func NewGrid ( nPerRow int , controls ... Control ) * Grid {
if len ( controls ) % nPerRow != 0 {
panic ( fmt . Errorf ( "incomplete grid given to NewGrid() (not enough controls to evenly divide %d controls into rows of %d controls each)" , len ( controls ) , nPerRow ) )
}
2014-02-25 17:40:36 -06:00
nRows := len ( controls ) / nPerRow
2014-02-25 17:21:58 -06:00
cc := make ( [ ] [ ] Control , nRows )
2014-02-25 20:54:13 -06:00
cf := make ( [ ] [ ] bool , nRows )
2014-02-25 17:21:58 -06:00
cw := make ( [ ] [ ] int , nRows )
2014-02-25 17:40:36 -06:00
ch := make ( [ ] [ ] int , nRows )
2014-02-25 17:21:58 -06:00
i := 0
for row := 0 ; row < nRows ; row ++ {
cc [ row ] = make ( [ ] Control , nPerRow )
2014-02-25 20:54:13 -06:00
cf [ row ] = make ( [ ] bool , nPerRow )
2014-02-25 17:21:58 -06:00
cw [ row ] = make ( [ ] int , nPerRow )
ch [ row ] = make ( [ ] int , nPerRow )
for x := 0 ; x < nPerRow ; x ++ {
cc [ row ] [ x ] = controls [ i ]
i ++
}
}
return & Grid {
controls : cc ,
2014-02-25 20:54:13 -06:00
filling : cf ,
2014-02-26 06:01:02 -06:00
stretchyrow : - 1 ,
stretchycol : - 1 ,
2014-02-25 17:21:58 -06:00
widths : cw ,
heights : ch ,
rowheights : make ( [ ] int , nRows ) ,
colwidths : make ( [ ] int , nPerRow ) ,
}
}
2014-02-26 06:01:02 -06:00
// SetFilling sets the given Control of the Grid as filling its cell instead of staying at its preferred size.
2014-02-25 20:54:13 -06:00
// This function cannot be called after the Window that contains the Grid has been created.
func ( g * Grid ) SetFilling ( row int , column int ) {
g . lock . Lock ( )
defer g . lock . Unlock ( )
if g . created {
panic ( "Grid.SetFilling() called after window create" ) // TODO
}
g . filling [ row ] [ column ] = true
}
2014-02-26 06:01:02 -06:00
// SetStretchy sets the given Control of the Grid as stretchy.
// Stretchy implies filling.
// This function cannot be called after the Window that contains the Grid has been created.
func ( g * Grid ) SetStretchy ( row int , column int ) {
g . lock . Lock ( )
defer g . lock . Unlock ( )
if g . created {
panic ( "Grid.SetFilling() called after window create" ) // TODO
}
g . stretchyrow = row
g . stretchycol = column
g . filling [ row ] [ column ] = true
}
2014-02-25 17:21:58 -06:00
func ( g * Grid ) make ( window * sysData ) error {
g . lock . Lock ( )
defer g . lock . Unlock ( )
for row , xcol := range g . controls {
2014-02-25 17:40:36 -06:00
for col , c := range xcol {
2014-02-25 17:21:58 -06:00
err := c . make ( window )
if err != nil {
return fmt . Errorf ( "error adding control (%d,%d) to Grid: %v" , row , col , err )
}
}
}
g . created = true
return nil
}
func ( g * Grid ) setRect ( x int , y int , width int , height int ) error {
g . lock . Lock ( )
defer g . lock . Unlock ( )
max := func ( a int , b int ) int {
if a > b {
return a
}
return b
}
// 1) clear data structures
for i := range g . rowheights {
g . rowheights [ i ] = 0
}
for i := range g . colwidths {
g . colwidths [ i ] = 0
}
// 2) get preferred sizes; compute row/column sizes
for row , xcol := range g . controls {
for col , c := range xcol {
w , h , err := c . preferredSize ( )
if err != nil {
return fmt . Errorf ( "error getting preferred size of control (%d,%d) in Grid.setRect(): %v" , row , col , err )
}
2014-02-25 17:40:36 -06:00
g . widths [ row ] [ col ] = w
2014-02-25 17:21:58 -06:00
g . heights [ row ] [ col ] = h
g . rowheights [ row ] = max ( g . rowheights [ row ] , h )
2014-02-25 17:40:36 -06:00
g . colwidths [ col ] = max ( g . colwidths [ col ] , w )
2014-02-25 17:21:58 -06:00
}
}
2014-02-26 06:01:02 -06:00
// 3) handle the stretchy control
if g . stretchyrow != - 1 && g . stretchycol != - 1 { // TODO internal error if one is -1 but not both
for i , w := range g . colwidths {
if i != g . stretchycol {
width -= w
}
}
for i , h := range g . rowheights {
if i != g . stretchyrow {
height -= h
}
}
g . colwidths [ g . stretchycol ] = width
g . rowheights [ g . stretchyrow ] = height
}
// 4) draw
2014-02-25 17:21:58 -06:00
startx := x
for row , xcol := range g . controls {
for col , c := range xcol {
2014-02-25 20:54:13 -06:00
w := g . widths [ row ] [ col ]
h := g . heights [ row ] [ col ]
if g . filling [ row ] [ col ] {
w = g . colwidths [ col ]
h = g . rowheights [ row ]
}
err := c . setRect ( x , y , w , h )
2014-02-25 17:21:58 -06:00
if err != nil {
return fmt . Errorf ( "error setting size of control (%d,%d) in Grid.setRect(): %v" , row , col , err )
}
2014-02-25 17:40:36 -06:00
x += g . colwidths [ col ]
2014-02-25 17:21:58 -06:00
}
x = startx
2014-02-25 17:40:36 -06:00
y += g . rowheights [ row ]
2014-02-25 17:21:58 -06:00
}
return nil
}
2014-02-26 06:01:02 -06:00
// filling and stretchy are ignored for preferred size calculation
2014-02-25 17:21:58 -06:00
func ( g * Grid ) preferredSize ( ) ( width int , height int , err error ) {
g . lock . Lock ( )
defer g . lock . Unlock ( )
max := func ( a int , b int ) int {
if a > b {
return a
}
return b
}
// 1) clear data structures
for i := range g . rowheights {
g . rowheights [ i ] = 0
}
for i := range g . colwidths {
g . colwidths [ i ] = 0
}
// 2) get preferred sizes; compute row/column sizes
for row , xcol := range g . controls {
for col , c := range xcol {
w , h , err := c . preferredSize ( )
if err != nil {
2014-02-25 17:40:36 -06:00
return 0 , 0 , fmt . Errorf ( "error getting preferred size of control (%d,%d) in Grid.setRect(): %v" , row , col , err )
2014-02-25 17:21:58 -06:00
}
2014-02-25 17:40:36 -06:00
g . widths [ row ] [ col ] = w
2014-02-25 17:21:58 -06:00
g . heights [ row ] [ col ] = h
g . rowheights [ row ] = max ( g . rowheights [ row ] , h )
2014-02-25 17:40:36 -06:00
g . colwidths [ col ] = max ( g . colwidths [ col ] , w )
2014-02-25 17:21:58 -06:00
}
}
// 3) now compute
for _ , w := range g . colwidths {
width += w
}
for _ , h := range g . rowheights {
height += h
}
return width , height , nil
}