objects/layouts/flow.go

136 lines
3.5 KiB
Go

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.BoxQuerier) 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.BoxArranger) {
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 index := 0; index < boxes.Len(); index ++ {
boxSize := flow.minor(boxes.MinimumSize(index))
if boxSize > minorSize { minorSize = boxSize }
}
if minorSize == 0 { return }
minorSteps :=
(flow.deltaMinor(hints.Bounds) + flow.minor(hints.Gap)) /
(minorSize + flow.minor(hints.Gap))
if minorSteps < 1 { minorSteps = 1 }
// arrange
point := hints.Bounds.Min
index := 0
for index < boxes.Len() {
// get a slice of boxes for this major step
stepIndexEnd := index + minorSteps
if stepIndexEnd > boxes.Len() { stepIndexEnd = boxes.Len() }
index += minorSteps
// find a major size that will fit all boxes on this major step
majorSize := 0
for index := index; index < stepIndexEnd; index ++ {
boxSize := flow.major(boxes.MinimumSize(index))
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 index := index; index < stepIndexEnd; index ++ {
bounds := image.Rectangle { Min: point, Max: point.Add(size) }
boxes.SetBounds(index, 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)
}
func (flow Flow) RecommendedHeight (hints tomo.LayoutHints, boxes tomo.BoxQuerier, width int) int {
// TODO
return 0
}
func (flow Flow) RecommendedWidth (hints tomo.LayoutHints, boxes tomo.BoxQuerier, height int) int {
// TODO
return 0
}