Add tutorials (#115)
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.
21
doc/tutorials/01-What-is-QSkinny.asciidoc
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: 1. What is QSkinny?
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 1. What is QSkinny?
|
||||
:notitle:
|
||||
|
||||
QSkinny is a UI framework based on the Qt graphic stack and written in
|
||||
{cpp}. It allows users to write their UIs in {cpp} and/or QML.
|
||||
|
||||
.The Fendt Tractor GUI
|
||||
image::https://camo.githubusercontent.com/3eea80daf41ce6a86f08c73353d05000363c4df0/68747470733a2f2f7777772e66656e64742e636f6d2f696e742f67656e6576612d6173736574732f7769646765742f32383239312f6e6577732d332d6c6f772e6a7067[Fendt Tractor GUI]
|
||||
|
||||
It is currently being used in the Fendt Tractor GUI project, see the
|
||||
picture above. For the Fendt Tractor GUI there is no QML used at all;
|
||||
the whole codebase is written in {cpp}. An overview of how QSkinny fits
|
||||
into the Qt architecture is depicted below:
|
||||
|
||||
.QSkinny sits on top of QtQuick, while QML is optional
|
||||
image::../images/architecture-simple.png[QSkinny architecture]
|
130
doc/tutorials/02-Why-QSkinny.asciidoc
Normal file
@ -0,0 +1,130 @@
|
||||
---
|
||||
title: 2. Why QSkinny?
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 2. Why QSkinny?
|
||||
:notitle:
|
||||
|
||||
The typical questions about QSkinny are: Why was QSkinny created? And why would
|
||||
somebody use QSkinny and not QML?
|
||||
|
||||
Which technology to use always depends on the specific use case. However,
|
||||
QSkinny does have some advantages:
|
||||
|
||||
== 1. It's {cpp}
|
||||
|
||||
QSkinny is written in {cpp}, so there is no new syntax or programming paradigm
|
||||
to learn as is the case with QML. Of course QSkinny has concepts that
|
||||
new programmers need to become familiar with, but they should be understandable
|
||||
for people who know {cpp}. Especially programmers experienced with
|
||||
QtWidgets should feel comfortable with QSkinny right away.
|
||||
|
||||
=== 1.1 Integration with other build systems / IDEs
|
||||
|
||||
While QtCreator is the natural choice of *IDE* for Qt programmers,
|
||||
some people prefer other IDEs, e.g. Visual
|
||||
Studio (Code), Eclipse, CLion etc. Such IDEs usually don't have language support
|
||||
for QML like type completion and other features. So when using QML you are
|
||||
either bound to using QtCreator, or use another IDE and live with the fact that
|
||||
the IDE will not understand QML.
|
||||
|
||||
When it comes to *build systems*, some QML tools might be hard to integrate:
|
||||
For instance in Visual Studio projects it is difficult to invoke the QML
|
||||
compiler through the build system.
|
||||
|
||||
With QSkinny being written completely in {cpp}, it can be used with any IDE and
|
||||
should integrate nicely with other build systems. QSkinny is using Qt-specific
|
||||
concepts like signals and slots and invokable methods though.
|
||||
|
||||
=== 1.2 Use {cpp} tooling for your whole codebase
|
||||
|
||||
{cpp} has extensive tooling that assists with writing code, for instance:
|
||||
|
||||
- gdb and other debuggers
|
||||
- valgrind
|
||||
- address sanitizer and other sanitizers
|
||||
- static code analysis tools
|
||||
- code coverage tools (e.g. gcov)
|
||||
- auto test frameworks
|
||||
- (a lot more, e.g. clang tools)
|
||||
|
||||
E.g. QtCreator will let you know about potential problems in your code while
|
||||
you type, e.g. "unused variable", "calling a virtual method from the constructor
|
||||
of a class" etc., and it might even suggest an automatic fix for it.
|
||||
|
||||
QML does have some tooling, but its feature set is nowhere near the support of
|
||||
{cpp}.
|
||||
|
||||
When writing your whole codebase in {cpp} with QSkinny, the tooling can be used
|
||||
for the whole codebase, so also UI code can be debugged, auto tested for a
|
||||
CI system, and so on.
|
||||
|
||||
In addition, {cpp} has concepts that QML as a declarative language doesn't,
|
||||
like inheritance and overloading. This makes it easier to implement concepts
|
||||
like event handling, see <<Styling>> below.
|
||||
|
||||
|
||||
== 2. Easy data binding
|
||||
|
||||
When displaying data from a backend in a QML UI, that data needs to be in a
|
||||
certain format: It needs to be made readable by Qt's Meta Object system via
|
||||
`Q_PROPERTY`, `Q_INVOKABLE`, `Q_SIGNAL` and others.
|
||||
|
||||
Also, for each model that is used in QML there typically needs to be one
|
||||
subclass of `QAbstractListModel`, which serves as an adapter class. The process
|
||||
of subclassing and implementing virtual methods can be cumbersome, and lead to
|
||||
lots of boilerplate code.
|
||||
|
||||
QSkinny doesn't need any adaptation layer per se, the data just needs to be
|
||||
connected to the frontend with standard {cpp} functionality. Of course classes
|
||||
like the aforementioned `QAbstractListModel` can be used when it makes sense,
|
||||
but this is up to the user.
|
||||
|
||||
|
||||
== 3. Layouts
|
||||
|
||||
Whe it comes to *layouts*, QSkinny has a complete concept of laying out the UI,
|
||||
or in other words: The user can determine in a fine-grained way what happens
|
||||
when there is too little or too much space available.
|
||||
Concepts like size hints, size policies, stretch factors and others are concepts
|
||||
that were already available in QtWidgets and Qt's Graphics View Framework, and
|
||||
are now supported in QSkinny.
|
||||
|
||||
Why are layouts important? QML was created under the premise that in contrast to
|
||||
desktop UIs, embedded UIs run as fullscreen window on an embedded board and
|
||||
thus size changes will rarely happen.
|
||||
|
||||
This is true for many cases, however layout code gets important when one of the
|
||||
following events happen:
|
||||
|
||||
- The UI needs to run on two or more screen sizes
|
||||
- Language or style changes need to be supported
|
||||
- The window is resized, e.g. when the Android virtual keyboard pops up
|
||||
|
||||
QSkinny allows the user to take the above use cases into account, but doesn't
|
||||
force the developer to write overly complex code: A UI written with QSkinny can
|
||||
be coded with fixed sizes for UI elements, as it is typically done in QML.
|
||||
|
||||
|
||||
== [[Styling]] 4. Styling / Adding custom controls
|
||||
|
||||
Qt Quick Controls 2 support different styles, and it even comes with several
|
||||
built-in styles like a Google Material style and a Microsoft Universal style.
|
||||
|
||||
One drawback with Qt Quick Controls 2 is that application developers can only
|
||||
add custom types in QML, not in {cpp}. This makes it cumbersome for concepts
|
||||
like event handling, as is noted in the Qt documentation:
|
||||
https://doc.qt.io/qt-5/qtquickcontrols2-differences.html[Differences with Qt Quick Controls 1,role=external,window=_blank].
|
||||
|
||||
So an application developer who wants to add own types, as is common for medium
|
||||
to large-scale projects, will have to implement these custom types in QML.
|
||||
Since being able to use {cpp} for application logic of components seems to have been
|
||||
one reason to create Qt Quick Controls 2 (another reason being performance
|
||||
issues with Qt Quick Controls 1, see
|
||||
https://www.qt.io/blog/2015/03/31/qt-quick-controls-for-embedded[Qt Quick Controls for Embedded,role=external,window=_blank]), allowing the user to write controls in {cpp} gives the user more flexibility.
|
||||
|
||||
QSkinny allows for implementing custom types in {cpp}; also both built-in
|
||||
components like push buttons, sliders etc. as well as custom types can be easily
|
||||
styled from {cpp}. The latter can be achieved by simply adding style
|
||||
descriptions in user code.
|
126
doc/tutorials/03-writing-your-first-application.asciidoc
Normal file
@ -0,0 +1,126 @@
|
||||
---
|
||||
title: 3. Writing your first application
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 3. Writing your first application
|
||||
:notitle:
|
||||
|
||||
== Writing your first application
|
||||
|
||||
=== Building the QSkinny repository
|
||||
|
||||
In this chapter we will write a simple QSkinny application on Linux from scratch. As a prerequisite, a recent Qt version (>= 5.6) should be available and the directory of its `qmake` binary in the current `$PATH`. The we can build the QSkinny repository with the following commands:
|
||||
|
||||
[source,xml]
|
||||
....
|
||||
cd /home/user/dev/
|
||||
git clone https://github.com/uwerat/qskinny.git
|
||||
cd qskinny
|
||||
qmake
|
||||
make
|
||||
....
|
||||
|
||||
This will produce the libraries `libqskinny.so` and others in the `qskinny/lib` directory. Optionally we could install the libraries to `/usr/local` via `make install`; for now we will use the ones from the local build at `/home/user/dev/qskinny/lib`. If you checked out the repository in another directory, you will have to adapt the include and library paths used below.
|
||||
|
||||
=== Compiling our first app
|
||||
|
||||
As a next step, we need to write our app. Let's start with a simple `main.cpp` file in a directory `myapp`:
|
||||
|
||||
.main.cpp
|
||||
[source]
|
||||
....
|
||||
#include <SkinnyFont.h>
|
||||
#include <QskWindow.h>
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
QGuiApplication app( argc, argv );
|
||||
|
||||
SkinnyFont::init( &app );
|
||||
|
||||
QskWindow window;
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
....
|
||||
|
||||
For now this will just create an empty window (the `QskWindow`) without any controls. Next, we need to create a `myapp.pro` file in our `myapp` directory:
|
||||
|
||||
.myapp.pro
|
||||
[source,xml]
|
||||
....
|
||||
TEMPLATE = app
|
||||
TARGET = myapp
|
||||
|
||||
INCLUDEPATH += /home/user/dev/qskinny/support \
|
||||
/home/user/dev/qskinny/src/common \
|
||||
/home/user/dev/qskinny/src/controls \
|
||||
/home/peter/temp/qskinny/src/layouts
|
||||
|
||||
LIBS += -L/home/user/dev/qskinny/lib -lqskinny -lqsktestsupport
|
||||
|
||||
SOURCES += \
|
||||
main.cpp
|
||||
....
|
||||
|
||||
Now we can compile our app:
|
||||
|
||||
[source,xml]
|
||||
....
|
||||
cd myapp
|
||||
qmake
|
||||
make
|
||||
....
|
||||
|
||||
When running the app we will have to supply the `LD_LIBRARY_PATH`:
|
||||
|
||||
[source,xml]
|
||||
....
|
||||
LD_LIBRARY_PATH=/home/user/dev/qskinny/lib ./myapp
|
||||
....
|
||||
|
||||
This should show just an empty window.
|
||||
|
||||
=== Adding UI controls
|
||||
|
||||
Now that we have our app running, we can add some UI controls to it by extending the `main.cpp` file we created earlier. We will add some additional include directives, and then create a horizontal layout containing two push buttons. The layout with the two buttons will be shown in the window. Below is the complete updated source file:
|
||||
|
||||
.main.cpp
|
||||
[source]
|
||||
....
|
||||
#include <SkinnyFont.h>
|
||||
#include <QskWindow.h>
|
||||
#include <QskLinearBox.h>
|
||||
#include <QskPushButton.h>
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
QGuiApplication app( argc, argv );
|
||||
|
||||
SkinnyFont::init( &app );
|
||||
|
||||
auto* horizontalBox = new QskLinearBox( Qt::Horizontal );
|
||||
auto* button1 = new QskPushButton( "button 1", horizontalBox );
|
||||
auto* button2 = new QskPushButton( "button 2", horizontalBox );
|
||||
|
||||
QskWindow window;
|
||||
window.addItem( horizontalBox );
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
....
|
||||
|
||||
Now the app is displaying the two buttons:
|
||||
|
||||
image::../images/writing-first-application.png[An app showing two buttons]
|
||||
|
||||
That's it; you just created a QSkinny application from scratch.
|
||||
|
||||
For information on how the controls and layouts above behave, see the next chapters.
|
436
doc/tutorials/04-Layouts.asciidoc
Normal file
@ -0,0 +1,436 @@
|
||||
---
|
||||
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
|
||||
|
305
doc/tutorials/05-Skins.asciidoc
Normal file
@ -0,0 +1,305 @@
|
||||
---
|
||||
title: 5. Skins
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 5. Skins
|
||||
:notitle:
|
||||
|
||||
== Skins, Skin hints and Skinlets
|
||||
|
||||
Skins, Skin hints and Skinlets allow the user to define how specific
|
||||
controls looke like. Controls are drawn on the screen by the
|
||||
skinlet, and therefore it will read information from both the control
|
||||
itself as well as read the skin hints from the skin:
|
||||
|
||||
.Skinlets query the control and the skin
|
||||
image::../images/skins-1.png[Styling controls]
|
||||
|
||||
For instance, a button skinlet will read the margins from the skin and
|
||||
the text to render from the button.
|
||||
|
||||
=== Skins
|
||||
|
||||
Skins are a way to define a look and feel for a whole set of UI
|
||||
controls, e.g. a night time vs. day time skin, skins for different
|
||||
brands or an Android Material skin. They contain all kinds of properties
|
||||
(i.e. skin hints) like colors, margins, fonts and more.
|
||||
|
||||
[source]
|
||||
....
|
||||
class MySkin : public QskSkin
|
||||
{
|
||||
|
||||
public:
|
||||
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
|
||||
{
|
||||
// here define the skin with skin hints
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
The example below shows different implementations for a push button: One
|
||||
has a traditional desktop skin, the other is a flat button with a skin
|
||||
often found in mobile devices.
|
||||
|
||||
.desktop style button
|
||||
image::../images/skinlets-button-1.png[desktop style button]
|
||||
|
||||
.flat button
|
||||
image::../images/skinlets-button-2.png[flat button]
|
||||
|
||||
=== Skin hints
|
||||
|
||||
Each instance of a button will have unique properties like its text or
|
||||
icon file name, but all buttons will have common properties like the
|
||||
(default) background color and font size. These common properties are
|
||||
called skin hints, and are defined in a skin. Skin hints are either
|
||||
colors, e.g. the background color of a button, metrics (e.g. padding) or
|
||||
flags (e.g. text alignment).
|
||||
|
||||
Skin hints being part of a skin means that each skin can have different
|
||||
skin hints:
|
||||
|
||||
All buttons in a day time-like skin would have a light background color
|
||||
and dark text color, while a night time skin would have a dark
|
||||
background color and light text color by default.
|
||||
|
||||
Extending the `MySkin` example from above, here is an example of some
|
||||
skin hints for a push button, setting the padding to 10 pixels, the
|
||||
background color to magenta and the text color to black:
|
||||
|
||||
[source]
|
||||
....
|
||||
class MySkin : public QskSkin
|
||||
{
|
||||
|
||||
public:
|
||||
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
|
||||
{
|
||||
setGradient( QskPushButton::Panel, Qt::magenta );
|
||||
setMargins( QskPushButton::Panel | QskAspect::Padding, 10 );
|
||||
setColor( QskPushButton::Text, Qt::black );
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
.A button styled with skin hints
|
||||
image::../images/skin-hints.png[Button with skin hints]
|
||||
|
||||
When writing a new skin, a developer needs to know which hints to set
|
||||
for which control. This usually depends on the control itself; however,
|
||||
since usually controls are broken down into the three primitives box,
|
||||
text and graphic, the methods for rendering each of them will take the
|
||||
following skin hints into account:
|
||||
|
||||
[cols=",",options="header",]
|
||||
|=======================================================================
|
||||
|Primitive |Skin hint from QskAspect
|
||||
|Text |`Alignment` +
|
||||
`Color` +
|
||||
`TextColor` +
|
||||
`StyleColor` +
|
||||
`LinkColor` +
|
||||
`Style` +
|
||||
`FontRole`
|
||||
|
||||
|Graphic |`Alignment` +
|
||||
`GraphicRole`
|
||||
|
||||
|Box | `Margin` +
|
||||
`Metric` \| `Border` +
|
||||
`Color` \| `Border` +
|
||||
`Color` +
|
||||
`Metric` \| `Shape`
|
||||
|=======================================================================
|
||||
|
||||
Some special cases exist where elements other than the primitives above
|
||||
are used.
|
||||
|
||||
==== States and animations
|
||||
|
||||
Skin hints can also depend on the state a control is in: Buttons for
|
||||
instance can be in a `Pressed` or `Hovered` state. For such cases, skin
|
||||
hints cannot only be set on a subcontrol, but also be made dependent on
|
||||
a specific state. In the example below we define the background color of
|
||||
the button to be magenta in the default state and cyan in the `Hovered`
|
||||
state.
|
||||
|
||||
When dealing with states, QSkinny allows for animations between those (and other entities
|
||||
like skins). The example below adds a different color for the `Hovered`
|
||||
state and an animation when transitioning between the background colors.
|
||||
The duration is set to be one second (1000 milliseconds in the
|
||||
`setAnimation()` call below). Now when a user will hover over the
|
||||
button, there will be a smooth animation from magenta to cyan
|
||||
interpolating between the colors. Without the `setAnimation()` call, the
|
||||
button would just switch to magenta when hovered right away.
|
||||
|
||||
[source]
|
||||
....
|
||||
class MySkin : public QskSkin
|
||||
{
|
||||
|
||||
public:
|
||||
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
|
||||
{
|
||||
setGradient( QskPushButton::Panel, Qt::magenta );
|
||||
setMargins( QskPushButton::Panel | QskAspect::Padding, 10 );
|
||||
setColor( QskPushButton::Text, Qt::black );
|
||||
|
||||
setGradient( QskPushButton::Panel | QskPushButton::Hovered, Qt::cyan );
|
||||
setAnimation( QskPushButton::Panel | QskAspect::Color, 1000 );
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
.button in normal state
|
||||
image::../images/skin-hints-states-1.png[button in normal state]
|
||||
|
||||
.button in hovered state
|
||||
image::../images/skin-hints-states-2.png[button in hovered state]
|
||||
|
||||
==== Local skin hints
|
||||
|
||||
It is possible to set local skin hints on specific controls to override
|
||||
skin-wide settings:
|
||||
|
||||
[source]
|
||||
....
|
||||
auto* label1 = new QskTextLabel( "control 1" );
|
||||
label1->setMargins( 20 );
|
||||
label1->setBackgroundColor( Qt::blue );
|
||||
....
|
||||
|
||||
In general it is recommended to set the skin hints in the skin rather
|
||||
than on the control locally, in order to separate the style from the
|
||||
implementation, and to allow switching between skins. How to write
|
||||
controls that are themable is explained in the section about
|
||||
link:Writing-own-controls.html[writing own controls].
|
||||
|
||||
Taking animations and local skin hints into account, the architecture
|
||||
diagram now looks like this:
|
||||
|
||||
.Skinlets can also read from local skinlets and animators
|
||||
image::../images/skins-2.png[Animators and local skin hints]
|
||||
|
||||
=== Skinlets
|
||||
|
||||
A skinlet is in charge of drawing a control on the screen, similar to a
|
||||
Delegate in QML. It will read all the hints it needs from either the
|
||||
control itself or the skin, then it will draw the subcontrols that
|
||||
represent the control: In the sample case of a button, the skinlet will
|
||||
first draw the background panel, potentially consisting of a rectangle
|
||||
with a fill color. Then it will draw the text of the button, and last it
|
||||
will draw an icon, in case the button has one set.
|
||||
|
||||
Each skin can have a different skinlet to draw a control. Often the
|
||||
skinlet is the same across different skins and the skins only differ in
|
||||
skin hints, e.g. buttons having different fonts. However, it is also
|
||||
possible to have completely different skinlets per skin. This ensures a
|
||||
separation of application code instantiating the controls itself from
|
||||
the visual representation of the controls.
|
||||
|
||||
QSkinny already contains implementations of many common controls like
|
||||
text labels, buttons and so on. However, some custom controls might
|
||||
need to be written from scratch, including the skinlet; for an
|
||||
explanation on how to do this, see the example of
|
||||
link:Writing-own-controls.html[writing own controls].
|
||||
|
||||
For a closer look at how the skinlet draws the controls in the scene
|
||||
graph, see link:scene-graph.html[scene graph representations of controls].
|
||||
|
||||
Of course each app has different controls and therefore there are also
|
||||
different skinlets, so a more complete version of the architecture
|
||||
diagram looks like this:
|
||||
|
||||
.There is one skinlet for each atomic control
|
||||
image::../images/skins-3.png[Animators and local skin hints]
|
||||
|
||||
=== Skin factories and switching between skins
|
||||
|
||||
Skins are usually not created by the user directly, but by a skin
|
||||
factory. Such a factory keeps track of the skins registered in the
|
||||
system, and handles creating a new skin when the user switches them
|
||||
during application lifetime.
|
||||
|
||||
When having two skins called `MySkin` and `OtherSkin` in an app, the
|
||||
corresponding skin factory might look like this:
|
||||
|
||||
[source]
|
||||
....
|
||||
class MySkinFactory : public QskSkinFactory
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QStringList skinNames() const override
|
||||
{
|
||||
return { "MySkin", "OtherSkin" };
|
||||
}
|
||||
|
||||
QskSkin* createSkin( const QString& skinName ) override
|
||||
{
|
||||
if ( skinName == "MySkin" )
|
||||
return new MySkin;
|
||||
|
||||
if ( skinName == "OtherSkin" )
|
||||
return new OtherSkin;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
That skin factory has to be registered during app start; it is also a
|
||||
good idea to set a default skin right away:
|
||||
|
||||
[source]
|
||||
....
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
auto* skinFactory = new MySkinFactory;
|
||||
qskSkinManager->registerFactory( "MySkinFactory", skinFactory );
|
||||
|
||||
QGuiApplication app( argc, argv );
|
||||
|
||||
qskSetup->setSkin( "MySkin" );
|
||||
|
||||
...
|
||||
QskWindow window;
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
....
|
||||
|
||||
Now we can define the `OtherSkin` and define different skin hints for
|
||||
e.g. push buttons. Here we define the background color and padding to be
|
||||
different; also we configure buttons to have a blue border:
|
||||
|
||||
[source]
|
||||
....
|
||||
class OtherSkin : public QskSkin
|
||||
{
|
||||
|
||||
public:
|
||||
OtherSkin( QObject* parent = nullptr ) : QskSkin( parent )
|
||||
{
|
||||
setGradient( QskPushButton::Panel, Qt::cyan );
|
||||
setMargins( QskPushButton::Panel | QskAspect::Padding, 15 );
|
||||
setBoxBorderColors( QskPushButton::Panel, Qt::blue );
|
||||
setBoxBorderMetrics( QskPushButton::Panel, 1 );
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
Switching between skins will change the look of `QskPushButton`
|
||||
instances:
|
||||
|
||||
.button in `MySkin` (as above)
|
||||
image::../images/skin-hints-states-1.png[button in normal state]
|
||||
|
||||
.button in `OtherSkin`
|
||||
image::../images/skin-factory.png[Styling controls]
|
||||
|
62
doc/tutorials/06-scalable-graphics.asciidoc
Normal file
@ -0,0 +1,62 @@
|
||||
---
|
||||
title: 6. (Scalable) graphics
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 6. (Scalable) graphics
|
||||
:notitle:
|
||||
|
||||
== (Scalable) graphics
|
||||
|
||||
QSkinny offers support for scalable graphics, i.e. rendering SVGs that
|
||||
adapt to a specific size. This means that when a graphic is embedded in
|
||||
a layout, it can change its size when the layout is growing or
|
||||
shrinking, while still maintaining a correct aspect ratio.
|
||||
|
||||
Imagine the following code, which produces the image depicted below:
|
||||
|
||||
[source]
|
||||
....
|
||||
auto horizontalBox = new QskLinearBox( Qt::Horizontal );
|
||||
horizontalBox->setPreferredSize( { 200, 75 } );
|
||||
|
||||
QImage image1( ":/images/cloud.svg" );
|
||||
QskGraphic graphic1 = QskGraphic::fromImage( image1 );
|
||||
auto* label1 = new QskGraphicLabel( graphic1, horizontalBox );
|
||||
label1->setSizePolicy( QskSizePolicy::ConstrainedPreferred, QskSizePolicy::Expanding );
|
||||
|
||||
QImage image2( ":/images/train.svg" );
|
||||
QskGraphic graphic2 = QskGraphic::fromImage( image2 );
|
||||
auto* label2 = new QskGraphicLabel( graphic2, horizontalBox );
|
||||
label2->setSizePolicy( QskSizePolicy::ConstrainedPreferred, QskSizePolicy::Expanding );
|
||||
...
|
||||
....
|
||||
|
||||
.graphics with preferred size
|
||||
image::../images/scalable-graphics-1.png[Scalable graphics default]
|
||||
|
||||
When resizing the window, the graphics will scale according to the size
|
||||
available in the layout:
|
||||
|
||||
.graphics bounded by width
|
||||
image::../images/scalable-graphics-2.png[Scalable graphics bounded by width]
|
||||
|
||||
.graphics bounded by height
|
||||
image::../images/scalable-graphics-3.png[Scalable graphics bounded by height]
|
||||
|
||||
Since we set the horizontal size policy of the graphics to
|
||||
`ConstrainedPreferred`, the scaling is done through QskGraphic’s
|
||||
`widthForHeight()` methods to maintain the correct aspect ratio. If we
|
||||
had set the vertical policy to `ConstrainedPreferred` and the horizontal
|
||||
one to e.g. `Expanding`, the layout would have queried the
|
||||
`heightForWidth()` method instead.
|
||||
|
||||
Of course non-scalable graphics like PNGs and JPGs are also supported:
|
||||
|
||||
[source]
|
||||
....
|
||||
QImage image( "background.jpg" );
|
||||
QskGraphic graphic = QskGraphic::fromImage( image );
|
||||
...
|
||||
....
|
||||
|
191
doc/tutorials/07-parents-and-parent-items.asciidoc
Normal file
@ -0,0 +1,191 @@
|
||||
---
|
||||
title: 7. Parents and parent items
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 7. Parents and parent items
|
||||
:notitle:
|
||||
|
||||
== Parents and parent items
|
||||
|
||||
Creating an app with QSkinny consists of creating controls, putting them
|
||||
into layouts and nesting layouts and controls inside each other. The
|
||||
nesting already creates some sort of a hierarchy in the app, see the
|
||||
"Nesting layouts" section in the link:Layouts.html[layouts page]. In
|
||||
more general terms, all controls are part of several hierarchies:
|
||||
|
||||
* The *object tree*. This is a tree of `QObject` instances which manages
|
||||
lifetime: Objects created with a parent will get deleted whenever their
|
||||
parent is deleted. For more information, see the Qt documentation on
|
||||
https://doc.qt.io/qt-5/objecttrees.html[Object Trees & Ownership].
|
||||
* The *item tree*. This is a tree of items displayed on the screen,
|
||||
i.e. `QQuickItem` instances. Qt will traverse the item tree when
|
||||
rendering items on the screen. The positioning of an item depends on its
|
||||
parent item, e.g. layouts will position their child items according to
|
||||
certain policies. In addition, visual items will inherit properties from
|
||||
its parent item like visibility or opacity. The item tree is often
|
||||
similar to the object tree, but not necessarily: Instances of
|
||||
`QQuickItem` can have a parent item set, but have another parent, or no
|
||||
parent at all. See also the Qt documentation on
|
||||
https://doc.qt.io/qt-5/qtquick-visualcanvas-visualparent.html[Concepts -
|
||||
Visual Parent in Qt Quick].
|
||||
* The *scene graph*. The scene graph contains a representation of
|
||||
graphic primitives like rectangles, textures (i.e. images) and text, to
|
||||
allow efficient rendering on the screen with OpenGL or other backends.
|
||||
This is described in more details in link:scene-graph.html[scene graph
|
||||
representations of controls].
|
||||
|
||||
=== Example
|
||||
|
||||
Let’s look at the "Nesting layouts" example from the
|
||||
link:Layouts.html[layouts documentation]. The UI looks like this:
|
||||
|
||||
.UI with nested layouts
|
||||
image::../images/nesting-layouts.png[Nested layouts]
|
||||
|
||||
The code for this UI is below:
|
||||
|
||||
[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 );
|
||||
|
||||
QskWindow window;
|
||||
window.addItem( outerBox );
|
||||
window.show();
|
||||
....
|
||||
|
||||
==== Object tree
|
||||
|
||||
In the example above, when we created a new element, we always passed
|
||||
the `QObject` parent as an argument to the constructor, which is good
|
||||
practice. We do that for instance in this line:
|
||||
|
||||
[source]
|
||||
....
|
||||
auto* topLabel1 = new QskTextLabel( "top bar label 1", topBar );
|
||||
....
|
||||
|
||||
This makes sure `topBar` is a parent of `topLabel1`. It means that when
|
||||
`topBar` is deleted, it will automatically delete `topLabel1`, because
|
||||
the latter is a child of the `topBar`.
|
||||
|
||||
Below is an image of the object tree, i.e. the `QObject` parent-child
|
||||
relationship. The `QskWindow` is hereby the parent of the
|
||||
`QQuickRootItem`, which itself is the parent of the `outer box`, and so
|
||||
on. For information on how to obtain this tree, see
|
||||
https://doc.qt.io/qt-5/qobject.html#dumpObjectTree[QObject::dumpObjectTree()].
|
||||
|
||||
.QObject tree (and item tree) of the nested layouts UI
|
||||
image::../images/object-hierarchy.png[QObject hierarchy]
|
||||
|
||||
==== Item tree
|
||||
|
||||
The Item tree for the example above is identical to the object tree. As
|
||||
described, we always pass the parent object in the constructor:
|
||||
|
||||
[source]
|
||||
....
|
||||
auto* topLabel1 = new QskTextLabel( "top bar label 1", topBar );
|
||||
....
|
||||
|
||||
The line above will (in addition to the setting the parent) also ensure
|
||||
that `topBar` will be a *parent item* of `topLabel1`; this is done by
|
||||
the `QQuickItem` constructor.
|
||||
|
||||
Even if we had not passed the parent in the constructor, we could still
|
||||
add the label to the `topBar` via an explicit call:
|
||||
|
||||
[source]
|
||||
....
|
||||
auto* topLabel1 = new QskTextLabel( "top bar label 1" );
|
||||
topBar->addItem( topLabel1 );
|
||||
....
|
||||
|
||||
The call to `addItem()` above sets the parent item of `topLabel1` to
|
||||
`topBar` and thus the latter will display it as one of its children. In
|
||||
this case it would also set the parent, because the `topLabel1` does not
|
||||
have one yet. In other words, setting a parent item will also set the
|
||||
parent *if* the parent is null.
|
||||
|
||||
So since the `topBar` is a parent item of `topLabel1`, it means that
|
||||
`topLabel1` will inherit settings like visibility and opacity from
|
||||
`topBar`. For instance, if we set the the visibility of the `topBar` to
|
||||
false, all its child items will be invisible as well (which in this case
|
||||
would be all top bar labels). If we set the opacity to 0.2, all its
|
||||
child items will be almost transparent:
|
||||
|
||||
[source]
|
||||
....
|
||||
topBar->setOpacity( 0.2 );
|
||||
....
|
||||
|
||||
.Changing opacity of an item will affect all its child items
|
||||
image::../images/nesting-layouts-item-tree-1.png[Changing the item tree]
|
||||
|
||||
==== Difference in object trees and item trees
|
||||
|
||||
As an example for when the object tree and item tree differ, let’s
|
||||
decide to add a bottom bar to our UI and move our `topLabel1` from the
|
||||
top bar to the bottom bar. This is easy:
|
||||
|
||||
[source]
|
||||
....
|
||||
auto* bottomBar = new QskLinearBox( Qt::Horizontal, outerBox );
|
||||
topLabel1->setParentItem( bottomBar );
|
||||
....
|
||||
|
||||
.Moving a label from the top bar to the bottom bar
|
||||
image::../images/nesting-layouts-item-tree-2.png[Moving a label to the bottom bar]
|
||||
|
||||
Now we decide to get rid of our top bar altogether:
|
||||
|
||||
[source]
|
||||
....
|
||||
topBar->deleteLater();
|
||||
....
|
||||
|
||||
This will also delete our label from the bottom bar:
|
||||
|
||||
.Deleting the top bar will delete all its children
|
||||
image::../images/nesting-layouts-item-tree-3.png[Deleting the top bar]
|
||||
|
||||
The reason why the label from the bottom bar was also deleted is that
|
||||
with the call to `setParentItem()` above we set a new parent item; the
|
||||
parent, however, was still `topBar` (the call to `setParentItem()` did
|
||||
not change the parent, because it was not null). So when the `topBar`
|
||||
was deleted, it deleted all of its children, including the moved label
|
||||
`topLabel1`.
|
||||
|
||||
After we moved the label to the bottom bar, the object tree was
|
||||
different from the item tree, hence we got a surprising result when
|
||||
deleting the top bar. It is a good idea to try to keep the trees the
|
||||
same, and be aware of the existence of both of them.
|
||||
|
||||
If we reparent our label to the bottom bar before deleting the top bar,
|
||||
we get the desired effect:
|
||||
|
||||
[source]
|
||||
....
|
||||
topLabel1->setParent( bottomBar );
|
||||
topLabel1->setParentItem( bottomBar );
|
||||
topBar->deleteLater();
|
||||
....
|
||||
|
||||
.Reparenting the label will keep it alive when deleting the top bar
|
||||
image::../images/nesting-layouts-item-tree-4.png[Reparenting the item]
|
53
doc/tutorials/08-qskinny-and-qml.asciidoc
Normal file
@ -0,0 +1,53 @@
|
||||
---
|
||||
title: 8. Using QSkinny and QML
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 8. Using QSkinny and QML
|
||||
:notitle:
|
||||
|
||||
== QSkinny - Using QSkinny and QML
|
||||
|
||||
Combining QSkinny and QML is possible: Since both QML elements and
|
||||
QSkinny controls derive from `QQuickItem`, they can be combined and
|
||||
arranged in a common app. The
|
||||
https://github.com/uwerat/qskinny/tree/master/examples/buttons[QSkinny
|
||||
buttons example] shows how QSkinny controls can be used from QML.
|
||||
|
||||
When using a QSkinny control, all the methods exposed as either properties,
|
||||
slots or invokables can be used in QML. For example, the QSkinny control
|
||||
`QskLinearBox` defines the following properties:
|
||||
|
||||
[source]
|
||||
....
|
||||
class QSK_EXPORT QskLinearBox : public QskIndexedLayoutBox
|
||||
{
|
||||
Q_PROPERTY( Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL )
|
||||
Q_PROPERTY( qreal spacing READ spacing WRITE setSpacing RESET resetSpacing NOTIFY spacingChanged FINAL )
|
||||
...
|
||||
};
|
||||
....
|
||||
|
||||
The `QskLinearBox` class is registered to QML as `Qsk.LinearBox` via
|
||||
Qt’s `qmlRegisterType`, so the exposed properties `orientation` and
|
||||
`spacing` can be used like this:
|
||||
|
||||
[source]
|
||||
....
|
||||
Qsk.LinearBox
|
||||
{
|
||||
orientation: Qt.Horizontal
|
||||
spacing: 10
|
||||
|
||||
// here define elements inside the box
|
||||
...
|
||||
}
|
||||
....
|
||||
|
||||
The full Buttons example is depicted below.
|
||||
|
||||
.The buttons example shows how to mix QSkinny and QML
|
||||
image::../images/buttons-example.png[Buttons example]
|
||||
|
||||
For more information on using C++ classes from QML, see the article about exposing attributes of {cpp} types to QML in the
|
||||
https://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html[Qt documentation].
|
314
doc/tutorials/09-writing-own-controls.asciidoc
Normal file
@ -0,0 +1,314 @@
|
||||
---
|
||||
title: 9. Writing own controls
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 9. Writing own controls
|
||||
:notitle:
|
||||
|
||||
== Writing own controls
|
||||
|
||||
Writing own controls is either done by subclassing or compositing an
|
||||
existing displayable control like `QskTextLabel`, or by writing a
|
||||
completely new class including a skinlet, which is typically derived
|
||||
directly from `QskControl`.
|
||||
|
||||
=== Subclassing existing controls
|
||||
|
||||
Let’s say an app is displaying a text label with a specific style at
|
||||
several different places, then it makes sense to subclass `QskTextLabel`
|
||||
and set the needed properties like font size etc. in the derived class:
|
||||
|
||||
[source]
|
||||
....
|
||||
class TextLabel : public QskTextLabel
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TextLabel( const QString& text, QQuickItem* parent = nullptr ) : QskTextLabel( text, parent )
|
||||
{
|
||||
setMargins( 15 );
|
||||
setBackgroundColor( Qt::cyan );
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
.A subclassed control with local skin hints
|
||||
image::../images/subclassing-existing-controls.png[Subclassing existing controls]
|
||||
|
||||
Then there is no need to set the margins and background color for every
|
||||
instance of the custom text label.
|
||||
|
||||
=== Making custom classes skinnable
|
||||
|
||||
To make custom classes like the `TextLabel` class above skinnable, we
|
||||
need to define our own subcontrols and style them in our skin, in
|
||||
contrast to setting the values directly in the class. To be able to set
|
||||
specific values for our `TextLabel` class that are different from the
|
||||
generic `QskTextLabel`, we need to define our own subcontrols and
|
||||
substitute the generic subcontrols for them in an overriden method
|
||||
`effectiveSubcontrol()`:
|
||||
|
||||
[source]
|
||||
....
|
||||
class TextLabel : public QskTextLabel
|
||||
{
|
||||
QSK_SUBCONTROLS( Panel )
|
||||
|
||||
TextLabel( const QString& text, QQuickItem* parent = nullptr ) : QskTextLabel( text, parent )
|
||||
{
|
||||
}
|
||||
|
||||
QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol subControl ) const override final
|
||||
{
|
||||
if ( subControl == QskTextLabel::Panel )
|
||||
return TextLabel::Panel;
|
||||
|
||||
return subControl;
|
||||
}
|
||||
...
|
||||
}
|
||||
....
|
||||
|
||||
When the skinlet is drawing a `TextLabel` instance, it queries it for
|
||||
its subcontrols through `effectiveSubcontrol()` in order to style them
|
||||
properly. Now that we substitute the `QskTextLabel::Panel` for our
|
||||
`TextLabel::Panel`, we can style it accordingly in our skin, so we don’t
|
||||
need to set the local skin hints in the constructor of `TextLabel`
|
||||
anymore.
|
||||
|
||||
[source]
|
||||
....
|
||||
class MySkin : public QskSkin
|
||||
{
|
||||
|
||||
public:
|
||||
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
|
||||
{
|
||||
setGradient( TextLabel::Panel, Qt::cyan );
|
||||
setMargins( TextLabel::Panel | QskAspect::Padding, 15 );
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
.A subclassed control with skin hints defined in the skin
|
||||
image::../images/subclassing-existing-controls.png[Subclassing existing controls]
|
||||
|
||||
The styling described above has the same effect as in the simpler
|
||||
example, but now the `TextLabel` control can be given a different style
|
||||
depending on the skin.
|
||||
|
||||
In our class we only set a custom skin hint for the panel, but as
|
||||
`QskTextLabel` also has a `Text` subcontrol, we could of course also
|
||||
define our own one for the text.
|
||||
|
||||
=== Compositing controls
|
||||
|
||||
Controls can also be composited; e.g. when writing a class with a text
|
||||
label on the left and a graphic on the right side, it could look like
|
||||
this:
|
||||
|
||||
[source]
|
||||
....
|
||||
class TextAndGraphic : public QskLinearBox
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TextAndGraphic( const QString& text, const QString& graphicName, QQuickItem* parent = nullptr )
|
||||
: QskLinearBox( Qt::Horizontal, parent ),
|
||||
m_textLabel( new QskTextLabel( text, this ) )
|
||||
{
|
||||
addItem( m_textLabel );
|
||||
|
||||
QImage image( QString( ":/images/%1.svg" ).arg( graphicName ) );
|
||||
auto graphic = QskGraphic::fromImage( image );
|
||||
|
||||
m_graphicLabel = new QskGraphicLabel( graphic );
|
||||
m_graphicLabel->setExplicitSizeHint( Qt::PreferredSize, { 30, 30 } );
|
||||
addItem( m_graphicLabel );
|
||||
|
||||
setAutoLayoutChildren( true );
|
||||
...
|
||||
}
|
||||
|
||||
private:
|
||||
QskTextLabel* m_textLabel;
|
||||
QskGraphicLabel* m_graphicLabel;
|
||||
};
|
||||
....
|
||||
|
||||
This allows for easy instantiation of the class with a text and a file
|
||||
name for the graphic:
|
||||
|
||||
[source]
|
||||
....
|
||||
auto* textAndGraphic = new TextAndGraphic( "Text", "cloud" );
|
||||
....
|
||||
|
||||
.A composited control
|
||||
image::../images/compositing-controls.png[Compositing controls]
|
||||
|
||||
=== Writing controls with a skinlet
|
||||
|
||||
QSkinny already comes with controls like text labels, list views,
|
||||
buttons etc. When there is a completely new control to be written that
|
||||
cannot be subclassed or composited, the skinlet for the class needs to
|
||||
be implemented as well.
|
||||
|
||||
==== Writing the class
|
||||
|
||||
For demo purposes we create a class called `CustomShape` which shall
|
||||
display an outer circle and an inner circle, with minimal API. There are
|
||||
only 2 subcontrols that will be painted in the skinlet later:
|
||||
|
||||
[source]
|
||||
....
|
||||
class CustomShape : public QskControl
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( Panel, InnerShape )
|
||||
|
||||
CustomShape( QQuickItem* parent = nullptr ) : QskControl( parent )
|
||||
{
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
==== Writing the skinlet
|
||||
|
||||
Writing the skinlet is the hard part of the work. We need the following
|
||||
things in our skinlet:
|
||||
|
||||
* A definition of node roles. They typically correspond to subcontrols
|
||||
from the control, so since in our case we have a subcontrol `Panel` and
|
||||
`InnerShape`, there will be the node roles `PanelRole` and
|
||||
`InnerShapeRole`. The node roles are often set in the constructor of the
|
||||
class.
|
||||
|
||||
IMPORTANT: The constructor of the skinlet needs to be invokable!
|
||||
|
||||
[source]
|
||||
....
|
||||
class CustomShapeSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
enum NodeRole
|
||||
{
|
||||
PanelRole, InnerShapeRole
|
||||
};
|
||||
|
||||
Q_INVOKABLE CustomShapeSkinlet( QskSkin* skin = nullptr ) : QskSkinlet( skin )
|
||||
{
|
||||
setNodeRoles( { PanelRole, InnerShapeRole } );
|
||||
}
|
||||
....
|
||||
|
||||
* The enclosing rectangle for each subcontrol. This can be just the
|
||||
`contentsRect`, but we can define it more accurately if we want by
|
||||
applying some metrics. If the code below is hard to understand, the
|
||||
important thing to take away from it is that different subcontrols can
|
||||
have different enclosing rectangles.
|
||||
|
||||
[source]
|
||||
....
|
||||
QRectF subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const override
|
||||
{
|
||||
const auto* customShape = static_cast< const CustomShape* >( skinnable );
|
||||
|
||||
if ( subControl == CustomShape::Panel )
|
||||
{
|
||||
return contentsRect;
|
||||
}
|
||||
else if ( subControl == CustomShape::InnerShape )
|
||||
{
|
||||
const auto margins = customShape->marginsHint( CustomShape::InnerShape );
|
||||
return contentsRect.marginsRemoved( margins );
|
||||
}
|
||||
|
||||
return QskSkinlet::subControlRect( skinnable, contentsRect, subControl );
|
||||
....
|
||||
|
||||
* The code to actually draw the nodes. In our case of an outer circle
|
||||
and an inner circle, the code for each subcontrol / node role is quite
|
||||
similar. The method `updateSubNode()`, which is reimplemented from
|
||||
`QQuickItem`, is called once for each node role. The code below again
|
||||
might not be straight forward to understand, the gist of it is that for
|
||||
each node role we draw a circle by creating a `BoxNode`.
|
||||
|
||||
[source]
|
||||
....
|
||||
protected:
|
||||
QSGNode* updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const override
|
||||
{
|
||||
const auto* customShape = static_cast< const CustomShape* >( skinnable );
|
||||
|
||||
switch ( nodeRole )
|
||||
{
|
||||
case PanelRole:
|
||||
{
|
||||
auto panelNode = static_cast< QskBoxNode* >( node );
|
||||
|
||||
...
|
||||
const auto panelRect = subControlRect( customShape, customShape->contentsRect(), CustomShape::Panel );
|
||||
const qreal radius = panelRect.width() / 2;
|
||||
panelNode->setBoxData( panelRect, shapeMetrics, borderMetrics, borderColors, gradient );
|
||||
|
||||
return panelNode;
|
||||
}
|
||||
case InnerShapeRole:
|
||||
{
|
||||
auto innerNode = static_cast< QskBoxNode* >( node );
|
||||
|
||||
...
|
||||
const auto innerRect = subControlRect( customShape, customShape->contentsRect(), CustomShape::InnerShape );
|
||||
const qreal radius = innerRect.width() / 2;
|
||||
innerNode->setBoxData( innerRect, shapeMetrics, borderMetrics, borderColors, gradient );
|
||||
|
||||
return innerNode;
|
||||
}
|
||||
}
|
||||
|
||||
return QskSkinlet::updateSubNode( skinnable, nodeRole, node );
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
==== Connecting class and skinlet
|
||||
|
||||
In our skin, we need to declare that the skinlet above will be
|
||||
responsible of drawing our control via `declareSkinlet`. Also, we can
|
||||
style our control with skin hints:
|
||||
|
||||
[source]
|
||||
....
|
||||
class MySkin : public QskSkin
|
||||
{
|
||||
|
||||
public:
|
||||
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
|
||||
{
|
||||
declareSkinlet< CustomShape, CustomShapeSkinlet >();
|
||||
|
||||
setGradient( CustomShape::Panel, Qt::blue );
|
||||
setMargins( CustomShape::InnerShape, 20 );
|
||||
setGradient( CustomShape::InnerShape, Qt::magenta );
|
||||
}
|
||||
};
|
||||
....
|
||||
|
||||
SkinFactories etc. are again omitted here. Finally we can draw our
|
||||
control; the effort might seem excessive, but we wrote the control with
|
||||
all capabilities of styling; in addition, the control will react to size
|
||||
changes properly. A simpler version with hardcoded values for margins,
|
||||
colors etc. can be written with less code.
|
||||
|
||||
.A class with an own skinlet
|
||||
image::../images/control-with-skinlet.png[Control with skinlet]
|
74
doc/tutorials/10-scene-graph.asciidoc
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: 9. Scene graph representations of controls
|
||||
layout: docs
|
||||
---
|
||||
|
||||
:doctitle: 9. Scene graph representations of controls
|
||||
:notitle:
|
||||
|
||||
== QSkinny - Scene graph representations of controls
|
||||
|
||||
Each control that is displayed on the screen consists of one or more
|
||||
scene graph nodes. Those nodes can be either basic shapes like
|
||||
rectangles, or they can contain other information like positioning (used
|
||||
with transform nodes), opacity or clipping.
|
||||
|
||||
The source code below shows a minimal example displaying a button:
|
||||
|
||||
[source]
|
||||
....
|
||||
auto* button = new QskPushButton( "button" );
|
||||
|
||||
QskWindow window;
|
||||
window.addItem( button );
|
||||
window.show();
|
||||
....
|
||||
|
||||
For this example, the scene graph will contain the following nodes:
|
||||
|
||||
.Scene graph representation of a button
|
||||
image::../images/skins-sg-1.png[Scene graph nodes for a button]
|
||||
|
||||
The top two nodes (root and Quick root item) are created for every
|
||||
QtQuick application. The button itself consists of 5 nodes in our case:
|
||||
One root note (`button node`), one node just to group its children (just
|
||||
labeled `node`), one geometry node for drawing the background (`panel
|
||||
node`), one transform node for setting the position of the text and
|
||||
another geometry node for displaying the text (`text node`).
|
||||
|
||||
For an explanation of the different scene graph node types, see the Qt
|
||||
documentation of
|
||||
https://doc.qt.io/qt-5/qsgnode.html#NodeType-enum[QSGNode::NodeType].
|
||||
|
||||
The example above is the simplest form of a button, in practice there
|
||||
might be more nodes per control, for instance an opacity node or a clip
|
||||
node.
|
||||
|
||||
Now we add more elements to the UI by putting the button inside a layout
|
||||
(`QskBox`):
|
||||
|
||||
[source]
|
||||
....
|
||||
auto* box = new QskBox;
|
||||
auto* button = new QskPushButton( "button", box );
|
||||
|
||||
QskWindow window;
|
||||
window.addItem( box );
|
||||
window.show();
|
||||
....
|
||||
|
||||
Then the scene graph has the following structure:
|
||||
|
||||
.Scene graph representation of a button inside a box
|
||||
image::../images/skins-sg-2.png[Scene graph nodes for a button in a box]
|
||||
|
||||
Here we can see that since the box is a parent of the button, the `box
|
||||
node` is also a parent of the `button node` in the scene graph. Also, the
|
||||
box has two child nodes: The button, which is the same as in the earlier
|
||||
example, and a node for the panel of the box, in case the panel itself has a
|
||||
background color.
|
||||
|
||||
In a more complicated UI with multiple elements and more advanced
|
||||
layouts, the number of scene graph nodes can be quite high. This is why
|
||||
QSkinny tries to create as little nodes as possible and reuse as many as
|
||||
it can.
|
BIN
doc/tutorials/images/architecture-simple.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
doc/tutorials/images/architecture.png
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
doc/tutorials/images/buttons-example.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
doc/tutorials/images/compositing-controls.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
doc/tutorials/images/control-with-skinlet.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
doc/tutorials/images/horizontal-layout.gif
Normal file
After Width: | Height: | Size: 214 KiB |
BIN
doc/tutorials/images/layout-grid.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
doc/tutorials/images/layout-horizontal.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
doc/tutorials/images/layout-stack.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
doc/tutorials/images/layout-vertical.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
doc/tutorials/images/nesting-layouts-architecture.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
doc/tutorials/images/nesting-layouts-item-tree-1.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
doc/tutorials/images/nesting-layouts-item-tree-2.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
doc/tutorials/images/nesting-layouts-item-tree-3.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
doc/tutorials/images/nesting-layouts-item-tree-4.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
doc/tutorials/images/nesting-layouts.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
doc/tutorials/images/object-hierarchy.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
doc/tutorials/images/scalable-graphics-1.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
doc/tutorials/images/scalable-graphics-2.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
doc/tutorials/images/scalable-graphics-3.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
doc/tutorials/images/size-hints-1.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
doc/tutorials/images/size-hints-2.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
doc/tutorials/images/size-hints-calculation.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
doc/tutorials/images/size-policies-horizontal-minimum-1.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
doc/tutorials/images/size-policies-horizontal-minimum-2.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
doc/tutorials/images/size-policies-horizontal-minimum-3.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
doc/tutorials/images/size-policies-horizontal-minimum-4.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
doc/tutorials/images/skin-factory.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
doc/tutorials/images/skin-hints-states-1.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
doc/tutorials/images/skin-hints-states-2.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
doc/tutorials/images/skin-hints.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
doc/tutorials/images/skinlets-button-1.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
doc/tutorials/images/skinlets-button-2.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
doc/tutorials/images/skins-1.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
doc/tutorials/images/skins-2.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
doc/tutorials/images/skins-3.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
doc/tutorials/images/skins-sg-1.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
doc/tutorials/images/skins-sg-2.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
doc/tutorials/images/stretch-factors-1.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
doc/tutorials/images/stretch-factors-2.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
doc/tutorials/images/stretch-factors-3.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
doc/tutorials/images/subclassing-existing-controls.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
doc/tutorials/images/writing-first-application.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
6
doc/tutorials/index.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Tutorials
|
||||
excerpt: In this section you'll find the QSkinny tutorials.
|
||||
layout: docs
|
||||
---
|
||||
|