package layout import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/theme" ) const formLayoutCols = 2 // Declare conformity with Layout interface 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 } // tableCellsSize defines the size for all the cells of the form table. // The height of each row will be set as the max value between the label and content cell heights. // The width of the label column will be set as the max width value between all the label cells. // The width of the content column will be set as the max width value between all the content cells // or the remaining space of the bounding containerWidth, if it is larger. func (f *formLayout) tableCellsSize(objects []fyne.CanvasObject, containerWidth float32) [][2]fyne.Size { rows := f.countRows(objects) table := make([][2]fyne.Size, rows) if (len(objects))%formLayoutCols != 0 { return table } padding := theme.Padding() 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() { continue } labelCell := currentRow[0].MinSize() if _, ok := currentRow[0].(*canvas.Text); ok { labelCell.Width += padding * 4 } labelCellMaxWidth = fyne.Max(labelCellMaxWidth, labelCell.Width) contentCell := currentRow[1].MinSize() contentCellMaxWidth = fyne.Max(contentCellMaxWidth, contentCell.Width) rowHeight := fyne.Max(labelCell.Height, contentCell.Height) labelCell.Height = rowHeight contentCell.Height = rowHeight table[row][0] = labelCell table[row][1] = contentCell row++ } contentWidth := fyne.Max(contentCellMaxWidth, containerWidth-labelCellMaxWidth-padding) for row := 0; row < rows; row++ { table[row][0].Width = labelCellMaxWidth table[row][1].Width = contentWidth } return table } // 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) { table := f.tableCellsSize(objects, size.Width) 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 += table[row-1][0].Height + padding } tableRow := table[row] if _, ok := objects[i].(*canvas.Text); ok { objects[i].Move(fyne.NewPos(innerPadding, y+innerPadding)) objects[i].Resize(fyne.NewSize(tableRow[0].Width-innerPadding*2, objects[i].MinSize().Height)) } else { objects[i].Move(fyne.NewPos(0, y)) objects[i].Resize(fyne.NewSize(tableRow[0].Width, tableRow[0].Height)) } if i+1 < len(objects) { if _, ok := objects[i+1].(*canvas.Text); ok { objects[i+1].Move(fyne.NewPos(padding+tableRow[0].Width+innerPadding, y+innerPadding)) objects[i+1].Resize(fyne.NewSize(tableRow[1].Width-innerPadding*2, objects[i+1].MinSize().Height)) } else { objects[i+1].Move(fyne.NewPos(padding+tableRow[0].Width, y)) objects[i+1].Resize(fyne.NewSize(tableRow[1].Width, tableRow[0].Height)) } } row++ } } // 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 { table := f.tableCellsSize(objects, 0) padding := theme.Padding() minSize := fyne.NewSize(0, 0) if len(table) == 0 { return minSize } added := false minSize.Width = table[0][0].Width + table[0][1].Width + padding for row := 0; row < len(table); row++ { minSize.Height += table[row][0].Height if added { minSize.Height += padding } added = true } return minSize } // NewFormLayout returns a new FormLayout instance func NewFormLayout() fyne.Layout { return &formLayout{} }