Convert tutorials to markdown
This commit is contained in:
parent
3f946380e7
commit
40547ef540
|
|
@ -943,6 +943,7 @@ WARN_LOGFILE = Doxygen.log
|
|||
INPUT = . \
|
||||
classes \
|
||||
../src \
|
||||
tutorials \
|
||||
../README.md
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
---
|
||||
title: 1. What is QSkinny?
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 1. What is QSkinny?
|
||||
:notitle:
|
||||
## What is QSkinny?
|
||||
|
||||
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://www.fendt.com/de/images/5d19bb4e7b260601c8134e14_1673943667_web_de-DE.jpg[Fendt Tractor GUI]
|
||||
**The 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::/doc/tutorials/images/architecture-simple.png[QSkinny architecture]
|
||||
**QSkinny sits on top of QtQuick, while QML is optional**
|
||||
|
||||

|
||||
|
|
@ -1,10 +1,6 @@
|
|||
---
|
||||
title: 2. Why QSkinny?
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 2. Why QSkinny?
|
||||
:notitle:
|
||||
## Why QSkinny?
|
||||
|
||||
The typical questions about QSkinny are: Why was QSkinny created? And why would
|
||||
somebody use QSkinny and not QML?
|
||||
|
|
@ -12,63 +8,62 @@ 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}
|
||||
### 1. It's C++
|
||||
|
||||
QSkinny is written in {cpp}, so there is no new syntax or programming paradigm
|
||||
QSkinny is written in C++, 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
|
||||
for people who know C++. Especially programmers experienced with
|
||||
QtWidgets should feel comfortable with QSkinny right away.
|
||||
|
||||
=== 1.1 Integration with other build systems / IDEs
|
||||
#### 1.1 Integration with other build systems / IDEs
|
||||
|
||||
While QtCreator is the natural choice of *IDE* for Qt programmers,
|
||||
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
|
||||
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:
|
||||
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
|
||||
With QSkinny being written completely in C++, 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
|
||||
#### 1.2 Use C++ tooling for your whole codebase
|
||||
|
||||
{cpp} has extensive tooling that assists with writing code, for instance:
|
||||
C++ 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)
|
||||
* 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}.
|
||||
C++.
|
||||
|
||||
When writing your whole codebase in {cpp} with QSkinny, the tooling can be used
|
||||
When writing your whole codebase in C++ 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,
|
||||
In addition, C++ 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.
|
||||
like event handling, see [Styling](#Styling) below.
|
||||
|
||||
|
||||
== 2. Easy data binding
|
||||
### 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
|
||||
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
|
||||
|
|
@ -76,19 +71,18 @@ 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
|
||||
QSkinny doesn’t need any adaptation layer per se, the data just needs to be
|
||||
connected to the frontend with standard C++ functionality. Of course classes
|
||||
like the aforementioned `QAbstractListModel` can be used when it makes sense,
|
||||
but this is up to the user.
|
||||
|
||||
### 3. Layouts
|
||||
|
||||
== 3. Layouts
|
||||
|
||||
Whe it comes to *layouts*, QSkinny has a complete concept of laying out the UI,
|
||||
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
|
||||
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
|
||||
|
|
@ -98,33 +92,31 @@ 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
|
||||
* 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
|
||||
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
|
||||
### 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
|
||||
add custom types in QML, not in C++. 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].
|
||||
[Differences with Qt Quick Controls 1](https://doc.qt.io/qt-5/qtquickcontrols2-differences.html).
|
||||
|
||||
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.
|
||||
Since being able to use C++ for application logic of components seems to have been
|
||||
one reason to create Qt Quick Controls 2 (another reason apparently being performance
|
||||
issues with Qt Quick Controls 1), allowing the user to write controls in C++ gives the user more flexibility.
|
||||
|
||||
QSkinny allows for implementing custom types in {cpp}; also both built-in
|
||||
QSkinny allows for implementing custom types in C++; 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
|
||||
styled from C++. The latter can be achieved by simply adding style
|
||||
descriptions in user code.
|
||||
|
|
@ -1,14 +1,8 @@
|
|||
---
|
||||
title: 3. Writing your first application
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 3. Writing your first application
|
||||
:notitle:
|
||||
## Writing your first application
|
||||
|
||||
== Writing your first application
|
||||
|
||||
=== Building the QSkinny repository
|
||||
### Building the QSkinny repository
|
||||
|
||||
In this chapter we will write a simple QSkinny application on Linux from scratch in C++ with Qt6.
|
||||
As a prerequisite, a supported Qt6 version should be available.
|
||||
|
|
@ -20,39 +14,35 @@ Optional packages for the virtual keyboard are `libhunspell-dev libimepinyin-dev
|
|||
|
||||
Then we can build and install QSkinny to `/opt/qskinny` with the following commands:
|
||||
|
||||
[source,shell]
|
||||
....
|
||||
```shell
|
||||
$ git clone https://github.com/uwerat/qskinny.git # clone
|
||||
$ cd qskinny
|
||||
$ mkdir build && cd build
|
||||
$ cmake ..
|
||||
$ cmake --build .
|
||||
$ sudo cmake --install . --prefix "/opt/qskinny"
|
||||
....
|
||||
|
||||
```
|
||||
|
||||
Considering that you want to use a specific Qt version that is installed below "/path/to/qt"
|
||||
you have 2 options:
|
||||
|
||||
[source,shell]
|
||||
....
|
||||
```shell
|
||||
$ cmake .. -DCMAKE_PREFIX_PATH=/path/to/qt
|
||||
....
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
[source,shell]
|
||||
....
|
||||
```shell
|
||||
$ /path/to/qt/bin/qt-cmake ..
|
||||
....
|
||||
```
|
||||
|
||||
=== Compiling our first app
|
||||
### 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`:
|
||||
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]
|
||||
....
|
||||
**main.cpp**
|
||||
|
||||
```
|
||||
#include <QskWindow.h>
|
||||
#include <QGuiApplication>
|
||||
|
||||
|
|
@ -65,14 +55,14 @@ int main( int argc, char* argv[] )
|
|||
|
||||
return app.exec();
|
||||
}
|
||||
....
|
||||
```
|
||||
|
||||
For now this will just create an empty window (the `QskWindow`) without any controls.
|
||||
Next, we need to create a `CMakeLists.txt` file in our `myapp` directory.
|
||||
|
||||
.CMakeLists.txt
|
||||
[source,cmake]
|
||||
....
|
||||
**CMakeLists.txt**
|
||||
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
|
||||
project(myapp
|
||||
|
|
@ -95,36 +85,34 @@ add_executable(myapp
|
|||
target_link_libraries(myapp PRIVATE
|
||||
Qt6::Quick
|
||||
Qsk::QSkinny)
|
||||
....
|
||||
```
|
||||
|
||||
Now we can compile our app:
|
||||
|
||||
[source,shell]
|
||||
....
|
||||
```shell
|
||||
$ cd myapp
|
||||
$ mkdir build && cd build
|
||||
$ cmake ../ && make
|
||||
....
|
||||
```
|
||||
|
||||
When running myapp it needs to find the skin plugins. Setting QT_PLUGIN_PATH is one
|
||||
option ( see https://doc.qt.io/qt/deployment-plugins.html ):
|
||||
|
||||
[source,shell]
|
||||
....
|
||||
```shell
|
||||
$ QT_PLUGIN_PATH=/opt/qskinny/plugins ./myapp
|
||||
....
|
||||
```
|
||||
|
||||
This should show just an empty window.
|
||||
|
||||
=== Adding UI controls
|
||||
### 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, cpp]
|
||||
....
|
||||
**main.cpp**
|
||||
|
||||
```cpp
|
||||
#include <QskWindow.h>
|
||||
#include <QskLinearBox.h>
|
||||
#include <QskPushButton.h>
|
||||
|
|
@ -153,12 +141,12 @@ int main( int argc, char* argv[] )
|
|||
|
||||
return app.exec();
|
||||
}
|
||||
....
|
||||
```
|
||||
|
||||
Now the app is displaying the two buttons:
|
||||
|
||||
image::/doc/tutorials/images/writing-first-application.png[An app showing two buttons]
|
||||

|
||||
|
||||
That's it; you just created a QSkinny application from scratch.
|
||||
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.
|
||||
|
|
@ -1,24 +1,18 @@
|
|||
---
|
||||
title: 4. Layouts
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 4. Layouts
|
||||
:notitle:
|
||||
|
||||
== Layouts
|
||||
## 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
|
||||
|
||||
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
|
||||
of that size"), while implicit sizes are deduced from the elements
|
||||
themselves. Explicit size hints always take precedence over implicit
|
||||
ones.
|
||||
|
||||
|
|
@ -26,8 +20,9 @@ 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::/doc/tutorials/images/size-hints-calculation.png[implicit horizontal size hint of a button]
|
||||
**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’
|
||||
|
|
@ -40,14 +35,14 @@ 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*.
|
||||
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
|
||||
* 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
|
||||
* 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
|
||||
* 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`
|
||||
|
|
@ -59,7 +54,7 @@ Minimum and maximum sizes, i.e. the methods `minimumSize()` and
|
|||
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
|
||||
#### 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
|
||||
|
|
@ -67,25 +62,26 @@ 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::/doc/tutorials/images/size-hints-1.png[Image without explicit size hint]
|
||||
**control without explicit size hint**
|
||||
|
||||

|
||||
|
||||
If we set an explicit size hint of 150x60 pixels ourselves for the
|
||||
preferred size, the control will be rendered differently:
|
||||
|
||||
....
|
||||
```
|
||||
label1->setExplicitSizeHint( Qt::PreferredSize, { 150, 60 } );
|
||||
....
|
||||
```
|
||||
|
||||
.control with explicit size hint
|
||||
image::/doc/tutorials/images/size-hints-2.png[Image with explicit size hint]
|
||||
**control with explicit size hint**
|
||||
|
||||

|
||||
|
||||
When dealing with standard controls or layouts, the size hints don’t
|
||||
need to be specified explicitly, as it can be deduced from its standard
|
||||
|
|
@ -94,7 +90,7 @@ 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
|
||||
|
||||
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
|
||||
|
|
@ -109,7 +105,6 @@ 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.
|
||||
|
|
@ -154,21 +149,20 @@ 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()]
|
||||
[QSizePolicy::hasHeightForWidth()](https://doc.qt.io/qt-5/qsizepolicy.html#hasHeightForWidth)
|
||||
or
|
||||
https://doc.qt.io/qt-5/qsizepolicy.html#hasWidthForHeight[QSizePolicy::hasWidthForHeight()]
|
||||
[QSizePolicy::hasWidthForHeight()](https://doc.qt.io/qt-5/qsizepolicy.html#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
|
||||
#### 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" );
|
||||
|
|
@ -179,53 +173,56 @@ 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::/doc/tutorials/images/size-policies-horizontal-minimum-1.png[Fixed vs. Minimum size policy]
|
||||
**Size policies with preferred size**
|
||||
|
||||

|
||||
|
||||
After growing the window horizontally, the button with the Fixed
|
||||
horizontal size policy keeps its width, while the button with the
|
||||
Minimum policy will grow:
|
||||
|
||||
.Size policies when increasing window width
|
||||
image::/doc/tutorials/images/size-policies-horizontal-minimum-2.png[Fixed vs. Minimum size policy]
|
||||
**Size policies when increasing window width**
|
||||
|
||||

|
||||
|
||||
When shrinking the window below its original size, both buttons stay
|
||||
with their width: The one on the left because of its `Fixed` size policy,
|
||||
and the one on the right because it won’t shrink below its original size
|
||||
due to the `Minimum` size policy.
|
||||
|
||||
.Size policies when shrinking window width
|
||||
image::/doc/tutorials/images/size-policies-horizontal-minimum-3.png[Fixed vs. Minimum size policy]
|
||||
**Size policies when shrinking window width**
|
||||
|
||||

|
||||
|
||||
If we change the policy of the right button to `Preferred`, it will shrink
|
||||
below its original size (even though the text is too wide now):
|
||||
|
||||
....
|
||||
```
|
||||
label2->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred );
|
||||
label2->setText( "size policy: preferred" );
|
||||
....
|
||||
```
|
||||
|
||||
.Size policies when changing to preferred size policy
|
||||
image::/doc/tutorials/images/size-policies-horizontal-minimum-4.png[Fixed vs. Minimum size policy]
|
||||
**Size policies when changing to preferred size policy**
|
||||
|
||||
=== Types of layouts
|
||||

|
||||
|
||||
### 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)
|
||||
#### 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" );
|
||||
|
|
@ -237,13 +234,13 @@ horizontalBox->addItem( label2 );
|
|||
auto* label3 = new QskTextLabel( "control 3" );
|
||||
horizontalBox->addItem( label3 );
|
||||
...
|
||||
....
|
||||
```
|
||||
|
||||
.Horizontal layout
|
||||
image::/doc/tutorials/images/layout-horizontal.png[Horizontal layout]
|
||||
**Horizontal layout**
|
||||
|
||||
[source]
|
||||
....
|
||||

|
||||
|
||||
```
|
||||
auto verticalBox = new QskLinearBox( Qt::Vertical );
|
||||
|
||||
auto* label1 = new QskTextLabel( "control 1" );
|
||||
|
|
@ -255,18 +252,18 @@ verticalBox->addItem( label2 );
|
|||
auto* label3 = new QskTextLabel( "control 3" );
|
||||
verticalBox->addItem( label3 );
|
||||
...
|
||||
....
|
||||
```
|
||||
|
||||
.Vertical layout
|
||||
image::/doc/tutorials/images/layout-vertical.png[Vertical layout]
|
||||
**Vertical layout**
|
||||
|
||||
==== Grid layouts (QskGridBox)
|
||||

|
||||
|
||||
#### 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" );
|
||||
|
|
@ -289,19 +286,19 @@ gridBox->addItem( label6, 2, 0 );
|
|||
|
||||
auto* label7 = new QskTextLabel( "control 7" );
|
||||
gridBox->addItem( label7, 2, 1, 1, 2 );
|
||||
....
|
||||
```
|
||||
|
||||
.Grid layout
|
||||
image::/doc/tutorials/images/layout-grid.png[Grid layout]
|
||||
**Grid layout**
|
||||
|
||||
==== Stack layouts (QskStackBox)
|
||||

|
||||
|
||||
#### 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" );
|
||||
|
|
@ -318,25 +315,27 @@ stackBox->addItem( label3 );
|
|||
|
||||
stackBox->setCurrentIndex( 2 );
|
||||
...
|
||||
....
|
||||
```
|
||||
|
||||
.Stack layout (symbolized)
|
||||
image::/doc/tutorials/images/layout-stack.png[Stack layout]
|
||||
**Stack layout (symbolized)**
|
||||
|
||||

|
||||
|
||||
In this example, "control 3" is stacked on top of the blue and the
|
||||
cyan control. Controls in a stacked layout can be of different sizes.
|
||||
|
||||
NOTE: The image above is just for illustrating purposes. In practice
|
||||
**📌 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()
|
||||
#### 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
|
||||
|
||||
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
|
||||
|
|
@ -347,8 +346,7 @@ 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" );
|
||||
|
|
@ -360,45 +358,48 @@ 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::/doc/tutorials/images/stretch-factors-1.png[Stretch factors preferred size]
|
||||
**Stretch factors with preferred size**
|
||||
|
||||

|
||||
|
||||
When the layout gets more width, the stretch factors come into play:
|
||||
|
||||
.A stretch factor of 1:2
|
||||
image::/doc/tutorials/images/stretch-factors-2.png[Stretch factors increasing width]
|
||||
**A stretch factor of 1:2**
|
||||
|
||||

|
||||
|
||||
No matter how wide the layout is, the aspect ratio of 1:2 will always be
|
||||
kept, meaning that the label on the left will get 33% of the space, and
|
||||
the label on the right 67%:
|
||||
|
||||
.A stretch factor of 1:2 with different widths
|
||||
image::/doc/tutorials/images/stretch-factors-3.png[Stretch factors even more width]
|
||||
**A stretch factor of 1:2 with different widths**
|
||||
|
||||

|
||||
|
||||
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
|
||||
### 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::/doc/tutorials/images/nesting-layouts.png[Nested layouts]
|
||||
**A UI with 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 );
|
||||
|
|
@ -417,7 +418,7 @@ 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
|
||||
|
|
@ -427,10 +428,10 @@ with the menu buttons is again a vertical layout.
|
|||
|
||||
The following diagram makes the layouts visible:
|
||||
|
||||
.The layout structure of the UI
|
||||
image::/doc/tutorials/images/nesting-layouts-architecture.png[Nested layouts architecture]
|
||||
**The layout structure of the UI**
|
||||
|
||||
=== Anchoring in QSkinny
|
||||

|
||||
|
||||
### Anchoring in QSkinny
|
||||
|
||||
TODO
|
||||
|
||||
|
|
@ -1,33 +1,27 @@
|
|||
---
|
||||
title: 5. Skins
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 5. Skins
|
||||
:notitle:
|
||||
|
||||
== Skins, Skin hints and Skinlets
|
||||
## 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::/doc/tutorials/images/skins-1.png[Styling controls]
|
||||
**Skinlets query the control and the skin**
|
||||
|
||||

|
||||
|
||||
For instance, a button skinlet will read the margins from the skin and
|
||||
the text to render from the button.
|
||||
|
||||
=== Skins
|
||||
### 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
|
||||
{
|
||||
|
||||
|
|
@ -37,19 +31,21 @@ public:
|
|||
// 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::/doc/tutorials/images/skinlets-button-1.png[desktop style button]
|
||||
**desktop style button**
|
||||
|
||||
.flat button
|
||||
image::/doc/tutorials/images/skinlets-button-2.png[flat button]
|
||||

|
||||
|
||||
=== Skin hints
|
||||
**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
|
||||
|
|
@ -69,8 +65,7 @@ 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
|
||||
{
|
||||
|
||||
|
|
@ -82,10 +77,11 @@ public:
|
|||
setColor( QskPushButton::Text, Qt::black );
|
||||
}
|
||||
};
|
||||
....
|
||||
```
|
||||
|
||||
.A button styled with skin hints
|
||||
image::/doc/tutorials/images/skin-hints.png[Button with skin hints]
|
||||
**A button styled 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,
|
||||
|
|
@ -93,31 +89,30 @@ 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` +
|
||||
|Text |`Alignment`\
|
||||
`Color`\
|
||||
`TextColor`\
|
||||
`StyleColor`\
|
||||
`LinkColor`\
|
||||
`Style`\
|
||||
`FontRole`
|
||||
|
||||
|Graphic |`Alignment` +
|
||||
|Graphic |`Alignment`\
|
||||
`GraphicRole`
|
||||
|
||||
|Box | `Margin` +
|
||||
`Metric` \| `Border` +
|
||||
`Color` \| `Border` +
|
||||
`Color` +
|
||||
|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
|
||||
#### 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
|
||||
|
|
@ -135,8 +130,7 @@ 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
|
||||
{
|
||||
|
||||
|
|
@ -151,39 +145,41 @@ public:
|
|||
setAnimation( QskPushButton::Panel | QskAspect::Color, 1000 );
|
||||
}
|
||||
};
|
||||
....
|
||||
```
|
||||
|
||||
.button in normal state
|
||||
image::/doc/tutorials/images/skin-hints-states-1.png[button in normal state]
|
||||
**button in normal state**
|
||||
|
||||
.button in hovered state
|
||||
image::/doc/tutorials/images/skin-hints-states-2.png[button in hovered state]
|
||||

|
||||
|
||||
==== Local skin hints
|
||||
**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].
|
||||
[writing own controls](Writing-own-controls.html).
|
||||
|
||||
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::/doc/tutorials/images/skins-2.png[Animators and local skin hints]
|
||||
**Skinlets can also read from local skinlets and animators**
|
||||
|
||||
=== Skinlets
|
||||

|
||||
|
||||
### 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
|
||||
|
|
@ -204,19 +200,20 @@ 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].
|
||||
[writing own controls](Writing-own-controls.html).
|
||||
|
||||
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].
|
||||
graph, see [scene graph representations of controls](scene-graph.html).
|
||||
|
||||
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::/doc/tutorials/images/skins-3.png[Animators and local skin hints]
|
||||
**There is one skinlet for each atomic control**
|
||||
|
||||
=== Skin factories and switching between skins
|
||||

