Add flow layout
This commit is contained in:
parent
f025ec3d8a
commit
68d49e1b02
127
layouts/flow.go
Normal file
127
layouts/flow.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package layouts
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "git.tebibyte.media/tomo/tomo"
|
||||||
|
|
||||||
|
// 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) - 1 }
|
||||||
|
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 {
|
||||||
|
if flow.v() {
|
||||||
|
return Column { }
|
||||||
|
} else {
|
||||||
|
return Row { }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user