Skip to content

Commit

Permalink
Separate box layouts into two empty types
Browse files Browse the repository at this point in the history
Each boxlayout object is now essentially allocation free and the layout/minsize calls branch less. A bit more code duplication but I think the logic is more readable and probably easier to optimize in the future.
  • Loading branch information
Jacalz committed Dec 29, 2023
1 parent 3365e7f commit 85a508d
Showing 1 changed file with 110 additions and 64 deletions.
174 changes: 110 additions & 64 deletions layout/boxlayout.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,113 @@ import (
"fyne.io/fyne/v2/theme"
)

// Declare conformity with Layout interface
var _ fyne.Layout = (*boxLayout)(nil)

type boxLayout struct {
horizontal bool
// NewVBoxLayout returns a vertical box layout for stacking a number of child
// canvas objects or widgets top to bottom. The objects are always displayed
// at their vertical MinSize. Use a different layout if the objects are intended
// to be larger then their vertical MinSize.
func NewVBoxLayout() fyne.Layout {
return vBoxLayout{}
}

// NewHBoxLayout returns a horizontal box layout for stacking a number of child
// canvas objects or widgets left to right. The objects are always displayed
// at their horizontal MinSize. Use a different layout if the objects are intended
// to be larger then their horizontal MinSize.
func NewHBoxLayout() fyne.Layout {
return &boxLayout{true}
return hBoxLayout{}
}

// NewVBoxLayout returns a vertical box layout for stacking a number of child
// canvas objects or widgets top to bottom. The objects are always displayed
// at their vertical MinSize. Use a different layout if the objects are intended
// to be larger then their vertical MinSize.
func NewVBoxLayout() fyne.Layout {
return &boxLayout{false}
}
// Declare conformity with Layout interface
var _ fyne.Layout = (*vBoxLayout)(nil)

func (g *boxLayout) isSpacer(obj fyne.CanvasObject) bool {
if !obj.Visible() {
return false // invisible spacers don't impact layout
type vBoxLayout struct{}

// Layout is called to pack all child objects into a specified size.
// This will pack objects into a single column where each item
// is full width but the height is the minimum required.
// Any spacers added will pad the view, sharing the space if there are two or more.
func (v vBoxLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
spacers := 0
visibleObjects := 0
// Size taken up by visible objects
total := float32(0)

for _, child := range objects {
if !child.Visible() {
continue
}

if isVerticalSpacer(child) {
spacers++
continue
}

visibleObjects++
total += child.MinSize().Height
}

spacer, ok := obj.(SpacerObject)
if !ok {
return false
padding := theme.Padding()

// Amount of space not taken up by visible objects and inter-object padding
extra := size.Height - total - (padding * float32(visibleObjects-1))

// Spacers split extra space equally
spacerSize := float32(0)
if spacers > 0 {
spacerSize = extra / float32(spacers)
}

if g.horizontal {
return spacer.ExpandHorizontal()
x, y := float32(0), float32(0)
for _, child := range objects {
if !child.Visible() {
continue
}

if isVerticalSpacer(child) {
y += spacerSize
continue
}
child.Move(fyne.NewPos(x, y))

height := child.MinSize().Height
y += padding + height
child.Resize(fyne.NewSize(size.Width, height))
}
}

// MinSize finds the smallest size that satisfies all the child objects.
// For a BoxLayout this is the width of the widest item and the height is
// the sum of of all children combined with padding between each.
func (v vBoxLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
minSize := fyne.NewSize(0, 0)
addPadding := false
padding := theme.Padding()
for _, child := range objects {
if !child.Visible() || isVerticalSpacer(child) {
continue
}

return spacer.ExpandVertical()
childMin := child.MinSize()
minSize.Width = fyne.Max(childMin.Width, minSize.Width)
minSize.Height += childMin.Height
if addPadding {
minSize.Height += padding
}
addPadding = true
}
return minSize
}

// Declare conformity with Layout interface
var _ fyne.Layout = (*hBoxLayout)(nil)

type hBoxLayout struct{}

// Layout is called to pack all child objects into a specified size.
// For a VBoxLayout this will pack objects into a single column where each item
// is full width but the height is the minimum required.
// Any spacers added will pad the view, sharing the space if there are two or more.
func (g *boxLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
func (g hBoxLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
spacers := 0
visibleObjects := 0
// Size taken up by visible objects
Expand All @@ -59,28 +121,20 @@ func (g *boxLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
if !child.Visible() {
continue
}
if g.isSpacer(child) {

if isHorizontalSpacer(child) {
spacers++
continue
}

visibleObjects++
if g.horizontal {
total += child.MinSize().Width
} else {
total += child.MinSize().Height
}
total += child.MinSize().Width
}

padding := theme.Padding()

// Amount of space not taken up by visible objects and inter-object padding
var extra float32
if g.horizontal {
extra = size.Width - total - (padding * float32(visibleObjects-1))
} else {
extra = size.Height - total - (padding * float32(visibleObjects-1))
}
extra := size.Width - total - (padding * float32(visibleObjects-1))

// Spacers split extra space equally
spacerSize := float32(0)
Expand All @@ -94,55 +148,47 @@ func (g *boxLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
continue
}

if g.isSpacer(child) {
if g.horizontal {
x += spacerSize
} else {
y += spacerSize
}
if isHorizontalSpacer(child) {
x += spacerSize
continue
}
child.Move(fyne.NewPos(x, y))

if g.horizontal {
width := child.MinSize().Width
x += padding + width
child.Resize(fyne.NewSize(width, size.Height))
} else {
height := child.MinSize().Height
y += padding + height
child.Resize(fyne.NewSize(size.Width, height))
}
width := child.MinSize().Width
x += padding + width
child.Resize(fyne.NewSize(width, size.Height))
}
}

// MinSize finds the smallest size that satisfies all the child objects.
// For a BoxLayout this is the width of the widest item and the height is
// the sum of of all children combined with padding between each.
func (g *boxLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
func (g hBoxLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
minSize := fyne.NewSize(0, 0)
addPadding := false
padding := theme.Padding()
for _, child := range objects {
if !child.Visible() || g.isSpacer(child) {
if !child.Visible() || isHorizontalSpacer(child) {
continue
}

childMin := child.MinSize()
if g.horizontal {
minSize.Height = fyne.Max(childMin.Height, minSize.Height)
minSize.Width += childMin.Width
if addPadding {
minSize.Width += padding
}
} else {
minSize.Width = fyne.Max(childMin.Width, minSize.Width)
minSize.Height += childMin.Height
if addPadding {
minSize.Height += padding
}
minSize.Height = fyne.Max(childMin.Height, minSize.Height)
minSize.Width += childMin.Width
if addPadding {
minSize.Width += padding
}
addPadding = true
}
return minSize
}

func isVerticalSpacer(obj fyne.CanvasObject) bool {
spacer, ok := obj.(SpacerObject)
return ok && spacer.ExpandVertical()
}

func isHorizontalSpacer(obj fyne.CanvasObject) bool {
spacer, ok := obj.(SpacerObject)
return ok && spacer.ExpandHorizontal()
}

0 comments on commit 85a508d

Please sign in to comment.