1package boxlayout 2 3import "math" 4 5type Dimensions struct { 6 X0 int 7 X1 int 8 Y0 int 9 Y1 int 10} 11 12type Direction int 13 14const ( 15 ROW Direction = iota 16 COLUMN 17) 18 19// to give a high-level explanation of what's going on here. We layout our windows by arranging a bunch of boxes in the available space. 20// If a box has children, it needs to specify how it wants to arrange those children: ROW or COLUMN. 21// If a box represents a window, you can put the window name in the Window field. 22// When determining how to divvy-up the available height (for row children) or width (for column children), we first 23// give the boxes with a static `size` the space that they want. Then we apportion 24// the remaining space based on the weights of the dynamic boxes (you can't define 25// both size and weight at the same time: you gotta pick one). If there are two 26// boxes, one with weight 1 and the other with weight 2, the first one gets 33% 27// of the available space and the second one gets the remaining 66% 28 29type Box struct { 30 // Direction decides how the children boxes are laid out. ROW means the children will each form a row i.e. that they will be stacked on top of eachother. 31 Direction Direction 32 33 // function which takes the width and height assigned to the box and decides which orientation it will have 34 ConditionalDirection func(width int, height int) Direction 35 36 Children []*Box 37 38 // function which takes the width and height assigned to the box and decides the layout of the children. 39 ConditionalChildren func(width int, height int) []*Box 40 41 // Window refers to the name of the window this box represents, if there is one 42 Window string 43 44 // static Size. If parent box's direction is ROW this refers to height, otherwise width 45 Size int 46 47 // dynamic size. Once all statically sized children have been considered, Weight decides how much of the remaining space will be taken up by the box 48 // TODO: consider making there be one int and a type enum so we can't have size and Weight simultaneously defined 49 Weight int 50} 51 52func ArrangeWindows(root *Box, x0, y0, width, height int) map[string]Dimensions { 53 children := root.getChildren(width, height) 54 if len(children) == 0 { 55 // leaf node 56 if root.Window != "" { 57 dimensionsForWindow := Dimensions{X0: x0, Y0: y0, X1: x0 + width - 1, Y1: y0 + height - 1} 58 return map[string]Dimensions{root.Window: dimensionsForWindow} 59 } 60 return map[string]Dimensions{} 61 } 62 63 direction := root.getDirection(width, height) 64 65 var availableSize int 66 if direction == COLUMN { 67 availableSize = width 68 } else { 69 availableSize = height 70 } 71 72 // work out size taken up by children 73 reservedSize := 0 74 totalWeight := 0 75 for _, child := range children { 76 // assuming either size or weight are non-zero 77 reservedSize += child.Size 78 totalWeight += child.Weight 79 } 80 81 remainingSize := availableSize - reservedSize 82 if remainingSize < 0 { 83 remainingSize = 0 84 } 85 86 unitSize := 0 87 extraSize := 0 88 if totalWeight > 0 { 89 unitSize = remainingSize / totalWeight 90 extraSize = remainingSize % totalWeight 91 } 92 93 result := map[string]Dimensions{} 94 offset := 0 95 for _, child := range children { 96 var boxSize int 97 if child.isStatic() { 98 boxSize = child.Size 99 // assuming that only one static child can have a size greater than the 100 // available space. In that case we just crop the size to what's available 101 if boxSize > availableSize { 102 boxSize = availableSize 103 } 104 } else { 105 // TODO: consider more evenly distributing the remainder 106 boxSize = unitSize * child.Weight 107 boxExtraSize := int(math.Min(float64(extraSize), float64(child.Weight))) 108 boxSize += boxExtraSize 109 extraSize -= boxExtraSize 110 } 111 112 var resultForChild map[string]Dimensions 113 if direction == COLUMN { 114 resultForChild = ArrangeWindows(child, x0+offset, y0, boxSize, height) 115 } else { 116 resultForChild = ArrangeWindows(child, x0, y0+offset, width, boxSize) 117 } 118 119 result = mergeDimensionMaps(result, resultForChild) 120 offset += boxSize 121 } 122 123 return result 124} 125 126func (b *Box) isStatic() bool { 127 return b.Size > 0 128} 129 130func (b *Box) getDirection(width int, height int) Direction { 131 if b.ConditionalDirection != nil { 132 return b.ConditionalDirection(width, height) 133 } 134 return b.Direction 135} 136 137func (b *Box) getChildren(width int, height int) []*Box { 138 if b.ConditionalChildren != nil { 139 return b.ConditionalChildren(width, height) 140 } 141 return b.Children 142} 143 144func mergeDimensionMaps(a map[string]Dimensions, b map[string]Dimensions) map[string]Dimensions { 145 result := map[string]Dimensions{} 146 for _, dimensionMap := range []map[string]Dimensions{a, b} { 147 for k, v := range dimensionMap { 148 result[k] = v 149 } 150 } 151 return result 152} 153