This commit is contained in:
Peter Hartmann 2025-06-24 18:54:54 +00:00 committed by GitHub
commit b46deb3297
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
92 changed files with 4341 additions and 872 deletions

View File

@ -40,8 +40,7 @@ direction - please contact support@qskinny.org.
![IOT dashboard](/doc/images/iotdashboard.png)
# Contributing to QSkinny
# Contributing
QSkinny is licensed under the BSD 3 Clause License ( https://opensource.org/license/bsd-3-clause ). However we want to be able to offer more licenses on request.

View File

@ -68,7 +68,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY = api
OUTPUT_DIRECTORY =
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
@ -278,7 +278,9 @@ ALIASES = "accessors=\par Access functions:^^" \
"states=\par States:^^" \
"skinlet=\par Default Skinlet:^^" \
"aspect=\par Aspect^^" \
"saqt=\sa ^^"
"embedWasm=<div> <div id=\"qtspinner\"> <div id=\"qtstatus\"></div> </div> <div id=\"qt-wasm-screen\"> </div> </div>"
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
@ -938,9 +940,10 @@ WARN_LOGFILE = Doxygen.log
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = . \
classes \
../src
INPUT = classes \
../src \
tutorials \
../README.md
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -1113,7 +1116,7 @@ FILTER_SOURCE_PATTERNS =
# (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output.
USE_MDFILE_AS_MAINPAGE =
USE_MDFILE_AS_MAINPAGE = ../README.md
# The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common
@ -1313,7 +1316,7 @@ HTML_FILE_EXTENSION = .html
# of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_HEADER =
HTML_HEADER = header.html
# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
# generated HTML page. If the tag is left blank doxygen will generate a standard
@ -1323,7 +1326,7 @@ HTML_HEADER =
# that doxygen normally uses.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FOOTER =
HTML_FOOTER = footer.html
# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
# sheet that is used by each HTML page. It can be used to fine-tune the look of
@ -1353,7 +1356,7 @@ HTML_STYLESHEET =
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = ./customdoxygen.css
HTML_EXTRA_STYLESHEET = ./doxygen-awesome.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@ -1376,7 +1379,7 @@ HTML_EXTRA_FILES =
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = AUTO_LIGHT
HTML_COLORSTYLE = LIGHT
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
@ -1696,7 +1699,7 @@ GENERATE_TREEVIEW = YES
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
FULL_SIDEBAR = YES
FULL_SIDEBAR = NO
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# doxygen will group on one line in the generated HTML documentation.

View File

@ -1,61 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<doxygenlayout version="1.0">
<!-- Generated by doxygen 1.8.20 -->
<!-- Generated by doxygen 1.9.8 -->
<!-- Navigation index tabs for HTML output -->
<navindex>
<tab type="mainpage" visible="yes" title="Home"/>
<tab type="pages" visible="no" title="" intro=""/>
<tab type="mainpage" visible="yes" title=""/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="topics" visible="yes" title="" intro=""/>
<tab type="modules" visible="yes" title="" intro="">
<tab type="modulelist" visible="yes" title="" intro=""/>
<tab type="modulemembers" visible="yes" title="" intro=""/>
</tab>
<tab type="namespaces" visible="yes" title="">
<tab type="namespacelist" visible="yes" title="" intro=""/>
<tab type="namespacemembers" visible="no" title="" intro=""/>
<tab type="namespacemembers" visible="yes" title="" intro=""/>
</tab>
<tab type="concepts" visible="yes" title="">
</tab>
<tab type="interfaces" visible="yes" title="">
<tab type="interfacelist" visible="yes" title="" intro=""/>
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="interfacehierarchy" visible="no" title="" intro=""/>
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
</tab>
<tab type="classes" visible="yes" title="">
<tab type="modules" visible="yes" title="Groups" intro=""/>
<tab type="classlist" visible="yes" title="" intro=""/>
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="hierarchy" visible="no" title="" intro=""/>
<tab type="classmembers" visible="no" title="" intro=""/>
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="hierarchy" visible="yes" title="" intro=""/>
<tab type="classmembers" visible="yes" title="" intro=""/>
</tab>
<tab type="structs" visible="yes" title="">
<tab type="structlist" visible="yes" title="" intro=""/>
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
</tab>
<tab type="exceptions" visible="no" title="">
<tab type="exceptionlist" visible="no" title="" intro=""/>
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="exceptionhierarchy" visible="no" title="" intro=""/>
<tab type="exceptions" visible="yes" title="">
<tab type="exceptionlist" visible="yes" title="" intro=""/>
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
</tab>
<tab type="files" visible="no" title="">
<tab type="filelist" visible="no" title="" intro=""/>
<tab type="globals" visible="no" title="" intro=""/>
<tab type="files" visible="yes" title="">
<tab type="filelist" visible="yes" title="" intro=""/>
<tab type="globals" visible="yes" title="" intro=""/>
</tab>
<tab type="examples" visible="no" title="" intro=""/>
<tab type="examples" visible="yes" title="" intro=""/>
</navindex>
<!-- Layout definition for a class page -->
<class>
<briefdescription visible="no"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<detaileddescription title=""/>
<inheritancegraph visible="$CLASS_GRAPH"/>
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
<collaborationgraph visible="yes"/>
<memberdecl>
<nestedclasses visible="yes" title=""/>
<publictypes title=""/>
<services title=""/>
<related title="" subtitle=""/>
<interfaces title=""/>
<properties title=""/>
<publicattributes title=""/>
<publicstaticattributes title=""/>
<publicmethods title=""/>
<publicstaticmethods title=""/>
<publicslots title=""/>
<signals title=""/>
<publicmethods title=""/>
<publicstaticmethods title=""/>
<publicattributes title=""/>
<publicstaticattributes title=""/>
<protectedtypes title=""/>
<protectedslots title=""/>
<protectedmethods title=""/>
@ -67,6 +72,7 @@
<packagestaticmethods title=""/>
<packageattributes title=""/>
<packagestaticattributes title=""/>
<properties title=""/>
<events title=""/>
<privatetypes title=""/>
<privateslots title=""/>
@ -75,6 +81,7 @@
<privateattributes title=""/>
<privatestaticattributes title=""/>
<friends title=""/>
<related title="" subtitle=""/>
<membergroups visible="yes"/>
</memberdecl>
<memberdef>
@ -103,6 +110,7 @@
<constantgroups visible="yes" title=""/>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<concepts visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<typedefs title=""/>
@ -126,12 +134,21 @@
<authorsection visible="yes"/>
</namespace>
<!-- Layout definition for a concept page -->
<concept>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<definition visible="yes" title=""/>
<detaileddescription title=""/>
<authorsection visible="yes"/>
</concept>
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includegraph visible="$INCLUDE_GRAPH"/>
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
<includegraph visible="yes"/>
<includedbygraph visible="yes"/>
<sourcelink visible="yes"/>
<memberdecl>
<interfaces visible="yes" title=""/>
@ -139,6 +156,7 @@
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
@ -166,12 +184,14 @@
<!-- Layout definition for a group page -->
<group>
<briefdescription visible="yes"/>
<groupgraph visible="$GROUP_GRAPHS"/>
<groupgraph visible="yes"/>
<memberdecl>
<nestedgroups visible="yes" title=""/>
<modules visible="yes" title=""/>
<dirs visible="yes" title=""/>
<files visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<classes visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
@ -213,6 +233,25 @@
<authorsection visible="yes"/>
</group>
<!-- Layout definition for a C++20 module page -->
<module>
<briefdescription visible="yes"/>
<exportedmodules visible="yes"/>
<memberdecl>
<concepts visible="yes" title=""/>
<classes visible="yes" title=""/>
<enums title=""/>
<typedefs title=""/>
<functions title=""/>
<variables title=""/>
<membergroups title=""/>
</memberdecl>
<detaileddescription title=""/>
<memberdecl>
<files visible="yes"/>
</memberdecl>
</module>
<!-- Layout definition for a directory page -->
<directory>
<briefdescription visible="yes"/>

View File

@ -0,0 +1,7 @@
/*!
\class QskCheckBox QskCheckBox.h
\embedWasm
\skinlet QskCheckBoxSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskComboBox QskComboBox.h
\embedWasm
\skinlet QskComboBoxSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskDrawer QskDrawer.h
\embedWasm
\skinlet QskDrawerSkinlet
*/

View File

@ -1,6 +1,8 @@
/*!
\class QskLinearBox QskLinearBox.h
\embedWasm
\brief Layout stringing items in rows and columns
QskLinearBox organizes layout items in vertical or horizontal order

View File

@ -0,0 +1,7 @@
/*!
\class QskPageIndicator QskPageIndicator.h
\embedWasm
\skinlet QskPageIndicatorSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskProgressBar QskProgressBar.h
\embedWasm
\skinlet QskProgressBarSkinlet
*/

View File

@ -0,0 +1,283 @@
/*!
\class QskProgressIndicator QskProgressIndicator.h
\ingroup Framework Controls
\brief Base class for progress indicators
QskProgressIndicator is the base class for circular (QskProgressRing) and linear (QskProgressBar)
progress indicators, and should not be instantiated directly.
There are two modes for progress indicators:
- **Determinate**, which means the user has to set a progress value to advance the indicator
- **Indeterminate**, which means the indicator will loop forever without the need for updating its value
By default progress indicators are determinate.
\subcontrols QskProgressIndicator::Groove, QskProgressIndicator::Fill
\skinlet QskProgressIndicatorSkinlet
\sa QskProgressBar, QskProgressRing
*/
/*!
\var QskProgressIndicator::Groove
Indicating the value range that the indicator can have; is drawn below the QskProgressIndicator::Fill.
*/
/*!
\var QskProgressIndicator::Fill
Showing the current value of the indicator; is drawn above the QskProgressIndicator::Groove.
*/
/*!
\property qreal QskProgressIndicator::extent
The extent of the indicator.
\accessors extent(), setExtent(), extentChanged(), resetExtent()
*/
/*!
\property bool QskProgressIndicator::indeterminate
Whether the indicator is indeterminate or not.
\accessors isIndeterminate(), setIndeterminate(), indeterminateChanged()
*/
/*!
\property qreal QskProgressIndicator::origin
The origin of the indicator.
\accessors qreal origin(), setOrigin(), originChanged(), resetOrigin()
*/
/*!
\property qreal QskProgressIndicator::value
The value of the indicator.
\accessors value(), setValue(), valueChanged()
*/
/*!
\property qreal QskProgressIndicator::valueAsRatio
The value of the indicator as ratio.
\accessors valueAsRatio(), setValueAsRatio(), valueChanged()
*/
/*!
\fn QskProgressIndicator::QskProgressIndicator( QQuickItem* )
Creates a new progress indicator with the given \a parent.
*/
/*!
\fn QskProgressIndicator::QskProgressIndicator( qreal, qreal, QQuickItem* )
Creates a new progress indicator with the progress interval [\a min, \a max] and the given \a parent.
*/
/*!
\fn QskProgressIndicator::QskProgressIndicator( const QskIntervalF& interval, QQuickItem* parent )
Creates a new progress indicator with the given progress \a interval and the given \a parent.
*/
/*!
\fn QskProgressIndicator::~QskProgressIndicator()
Destructor.
*/
/*!
\fn QskProgressIndicator::isIndeterminate() const
Returns whether the progress indicator is indeterminate, i.e. will loop forever.
\sa setIndeterminate(), indeterminateChanged()
*/
/*!
\fn QskProgressIndicator::setIndeterminate( bool )
Sets whether the progress indicator is indeterminate.
\sa isIndeterminate(), indeterminateChanged()
*/
/*!
\fn QskProgressIndicator::setFillGradient( const QskGradient& )
Sets the fill gradient by setting the gradient hint of the QskProgressIndicator::Fill subcontrol.
\sa fillGradient(), resetFillGradient()
*/
/*!
\fn QskProgressIndicator::resetFillGradient()
Resets the fill gradient.
\sa fillGradient(), setFillGradient()
*/
/*!
\fn QskProgressIndicator::fillGradient() const
Returns the fill gradient.
\sa setFillGradient(), resetFillGradient()
*/
/*!
\fn QskProgressIndicator::setExtent( qreal )
Sets the size of the extent, i.e. the QskProgressIndicator::Groove subcontrol.
\sa extent(), extentChanged(), resetExtent()
*/
/*!
\fn QskProgressIndicator::resetExtent()
Resets the extent.
\sa extent(), setExtent(), extentChanged()
*/
/*!
\fn QskProgressIndicator::extent() const
Returns the extent, i.e. the size of the QskProgressIndicator::Groove subcontrol.
\sa setExtent(), extentChanged(), resetExtent()
*/
/*!
\fn QskProgressIndicator::resetOrigin()
Resets the origin.
\sa hasOrigin(), origin( void ), setOrigin(), originChanged()
*/
/*!
\fn QskProgressIndicator::origin() const
Returns the origin, i.e. the value where the progress will start.
If no origin has been set via setOrigin(), the minimum value is returned.
\sa hasOrigin(), setOrigin(), originChanged(), resetOrigin(), QskBoundedControl::minimum()
*/
/*!
\fn QskProgressIndicator::hasOrigin() const
Returns true if an origin has been set via setOrigin().
\sa origin(), setOrigin(), originChanged(), resetOrigin()
*/
/*!
\fn QskProgressIndicator::value() const
Returns the current value.
\sa valueAsRatio(), setValue(), valueChanged()
*/
/*!
\fn QskProgressIndicator::valueAsRatio() const
Returns the current value as ratio, i.e. as percentage in the range of [0.0, 1.0].
The ratio is calculated like this: ratio = (value() - minimum()) / (maximum() - minimum()).
\sa value(), setValueAsRatio(), valueChanged()
*/
/*!
\fn QskProgressIndicator::setValue( qreal )
Sets the value to \a value. If \a value is outside of the boundaries(), it will be bound to this
range.
\sa setValueAsRatio(), value(), valueChanged()
*/
/*!
\fn QskProgressIndicator::setValueAsRatio( qreal )
Sets the value as ratio of [0.0, 1.0].
If \a ratio is outside of this range, it will be bound to the interval [0.0, 1.0], i.e. if it is
smaller than 0, it will be set to 0, and if it is greater than 1, it will be set to 1.
\sa valueAsRatio(), value(), setValue(), valueChanged()
*/
/*!
\fn QskProgressIndicator::setOrigin( qreal )
Sets the origin.
\sa origin(), hasOrigin(), originChanged(), resetOrigin()
*/
/*!
\fn QskProgressIndicator::extentChanged( qreal )
Will be emitted when the extent changes.
\sa extent(), setExtent(), resetExtent()
*/
/*!
\fn QskProgressIndicator::indeterminateChanged( bool )
Will be emitted when the indicator changes from indeterminate to determinate or vice versa.
\sa isIndeterminate(), setIndeterminate()
*/
/*!
\fn QskProgressIndicator::valueChanged( qreal )
Will be emitted when the value changes.
\sa value(), valueAsRatio(), setValue(), setValueAsRatio()
*/
/*!
\fn QskProgressIndicator::originChanged( qreal )
Will be emitted when the origin changes.
\sa origin(), hasOrigin(), setOrigin(), resetOrigin()
*/
/*!
\fn QskProgressIndicator::componentComplete() override
This will adjust the value appropriately when the component is complete.
Derived classes overriding this function should call it in their implementation.
*/
/*!
\fn QskProgressIndicator::itemChange( ItemChange, const ItemChangeData& )
Checks whether the visibility has changed and animates the indicator in case it is indeterminate.
I.e. when the indicator becomes visible it will start the animator, and when it becomes invisible
it will stop it.
Derived classes overriding this function should call it in their implementation.
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskProgressRing QskProgressRing.h
\embedWasm
\skinlet QskProgressRingSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskPushButton QskPushButton.h
\embedWasm
\skinlet QskPushButtonSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskRadioBox QskRadioBox.h
\embedWasm
\skinlet QskRadioBoxSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskSegmentedBar QskSegmentedBar.h
\embedWasm
\skinlet QskSegmentedBarSkinlet
*/

View File

@ -0,0 +1,5 @@
/*!
\class QskSimpleListBox QskSimpleListBox.h
\embedWasm
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskSlider QskSlider.h
\embedWasm
\skinlet QskSliderSkinlet
*/

View File

@ -1,6 +1,8 @@
/*!
\class QskSpinBox QskSpinBox.h
\embedWasm
\brief A control to edit, increment and decrement number values
QskSpinBox allows the user to choose a value by:

View File

@ -0,0 +1,5 @@
/*!
\class QskSwipeView QskSwipeView.h
\embedWasm
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskSwitchButton QskSwitchButton.h
\embedWasm
\skinlet QskSwitchButtonSkinlet
*/

View File

@ -0,0 +1,5 @@
/*!
\class QskTabBar QskTabBar.h
\embedWasm
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskTabButton QskTabButton.h
\embedWasm
\skinlet QskTabButtonSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskTabView QskTabView.h
\embedWasm
\skinlet QskTabViewSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskTextInput QskTextInput.h
\embedWasm
\skinlet QskTextInputSkinlet
*/

View File

@ -0,0 +1,7 @@
/*!
\class QskTextLabel QskTextLabel.h
\embedWasm
\skinlet QskTextLabelSkinlet
*/

View File

@ -1,182 +0,0 @@
/* Skia overrides for doxygen CSS. */
html {
--blue: rgb(0,114,178);
--green: rgb(0,158,115);
--red: rgb(213,94,0);
--orange: rgb(230,159,0);
--purple: rgb(204,121,167);
--brown: rgb(177,89,40);
--gray: rgb(79,79,79);
--light-blue: rgb(128,185,217);
--light-green: rgb(128,207,185);
--light-red: rgb(234,175,128);
--light-orange: rgb(243,207,128);
--light-purple: rgb(230,188,211);
--light-brown: rgb(216,172,148);
--light-gray: rgb(168,168,168);
--dark-blue: rgb(0,65,101);
--dark-red: rgb(156,44,8);
--white: rgb(254,254,254);
--dark-white: rgb(240,240,240);
--black: rgb(10,10,10);
}
#titlearea {
/* background matches Skia logo. */
background: rgb(248,248,248);
color: var(--blue);
}
#main-nav .sm {
background-image: none;
}
h2.groupheader {
border-bottom: var(--gray);
color: var(--dark-blue);
}
div.qindex, div.navtab{
background-color: var(--light-gray);
border: 1px solid var(--light-blue);
}
a {
color: var(--blue);
}
.contents a:visited {
color: var(--blue);
}
a.qindexHL {
background-color: var(--light-gray);
color: var(--white);
border: 1px double var(--gray);
}
.contents a.qindexHL:visited {
color: var(--white);
}
a.code, a.code:visited, a.line, a.line:visited {
color: var(--blue);
}
a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited {
color: var(--blue);
}
pre.fragment {
border: 1px solid var(--orange);
background-color: var(--dark-white);
}
div.fragment {
background-color: var(--dark-white);
border: 1px solid var(--orange);
}
span.lineno {
border-right: 2px solid var(--green);
background-color: var(-light-gray);
}
span.lineno a {
background-color: var(--light-gray);
}
span.lineno a:hover {
background-color: var(--light-gray);
color: var(--blue);
}
div.ah, span.ah {
background-color: var(--black);
color: var(--white);
border: solid thin var(--gray);
box-shadow: 2px 2px 3px var(light-gray);
background-image: none;
}
td.indexkey {
background-color: var(--light-gray);
border: 1px solid var(--orange);
}
td.indexvalue {
background-color: var(--light-gray);
border: 1px solid var(--orange);
}
tr.memlist {
background-color: var(--light-gray);
}
span.keyword {
color: var(--green);
}
span.keywordtype {
color: var(--brown);
}
span.keywordflow {
color: var(--brown);
}
span.comment {
color: var(--brown);
}
span.charliteral {
color: var(--green);
}
span.vhdldigit {
color: var(--purple);
}
span.vhdlchar {
color: var(--black);
}
blockquote {
background-color: var(--light-gray);
border-left: 2px solid var(--gray);
}
.memtitle {
background-image: none;
}
.memdoc, dl.reflist dd {
background-image: none;
}
.paramname {
color: var(--dark-red);
}
.tabsearch {
background-image: none;
}
.navpath ul {
background-image: none;
}
.navpath li {
background-image: none;
}
.navpath li.navelem a:hover {
color: var(--blue)
}
.navpath li.footer {
background-image:none;
}

