Layouts

Layouts manage the position of UI elements on the screen, and how the elements react to size changes (e.g. window resize).

Size hints

Size hints let the layouting code know how big UI elements are, and to which size they may shrink or grow.

Size hints can be explicit or implicit. Explicit sizes are set by the user via an API call through setExplicitSizeHint() ("This element is of that size''), while implicit sizes are deduced from the elements themselves. Explicit size hints always take precedence over implicit ones.

For instance, the implicit size of a button is calculated from the text width (which itself depends on the font) and possibly padding and margins:

implicit horizontal size hint of a button
Figure 1. implicit horizontal size hint of a button

The implicit width of a composited UI element containing a graphic on the left and a text on the right would be the sum of the elements’ width, again with padding and margins.

Layouts, i.e. classes deriving from QskBox, are also controls (i.e. QskControl instances), so they also have size hints. A layout typically calculates its implicit size hint by summing up the size of its children. For instace a horizontal layout containing three buttons next to each other will calculate its implicit width by summing up the widths of the buttons (spacing and margins again come on top).

There are three types of size hints: Minimum, Preferred and Maximum.

  • The minimum size hint of a UI element is used by layouting code to determine how small an element can be.

  • The preferred size hint is the natural size of an element, and will be used in an ideal case, meaning there is enough space available.

  • The maximum size hint is used by layouting code to determine how big an element can be.

Minimum and maximum size hints of atomic controls like QskPushButton or QskTextLabel are typically not used, instead size policies are used to express how small or big a component can be (see next topic). Minimum and maximum sizes, i.e. the methods minimumSize() and maximumSize(), are typically used for layouts though.

So in total, a control can have up to 6 size hints: the three types described above, and each one can have an implicit and an explicit hint.

Example

Below is an image with an implicit size hint with a width of 91 pixels and a height of 39 pixels (91x39). The hint is determined by the size of the text (71x19 pixels) plus margins (10 pixels each for top, right, bottom, left). We don’t need to set a size hint explicitly, the control will be rendered correctly with the implicit size hint:

auto* label1 = new QskTextLabel( "control 1" );
label1->setMargins( 10 );
label1->setBackgroundColor( Qt::magenta );
Image without explicit size hint
Figure 2. control without explicit size hint

If we set an explicit size hint of 150x60 pixels ourselves for the preferred size, the control will be rendered differently:

label1->setExplicitSizeHint( Qt::PreferredSize, { 150, 60 } );
Image with explicit size hint
Figure 3. control with explicit size hint

When dealing with standard controls or layouts, the size hints don’t need to be specified explicitly, as it can be deduced from its standard values, as seen in the example above.

The actual size of a UI element also depends on its size policy, see the next topic.

Size policies

Size policies define the way UI elements can change their size depending on the available space. Imagine a UI with a top bar and a main content area: When a status bar at the bottom is to be faded in, the top bar and main content have less space to display. One way to deal with this would be to leave the top bar at the same size and shrink the main area. This can be achieved with size policies: The top bar would have a vertical size policy of Fixed, while the main area would be Preferred, meaning it can grow and shrink.

The size policies of QSkinny correspond to the size policies from QtWidgets:

QskSizePolicy::Policy description

Fixed

The control has a fixed size and can neither grow nor shrink.

Minimum

The control cannot shrink beyond its minimum size, but it can grow if needed.

Maximum

The control cannot grow beyond its maximum size, but it can shrink if needed.

Preferred

The control can grow and shrink, but it should be of the size given by sizeHint().

MinimumExpanding

The control cannot shrink beyond its minimum size, but it can grow and should get as much space as possible.

Expanding

The control can shrink and grow, and it should get as much space as possible.

Ignored

The sizeHint() is ignored, and the control will get as much space as possible.

Constrained

The size of the control depends on a constraint, i.e. the width is depending on the height or vice versa. For this policy and the other Constrained* ones below, QskControl::widthForHeight() or QskControl::heightForWidth() will be queried.

ConstrainedMinimum

The size of the control depends on a constraint, but it can grow if needed.

ConstrainedMaximum

The size of the control depends on a constraint, but it can shrink if needed.

ConstrainedPreferred

The size of the control depends on a constraint, but it can grow and srhink if needed.

ConstrainedMinimumExpanding

The size of the control depends on a constraint, but it can grow and should get as much space as possible.

ConstrainedExpanding

The size of the control depends on a constraint, and it should get as much space as possible.

All the Constrained* policies correspond to Qt’s QSizePolicy::hasHeightForWidth() or QSizePolicy::hasWidthForHeight() flag. E.g. if a control has a horizontal size policy of Constrained and a vertical size policy of Fixed, it will call widthForHeight() to determine the width that corresponds to the height.

Example

Below is an example of two buttons with different size policies. In this case only the horizontal size policies are considered; the vertical size policies behave correspondingly.

auto horizontalBox = new QskLinearBox( Qt::Horizontal );

auto* label1 = new QskTextLabel( "size policy: fixed" );
label1->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed );
horizontalBox->addItem( label1 );

