2024-05-17 01:51:24 -06:00
|
|
|
package layouts
|
|
|
|
|
|
|
|
import "image"
|
|
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
|
|
|
2024-06-11 14:46:04 -06:00
|
|
|
var _ tomo.Layout = FlowVertical
|
|
|
|
|
2024-05-17 01:51:24 -06:00
|
|
|
// 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
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (flow Flow) MinimumSize (hints tomo.LayoutHints, boxes tomo.BoxQuerier) image.Point {
|
2024-05-17 01:51:24 -06:00
|
|
|
// TODO: write down somewhere that layout minimums aren't taken into
|
|
|
|
// account when the respective direction is overflowed
|
|
|
|
return flow.fallback().MinimumSize(hints, boxes)
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (flow Flow) Arrange (hints tomo.LayoutHints, boxes tomo.BoxArranger) {
|
2024-05-17 01:51:24 -06:00
|
|
|
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
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
boxSize := flow.minor(boxes.MinimumSize(index))
|
2024-05-17 01:51:24 -06:00
|
|
|
if boxSize > minorSize { minorSize = boxSize }
|
|
|
|
}
|
|
|
|
if minorSize == 0 { return }
|
|
|
|
minorSteps :=
|
|
|
|
(flow.deltaMinor(hints.Bounds) + flow.minor(hints.Gap)) /
|
|
|
|
(minorSize + flow.minor(hints.Gap))
|
2024-06-15 16:12:08 -06:00
|
|
|
if minorSteps < 1 { minorSteps = 1 }
|
2024-05-17 01:51:24 -06:00
|
|
|
|
|
|
|
// arrange
|
|
|
|
point := hints.Bounds.Min
|
|
|
|
index := 0
|
2024-07-21 09:48:28 -06:00
|
|
|
for index < boxes.Len() {
|
2024-05-17 01:51:24 -06:00
|
|
|
// get a slice of boxes for this major step
|
|
|
|
stepIndexEnd := index + minorSteps
|
2024-07-21 09:48:28 -06:00
|
|
|
if stepIndexEnd > boxes.Len() { stepIndexEnd = boxes.Len() }
|
2024-05-17 01:51:24 -06:00
|
|
|
index += minorSteps
|
|
|
|
|
|
|
|
// find a major size that will fit all boxes on this major step
|
|
|
|
majorSize := 0
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := index; index < stepIndexEnd; index ++ {
|
|
|
|
boxSize := flow.major(boxes.MinimumSize(index))
|
2024-05-17 01:51:24 -06:00
|
|
|
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)
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := index; index < stepIndexEnd; index ++ {
|
2024-05-17 01:51:24 -06:00
|
|
|
bounds := image.Rectangle { Min: point, Max: point.Add(size) }
|
2024-07-21 09:48:28 -06:00
|
|
|
boxes.SetBounds(index, bounds)
|
2024-05-17 01:51:24 -06:00
|
|
|
|
|
|
|
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 {
|
2024-05-17 01:56:49 -06:00
|
|
|
return Contract(flow)
|
2024-05-17 01:51:24 -06:00
|
|
|
}
|
2024-06-11 15:12:18 -06:00
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (flow Flow) RecommendedHeight (hints tomo.LayoutHints, boxes tomo.BoxQuerier, width int) int {
|
2024-06-11 15:12:18 -06:00
|
|
|
// TODO
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (flow Flow) RecommendedWidth (hints tomo.LayoutHints, boxes tomo.BoxQuerier, height int) int {
|
2024-06-11 15:12:18 -06:00
|
|
|
// TODO
|
|
|
|
return 0
|
|
|
|
}
|