2020-08-15 00:28:02 +02:00
package boxlayout
2020-05-16 04:35:19 +02:00
import "math"
2020-08-15 00:28:02 +02:00
type Dimensions struct {
X0 int
X1 int
Y0 int
Y1 int
2020-05-17 13:32:17 +02:00
}
2021-03-31 14:55:06 +02:00
type Direction int
2020-05-16 04:35:19 +02:00
const (
2021-03-31 14:55:06 +02:00
ROW Direction = iota
2020-05-16 04:35:19 +02:00
COLUMN
)
2020-08-21 11:53:45 +02:00
// 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.
2020-05-18 14:00:07 +02:00
// If a box has children, it needs to specify how it wants to arrange those children: ROW or COLUMN.
2020-08-21 11:53:45 +02:00
// If a box represents a window, you can put the window name in the Window field.
2020-05-18 14:00:07 +02:00
// When determining how to divvy-up the available height (for row children) or width (for column children), we first
// give the boxes with a static `size` the space that they want. Then we apportion
// the remaining space based on the weights of the dynamic boxes (you can't define
// both size and weight at the same time: you gotta pick one). If there are two
// boxes, one with weight 1 and the other with weight 2, the first one gets 33%
// of the available space and the second one gets the remaining 66%
2020-08-15 00:28:02 +02:00
type Box struct {
// 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.
2021-03-31 14:55:06 +02:00
Direction Direction
2020-05-16 04:35:19 +02:00
// function which takes the width and height assigned to the box and decides which orientation it will have
2021-03-31 14:55:06 +02:00
ConditionalDirection func ( width int , height int ) Direction
2020-05-16 04:35:19 +02:00
2020-08-15 00:28:02 +02:00
Children [ ] * Box
2020-05-16 04:35:19 +02:00
2020-05-17 13:32:17 +02:00
// function which takes the width and height assigned to the box and decides the layout of the children.
2020-08-15 00:28:02 +02:00
ConditionalChildren func ( width int , height int ) [ ] * Box
2020-05-17 13:32:17 +02:00
2020-08-21 11:53:45 +02:00
// Window refers to the name of the window this box represents, if there is one
Window string
2020-05-16 04:35:19 +02:00
2020-08-15 00:28:02 +02:00
// static Size. If parent box's direction is ROW this refers to height, otherwise width
Size int
2020-05-16 04:35:19 +02:00
2020-08-15 00:28:02 +02:00
// 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
// TODO: consider making there be one int and a type enum so we can't have size and Weight simultaneously defined
Weight int
2020-05-16 04:35:19 +02:00
}
2020-08-21 11:53:45 +02:00
func ArrangeWindows ( root * Box , x0 , y0 , width , height int ) map [ string ] Dimensions {
2020-05-17 13:44:59 +02:00
children := root . getChildren ( width , height )
if len ( children ) == 0 {
2020-05-16 04:35:19 +02:00
// leaf node
2020-08-21 11:53:45 +02:00
if root . Window != "" {
dimensionsForWindow := Dimensions { X0 : x0 , Y0 : y0 , X1 : x0 + width - 1 , Y1 : y0 + height - 1 }
return map [ string ] Dimensions { root . Window : dimensionsForWindow }
2020-05-16 04:35:19 +02:00
}
2020-08-15 00:28:02 +02:00
return map [ string ] Dimensions { }
2020-05-16 04:35:19 +02:00
}
direction := root . getDirection ( width , height )
var availableSize int
if direction == COLUMN {
availableSize = width
} else {
availableSize = height
}
// work out size taken up by children
reservedSize := 0
totalWeight := 0
2020-05-17 13:44:59 +02:00
for _ , child := range children {
2020-05-16 04:35:19 +02:00
// assuming either size or weight are non-zero
2020-08-15 00:28:02 +02:00
reservedSize += child . Size
totalWeight += child . Weight
2020-05-16 04:35:19 +02:00
}
remainingSize := availableSize - reservedSize
if remainingSize < 0 {
remainingSize = 0
}
unitSize := 0
extraSize := 0
if totalWeight > 0 {
unitSize = remainingSize / totalWeight
extraSize = remainingSize % totalWeight
}
2020-08-15 00:28:02 +02:00
result := map [ string ] Dimensions { }
2020-05-16 04:35:19 +02:00
offset := 0
2020-05-17 13:44:59 +02:00
for _ , child := range children {
2020-05-16 04:35:19 +02:00
var boxSize int
if child . isStatic ( ) {
2020-08-15 00:28:02 +02:00
boxSize = child . Size
2021-04-11 04:12:26 +02:00
// assuming that only one static child can have a size greater than the
// available space. In that case we just crop the size to what's available
if boxSize > availableSize {
boxSize = availableSize
}
2020-05-16 04:35:19 +02:00
} else {
// TODO: consider more evenly distributing the remainder
2020-08-15 00:28:02 +02:00
boxSize = unitSize * child . Weight
boxExtraSize := int ( math . Min ( float64 ( extraSize ) , float64 ( child . Weight ) ) )
2020-05-16 04:35:19 +02:00
boxSize += boxExtraSize
extraSize -= boxExtraSize
}
2020-08-15 00:28:02 +02:00
var resultForChild map [ string ] Dimensions
2020-05-16 04:35:19 +02:00
if direction == COLUMN {
2020-08-21 11:53:45 +02:00
resultForChild = ArrangeWindows ( child , x0 + offset , y0 , boxSize , height )
2020-05-16 04:35:19 +02:00
} else {
2020-08-21 11:53:45 +02:00
resultForChild = ArrangeWindows ( child , x0 , y0 + offset , width , boxSize )
2020-05-16 04:35:19 +02:00
}
2020-08-15 00:28:02 +02:00
result = mergeDimensionMaps ( result , resultForChild )
2020-05-16 04:35:19 +02:00
offset += boxSize
}
return result
}
2020-08-15 00:58:58 +02:00
func ( b * Box ) isStatic ( ) bool {
return b . Size > 0
}
2021-03-31 14:55:06 +02:00
func ( b * Box ) getDirection ( width int , height int ) Direction {
2020-08-15 00:58:58 +02:00
if b . ConditionalDirection != nil {
return b . ConditionalDirection ( width , height )
}
return b . Direction
}
func ( b * Box ) getChildren ( width int , height int ) [ ] * Box {
if b . ConditionalChildren != nil {
return b . ConditionalChildren ( width , height )
}
return b . Children
}
2020-08-15 00:28:02 +02:00
func mergeDimensionMaps ( a map [ string ] Dimensions , b map [ string ] Dimensions ) map [ string ] Dimensions {
result := map [ string ] Dimensions { }
for _ , dimensionMap := range [ ] map [ string ] Dimensions { a , b } {
2020-05-16 04:35:19 +02:00
for k , v := range dimensionMap {
result [ k ] = v
}
}
return result
}