|
||||
|
||||
### 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
|
||||
|
|
@ -226,8 +223,7 @@ 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
|
||||
{
|
||||
|
||||
|
|
@ -250,13 +246,12 @@ public:
|
|||
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;
|
||||
|
|
@ -272,14 +267,13 @@ int main( int argc, char* argv[] )
|
|||
|
||||
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
|
||||
{
|
||||
|
||||
|
|
@ -292,14 +286,15 @@ public:
|
|||
setBoxBorderMetrics( QskPushButton::Panel, 1 );
|
||||
}
|
||||
};
|
||||
....
|
||||
```
|
||||
|
||||
Switching between skins will change the look of `QskPushButton`
|
||||
instances:
|
||||
|
||||
.button in `MySkin` (as above)
|
||||
image::/doc/tutorials/images/skin-hints-states-1.png[button in normal state]
|
||||
**button in `MySkin` (as above)**
|
||||
|
||||
.button in `OtherSkin`
|
||||
image::/doc/tutorials/images/skin-factory.png[Styling controls]
|
||||

|
||||
|
||||
**button in `OtherSkin`**
|
||||
|
||||

|
||||
|
|
@ -1,12 +1,6 @@
|
|||
---
|
||||
title: 6. (Scalable) graphics
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 6. (Scalable) graphics
|
||||
:notitle:
|
||||
|
||||
== (Scalable) graphics
|
||||
## (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
|
||||
|
|
@ -15,8 +9,7 @@ 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 } );
|
||||
|
||||
|
|
@ -30,19 +23,22 @@ QskGraphic graphic2 = QskGraphic::fromImage( image2 );
|
|||
auto* label2 = new QskGraphicLabel( graphic2, horizontalBox );
|
||||
label2->setSizePolicy( QskSizePolicy::ConstrainedPreferred, QskSizePolicy::Expanding );
|
||||
...
|
||||
....
|
||||
```
|
||||
|
||||
.graphics with preferred size
|
||||
image::/doc/tutorials/images/scalable-graphics-1.png[Scalable graphics default]
|
||||
**graphics with preferred size**
|
||||
|
||||