2681
doc/doxygen-awesome.css Normal file

File diff suppressed because it is too large Load Diff

17
doc/footer.html Normal file
View File

@ -0,0 +1,17 @@
<!-- HTML footer for doxygen 1.9.8-->
<!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion </li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/><address class="footer"><small>
$generatedby&#160;<a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion
</small></address>
<!--END !GENERATE_TREEVIEW-->
</body>
</html>

View File

@ -30,12 +30,53 @@ doxygen
```
This will generate the documentation into the `api` folder.
### Generating specific header and footer files for doxygen
*This needs to be done only when the doxygen version changes.*
```
cd ~/dev/qskinny/doc
doxygen -w html api/header.html api/footer.html
```
Now open `api/header.html` and copy the code block starting with
the comment `<!-- QSkinny WASM code -->` into the new header file.
*Also*, change the `<body>` tag to load the WASM code at
startup:
```
<body onLoad="docSampleInit()">
```
### Generating a doxygen layout page
*This needs to be done only when the doxygen version changes.*
```
doxygen -l
```
Make sure that the tag labeled `<detaileddescription>` comes before
the tag `<inheritancegraph>`, because the former contains the WASM
code, which should be displayed near the top of the page.
### Using a different stylesheet for doxygen
```
git clone git clone git@github.com:jothepro/doxygen-awesome-css.git
cd doxygen-awesome-css
git checkout v2.3.4
cp doxygen-awesome.css ~/dev/qskinny/doc/
```
### Testing and building the website locally
First copy the generated files from above to the website repo:
```
cp -r api ~/dev/qskinny-website/docs/
cp -r html ~/dev/qskinny-website/docs/
cp -r images ~/dev/qskinny-website/docs/
```
Then test the website locally:
@ -62,7 +103,7 @@ new version of the homepage:
cp -r _site/* ~/dev/qskinny.github.io/
cd ~/dev/qskinny.github.io/
git commit -a -m "new version" # you might want to add new files
gith push
git push
```
That's it, the new website is now published at https://qskinny.github.io/ .

