Add grid layout

This commit is contained in:
Sasha Koshka 2023-08-20 17:41:14 -04:00
parent 84313885df
commit 8a7b2832df
2 changed files with 110 additions and 2 deletions

108
layouts/grid.go Normal file
View File

@ -0,0 +1,108 @@
package layouts
import "math"
import "image"
import "git.tebibyte.media/tomo/tomo"
// Grid is a layout that arranges boxes in a grid formation with distinct rows
// and columns. It is great for creating forms.
type Grid struct {
xExpand []bool
yExpand []bool
}
// NewGrid creates a new grid layout. Rows and columns are specified as slices
// of booleans, where true means a row or column will expand and false means it
// will contract. Boxes are laid out left to right, then top to bottom. Boxes
// that go beyond the lengh of rows will be laid out according to columns, but
// they will not expand vertically.
func NewGrid (columns, rows []bool) *Grid {
this := &Grid {
xExpand: columns,
yExpand: rows,
}
return this
}
func (this *Grid) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
cols, rows := this.minimums(boxes)
size := image.Pt (
(len(cols) - 1) * hints.Gap.X,
(len(rows) - 1) * hints.Gap.Y)
for _, width := range cols { size.X += width }
for _, height := range rows { size.Y += height }
return size
}
func (this *Grid) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
xExpand := func (index int) bool {
return this.xExpand[index]
}
yExpand := func (index int) bool {
if index < len(this.yExpand) { return this.yExpand[index] }
return false
}
cols, rows := this.minimums(boxes)
expand(hints, cols, hints.Bounds.Dx(), xExpand)
expand(hints, rows, hints.Bounds.Dy(), yExpand)
position := hints.Bounds.Min
for index, box := range boxes {
col, row := index % len(cols), index / len(cols)
box.SetBounds(image.Rectangle {
Min: position,
Max: position.Add(image.Pt(cols[col], rows[row])),
})
if col == len(cols) - 1 {
position.X = hints.Bounds.Min.X
position.Y += rows[row] + hints.Gap.Y
} else {
position.X += cols[col] + hints.Gap.X
}
}
}
func (this *Grid) minimums (boxes []tomo.Box) ([]int, []int) {
nCols, nRows := this.dimensions(boxes)
cols, rows := make([]int, nCols), make([]int, nRows)
for index, box := range boxes {
col, row := index % len(cols), index / len(cols)
minimum := box.MinimumSize()
if cols[col] < minimum.X {
cols[col] = minimum.X
}
if rows[row] < minimum.Y {
rows[row] = minimum.Y
}
}
return cols, rows
}
func (this *Grid) dimensions (boxes []tomo.Box) (int, int) {
return len(this.xExpand), ceilDiv(len(boxes), len(this.xExpand))
}
func expand (hints tomo.LayoutHints, sizes []int, space int, expands func (int) bool) {
gaps := len(sizes) - 1
freeSpace := float64(space - hints.Gap.Y * gaps)
nExpanding := 0; for index, minimum := range sizes {
if expands(index) {
nExpanding ++
} else {
freeSpace -= float64(minimum)
}
}
expandingSize := freeSpace / float64(nExpanding)
for index := range sizes {
if expands(index) {
sizes[index] = int(expandingSize)
}
}
}
func ceilDiv (x, y int) int {
return int(math.Ceil(float64(x) / float64(y)))
}

View File

@ -189,7 +189,7 @@ func (this sliderLayout) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
if this.vertical { if this.vertical {
height := gutter.Dy() - handle.Dy() height := gutter.Dy() - handle.Dy()
offset := int(float64(height) * this.value) offset := int(float64(height) * this.value)
gutter.Max.X = handle.Max.X handle.Max.X = gutter.Dx()
boxes[0].SetBounds ( boxes[0].SetBounds (
handle. handle.
Add(image.Pt(0, offset)). Add(image.Pt(0, offset)).
@ -197,7 +197,7 @@ func (this sliderLayout) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
} else { } else {
width := gutter.Dx() - handle.Dx() width := gutter.Dx() - handle.Dx()
offset := int(float64(width) * this.value) offset := int(float64(width) * this.value)
gutter.Max.Y = handle.Max.Y handle.Max.Y = gutter.Dy()
boxes[0].SetBounds ( boxes[0].SetBounds (
handle. handle.
Add(image.Pt(offset, 0)). Add(image.Pt(offset, 0)).