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
|
|
|
|
}
|