auto* label2 = new QskTextLabel( "size policy: minimum" );
label2->setSizePolicy( Qt::Horizontal, QskSizePolicy::Minimum );
horizontalBox->addItem( label2 );
...

By default the width of the buttons is determined by its text plus its margins:

Fixed vs. Minimum size policy
Figure 4. Size policies with preferred size

After growing the window horizontally, the button with the Fixed horizontal size policy keeps its width, while the button with the Minimum policy will grow:

Fixed vs. Minimum size policy
Figure 5. Size policies when increasing window width

When shrinking the window below its original size, both buttons stay with their width: The one on the left because of its Fixed size policy, and the one on the right because it won’t shrink below its original size due to the Minimum size policy.

Fixed vs. Minimum size policy
Figure 6. Size policies when shrinking window width

If we change the policy of the right button to Preferred, it will shrink below its original size (even though the text is too wide now):

label2->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred );
label2->setText( "size policy: preferred" );
Fixed vs. Minimum size policy
Figure 7. Size policies when changing to preferred size policy

Types of layouts

There are different types of layouts that can group UI elements together. Internally, layouts use the layoutRect() method to determine the available space to place its children.

Linear layouts (QskLinearBox)

A linear layout can group elements either horizontally or vertically, as in the images below.

auto horizontalBox = new QskLinearBox( Qt::Horizontal );

auto* label1 = new QskTextLabel( "control 1" );
horizontalBox->addItem( label1 );

auto* label2 = new QskTextLabel( "control 2" );
horizontalBox->addItem( label2 );

auto* label3 = new QskTextLabel( "control 3" );
horizontalBox->addItem( label3 );
...
Horizontal layout
Figure 8. Horizontal layout
auto verticalBox = new QskLinearBox( Qt::Vertical );

auto* label1 = new QskTextLabel( "control 1" );
verticalBox->addItem( label1 );

auto* label2 = new QskTextLabel( "control 2" );
verticalBox->addItem( label2 );

auto* label3 = new QskTextLabel( "control 3" );
verticalBox->addItem( label3 );
...
Vertical layout
Figure 9. Vertical layout

Grid layouts (QskGridBox)

Grid layouts are like linear layouts, but 2 dimensional, and support laying out UI controls in a grid, including spanning columns and rows.

auto* gridBox = new QskGridBox;

auto* label1 = new QskTextLabel( "control 1" );
gridBox->addItem( label1, 0, 0 ); // last two arguments are row and column

auto* label2 = new QskTextLabel( "control 2" );
gridBox->addItem( label2, 0, 1 );

auto* label3 = new QskTextLabel( "control 3" );
gridBox->addItem( label3, 0, 2 );

auto* label4 = new QskTextLabel( "control 4" );
gridBox->addItem( label4, 1, 0, 1, 2 ); // additional arguments are rowSpan and columnSpan

auto* label5 = new QskTextLabel( "control 5" );
gridBox->addItem( label5, 1, 2 );

auto* label6 = new QskTextLabel( "control 6" );
gridBox->addItem( label6, 2, 0 );

auto* label7 = new QskTextLabel( "control 7" );
gridBox->addItem( label7, 2, 1, 1, 2 );
Grid layout
Figure 10. Grid layout