|
||||
|
||||
When resizing the window, the graphics will scale according to the size
|
||||
available in the layout:
|
||||
|
||||
.graphics bounded by width
|
||||
image::/doc/tutorials/images/scalable-graphics-2.png[Scalable graphics bounded by width]
|
||||
**graphics bounded by width**
|
||||
|
||||
.graphics bounded by height
|
||||
image::/doc/tutorials/images/scalable-graphics-3.png[Scalable graphics bounded by height]
|
||||

|
||||
|
||||
**graphics bounded by height**
|
||||
|
||||

|
||||
|
||||
Since we set the horizontal size policy of the graphics to
|
||||
`ConstrainedPreferred`, the scaling is done through QskGraphic’s
|
||||
|
|
@ -53,10 +49,8 @@ one to e.g. `Expanding`, the layout would have queried the
|
|||
|
||||
Of course non-scalable graphics like PNGs and JPGs are also supported:
|
||||
|
||||
[source]
|
||||
....
|
||||
```
|
||||
QImage image( "background.jpg" );
|
||||
QskGraphic graphic = QskGraphic::fromImage( image );
|
||||
...
|
||||
....
|
||||
|
||||
```
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
---
|
||||
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::/doc/tutorials/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::/doc/tutorials/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::/doc/tutorials/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::/doc/tutorials/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::/doc/tutorials/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::/doc/tutorials/images/nesting-layouts-item-tree-4.png[Reparenting the item]
|
||||
|
|
@ -1,12 +1,6 @@
|
|||
---
|
||||
title: 8. Using QSkinny and QML
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 8. Using QSkinny and QML
|
||||
:notitle:
|
||||
|
||||
== QSkinny - Using QSkinny and QML
|
||||
## 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
|
||||
|
|
@ -18,31 +12,29 @@ 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:
|
||||
|
||||
.CMakeLists.txt
|
||||
[source,cmake]
|
||||
....
|
||||
**CMakeLists.txt**
|
||||
|
||||
```cmake
|
||||
target_link_libraries(myapp PRIVATE
|
||||
...
|
||||
Qsk::QmlExport)
|
||||
...
|
||||
....
|
||||
```
|
||||
|
||||
[source,cpp]
|
||||
....
|
||||
```cpp
|
||||
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
|
||||
|
|
@ -51,12 +43,13 @@ Qsk.LinearBox
|
|||
// here define elements inside the box
|
||||
...
|
||||
}
|
||||
....
|
||||
```
|
||||
|
||||
The full Buttons example is depicted below.
|
||||
|
||||
.The buttons example shows how to mix QSkinny and QML
|
||||
image::/doc/tutorials/images/buttons-example.png[Buttons example]
|
||||
**The buttons example shows how to mix QSkinny and QML**
|
||||
|
||||