126
doc/header.html Normal file
View File

@ -0,0 +1,126 @@
<!-- HTML header for doxygen 1.9.8-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN FULL_SIDEBAR-->
<script type="text/javascript">var page_layout=1;</script>
<!--END FULL_SIDEBAR-->
<!--END DISABLE_INDEX-->
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
<!-- QSkinny WASM code -->
<script type="text/javascript">
async function docSampleInit()
{
const spinner = document.querySelector('#qtspinner');
const screen = document.querySelector('#qt-wasm-screen');
const status = document.querySelector('#qtstatus');
const showUi = (ui) => {
[spinner, screen].forEach(element => element.style.display = 'none');
if (screen === ui)
{
screen.style.position = 'default';
screen.style.width = '500px';
screen.style.height = '300px';
}
ui.style.display = 'block';
}
try {
if(spinner)
{
showUi(spinner);
status.innerHTML = 'Loading...';
const instance = await qtLoad({
qt: {
onLoaded: () => showUi(screen),
onExit: exitData =>
{
status.innerHTML = 'Application exit';
status.innerHTML +=
exitData.code !== undefined ? ` with code ` : '';
status.innerHTML +=
exitData.text !== undefined ? ` ()` : '';
showUi(spinner);
},
entryFunction: window.createQtAppInstance,
containerElements: [screen],
}
});
}
} catch (e) {
console.error(e);
console.error(e.stack);
}
}
</script>
<script type="text/javascript" src="apiDocumentationSamples.js"></script>
<script type="text/javascript" src="qtloader.js"></script>
<!-- end QSkinny WASM code -->
$treeview
$search
$mathjax
$darkmode
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body onLoad="docSampleInit()">
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN FULL_SIDEBAR-->
<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! -->
<!--END FULL_SIDEBAR-->
<!--END DISABLE_INDEX-->
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr id="projectrow">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign">
<div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td>
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<!--BEGIN !FULL_SIDEBAR-->
<td>$searchbox</td>
<!--END !FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
<!--BEGIN SEARCHENGINE-->
<!--BEGIN FULL_SIDEBAR-->
<tr><td colspan="2">$searchbox</td></tr>
<!--END FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