Stack layouts (QskStackBox)

Stack layouts allow for items to be arranged on top of each other. Usually there is one current (visible) item, while the rest of the items are hidden below the current one:

auto* stackBox = new QskStackBox;

auto* label1 = new QskTextLabel( "control 1" );
label1->setBackgroundColor( Qt::blue );
stackBox->addItem( label1 );

auto* label2 = new QskTextLabel( "control 2" );
label2->setBackgroundColor( Qt::cyan );
stackBox->addItem( label2 );

auto* label3 = new QskTextLabel( "control 3" );
label3->setBackgroundColor( Qt::magenta );
stackBox->addItem( label3 );

stackBox->setCurrentIndex( 2 );
...
Stack layout
Figure 11. Stack layout (symbolized)

In this example, "control 3" is stacked on top of the blue and the cyan control. Controls in a stacked layout can be of different sizes.

Note
The image above is just for illustrating purposes. In practice the topmost control ("control 3" here) is completely covering the ones below it.

QskControl::autoLayoutChildren()

When the QskControl::autoLayoutChildren() flag is set, the control will recalculate the geometry of its children whenever the item is updating its layout.

Stretch factors

Stretch factors allow layouts to keep a size ratio for their elements. Let’s say a horizontal layout contains two elements, and when filling up additional space, the second element should always have twice the width of the first element. Then the first element should have a stretch factor of 1 and the second element a factor of 2.

Stretch factors are set on the layout rather than on the controls itself:

auto horizontalBox = new QskLinearBox( Qt::Horizontal );

auto* label1 = new QskTextLabel( "stretch factor 1" );
horizontalBox->addItem( label1 );
horizontalBox->setStretchFactor( label1, 1 );

auto* label2 = new QskTextLabel( "stretch factor 2" );
horizontalBox->addItem( label2 );
horizontalBox->setStretchFactor( label2, 2 );

...

When the layout has all the space it needs (but not more), both elements are rendered with their preferred size:

Stretch factors preferred size
Figure 12. Stretch factors with preferred size

When the layout gets more width, the stretch factors come into play:

Stretch factors increasing width
Figure 13. A stretch factor of 1:2

No matter how wide the layout is, the aspect ratio of 1:2 will always be kept, meaning that the label on the left will get 33% of the space, and the label on the right 67%:

Stretch factors even more width
Figure 14. A stretch factor of 1:2 with different widths

Stretch factors in QSkinny are the same as in the Qt Graphics View Framework, see Stretch Factor in QGraphicsLinearLayout.

Nesting layouts

In a real-world application it is typical to nest several layouts in each other. The example below depicts a UI with a top bar and menu items on the left:

Nested layouts
Figure 15. A UI with nested layouts

The code to produce the above UI could look like this (setting colors etc. omitted for brevity):

auto* outerBox = new QskLinearBox( Qt::Vertical );

auto* topBar = new QskLinearBox( Qt::Horizontal, outerBox );

auto* topLabel1 = new QskTextLabel( "top bar label 1", topBar );
auto* topLabel2 = new QskTextLabel( "top bar label 2", topBar );
auto* topLabel3 = new QskTextLabel( "top bar label 3", topBar );

auto* mainBox = new QskLinearBox( Qt::Horizontal, outerBox );

auto* menuBox = new QskLinearBox( Qt::Vertical, mainBox );

auto* menuLabel1 = new QskTextLabel( "menu 1", menuBox );
auto* menuLabel2 = new QskTextLabel( "menu 2", menuBox );
auto* menuLabel3 = new QskTextLabel( "menu 3", menuBox );

auto* mainText = new QskTextLabel( "here main area", mainBox );
...

Here we have an outer vertical layout which divides the content into a top bar and a main box. The top bar itself consists of a horizontal layout with 3 buttons, while the main area is split into a left part with menu buttons and a right part for the main area. That left part with the menu buttons is again a vertical layout.

The following diagram makes the layouts visible:

Nested layouts architecture
Figure 16. The layout structure of the UI

Anchoring in QSkinny

TODO