objects/layouts/cut.go

213 lines
5.0 KiB
Go
Raw Permalink Normal View History

2023-08-13 19:47:51 -06:00
package layouts
import "image"
import "git.tebibyte.media/tomo/tomo"
2023-08-13 19:52:09 -06:00
// Cut is a layout that can be divided into smaller and smaller sections.
2023-08-13 19:47:51 -06:00
type Cut struct {
branches [2]*Cut
expand [2]bool
vertical bool
}
2023-08-13 19:52:09 -06:00
// Vertical divides the layout in equal halves vertically.
2023-08-13 19:47:51 -06:00
func (this *Cut) Vertical () (top, bottom *Cut) {
this.fill()
this.even()
this.vertical = true
return this.Branches()
}
2023-08-13 19:52:09 -06:00
// Top divides the layout vertically, expanding the top half and contracting the
// bottom half.
2023-08-13 19:47:51 -06:00
func (this *Cut) Top () (top, bottom *Cut) {
this.fill()
this.first()
this.vertical = true
return this.Branches()
}
2023-08-13 19:52:09 -06:00
// Bottom divides the layout vertically, expanding the bottom half and
// contracting the top half.
2023-08-13 19:47:51 -06:00
func (this *Cut) Bottom () (top, bottom *Cut) {
this.fill()
this.second()
this.vertical = true
return this.Branches()
}
2023-08-13 19:52:09 -06:00
// Horizontal divides the layout in equal halves horizontally.
2023-08-13 19:47:51 -06:00
func (this *Cut) Horizontal () (top, bottom *Cut) {
this.fill()
this.even()
return this.Branches()
}
2023-08-13 19:52:09 -06:00
// Left divides the layout horizontally, expanding the left half and contracting
// the right half.
2023-08-13 19:47:51 -06:00
func (this *Cut) Left () (top, bottom *Cut) {
this.fill()
this.first()
return this.Branches()
}
2023-08-13 19:52:09 -06:00
// Right divides the layout horizontally, expanding the right half and
// contracting the left half.
2023-08-13 19:47:51 -06:00
func (this *Cut) Right () (top, bottom *Cut) {
this.fill()
this.second()
return this.Branches()
}
2023-08-13 19:52:09 -06:00
// Branches returns the two halves of this layout.
2023-08-13 19:47:51 -06:00
func (this *Cut) Branches () (first, second *Cut) {
return this.branches[0], this.branches[1]
}
func (this *Cut) real () bool {
return this != nil && this.branches[0] != nil && this.branches[1] != nil
}
func (this *Cut) fill () {
this.branches[0] = &Cut { }
this.branches[1] = &Cut { }
}
func (this *Cut) first () {
this.expand[0] = true
this.expand[1] = false
}
func (this *Cut) second () {
this.expand[0] = false
this.expand[1] = true
}
func (this *Cut) even () {
this.expand[0] = true
this.expand[1] = true
}
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 {
// collect minimum sizes and physical endpoints
var minimums [2]image.Point
var leaves [2]tomo.Box
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 [2]image.Rectangle
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
}