![Peter Hartmann](/assets/img/avatar_default.png)
These are the tutorials written a while ago, plus one chapter called "Why QSkinny?". They will be part of the website once it is published.
437 lines
14 KiB
Plaintext
437 lines
14 KiB
Plaintext
---
|
||
title: 4. Layouts
|
||
layout: docs
|
||
---
|
||
|
||
:doctitle: 4. Layouts
|
||
:notitle:
|
||
|
||
== 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
|
||
image::../images/size-hints-calculation.png[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:
|
||
|
||
[source]
|
||
....
|
||
auto* label1 = new QskTextLabel( "control 1" );
|
||
label1->setMargins( 10 );
|
||
label1->setBackgroundColor( Qt::magenta );
|
||
....
|
||
|
||
.control without explicit size hint
|
||
image::../images/size-hints-1.png[Image 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 } );
|
||
....
|
||
|
||
.control with explicit size hint
|
||
image::../images/size-hints-2.png[Image 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
|
||
*https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum[size policies from
|
||
QtWidgets]*:
|
||
|
||
[width="100%",cols="50%,50%",options="header",]
|
||
|=======================================================================
|
||
|`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
|
||
https://doc.qt.io/qt-5/qsizepolicy.html#hasHeightForWidth[QSizePolicy::hasHeightForWidth()]
|
||
or
|
||
https://doc.qt.io/qt-5/qsizepolicy.html#hasWidthForHeight[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.
|
||
|
||
[source]
|
||
....
|
||
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:
|
||
|
||
.Size policies with preferred size
|
||
image::../images/size-policies-horizontal-minimum-1.png[Fixed vs. Minimum size policy]
|
||
|
||
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:
|
||
|
||
.Size policies when increasing window width
|
||
image::../images/size-policies-horizontal-minimum-2.png[Fixed vs. Minimum size policy]
|
||
|
||
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.
|
||
|
||
.Size policies when shrinking window width
|
||
image::../images/size-policies-horizontal-minimum-3.png[Fixed vs. Minimum size policy]
|
||
|
||
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" );
|
||
....
|
||
|
||
.Size policies when changing to preferred size policy
|
||
image::../images/size-policies-horizontal-minimum-4.png[Fixed vs. Minimum 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.
|
||
|
||
[source]
|
||
....
|
||
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
|
||
image::../images/layout-horizontal.png[Horizontal layout]
|
||
|
||
[source]
|
||
....
|
||
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
|
||
image::../images/layout-vertical.png[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.
|
||
|
||
[source]
|
||
....
|
||
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
|
||
image::../images/layout-grid.png[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:
|
||
|
||
[source]
|
||
....
|
||
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 (symbolized)
|
||
image::../images/layout-stack.png[Stack layout]
|
||
|
||
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:
|
||
|
||
[source]
|
||
....
|
||
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 with preferred size
|
||
image::../images/stretch-factors-1.png[Stretch factors preferred size]
|
||
|
||
When the layout gets more width, the stretch factors come into play:
|
||
|
||
.A stretch factor of 1:2
|
||
image::../images/stretch-factors-2.png[Stretch factors increasing width]
|
||
|
||
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%:
|
||
|
||
.A stretch factor of 1:2 with different widths
|
||
image::../images/stretch-factors-3.png[Stretch factors even more width]
|
||
|
||
Stretch factors in QSkinny are the same as in the Qt Graphics View
|
||
Framework, see
|
||
https://doc.qt.io/qt-5/qgraphicslinearlayout.html#stretch-factor-in-qgraphicslinearlayout[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:
|
||
|
||
.A UI with nested layouts
|
||
image::../images/nesting-layouts.png[Nested layouts]
|
||
|
||
The code to produce the above UI could look like this (setting colors
|
||
etc. omitted for brevity):
|
||
|
||
[source]
|
||
....
|
||
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:
|
||
|
||
.The layout structure of the UI
|
||
image::../images/nesting-layouts-architecture.png[Nested layouts architecture]
|
||
|
||
=== Anchoring in QSkinny
|
||
|
||
TODO
|
||
|