Standard UI Layout
Introduction
UI elements in a window are arranged in a hierarchy (a rooted tree). Each element is responsible for the positioning and sizing of its child elements.
By default elements use the standard UI layout algorithm, which is implemented by the system. If an element has the PBElement_CUSTOM_LAYOUT
flag, then it will be sent various messages allowing to implement a custom layout algorithm.
For example, window elements (the roots of their respective hierarchies) have a custom algorithm that positions the first child element to match the size of the window, excluding the space taken by decorations like the titlebar.
This document describes the standard layout algorithm.
Properties Affecting Layout
Each element has a set of flags (see PBElementFlags
) and some layout metrics (see PBLayoutMetrics
).
Some of these properties affect how the element lays out its children. For example, the PBElement_LAYOUT_HORIZONTAL
flag instructs an element using the standard layout algorithm to lay out its children in a horizontal row.
Some of these properties affect the layout of the element's parent. For example, the PBElement_ALIGN_RIGHT
flag instructs the parent element to align this specific child on the right side.
The algorithm obtains the preferred size of each child element using the PBElementMeasure
function. See its documentation for details.
Columns
By default, the algorithm lays out the child elements in a column, one after another from top to bottom. The algorithm determines the height of each element with PBElementMeasure
, and accumulates these values to obtain the y-offset of each successive child. The algorithm obtains the width of each element with PBElementMeasure
, and then horizontally centers each element within its bounds.
If a child element has the PBElement_ALIGN_LEFT
flag, then that element will be left aligned instead of centered. If a child element has the PBElement_ALIGN_RIGHT
flag, then that element will be right aligned instead of centered. If a child element has both these flags specified, then its preferred width (from PBElementMeasure
) is ignored and it is resized to precisely match the width of the parent, even if this means truncating the contents of the child.
PBElement_H_FILL
is an alias for PBElement_ALIGN_LEFT|PBElement_ALIGN_RIGHT
. Note that this mode only causes the child element to take up the exact width of its container. It does not force the container take up the full width of its container; you may need to also set this flag on the container and some of its ancestors to get the desired layout.
Each child element can have different alignment flags.
Setting the PBElement_ALIGN_TOP
or PBElement_ALIGN_BOTTOM
flags on the child elements has no effect.
Rows
When the PBElement_LAYOUT_HORIZONTAL
flag is set on an element, the standard layout algorithm will instead lay out the child elements in a horizontal row, one after another going left to right.
Here, setting the the PBElement_ALIGN_TOP
or PBElement_ALIGN_BOTTOM
flags on the child elements will change their respective alignments. The PBElement_ALIGN_LEFT
and PBElement_ALIGN_RIGHT
flags will have no effect.
PBElement_V_FILL
is an alias for PBElement_ALIGN_TOP|PBElement_ALIGN_BOTTOM
.
For a vertical column, the primary axis is the vertical axis. For a horizontal row, the primary axis is the horizontal axis.
Insets and Gaps
The insets of an element give the amount of space between its borders and where the child elements should be placed. There is a measurement in dps for each side: left, right, top and bottom.
The gap gives the amount of space in dps left between each child element on the primary axis.
If the size of the container element has already been determined, then the following apply:
- for columns, if all child elements are horizontally centered, then the left and right insets are not used;
- for rows, if all child elements are vertically centered, then the top and bottom insets are not used;
- for columns, if no child element has a primary axis fill, then the bottom inset is not used;
- for rows, if no child element has a primary axis fill, then the right inset is not used.
Primary Axis Fills
After positioning its children, an element using the standard layout algorithm will then calculate the amount of remaining space on its primary axis. It then evenly distributes this space between all its child elements that have the corresponding fill flag for the primary axis.
Setting the primary axis fill flag on a child does not cause the parent to also fill the remaining space of its parent. If setting this flag on an element has no effect, that likely means the parent's size on the primary axis is set to perfectly match the size needed for its child elements. Thus, there will be no leftover space.
Reverse Layout
Setting the PBElement_LAYOUT_REVERSE
flag on an element causes it to reverse its layout. That is, for a column, the elements are placed from right to left instead of left to right, and for a row, the elements are placed from bottom to top instead of top to bottom.
Porting from Flexbox
The standard layout algorithm is simpler than CSS "flexbox" to make it easy to construct the most common layouts in GUI applications. However, many of the features of "flexbox" have some equivalent.
flex-direction: row
: Set thePBElement_LAYOUT_HORIZONTAL
flag on the container.flex-direction: column
: The default.flex-direction: row-reverse
: SetPBElement_LAYOUT_HORIZONTAL|PBElement_LAYOUT_REVERSE
on the container and add a child element at the end with the flagPBElement_FILL
.flex-direction: column-reverse
: SetPBElement_LAYOUT_REVERSE
on the container and add a child element at the end with the flagPBElement_FILL
.flex-wrap: nowrap
: The default.flex-wrap: wrap
: No equivalent. UsePBWrapPanel
instead.flex-wrap: wrap-reverse
: No equivalent. UsePBWrapPanel
instead.justify-content: flex-start
: The default.justify-content: flex-end
: Add a child element at the start with the flagPBElement_FILL
.justify-content: center
: Add a child element at the start with the flagPBElement_FILL
and similarly another at the end.justify-content: space-between
: Add a child element between each pair of children with the flagPBElement_FILL
.justify-content: space-around
: No direct equivalent.justify-content: space-evenly
: Add a child element between each pair of children with the flagPBElement_FILL
and similarly one at the start and one at the end.align-items: flex-start
: No direct equivalent. But seealign-self: flex-start
.align-items: flex-end
: No direct equivalent. But seealign-self: flex-end
.align-items: center
: No direct equivalent. But seealign-self: flex-center
.align-items: stretch
: No direct equivalent. But seealign-self: flex-stretch
.align-items: baseline
: No equivalent. The standard layout algorithm is not designed for complex text layout.gap: <N>px
: Set thegap
value in the container'sPBLayoutMetrics
structure.padding: ...
: Set theinsets
value in the container'sPBLayoutMetrics
structure.order: <N>
: No direct equivalent. UsePBElementChangeOrdering
to manually get the desired order.flex-grow: <N>
: No direct equivalent. But see the "Primary Axis Fills" section for a discussion on how an element can fill up the remaining space of a container.flex-shrink: <N>
: No direct equivalent. But see the "Primary Axis Fills" section.flex-basis: <N>px
: Set the fixed width or height in the child'sPBLayoutMetrics
structure.flex-basis: auto
: The default.align-self: flex-start
: Set thePBElement_ALIGN_LEFT
(for a column) orPBElement_ALIGN_TOP
(for a row) flag on the child.align-self: flex-end
: Set thePBElement_ALIGN_RIGHT
(for a column) orPBElement_ALIGN_BOTTOM
(for a row) flag on the child.align-self: center
: The default.align-self: stretch
: Set thePBElement_H_FILL
(for a column) orPBElement_V_FILL
(for a row) flags on the child.align-self: baseline
: No equivalent. The standard layout algorithm is not designed for complex text layout.