2014-02-13 16:04:57 -06:00
// 13 february 2014
2014-02-19 10:41:10 -06:00
package ui
2014-02-13 16:04:57 -06:00
import (
"fmt"
"sync"
)
// Orientation defines the orientation of controls in a Stack.
2014-02-24 09:30:14 -06:00
type Orientation bool
2014-02-13 16:04:57 -06:00
const (
2014-02-24 09:30:14 -06:00
Horizontal Orientation = false
Vertical Orientation = true
2014-02-13 16:04:57 -06:00
)
2014-02-24 09:29:15 -06:00
// A Stack stacks controls horizontally or vertically within the Stack's parent.
// A horizontal Stack gives all controls the same height and their preferred widths.
// A vertical Stack gives all controls the same width and their preferred heights.
2014-02-25 14:38:04 -06:00
// Any extra space at the end of a Stack is left blank.
2014-02-24 09:29:15 -06:00
// Some controls may be marked as "stretchy": when the Window they are in changes size, stretchy controls resize to take up the remaining space after non-stretchy controls are laid out. If multiple controls are marked stretchy, they are alloted equal distribution of the remaining space.
2014-02-13 16:04:57 -06:00
type Stack struct {
lock sync . Mutex
created bool
orientation Orientation
2014-02-14 09:58:16 -06:00
controls [ ] Control
2014-02-24 09:29:15 -06:00
stretchy [ ] bool
2014-02-24 09:42:58 -06:00
width , height [ ] int // caches to avoid reallocating these each time
2014-02-13 16:04:57 -06:00
}
// NewStack creates a new Stack with the specified orientation.
2014-02-14 09:58:16 -06:00
func NewStack ( o Orientation , controls ... Control ) * Stack {
2014-02-13 16:04:57 -06:00
return & Stack {
orientation : o ,
2014-02-14 09:58:16 -06:00
controls : controls ,
2014-02-24 09:29:15 -06:00
stretchy : make ( [ ] bool , len ( controls ) ) ,
width : make ( [ ] int , len ( controls ) ) ,
height : make ( [ ] int , len ( controls ) ) ,
2014-02-13 16:04:57 -06:00
}
}
2014-02-24 10:19:57 -06:00
// SetStretchy marks a control in a Stack as stretchy. This cannot be called once the Window containing the Stack has been opened.
2014-02-24 09:29:15 -06:00
func ( s * Stack ) SetStretchy ( index int ) {
s . lock . Lock ( )
defer s . lock . Unlock ( )
2014-02-24 10:19:57 -06:00
if s . created {
panic ( "call to Stack.SetStretchy() after Stack has been created" )
}
2014-02-24 09:44:20 -06:00
s . stretchy [ index ] = true // TODO explicitly check for index out of bounds?
2014-02-24 09:29:15 -06:00
}
2014-02-14 10:12:08 -06:00
func ( s * Stack ) make ( window * sysData ) error {
2014-02-15 14:38:41 -06:00
for i , c := range s . controls {
2014-02-14 10:12:08 -06:00
err := c . make ( window )
2014-02-13 16:04:57 -06:00
if err != nil {
2014-02-24 09:29:15 -06:00
return fmt . Errorf ( "error adding control %d to Stack: %v" , i , err )
2014-02-13 16:04:57 -06:00
}
}
2014-02-14 19:41:36 -06:00
s . created = true
2014-02-13 16:04:57 -06:00
return nil
}
func ( s * Stack ) setRect ( x int , y int , width int , height int ) error {
2014-02-24 09:42:58 -06:00
var stretchywid , stretchyht int
2014-02-13 16:04:57 -06:00
2014-02-14 09:58:16 -06:00
if len ( s . controls ) == 0 { // do nothing if there's nothing to do
2014-02-13 16:04:57 -06:00
return nil
}
2014-02-24 09:42:58 -06:00
// 1) get height and width of non-stretchy controls; figure out how much space is alloted to stretchy controls
stretchywid = width
stretchyht = height
nStretchy := 0
for i , c := range s . controls {
if s . stretchy [ i ] {
nStretchy ++
continue
}
w , h , err := c . preferredSize ( )
if err != nil {
return fmt . Errorf ( "error getting preferred size of control %d in Stack.setRect(): %v" , i , err )
}
if s . orientation == Horizontal { // all controls have same height
s . width [ i ] = w
s . height [ i ] = height
stretchywid -= w
} else { // all controls have same width
s . width [ i ] = width
s . height [ i ] = h
stretchyht -= h
}
2014-02-13 16:04:57 -06:00
}
2014-02-24 09:42:58 -06:00
// 2) figure out size of stretchy controls
if nStretchy != 0 {
if s . orientation == Horizontal { // split rest of width
stretchywid /= nStretchy
} else { // split rest of height
stretchyht /= nStretchy
}
}
2014-02-24 10:11:05 -06:00
for i := range s . controls {
2014-02-24 09:42:58 -06:00
if ! s . stretchy [ i ] {
continue
}
2014-02-24 10:11:05 -06:00
s . width [ i ] = stretchywid
s . height [ i ] = stretchyht
2014-02-24 09:42:58 -06:00
}
// 3) now actually place controls
2014-02-15 14:38:41 -06:00
for i , c := range s . controls {
2014-02-24 09:42:58 -06:00
err := c . setRect ( x , y , s . width [ i ] , s . height [ i ] )
2014-02-13 16:04:57 -06:00
if err != nil {
2014-02-24 09:42:58 -06:00
return fmt . Errorf ( "error setting size of control %d in Stack.setRect(): %v" , i , err )
}
if s . orientation == Horizontal {
x += s . width [ i ]
} else {
y += s . height [ i ]
2014-02-13 16:04:57 -06:00
}
}
return nil
}
2014-02-24 10:11:05 -06:00
// The preferred size of a Stack is the sum of the preferred sizes of non-stretchy controls + (the number of stretchy controls * the largest preferred size among all stretchy controls).
func ( s * Stack ) preferredSize ( ) ( width int , height int , err error ) {
max := func ( a int , b int ) int {
if a > b {
return a
}
return b
}
var nStretchy int
var maxswid , maxsht int
if len ( s . controls ) == 0 { // no controls, so return emptiness
return 0 , 0 , nil
}
for i , c := range s . controls {
w , h , err := c . preferredSize ( )
if err != nil {
return 0 , 0 , fmt . Errorf ( "error getting preferred size of control %d in Stack.preferredSize(): %v" , i , err )
}
if s . stretchy [ i ] {
nStretchy ++
maxswid = max ( maxswid , w )
maxsht = max ( maxsht , h )
}
if s . orientation == Horizontal { // max vertical size
if ! s . stretchy [ i ] {
width += w
}
height = max ( height , h )
} else {
width = max ( width , w )
if ! s . stretchy [ i ] {
height += h
}
}
}
if s . orientation == Horizontal {
width += nStretchy * maxswid
} else {
height += nStretchy * maxsht
}
return
}
2014-02-25 14:38:04 -06:00
2014-02-25 14:43:12 -06:00
// Space returns a null control intended for padding layouts with blank space where otherwise impossible (for instance, at the beginning or in the middle of a Stack).
2014-02-25 14:38:04 -06:00
// In order for Space() to work, it must be marked as stretchy in its parent layout; otherwise its size is undefined.
func Space ( ) Control {
// As above, a stack with no controls draws nothing and reports no errors; its parent will still size it properly.
return NewStack ( Horizontal )
}