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
|
|||
|
|