174 lines
4.3 KiB
Go
174 lines
4.3 KiB
Go
package layouts
|
|
|
|
import "image"
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
|
|
// Cut is a layout that can be divided into smaller and smaller sections.
|
|
type Cut struct {
|
|
branches []*Cut
|
|
expand []bool
|
|
vertical bool
|
|
}
|
|
|
|
// NewCut creates and returns a new Cut layout.
|
|
func NewCut () *Cut {
|
|
return new(Cut)
|
|
}
|
|
|
|
// Vertical divides the layout vertically. Sections are specified using
|
|
// booleans. If a section is true, it will expand. If false, it will contract.
|
|
func (this *Cut) Vertical (expand ...bool) {
|
|
this.expand = expand
|
|
this.vertical = true
|
|
this.fill()
|
|
}
|
|
|
|
// Horizontal divides the layout horizontally. Sections are specified using
|
|
// booleans. If a section is true, it will expand. If false, it will contract.
|
|
func (this *Cut) Horizontal (expand ...bool) {
|
|
this.expand = expand
|
|
this.vertical = false
|
|
this.fill()
|
|
}
|
|
|
|
// At returns the section of this layout at the specified index.
|
|
func (this *Cut) At (index int) *Cut {
|
|
return this.branches[index]
|
|
}
|
|
|
|
func (this *Cut) real () bool {
|
|
return this != nil && this.branches != nil
|
|
}
|
|
|
|
func (this *Cut) fill () {
|
|
this.branches = make([]*Cut, len(this.expand))
|
|
for index := range this.branches {
|
|
this.branches[index] = new(Cut)
|
|
}
|
|
}
|
|
|
|
func (this *Cut) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
|
|
size, _ := this.minimumSize(hints, boxes)
|
|
return size
|
|
}
|
|
|
|
func (this *Cut) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
|
|
this.arrange(hints, boxes)
|
|
}
|
|
|
|
func (this *Cut) minimumSize (hints tomo.LayoutHints, boxes []tomo.Box) (image.Point, []tomo.Box) {
|
|
size := image.Point { }
|
|
|
|
for index, branch := range this.branches {
|
|
if len(boxes) == 0 { break }
|
|
|
|
var point image.Point
|
|
if branch.real() {
|
|
point, boxes = branch.minimumSize(hints, boxes)
|
|
} else {
|
|
point = boxes[0].MinimumSize()
|
|
boxes = boxes[1:]
|
|
}
|
|
|
|
if this.vertical {
|
|
if point.X > size.X { size.X = point.X }
|
|
if index > 0 { size.Y += hints.Gap.Y }
|
|
size.Y += point.Y
|
|
} else {
|
|
if point.Y > size.Y { size.Y = point.Y }
|
|
if index > 0 { size.X += hints.Gap.X }
|
|
size.X += point.X
|
|
}
|
|
}
|
|
|
|
return size, boxes
|
|
}
|
|
|
|
func (this *Cut) arrange (hints tomo.LayoutHints, boxes []tomo.Box) []tomo.Box {
|
|
nChildren := len(this.branches)
|
|
|
|
// collect minimum sizes and physical endpoints
|
|
var minimums = make([]image.Point, nChildren)
|
|
var leaves = make([]tomo.Box, nChildren)
|
|
var nBranches int
|
|
remaining := boxes
|
|
for index, branch := range this.branches {
|
|
if branch.real() {
|
|
minimums[index], remaining = branch.minimumSize(hints, remaining)
|
|
} else {
|
|
if len(remaining) == 0 { break }
|
|
leaves[index] = remaining[0]
|
|
minimums[index] = remaining[0].MinimumSize()
|
|
remaining = remaining[1:]
|
|
}
|
|
nBranches ++
|
|
}
|
|
|
|
// determine the amount of space to divide among expanding branches
|
|
gaps := nBranches - 1
|
|
var freeSpace float64; if this.vertical {
|
|
freeSpace = float64(hints.Bounds.Dy() - hints.Gap.Y * gaps)
|
|
} else {
|
|
freeSpace = float64(hints.Bounds.Dx() - hints.Gap.X * gaps)
|
|
}
|
|
var nExpanding float64
|
|
for index, minimum := range minimums {
|
|
if this.expand[index] {
|
|
nExpanding ++
|
|
} else if this.vertical {
|
|
freeSpace -= float64(minimum.Y)
|
|
} else {
|
|
freeSpace -= float64(minimum.X)
|
|
}
|
|
}
|
|
expandingSize := freeSpace / nExpanding
|
|
|
|
// calculate the size and position of branches
|
|
var bounds = make([]image.Rectangle, nChildren)
|
|
x := float64(hints.Bounds.Min.X)
|
|
y := float64(hints.Bounds.Min.Y)
|
|
for index, minimum := range minimums {
|
|
// get size along significant axis
|
|
var size float64; if this.expand[index] {
|
|
size = expandingSize
|
|
} else if this.vertical {
|
|
size = float64(minimum.Y)
|
|
} else {
|
|
size = float64(minimum.X)
|
|
}
|
|
|
|
// figure out bounds from size
|
|
if this.vertical {
|
|
bounds[index].Max = image.Pt (
|
|
int(hints.Bounds.Dx()),
|
|
int(size))
|
|
} else {
|
|
bounds[index].Max = image.Pt (
|
|
int(size),
|
|
int(hints.Bounds.Dy()))
|
|
}
|
|
bounds[index] = bounds[index].Add(image.Pt(int(x), int(y)))
|
|
|
|
// move along
|
|
if this.vertical {
|
|
y += float64(hints.Gap.Y) + size
|
|
} else {
|
|
x += float64(hints.Gap.X) + size
|
|
}
|
|
}
|
|
|
|
// apply the size and position
|
|
for index, bound := range bounds {
|
|
if leaves[index] != nil {
|
|
leaves[index].SetBounds(bound)
|
|
boxes = boxes[1:]
|
|
} else if this.branches[index] != nil {
|
|
newHints := hints
|
|
newHints.Bounds = bound
|
|
boxes = this.branches[index].arrange(newHints, boxes)
|
|
}
|
|
}
|
|
|
|
return boxes
|
|
}
|