Skip to content

Proposal: Aligned and Expandable BoxLayout

Pablo Fuentes edited this page Feb 5, 2021 · 2 revisions

Glossary

MainAxis and CrossAxis:

Axis VBox HBox
MainAxis Vertical Horizontal
CrossAxis Horizontal Vertical

Introduction

Following the discussion in #1840, cross axis alignment would be useful for fyne.Layouts to allow them be able to organize widgets with different cross axis sizes. Expandable feature is proposed here too, however I am not clear what is the best approach to this or if it can be discarded. This proposal invites us to discuss if the proposed cross axis alignment and expandable feature for BoxLayout should be added to Fyne core or not, or if it needs some design changes. Please add any discussion to the Discussion section below.

Background

Currently, Fyne has two kinds of layout to work with horizontal and vertical alignment:

  • BoxLayouts => VBox and HBox
  • GridLayouts => GridWithColumns and GridWithRows

BoxLayouts resize objects to their min size in the main axis and stretch their cross axis size.

HBox stretch cross axis VBox stretch cross axis

GridLayouts resize objects to have the same size in the main axis and stretch their cross axis size.


None of them offers a way to align elements in the cross axis, although that behavior can be accomplished doing some workaround. For example if we want this result:

We should write something like:

c := container.NewVBox(
	container.NewHBox(
		widget.NewEntry(),
		container.NewVBox(layout.NewSpacer(), createIcon(theme.ContentCopyIcon())),
		container.NewVBox(layout.NewSpacer(), createIcon(theme.ContentPasteIcon())),
		container.NewVBox(layout.NewSpacer(), createIcon(theme.ContentRemoveIcon())),
	),
)

Proposed solution would allow us to write:

c := container.NewHBoxAligned(layout.CrossAlignmentEnd,
	widget.NewEntry(),
	createIcon(theme.ContentCopyIcon()),
	createIcon(theme.ContentPasteIcon()),
	createIcon(theme.ContentRemoveIcon()),
)

Now if we want to expand the entry to look like this:

We should write something like:

c := container.NewVBox(
	container.NewBorder(nil, nil, nil,
		container.NewHBox(
			container.NewVBox(layout.NewSpacer(), createIcon(theme.ContentCopyIcon())),
			container.NewVBox(layout.NewSpacer(), createIcon(theme.ContentPasteIcon())),
			container.NewVBox(layout.NewSpacer(), createIcon(theme.ContentRemoveIcon())),
		),
		widget.NewEntry(),
	),
	layout.NewSpacer(),
)

Proposed solution will allow us to write:

c := container.NewHBoxAligned(layout.CrossAlignmentEnd,
	container.NewHBoxExpanded(widget.NewEntry()),
	createIcon(theme.ContentCopyIcon()),
	createIcon(theme.ContentPasteIcon()),
	createIcon(theme.ContentRemoveIcon()),
)

Proposed solution would also allow us to solve cross axis alignment problems for text-based widgets as discussed in #1701.

Proposed changes

There should be a new type called CrossAlignment (name suggestions are welcome!):

// CrossAlignment defines cross axis alignment type.
type CrossAlignment int

// Cross Axis alignment options.
const (
	CrossAlignmentStart CrossAlignment = iota
	CrossAlignmentEnd
	CrossAlignmentCenter
	CrossAlignmentBaseline
	CrossAlignmentStretch
)

This type would help us to specify the cross axis alignment in box layouts.

  • CrossAlignmentStart: Align the content to the Top (HBox) or Left (VBox).
  • CrossAlignmentEnd: Align the content to the Bottom (HBox) or Right (VBox).
  • CrossAlignmentCenter: Center the content vertically (HBox) or horizontally (VBox).
  • CrossAlignmentBaseline: This option is only useful for HBox, it aligns the content to the text baseline. For VBox, this alignment would behave as CrossAlignmentStart.
  • CrossAlignmentStretch: Stretch the cross axis size of the children (current behavior).

So to support both expandable feature and cross axis alignment, boxLayout should have new fields:

type boxLayout struct {
	expanded       bool
	horizontal     bool
	crossAlignment CrossAlignment
}

Then, we would have new box layout containers like:

  • container.NewVBox(objects...): It would work just as before (setting crossAlignment to CrossAlignmentStretch).

  • container.NewVBoxAligned(crossAlignment, objects...): It would work just like NewVBox, but instead of stretching the cross axis by default, user can set the desired cross alignment.

  • container.NewVBoxExpanded(objects...): It would resize the objects to have the same size in the vertical axis, and stretching their horizontal axis (setting crossAlignment to CrossAlignmentStretch).

  • container.NewVBoxExpandedAligned(crossAlignment, objects...): It would work just like NewVBoxExpanded, but instead of stretching the cross axis by default, user can set the desired cross alignment.

  • container.NewHBox(objects...): It would work just as before (setting crossAlignment to CrossAlignmentStretch).

  • container.NewHBoxAligned(crossAlignment, objects...): It would work just like NewHBox, but instead of stretching the cross axis by default, user can set the desired cross alignment.

  • container.NewHBoxExpanded(objects...): It would resize the objects to have the same size in the horizontal axis, and stretching their vertical axis (setting crossAlignment to CrossAlignmentStretch).

  • container.NewHBoxExpandedAligned(crossAlignment, objects...): It would work just like NewHBoxExpanded, but instead of stretching the cross axis by default, user can set the desired cross alignment.

Alternative approach

A lot of constructors for BoxLayouts would maybe be quite confusing (indeed, I am almost sure, they are). So an alternative approach would be to only have:

  • container.NewVBox(objects...)
  • container.NewVBoxAligned(crossAlignment, objects...)
  • container.NewHBox(objects...)
  • container.NewHBoxAligned(crossAlignment, objects...)

And have a way to specify when we want to expand an object in its main axis. There could be a new container called ExpandedBox (or maybe we can use the container.NewMax, although I think a better name for it should be ExpandedBox), and internally the boxLayout will detect it and expand its main axis size (just like the spacers). However, this new container would only be useful as a child of a VBox/HBox.

Maybe its definition could be:

container.NewExpandedBox will expand the main axis of its children if its parent is a VBox/HBox, otherwise it will expand on both axis (just like MaxLayout).

Implementation

The initial implementation can be found in my fyne fork within the branch feature/aligned-expanded-boxlayout (while this remains as proposal): https://github.com/fpabl0/fyne/tree/feature/aligned-expanded-boxlayout

In the fork I am not replacing current VBox and HBox layouts, in order to compare them. I have added a new layout nboxLayout to test this implementation (under cmd/nboxlayout_demo).

Discussion