2024-06-12 01:15:38 -06:00
|
|
|
package layouts
|
|
|
|
|
|
|
|
import "image"
|
|
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
|
|
|
|
|
|
var _ tomo.Layout = ContractVertical
|
|
|
|
|
|
|
|
// Row arranges boxes in a row. Boxes that share an index with a true value will
|
|
|
|
// expand, and others will contract.
|
|
|
|
type Row []bool
|
|
|
|
|
|
|
|
// Column arranges boxes in a column. Boxes that share an index with a true
|
|
|
|
// value will expand, and others will contract.
|
|
|
|
type Column []bool
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (column Column) MinimumSize (hints tomo.LayoutHints, boxes tomo.BoxQuerier) image.Point {
|
2024-06-12 01:15:38 -06:00
|
|
|
dot := image.Point { }
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
minimum := boxes.MinimumSize(index)
|
2024-06-12 01:15:38 -06:00
|
|
|
dot.Y += minimum.Y
|
|
|
|
if dot.X < minimum.X {
|
|
|
|
dot.X = minimum.X
|
|
|
|
}
|
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
dot.Y += hints.Gap.Y * (boxes.Len() - 1)
|
2024-06-12 01:15:38 -06:00
|
|
|
return dot
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (row Row) MinimumSize (hints tomo.LayoutHints, boxes tomo.BoxQuerier) image.Point {
|
2024-06-12 01:15:38 -06:00
|
|
|
dot := image.Point { }
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
minimum := boxes.MinimumSize(index)
|
2024-06-12 01:15:38 -06:00
|
|
|
dot.X += minimum.X
|
|
|
|
if dot.Y < minimum.Y {
|
|
|
|
dot.Y = minimum.Y
|
|
|
|
}
|
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
dot.X += hints.Gap.X * (boxes.Len() - 1)
|
2024-06-12 01:15:38 -06:00
|
|
|
return dot
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (column Column) Arrange (hints tomo.LayoutHints, boxes tomo.BoxArranger) {
|
2024-06-12 01:15:38 -06:00
|
|
|
expands := func (index int) bool {
|
2024-06-12 01:32:32 -06:00
|
|
|
if index >= len(column) { return false }
|
2024-06-12 01:15:38 -06:00
|
|
|
return column[index]
|
|
|
|
}
|
|
|
|
|
|
|
|
// determine expanding box size
|
2024-06-12 01:32:32 -06:00
|
|
|
expandingSize := 0.0
|
2024-06-12 01:15:38 -06:00
|
|
|
if !hints.OverflowY {
|
2024-07-21 09:48:28 -06:00
|
|
|
gaps := boxes.Len() - 1
|
2024-06-12 01:15:38 -06:00
|
|
|
freeSpace := float64(hints.Bounds.Dy() - hints.Gap.Y * gaps)
|
2024-07-27 00:17:41 -06:00
|
|
|
nExpanding := 0
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
2024-06-12 01:15:38 -06:00
|
|
|
if expands(index) {
|
|
|
|
nExpanding ++
|
|
|
|
} else {
|
2024-07-21 09:48:28 -06:00
|
|
|
freeSpace -= float64(boxes.MinimumSize(index).Y)
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
}
|
2024-06-15 05:54:49 -06:00
|
|
|
expandingSize = freeSpace / float64(nExpanding)
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// determine width
|
|
|
|
width := 0
|
|
|
|
if hints.OverflowX {
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
minimum := boxes.MinimumSize(index)
|
2024-06-12 01:15:38 -06:00
|
|
|
if width < minimum.X { width = minimum.X }
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
width = hints.Bounds.Dx()
|
|
|
|
}
|
|
|
|
|
|
|
|
// arrange
|
|
|
|
dot := hints.Bounds.Min
|
2024-07-21 09:48:28 -06:00
|
|
|
bounds := make([]image.Rectangle, boxes.Len())
|
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
2024-06-12 01:15:38 -06:00
|
|
|
if index > 0 { dot.Y += hints.Gap.Y }
|
|
|
|
|
|
|
|
// determine height
|
2024-07-21 09:48:28 -06:00
|
|
|
height := boxes.MinimumSize(index).Y
|
2024-06-12 01:15:38 -06:00
|
|
|
if hints.OverflowY {
|
2024-07-21 09:48:28 -06:00
|
|
|
height = boxes.RecommendedHeight(index, width)
|
2024-06-12 01:15:38 -06:00
|
|
|
} else {
|
|
|
|
if expands(index) {
|
2024-06-12 01:32:32 -06:00
|
|
|
height = int(expandingSize)
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
// store bounds of this box
|
|
|
|
bounds[index] = image.Rectangle {
|
2024-06-12 01:15:38 -06:00
|
|
|
Min: dot,
|
|
|
|
Max: dot.Add(image.Pt(width, height)),
|
2024-07-21 09:48:28 -06:00
|
|
|
}
|
2024-06-12 01:15:38 -06:00
|
|
|
dot.Y += height
|
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
|
|
|
|
// align
|
2024-06-12 01:15:38 -06:00
|
|
|
height := dot.Y - hints.Bounds.Min.Y
|
|
|
|
offset := 0
|
|
|
|
switch hints.AlignY {
|
|
|
|
case tomo.AlignMiddle:
|
|
|
|
offset = (hints.Bounds.Dy() - height) / 2
|
|
|
|
case tomo.AlignEnd:
|
|
|
|
offset = hints.Bounds.Dy() - height
|
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
boxes.SetBounds(index, bounds[index].Add(image.Pt(0, offset)))
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (row Row) Arrange (hints tomo.LayoutHints, boxes tomo.BoxArranger) {
|
2024-06-12 01:15:38 -06:00
|
|
|
expands := func (index int) bool {
|
2024-06-12 01:32:32 -06:00
|
|
|
if index >= len(row) { return false }
|
2024-06-12 01:15:38 -06:00
|
|
|
return row[index]
|
|
|
|
}
|
|
|
|
|
|
|
|
// determine expanding box size
|
2024-06-12 01:32:32 -06:00
|
|
|
expandingSize := 0.0
|
2024-06-12 01:15:38 -06:00
|
|
|
if !hints.OverflowY {
|
2024-07-21 09:48:28 -06:00
|
|
|
gaps := boxes.Len() - 1
|
2024-06-12 01:15:38 -06:00
|
|
|
freeSpace := float64(hints.Bounds.Dx() - hints.Gap.X * gaps)
|
2024-07-27 00:17:41 -06:00
|
|
|
nExpanding := 0
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
2024-06-12 01:15:38 -06:00
|
|
|
if expands(index) {
|
|
|
|
nExpanding ++
|
|
|
|
} else {
|
2024-07-21 09:48:28 -06:00
|
|
|
freeSpace -= float64(boxes.MinimumSize(index).X)
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
}
|
2024-06-12 01:32:32 -06:00
|
|
|
expandingSize = freeSpace / float64(nExpanding)
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// determine height
|
|
|
|
height := 0
|
|
|
|
if hints.OverflowY {
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
minimum := boxes.MinimumSize(index)
|
2024-06-12 01:15:38 -06:00
|
|
|
if height < minimum.Y { height = minimum.Y }
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
height = hints.Bounds.Dy()
|
|
|
|
}
|
|
|
|
|
|
|
|
// arrange
|
|
|
|
dot := hints.Bounds.Min
|
2024-07-21 09:48:28 -06:00
|
|
|
bounds := make([]image.Rectangle, boxes.Len())
|
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
2024-06-12 01:15:38 -06:00
|
|
|
if index > 0 { dot.X += hints.Gap.X }
|
|
|
|
|
|
|
|
// determine width
|
2024-07-21 09:48:28 -06:00
|
|
|
width := boxes.MinimumSize(index).X
|
2024-06-12 01:15:38 -06:00
|
|
|
if hints.OverflowY {
|
2024-07-21 09:48:28 -06:00
|
|
|
width = boxes.RecommendedHeight(index, height)
|
2024-06-12 01:15:38 -06:00
|
|
|
} else {
|
|
|
|
if expands(index) {
|
2024-06-12 01:32:32 -06:00
|
|
|
width = int(expandingSize)
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
// store bounds
|
|
|
|
bounds[index] = image.Rectangle {
|
2024-06-12 01:15:38 -06:00
|
|
|
Min: dot,
|
|
|
|
Max: dot.Add(image.Pt(width, height)),
|
2024-07-21 09:48:28 -06:00
|
|
|
}
|
2024-06-12 01:15:38 -06:00
|
|
|
dot.X += width
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
// align
|
2024-06-12 01:15:38 -06:00
|
|
|
width := dot.X - hints.Bounds.Min.X
|
|
|
|
offset := 0
|
|
|
|
switch hints.AlignX {
|
|
|
|
case tomo.AlignMiddle:
|
|
|
|
offset = (hints.Bounds.Dx() - width) / 2
|
|
|
|
case tomo.AlignEnd:
|
|
|
|
offset = hints.Bounds.Dx() - width
|
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
boxes.SetBounds(index, bounds[index].Add(image.Pt(offset, 0)))
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (column Column) RecommendedHeight (hints tomo.LayoutHints, boxes tomo.BoxQuerier, width int) int {
|
2024-06-12 01:35:38 -06:00
|
|
|
height := 0
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
height += boxes.RecommendedHeight(index, width)
|
2024-06-12 01:35:38 -06:00
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
height += hints.Gap.Y * (boxes.Len() - 1)
|
2024-06-12 01:35:38 -06:00
|
|
|
return height
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (row Row) RecommendedHeight (hints tomo.LayoutHints, boxes tomo.BoxQuerier, width int) int {
|
2024-06-12 01:35:38 -06:00
|
|
|
height := 0
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
minimum := boxes.MinimumSize(index)
|
|
|
|
boxHeight := boxes.RecommendedHeight(index, minimum.X)
|
2024-06-12 01:35:38 -06:00
|
|
|
if boxHeight > height { height = boxHeight }
|
|
|
|
}
|
|
|
|
return height
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (column Column) RecommendedWidth (hints tomo.LayoutHints, boxes tomo.BoxQuerier, height int) int {
|
2024-06-12 01:35:38 -06:00
|
|
|
width := 0
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
minimum := boxes.MinimumSize(index)
|
|
|
|
boxWidth := boxes.RecommendedWidth(index, minimum.Y)
|
2024-06-12 01:35:38 -06:00
|
|
|
if boxWidth > width { width = boxWidth }
|
|
|
|
}
|
|
|
|
return width
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (row Row) RecommendedWidth (hints tomo.LayoutHints, boxes tomo.BoxQuerier, height int) int {
|
2024-06-12 01:35:38 -06:00
|
|
|
width := 0
|
2024-07-21 09:48:28 -06:00
|
|
|
for index := 0; index < boxes.Len(); index ++ {
|
|
|
|
width += boxes.RecommendedWidth(index, height)
|
2024-06-12 01:35:38 -06:00
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
width += hints.Gap.X * (boxes.Len() - 1)
|
2024-06-12 01:35:38 -06:00
|
|
|
return width
|
2024-06-12 01:15:38 -06:00
|
|
|
}
|