File diff suppressed because one or more lines are too long

Binary file not shown.

276
doc/html/qtloader.js Normal file
View File

@ -0,0 +1,276 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
/**
* Loads the instance of a WASM module.
*
* @param config May contain any key normally accepted by emscripten and the 'qt' extra key, with
* the following sub-keys:
* - environment: { [name:string] : string }
* environment variables set on the instance
* - onExit: (exitStatus: { text: string, code?: number, crashed: bool }) => void
* called when the application has exited for any reason. There are two cases:
* aborted: crashed is true, text contains an error message.
* exited: crashed is false, code contians the exit code.
*
* Note that by default Emscripten does not exit when main() returns. This behavior
* is controlled by the EXIT_RUNTIME linker flag; set "-s EXIT_RUNTIME=1" to make
* Emscripten tear down the runtime and exit when main() returns.
*
* - containerElements: HTMLDivElement[]
* Array of host elements for Qt screens. Each of these elements is mapped to a QScreen on
* launch.
* - fontDpi: number
* Specifies font DPI for the instance
* - onLoaded: () => void
* Called when the module has loaded.
* - entryFunction: (emscriptenConfig: object) => Promise<EmscriptenModule>
* Qt always uses emscripten's MODULARIZE option. This is the MODULARIZE entry function.
* - module: Promise<WebAssembly.Module>
* The module to create the instance from (optional). Specifying the module allows optimizing
* use cases where several instances are created from a single WebAssembly source.
* - qtdir: string
* Path to Qt installation. This path will be used for loading Qt shared libraries and plugins.
* The path is set to 'qt' by default, and is relative to the path of the web page's html file.
* This property is not in use when static linking is used, since this build mode includes all
* libraries and plugins in the wasm file.
* - preload: [string]: Array of file paths to json-encoded files which specifying which files to preload.
* The preloaded files will be downloaded at application startup and copied to the in-memory file
* system provided by Emscripten.
*
* Each json file must contain an array of source, destination objects:
* [
* {
* "source": "path/to/source",
* "destination": "/path/to/destination"
* },
* ...
* ]
* The source path is relative to the html file path. The destination path must be
* an absolute path.
*
* $QTDIR may be used as a placeholder for the "qtdir" configuration property (see @qtdir), for instance:
* "source": "$QTDIR/plugins/imageformats/libqjpeg.so"
*
* @return Promise<{
* instance: EmscriptenModule,
* exitStatus?: { text: string, code?: number, crashed: bool }
* }>
* The promise is resolved when the module has been instantiated and its main function has been
* called. The returned exitStatus is defined if the application crashed or exited immediately
* after its entry function has been called. Otherwise, config.onExit will get called at a
* later time when (and if) the application exits.
*
* @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten for
* EmscriptenModule
*/
async function qtLoad(config)
{
const throwIfEnvUsedButNotExported = (instance, config) =>
{
const environment = config.environment;
if (!environment || Object.keys(environment).length === 0)
return;
const isEnvExported = typeof instance.ENV === 'object';
if (!isEnvExported)
throw new Error('ENV must be exported if environment variables are passed');
};
const throwIfFsUsedButNotExported = (instance, config) =>
{
const environment = config.environment;
if (!environment || Object.keys(environment).length === 0)
return;
const isFsExported = typeof instance.FS === 'object';
if (!isFsExported)
throw new Error('FS must be exported if preload is used');
};
if (typeof config !== 'object')
throw new Error('config is required, expected an object');
if (typeof config.qt !== 'object')
throw new Error('config.qt is required, expected an object');
if (typeof config.qt.entryFunction !== 'function')
config.qt.entryFunction = window.createQtAppInstance;
config.qt.qtdir ??= 'qt';
config.qt.preload ??= [];
config.qtContainerElements = config.qt.containerElements;
delete config.qt.containerElements;
config.qtFontDpi = config.qt.fontDpi;
delete config.qt.fontDpi;
// Used for rejecting a failed load's promise where emscripten itself does not allow it,
// like in instantiateWasm below. This allows us to throw in case of a load error instead of
// hanging on a promise to entry function, which emscripten unfortunately does.
let circuitBreakerReject;
const circuitBreaker = new Promise((_, reject) => { circuitBreakerReject = reject; });
// If module async getter is present, use it so that module reuse is possible.
if (config.qt.module) {
config.instantiateWasm = async (imports, successCallback) =>
{
try {
const module = await config.qt.module;
successCallback(
await WebAssembly.instantiate(module, imports), module);
} catch (e) {
circuitBreakerReject(e);
}
}
}
const qtPreRun = (instance) => {
// Copy qt.environment to instance.ENV
throwIfEnvUsedButNotExported(instance, config);
for (const [name, value] of Object.entries(config.qt.environment ?? {}))
instance.ENV[name] = value;
// Copy self.preloadData to MEMFS
const makeDirs = (FS, filePath) => {
const parts = filePath.split("/");
let path = "/";
for (let i = 0; i < parts.length - 1; ++i) {
const part = parts[i];
if (part == "")
continue;
path += part + "/";
try {
FS.mkdir(path);
} catch (error) {
const EEXIST = 20;
if (error.errno != EEXIST)
throw error;
}
}
}
throwIfFsUsedButNotExported(instance, config);
for ({destination, data} of self.preloadData) {
makeDirs(instance.FS, destination);
instance.FS.writeFile(destination, new Uint8Array(data));
}
}
if (!config.preRun)
config.preRun = [];
config.preRun.push(qtPreRun);
config.onRuntimeInitialized = () => config.qt.onLoaded?.();
const originalLocateFile = config.locateFile;
config.locateFile = filename =>
{
const originalLocatedFilename = originalLocateFile ? originalLocateFile(filename) : filename;
if (originalLocatedFilename.startsWith('libQt6'))
return `${config.qt.qtdir}/lib/${originalLocatedFilename}`;
return originalLocatedFilename;
}
const originalOnExit = config.onExit;
config.onExit = code => {
originalOnExit?.();
config.qt.onExit?.({
code,
crashed: false
});
}
const originalOnAbort = config.onAbort;
config.onAbort = text =>
{
originalOnAbort?.();
aborted = true;
config.qt.onExit?.({
text,
crashed: true
});
};
const fetchPreloadFiles = async () => {
const fetchJson = async path => (await fetch(path)).json();
const fetchArrayBuffer = async path => (await fetch(path)).arrayBuffer();
const loadFiles = async (paths) => {
const source = paths['source'].replace('$QTDIR', config.qt.qtdir);
return {
destination: paths['destination'],
data: await fetchArrayBuffer(source)
};
}
const fileList = (await Promise.all(config.qt.preload.map(fetchJson))).flat();
self.preloadData = (await Promise.all(fileList.map(loadFiles))).flat();
}
await fetchPreloadFiles();
// Call app/emscripten module entry function. It may either come from the emscripten
// runtime script or be customized as needed.
let instance;
try {
instance = await Promise.race(
[circuitBreaker, config.qt.entryFunction(config)]);
} catch (e) {
config.qt.onExit?.({
text: e.message,
crashed: true
});
throw e;
}
return instance;
}
// Compatibility API. This API is deprecated,
// and will be removed in a future version of Qt.
function QtLoader(qtConfig) {
const warning = 'Warning: The QtLoader API is deprecated and will be removed in ' +
'a future version of Qt. Please port to the new qtLoad() API.';
console.warn(warning);
let emscriptenConfig = qtConfig.moduleConfig || {}
qtConfig.moduleConfig = undefined;
const showLoader = qtConfig.showLoader;
qtConfig.showLoader = undefined;
const showError = qtConfig.showError;
qtConfig.showError = undefined;
const showExit = qtConfig.showExit;
qtConfig.showExit = undefined;
const showCanvas = qtConfig.showCanvas;
qtConfig.showCanvas = undefined;
if (qtConfig.canvasElements) {
qtConfig.containerElements = qtConfig.canvasElements
qtConfig.canvasElements = undefined;
} else {
qtConfig.containerElements = qtConfig.containerElements;
qtConfig.containerElements = undefined;
}
emscriptenConfig.qt = qtConfig;
let qtloader = {
exitCode: undefined,
exitText: "",
loadEmscriptenModule: _name => {
try {
qtLoad(emscriptenConfig);
} catch (e) {
showError?.(e.message);
}
}
}
qtConfig.onLoaded = () => {
showCanvas?.();
}
qtConfig.onExit = exit => {
qtloader.exitCode = exit.code
qtloader.exitText = exit.text;
showExit?.();
}
showLoader?.("Loading");
return qtloader;
};

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 214 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,6 +1,4 @@
/*!
\mainpage Introduction
The (Q)Skinny library contains a set of lightweight Qt Quick Controls.
It is implemented to be 100% usable in C++, but can be used from C++ and/or QML
application code. Being "skinny", the framework is intended to be both lightweight

View File

@ -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**
![Fendt Tractor GUI](https://www.fendt.com/at/geneva-assets/widget/28282/news-3-low.jpg)
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**
![QSkinny architecture](/doc/images/tutorials/architecture-simple.png)

View File

@ -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 dont 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 doesnt,
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 Qts 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 doesnt 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 Qts 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 doesnt
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.

View File

@ -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. Lets 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]
![An app showing two buttons](/doc/images/tutorials/writing-first-application.png)
That's it; you just created a QSkinny application from scratch.
Thats it; you just created a QSkinny application from scratch.
For information on how the controls and layouts above behave, see the next chapters.

View File

@ -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**
![implicit horizontal size hint of a button](/doc/images/tutorials/size-hints-calculation.png)
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 dont 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**
![Image without explicit size hint](/doc/images/tutorials/size-hints-1.png)
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**
![Image with explicit size hint](/doc/images/tutorials/size-hints-2.png)
When dealing with standard controls or layouts, the size hints dont
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,66 +105,37 @@ 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.
|=======================================================================
|`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 Qts
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 +146,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**
![Fixed vs. Minimum size policy](/doc/images/tutorials/size-policies-horizontal-minimum-1.png)
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**
![Fixed vs. Minimum size policy](/doc/images/tutorials/size-policies-horizontal-minimum-2.png)
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 wont 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**
![Fixed vs. Minimum size policy](/doc/images/tutorials/size-policies-horizontal-minimum-3.png)
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
![Fixed vs. Minimum size policy](/doc/images/tutorials/size-policies-horizontal-minimum-4.png)
### 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 +207,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]
....
![Horizontal layout](/doc/images/tutorials/layout-horizontal.png)
```
auto verticalBox = new QskLinearBox( Qt::Vertical );
auto* label1 = new QskTextLabel( "control 1" );
@ -255,18 +225,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)
![Vertical layout](/doc/images/tutorials/layout-vertical.png)
#### 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 +259,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)
![Grid layout](/doc/images/tutorials/layout-grid.png)
#### 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 +288,27 @@ stackBox->addItem( label3 );
stackBox->setCurrentIndex( 2 );
...
....
```
.Stack layout (symbolized)
image::/doc/tutorials/images/layout-stack.png[Stack layout]
**Stack layout (symbolized)**
![Stack layout](/doc/images/tutorials/layout-stack.png)
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.
Lets say a horizontal layout contains two elements, and when filling up
@ -347,8 +319,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 +331,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**
![Stretch factors preferred size](/doc/images/tutorials/stretch-factors-1.png)
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**
![Stretch factors increasing width](/doc/images/tutorials/stretch-factors-2.png)
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 even more width](/doc/images/tutorials/stretch-factors-3.png)
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**
![Nested layouts](/doc/images/tutorials/nesting-layouts.png)
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 +391,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 +401,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
![Nested layouts architecture](/doc/images/tutorials/nesting-layouts-architecture.png)
### Anchoring in QSkinny
TODO

View File

@ -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**
![Styling controls](/doc/images/tutorials/skins-1.png)
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]
![desktop style button](/doc/images/tutorials/skinlets-button-1.png)
=== Skin hints
**flat button**
![flat button](/doc/images/tutorials/skinlets-button-2.png)
### 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**
![Button with skin hints](/doc/images/tutorials/skin-hints.png)
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]
![button in normal state](/doc/images/tutorials/skin-hints-states-1.png)
==== Local skin hints
**button in hovered state**
![button in hovered state](/doc/images/tutorials/skin-hints-states-2.png)
#### 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
![Animators and local skin hints](/doc/images/tutorials/skins-2.png)
### 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
![Animators and local skin hints](/doc/images/tutorials/skins-3.png)
### 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 normal state](/doc/images/tutorials/skin-hints-states-1.png)
**button in `OtherSkin`**
![Styling controls](/doc/images/tutorials/skin-factory.png)

View File

@ -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**
![Scalable graphics default](/doc/images/tutorials/scalable-graphics-1.png)
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]
![Scalable graphics bounded by width](/doc/images/tutorials/scalable-graphics-2.png)
**graphics bounded by height**
![Scalable graphics bounded by height](/doc/images/tutorials/scalable-graphics-3.png)
Since we set the horizontal size policy of the graphics to
`ConstrainedPreferred`, the scaling is done through QskGraphics
@ -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 );
...
....
```

View File

@ -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
Lets 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, lets
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]

View File

@ -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
Qts `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**
![Buttons example](/doc/images/tutorials/buttons-example.png)
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).

View File

@ -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
Lets 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**
![Subclassing existing controls](/doc/images/tutorials/subclassing-existing-controls.png)
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**
![Subclassing existing controls](/doc/images/tutorials/subclassing-existing-controls.png)
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
![Compositing controls](/doc/images/tutorials/compositing-controls.png)
### 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**
![Control with skinlet](/doc/images/tutorials/control-with-skinlet.png)

View File

@ -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**
![Scene graph nodes for a button](/doc/images/tutorials/skins-sg-1.png)
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**
![Scene graph nodes for a button in a box](/doc/images/tutorials/skins-sg-2.png)
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

View File

@ -1,51 +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**
![The IOT dashboard example in a browser](/doc/images/tutorials/iotdashboard-wasm.png)

View File

@ -1,6 +0,0 @@
---
title: Tutorials
excerpt: In this section you'll find the QSkinny tutorials.
layout: docs
---

View File

@ -11,6 +11,7 @@ add_subdirectory(qvgviewer)
add_subdirectory(thumbnails)
add_subdirectory(tabview)
add_subdirectory(iotdashboard)
add_subdirectory(apiDocumentationSamples)
if( BUILD_QML_EXPORT )
add_subdirectory(boxes)

View File

@ -0,0 +1,11 @@
############################################################################
# QSkinny - Copyright (C) The authors
# SPDX-License-Identifier: BSD-3-Clause
############################################################################
set(SOURCES
main.cpp
)
qsk_add_example(apiDocumentationSamples ${SOURCES})

View File

@ -0,0 +1,320 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include <SkinnyNamespace.h>
#include <QskCheckBox.h>
#include <QskComboBox.h>
#include <QskDrawer.h>
#include <QskIcon.h>
#include <QskSkinManager.h>
#include <QskLabelData.h>
#include <QskLinearBox.h>
#include <QskMainView.h>
#include <QskPageIndicator.h>
#include <QskSpinBox.h>
#include <QskProgressBar.h>
#include <QskProgressRing.h>
#include <QskPushButton.h>
#include <QskRadioBox.h>
#include <QskScrollArea.h>
#include <QskSegmentedBar.h>
#include <QskSeparator.h>
#include <QskSimpleListBox.h>
#include <QskSlider.h>
#include <QskSwipeView.h>
#include <QskSwitchButton.h>
#include <QskTabBar.h>
#include <QskTabButton.h>
#include <QskTabView.h>
#include <QskTextField.h>
#include <QskTextLabel.h>
#include <QskWindow.h>
#include <QGuiApplication>
#include <QRegularExpression>
#ifdef Q_OS_WASM
#include <emscripten/val.h>
#endif
namespace
{
class SkinsBar : public QskSegmentedBar
{
public:
SkinsBar() : QskSegmentedBar()
{
const auto skins = qskSkinManager->skinNames();
for( int i = 0; i < skins.count(); ++i )
{
addOption( {}, skins.at( i ) );
if( skins.at( i ) == qskSkinManager->skinName() )
{
setCurrentIndex( i );
}
}
connect( this, &QskSegmentedBar::currentIndexChanged, this, [ this ]( int i )
{
if( i >= 0 )
{
qskSkinManager->setSkin( optionAt( i ).text() );
}
} );
}
};
class BrightnessBar : public QskSegmentedBar
{
public:
BrightnessBar() : QskSegmentedBar()
{
QStringList options = { "bright", "dark" };
for( auto& option : options )
{
addOption( {}, option );
}
connect( this, &QskSegmentedBar::currentIndexChanged, this, [ this, options ]( int i )
{
if( i>= 0 )
{
auto s = qskSkinManager->skin();
const auto scheme = optionAt( currentIndex() ).text() == options.at( 0 )
? QskSkin::LightScheme : QskSkin::DarkScheme;
s->setColorScheme( scheme );
}
} );
connect( qskSkinManager, &QskSkinManager::skinChanged, this, [ this ]()
{
const auto scheme = ( selectedIndex() == 0 ) ? QskSkin::LightScheme : QskSkin::DarkScheme;
auto s = qskSkinManager->skin();
s->setColorScheme( scheme );
} );
}
};
QskControl* createControl( const QByteArray& name )
{
if( name == "CheckBox" )
{
return new QskCheckBox( "check box" );
}
if( name == "ComboBox" )
{
return new QskComboBox;
}
if( name == "Drawer" )
{
auto box = new QskLinearBox;
box->setPanel( true );
box->setPreferredSize( 200, 200 );
auto button = new QskPushButton( "open drawer", box );
auto d = new QskDrawer( box );
d->setEdge( Qt::RightEdge );
auto drawerBox = new QskLinearBox( Qt::Vertical, d );
drawerBox->setMargins( 20 );
new QskTextLabel( "drawer element 1 ", drawerBox );
new QskTextLabel( "drawer element 2 ", drawerBox );
new QskTextLabel( "drawer element 3 ", drawerBox );
QObject::connect( button, &QskPushButton::clicked, d, &QskDrawer::toggle );
return box;
}
if( name == "LinearBox" )
{
auto b = new QskLinearBox;
b->addItem( new QskTextLabel( "item 1" ) );
b->addItem( new QskTextLabel( "item 2" ) );
b->addItem( new QskTextLabel( "item 3" ) );
return b;
}
if( name == "PageIndicator" )
{
auto i = new QskPageIndicator( 5 );
i->setCurrentIndex( 0 );
return i;
}
if( name == "ProgressBar" )
{
auto b = new QskProgressBar( 1, 10 );
b->setIndeterminate( true );
return b;
}
if( name == "ProgressRing" )
{
auto r = new QskProgressRing( 1, 10 );
r->setIndeterminate( true );
return r;
}
if( name == "PushButton" )
{
return new QskPushButton( "button" );
}
if( name == "SpinBox" )
{
return new QskSpinBox( 1, 10, 2 );
}
if( name == "RadioBox" )
{
auto r = new QskRadioBox( { "one", "two", "three" } );
r->setSelectedIndex( 0 );
return r;
}
if( name == "ScrollArea" )
{
return nullptr; // ###
}
if( name == "SegmentedBar" )
{
auto b = new QskSegmentedBar;
b->addOption( {}, "one" );
b->addOption( {}, "two" );
b->addOption( {}, "three" );
return b;
}
if( name == "Separator" )
{
return nullptr; // ###
}
if( name == "SimpleListBox" )
{
auto b = new QskSimpleListBox;
b->setEntries( { "one", "two", "three", "four" } );
b->setSelectedRow( 0 );
b->setFixedSize( 200, 150 );
return b;
}
if( name == "Slider" )
{
auto s = new QskSlider;
s->setPreferredWidth( 200 );
return s;
}
if( name == "SpinBox" )
{
return new QskSpinBox;
}
if( name == "SwipeView" )
{
auto s = new QskSwipeView;
s->setPreferredSize( 200, 200 );
auto t1 = new QskTextLabel( "view 1 - swipe to the side" );
t1->setAlignment( Qt::AlignCenter );
auto t2 = new QskTextLabel( "view 2 - swipe to the side" );
t2->setAlignment( Qt::AlignCenter );
auto t3 = new QskTextLabel( "view 3 - swipe to the side" );
t3->setAlignment( Qt::AlignCenter );
s->addItem( t1, Qt::AlignCenter );
s->addItem( t2, Qt::AlignCenter );
s->addItem( t3, Qt::AlignCenter );
return s;
}
if( name == "SwitchButton" )
{
return new QskSwitchButton;
}
if( name == "TabBar" )
{
auto b = new QskTabBar;
b->setPreferredSize( 300, 100 );
b->addTab( "tab 1 " );
b->addTab( "tab 2 " );
b->addTab( "tab 3 " );
return b;
}
if( name == "TabButton" )
{
return new QskTabButton( "tab button" );
}
if( name == "TabView" )
{
auto t = new QskTabView;
t->setPreferredSize( 300, 200 );
t->addTab( "tab 1 ", new QskTextLabel( "tab content 1" ) );
t->addTab( "tab 2 ", new QskTextLabel( "tab content 2" ) );
t->addTab( "tab 3 ", new QskTextLabel( "tab content 3" ) );
return t;
}
if( name == "TextField" )
{
auto t = new QskTextField;
t->setPreferredWidth( 100 );
return t;
}
if( name == "TextLabel" )
{
return new QskTextLabel( "text label" );
}
else
{
return nullptr;
}
}
}
int main( int argc, char* argv[] )
{
QGuiApplication app( argc, argv );
QByteArray className;
#ifdef Q_OS_WASM // Doxygen file name will be classQskSlider.html etc.
emscripten::val location = emscripten::val::global( "location" );
const auto href = location["href"].as< std::string >();
const QUrl url( QString::fromStdString( href ) );
static QRegularExpression re( "classQsk(.*)\\.html" );
auto match = re.match( url.fileName() );
if( match.hasMatch() )
{
className = match.captured( 1 ).toUtf8();
}
else
{
qFatal() << "could not deduce class from" << url.fileName();
}
#else
if( argc < 2 )
{
qFatal( "usage: %s [CheckBox|PushButton|etc.]", argv[0] );
}
className = argv[1];
#endif
Skinny::init();
auto mainView = new QskMainView;
mainView->setPadding( 20 );
mainView->setSpacing( 20 );
mainView->setPanel( true );
auto* c = createControl( className );
mainView->setHeader( c );
mainView->setBody( new SkinsBar );
mainView->setFooter( new BrightnessBar );
QskWindow window;
window.addItem( mainView );
window.show();
return app.exec();
}
#include "main.moc"