|
||||
|
||||
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].
|
||||
[Qt documentation](https://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html).
|
||||
|
|
@ -1,26 +1,19 @@
|
|||
---
|
||||
title: 9. Writing own controls
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 9. Writing own controls
|
||||
:notitle:
|
||||
|
||||
== Writing own controls
|
||||
## 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
|
||||
### 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
|
||||
{
|
||||
|
||||
|
|
@ -33,15 +26,16 @@ public:
|
|||
setBackgroundColor( Qt::cyan );
|
||||
}
|
||||
};
|
||||
....
|
||||
```
|
||||
|
||||
.A subclassed control with local skin hints
|
||||
image::/doc/tutorials/images/subclassing-existing-controls.png[Subclassing existing controls]
|
||||
**A subclassed control with local skin hints**
|
||||
|
||||

|
||||
|
||||
Then there is no need to set the margins and background color for every
|
||||
instance of the custom text label.
|
||||
|
||||
=== Making custom classes skinnable
|
||||
### 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
|
||||
|
|
@ -51,8 +45,7 @@ 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 )
|
||||
|
|
@ -70,7 +63,7 @@ class TextLabel : public QskTextLabel
|
|||
}
|
||||
...
|
||||
}
|
||||
....
|
||||
```
|
||||
|
||||
When the skinlet is drawing a `TextLabel` instance, it queries it for
|
||||
its subcontrols through `effectiveSubcontrol()` in order to style them
|
||||
|
|
@ -79,8 +72,7 @@ properly. Now that we substitute the `QskTextLabel::Panel` for our
|
|||
need to set the local skin hints in the constructor of `TextLabel`
|
||||
anymore.
|
||||
|
||||
[source]
|
||||
....
|
||||
```
|
||||
class MySkin : public QskSkin
|
||||
{
|
||||
|
||||
|
|
@ -91,10 +83,11 @@ public:
|
|||
setMargins( TextLabel::Panel | QskAspect::Padding, 15 );
|
||||
}
|
||||
};
|
||||
....
|
||||
```
|
||||
|
||||
.A subclassed control with skin hints defined in the skin
|
||||
image::/doc/tutorials/images/subclassing-existing-controls.png[Subclassing existing controls]
|
||||
**A subclassed control with skin hints defined in the skin**
|
||||
|
||||

