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 [2]*Cut expand [2]bool vertical bool } // Vertical divides the layout in equal halves vertically. func (this *Cut) Vertical () (top, bottom *Cut) { this.fill() this.even() this.vertical = true return this.Branches() } // Top divides the layout vertically, expanding the top half and contracting the // bottom half. func (this *Cut) Top () (top, bottom *Cut) { this.fill() this.first() this.vertical = true return this.Branches() } // Bottom divides the layout vertically, expanding the bottom half and // contracting the top half. func (this *Cut) Bottom () (top, bottom *Cut) { this.fill() this.second() this.vertical = true return this.Branches() } // Horizontal divides the layout in equal halves horizontally. func (this *Cut) Horizontal () (top, bottom *Cut) { this.fill() this.even() return this.Branches() } // Left divides the layout horizontally, expanding the left half and contracting // the right half. func (this *Cut) Left () (top, bottom *Cut) { this.fill() this.first() return this.Branches() } // Right divides the layout horizontally, expanding the right half and // contracting the left half. func (this *Cut) Right () (top, bottom *Cut) { this.fill() this.second() return this.Branches() } // Branches returns the two halves of this layout. 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 }