package layouts import "image" import "git.tebibyte.media/tomo/tomo" var _ tomo.Layout = FlowVertical // Flow is a grid layout where the number of rows and columns changes depending // on the size of the container. It is designed to be used with an overflowing // container. If the container does not overflow in the correct direction, the // layout will behave like Contract. type Flow bool // FlowVertical is a vertical flow layout. const FlowVertical Flow = true // FlowHorizontal is a horizontal flow layout. const FlowHorizontal Flow = false func (flow Flow) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point { // TODO: write down somewhere that layout minimums aren't taken into // account when the respective direction is overflowed return flow.fallback().MinimumSize(hints, boxes) } func (flow Flow) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) { if flow.v() && !hints.OverflowY || flow.h() && !hints.OverflowX { flow.fallback().Arrange(hints, boxes) } // find a minor size value that will fit all boxes minorSize := 0 for _, box := range boxes { boxSize := flow.minor(box.MinimumSize()) if boxSize > minorSize { minorSize = boxSize } } if minorSize == 0 { return } minorSteps := (flow.deltaMinor(hints.Bounds) + flow.minor(hints.Gap)) / (minorSize + flow.minor(hints.Gap)) // arrange point := hints.Bounds.Min index := 0 for index < len(boxes) { // get a slice of boxes for this major step stepIndexEnd := index + minorSteps if stepIndexEnd > len(boxes) { stepIndexEnd = len(boxes) } step := boxes[index:stepIndexEnd] index += minorSteps // find a major size that will fit all boxes on this major step majorSize := 0 for _, box := range step { boxSize := flow.major(box.MinimumSize()) if boxSize > majorSize { majorSize = boxSize } } if majorSize == 0 { continue } // arrange all boxes on this major step var size image.Point size = flow.incrMajor(size, majorSize) size = flow.incrMinor(size, minorSize) for _, box := range step { bounds := image.Rectangle { Min: point, Max: point.Add(size) } box.SetBounds(bounds) point = flow.incrMinor(point, minorSize + flow.minor(hints.Gap)) } if flow.v() { point.Y += majorSize + hints.Gap.Y point.X = hints.Bounds.Min.X } else { point.X += majorSize + hints.Gap.X point.Y = hints.Bounds.Min.Y } } } func (flow Flow) v () bool { return flow == FlowVertical } func (flow Flow) h () bool { return flow == FlowHorizontal } func (flow Flow) minor (point image.Point) int { if flow.v() { return point.X } else { return point.Y } } func (flow Flow) major (point image.Point) int { if flow.v() { return point.Y } else { return point.X } } func (flow Flow) incrMinor (point image.Point, delta int) image.Point { if flow.v() { point.X += delta return point } else { point.Y += delta return point } } func (flow Flow) incrMajor (point image.Point, delta int) image.Point { if flow.v() { point.Y += delta return point } else { point.X += delta return point } } func (flow Flow) deltaMinor (rectangle image.Rectangle) int { if flow.v() { return rectangle.Dx() } else { return rectangle.Dy() } } func (flow Flow) fallback () tomo.Layout { return Contract(flow) }