|
||||
|
||||
The styling described above has the same effect as in the simpler
|
||||
example, but now the `TextLabel` control can be given a different style
|
||||
|
|
@ -104,14 +97,13 @@ 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
|
||||
### 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
|
||||
{
|
||||
|
||||
|
|
@ -139,34 +131,33 @@ 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::/doc/tutorials/images/compositing-controls.png[Compositing controls]
|
||||
**A composited control**
|
||||
|
||||
=== Writing controls with a skinlet
|
||||

|
||||
|
||||
### 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
|
||||
#### 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
|
||||
|
|
@ -178,9 +169,9 @@ public:
|
|||
{
|
||||
}
|
||||
};
|
||||
....
|
||||
```
|
||||
|
||||
==== Writing the skinlet
|
||||
#### Writing the skinlet
|
||||
|
||||
Writing the skinlet is the hard part of the work. We need the following
|
||||
things in our skinlet:
|
||||
|
|
@ -191,10 +182,10 @@ from the control, so since in our case we have a subcontrol `Panel` and
|
|||
`InnerShapeRole`. The node roles are often set in the constructor of the
|
||||
class.
|
||||
|
||||
IMPORTANT: The constructor of the skinlet needs to be invokable!
|
||||
**❗ IMPORTANT**\
|
||||
The constructor of the skinlet needs to be invokable!
|
||||
|
||||
[source]
|
||||
....
|
||||
```
|
||||
class CustomShapeSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
|
@ -209,7 +200,7 @@ public:
|
|||
{
|
||||
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
|
||||
|
|
@ -217,8 +208,7 @@ 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 );
|
||||
|
|
@ -234,7 +224,7 @@ have different enclosing rectangles.
|
|||
}
|
||||
|
||||
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
|
||||
|
|
@ -243,8 +233,7 @@ similar. The method `updateSubNode()`, which is reimplemented from
|
|||
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
|
||||
{
|
||||
|
|
@ -279,16 +268,15 @@ protected:
|
|||
return QskSkinlet::updateSubNode( skinnable, nodeRole, node );
|
||||
}
|
||||
};
|
||||
....
|
||||
```
|
||||
|
||||
==== Connecting class and skinlet
|
||||
#### 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
|
||||
{
|
||||
|
||||
|
|
@ -302,7 +290,7 @@ public:
|
|||
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
|
||||
|
|
@ -310,5 +298,6 @@ 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::/doc/tutorials/images/control-with-skinlet.png[Control with skinlet]
|
||||
**A class with an own skinlet**
|
||||
|
||||

|
||||
|
|
@ -1,12 +1,6 @@
|
|||
---
|
||||
title: 9. Scene graph representations of controls
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 9. Scene graph representations of controls
|
||||
:notitle:
|
||||
|
||||
== QSkinny - Scene graph representations of controls
|
||||
## 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
|
||||
|
|
@ -15,19 +9,19 @@ 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::/doc/tutorials/images/skins-sg-1.png[Scene graph nodes for a button]
|
||||
**Scene graph representation of 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:
|
||||
|
|
@ -38,7 +32,7 @@ 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].
|
||||
[QSGNode::NodeType](https://doc.qt.io/qt-5/qsgnode.html#NodeType-enum).
|
||||
|
||||
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
|
||||
|
|
@ -47,20 +41,20 @@ 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::/doc/tutorials/images/skins-sg-2.png[Scene graph nodes for a button in a box]
|
||||
**Scene graph representation of a button inside 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
|
||||
|
|
@ -1,53 +1,42 @@
|
|||
---
|
||||
title: 10. Building QSkinny for WebAssembly (Wasm)
|
||||
layout: docs
|
||||
---
|
||||
# Tutorials {#tutorials}
|
||||
|
||||
:doctitle: 10. Building QSkinny for WebAssembly (Wasm)
|
||||
:notitle:
|
||||
## Building QSkinny for WebAssembly (Wasm)
|
||||
|
||||
== 10. Building QSkinny for WebAssembly (Wasm)
|
||||
|
||||
|
||||
=== Build Qt for Wasm
|
||||
### Build Qt for Wasm
|
||||
|
||||
Build Qt for Wasm from source as described here: https://doc.qt.io/qt-6/wasm.html#building-qt-from-source ; The verified Qt version for QSkinny as of this writing was 6.6.0. It might also work to use a downloaded version of Qt for Wasm, but some additional libraries will need to be built.
|
||||
After configuring Qt as described in the link above, for QSkinny you will need the qtbase, qtdeclarative, qtshadertools and qtsvg modules.
|
||||
Assuming the Emscripten SDK in `~/dev/emscripten` and Qt in `~/dev/qt6`, Qt can be compiled the following way:
|
||||
|
||||
[source]
|
||||
....
|
||||
```
|
||||
cd ~/dev/qt6
|
||||
source "~/dev/emsdk/emsdk_env.sh"
|
||||
./configure -platform wasm-emscripten -qt-host-path ~/Qt/6.6.0/gcc_64/ -prefix $PWD/qtbase -submodules qtbase,qtdeclarative,qtshadertools,qtsvg
|
||||
cmake --build . -t qtbase -t qtdeclarative -t qtshadertools -t qtsvg
|
||||
....
|
||||
```
|
||||
|
||||
This will install all required libs in `~/dev/qt6/qtbase/lib`.
|
||||
|
||||
|
||||
=== Build QSkinny for Wasm
|
||||
### Build QSkinny for Wasm
|
||||
|
||||
With the Qt version from above QSkinny can be built for WAsm, assuming it has been checked out at `~/dev/qskinny`. Note the configuration option `BUILD_QSKDLL=OFF` for static
|
||||
builds:
|
||||
|
||||
[source]
|
||||
....
|
||||
```
|
||||
mkdir -p ~/dev/qskinny-wasm-build
|
||||
source "~/dev/emsdk/emsdk_env.sh"
|
||||
~/dev/qt6/qtbase/bin/qt-cmake -S ~/dev/qskinny -B ~/dev/qskinny-wasm-build -DBUILD_QSKDLL=OFF
|
||||
make
|
||||
....
|
||||
```
|
||||
|
||||
|
||||
=== Run QSkinny for Wasm
|
||||
### Run QSkinny for Wasm
|
||||
|
||||
Qt creates the HTML wrappers automatically, so there is not much to do except letting Emscripten start the server and open our app in the browser:
|
||||
|
||||
[source]
|
||||
....
|
||||
```
|
||||
/usr/bin/python3 ~/dev/emsdk/upstream/emscripten/emrun.py --browser firefox --port 30001 --no_emrun_detect ~/dev/qskinny-wasm-build/examples/bin/iotdashboard.html
|
||||
....
|
||||
```
|
||||
|
||||
.The IOT dashboard example in a browser
|
||||
image::/doc/tutorials/images/iotdashboard-wasm.png[The IOT dashboard example in a browser]
|
||||
**The IOT dashboard example in a browser**
|
||||
|
||||

|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
title: Tutorials
|
||||
excerpt: In this section you'll find the QSkinny tutorials.
|
||||
layout: docs
|
||||
---
|
||||
|
||||
Loading…
Reference in New Issue