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 }