Skip to content

Commit

Permalink
Merge pull request #4824 from Jacalz/remove-form-allocs
Browse files Browse the repository at this point in the history
Refactor form layout to not allocate a slice at all
  • Loading branch information
Jacalz committed May 2, 2024
2 parents 1178a91 + c66a712 commit 0f09fc6
Showing 1 changed file with 67 additions and 93 deletions.
160 changes: 67 additions & 93 deletions layout/formlayout.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,132 +12,106 @@ const formLayoutCols = 2
var _ fyne.Layout = (*formLayout)(nil)

// formLayout is two column grid where each row has a label and a widget.
type formLayout struct {
}

func (f *formLayout) countRows(objects []fyne.CanvasObject) int {
count := 0

for i := 0; i < len(objects); i += formLayoutCols {
if !objects[i].Visible() && !objects[i+1].Visible() {
continue
}
count++
}

return count
}

// calculateTableSizes calculates the size of all of the cells in the table.
// It returns the width of the left column (labels), the width of the right column (content)
// as well as a slice with the height of each row. The height of each row will be set as the max
// size of the label and content cell heights for that row. The width of the label column will
// be set as the max width of all the label cells. The width of the content column will be set as
// the max width of all the content cells or the remaining space of the bounding containerWidth,
// if it is larger.
func (f *formLayout) calculateTableSizes(objects []fyne.CanvasObject, containerWidth float32) (float32, float32, []float32) {
rows := f.countRows(objects)
heights := make([]float32, rows)

if (len(objects))%formLayoutCols != 0 {
return 0, 0, heights
type formLayout struct{}

// calculateTableSizes calculates the izes of the table.
// This includes the width of the label column (maximum width of all labels),
// the width of the content column (maximum width of all content cells and remaining space in container)
// and the total minimum height of the form.
func (f *formLayout) calculateTableSizes(objects []fyne.CanvasObject, containerWidth float32) (labelWidth float32, contentWidth float32, height float32) {
if len(objects)%formLayoutCols != 0 {
return 0, 0, 0
}

rows := 0
innerPadding := theme.InnerPadding()
lowBound := 0
highBound := 2
labelCellMaxWidth := float32(0)
contentCellMaxWidth := float32(0)
for row := 0; row < rows; {
currentRow := objects[lowBound:highBound]
lowBound = highBound
highBound += formLayoutCols
if !currentRow[0].Visible() && !currentRow[1].Visible() {
for i := 0; i < len(objects); i += formLayoutCols {
labelCell, contentCell := objects[i], objects[i+1]
if !labelCell.Visible() && !contentCell.Visible() {
continue
}

labelCell := currentRow[0].MinSize()
if _, ok := currentRow[0].(*canvas.Text); ok {
labelCell.Width += innerPadding * 2
// Label column width is the maximum of all labels.
labelSize := labelCell.MinSize()
if _, ok := labelCell.(*canvas.Text); ok {
labelSize.Width += innerPadding * 2
}
labelCellMaxWidth = fyne.Max(labelCellMaxWidth, labelCell.Width)
labelWidth = fyne.Max(labelWidth, labelSize.Width)

contentCell := currentRow[1].MinSize()
contentCellMaxWidth = fyne.Max(contentCellMaxWidth, contentCell.Width)
// Content column width is the maximum of all content items.
contentSize := contentCell.MinSize()
contentWidth = fyne.Max(contentWidth, contentSize.Width)

rowHeight := fyne.Max(labelCell.Height, contentCell.Height)
heights[row] = rowHeight
row++
rowHeight := fyne.Max(labelSize.Height, contentSize.Height)
height += rowHeight
rows++
}

contentCellMaxWidth = fyne.Max(contentCellMaxWidth, containerWidth-labelCellMaxWidth-theme.Padding())
return labelCellMaxWidth, contentCellMaxWidth, heights
padding := theme.Padding()
contentWidth = fyne.Max(contentWidth, containerWidth-labelWidth-padding)
return labelWidth, contentWidth, height + float32(rows-1)*padding
}

// Layout is called to pack all child objects into a table format with two columns.
func (f *formLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
labelWidth, contentWidth, heights := f.calculateTableSizes(objects, size.Width)
labelWidth, contentWidth, _ := f.calculateTableSizes(objects, size.Width)

y := float32(0)
padding := theme.Padding()
innerPadding := theme.InnerPadding()

row := 0
y := float32(0)
for i := 0; i < len(objects); i += formLayoutCols {
if !objects[i].Visible() && (i+1 < len(objects) && !objects[i+1].Visible()) {
continue
}
if row > 0 {
y += heights[row-1] + padding
}

pos := fyne.NewPos(0, y)
size := fyne.NewSize(labelWidth, heights[row])
if _, ok := objects[i].(*canvas.Text); ok {
// Calculate size and position of object. Position and size is returned (instead of calling Move() and Resize()) to make inlineable.
objectLayout := func(obj fyne.CanvasObject, offset, width, rowHeight, itemHeight float32) (fyne.Position, fyne.Size) {
pos := fyne.NewPos(offset, y)
size := fyne.NewSize(width, rowHeight)
if _, ok := obj.(*canvas.Text); ok {
pos = pos.AddXY(innerPadding, innerPadding)
size.Width -= innerPadding * 2
size.Height = objects[i].MinSize().Height
size.Height = itemHeight
}

objects[i].Move(pos)
objects[i].Resize(size)

if i+1 < len(objects) {
pos = fyne.NewPos(padding+labelWidth, y)
size = fyne.NewSize(contentWidth, heights[row])
if _, ok := objects[i+1].(*canvas.Text); ok {
pos = pos.AddXY(innerPadding, innerPadding)
size.Width -= innerPadding * 2
size.Height = objects[i+1].MinSize().Height
}
return pos, size
}

objects[i+1].Move(pos)
objects[i+1].Resize(size)
remainer := len(objects) % formLayoutCols
for i := 0; i < len(objects)-remainer; i += formLayoutCols {
labelCell, contentCell := objects[i], objects[i+1]
if !labelCell.Visible() && !contentCell.Visible() {
continue
}
row++

labelMin := labelCell.MinSize()
contentMin := contentCell.MinSize()
rowHeight := fyne.Max(labelMin.Height, contentMin.Height)

pos, size := objectLayout(labelCell, 0, labelWidth, rowHeight, labelMin.Height)
labelCell.Move(pos)
labelCell.Resize(size)

pos, size = objectLayout(contentCell, labelWidth+padding, contentWidth, rowHeight, contentMin.Height)
contentCell.Move(pos)
contentCell.Resize(size)

y += rowHeight + padding
}

// Handle remaining item in the case of uneven number of objects:
if remainer == 1 {
lastCell := objects[len(objects)-1]
lastMin := lastCell.MinSize()
objectLayout(lastCell, 0, labelWidth, lastMin.Height, lastMin.Height)
}
}

// MinSize finds the smallest size that satisfies all the child objects.
// For a FormLayout this is the width of the widest label and content items and the height is
// the sum of all column children combined with padding between each.
func (f *formLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
labelWidth, contentWidth, heights := f.calculateTableSizes(objects, 0)
if len(heights) == 0 {
return fyne.Size{}
}

padding := theme.Padding()
minSize := fyne.Size{
Width: labelWidth + contentWidth + padding,
Height: padding * float32(len(heights)-1),
}

for _, height := range heights {
minSize.Height += height
labelWidth, contentWidth, height := f.calculateTableSizes(objects, 0)
return fyne.Size{
Width: labelWidth + contentWidth + theme.Padding(),
Height: height,
}

return minSize
}

// NewFormLayout returns a new FormLayout instance
Expand Down

0 comments on commit 0f09fc6

Please sign in to comment.