qskinny/doc/tutorials/05-Skins.asciidoc
Peter Hartmann 920f7e24d6 tutorials: Display images on github files
... by making the references absolute. Now they should work both
on github and on the homepage.

Resolves #414
2024-10-01 11:30:45 +02:00

306 lines
9.4 KiB
Plaintext

---
title: 5. Skins
layout: docs
---
:doctitle: 5. Skins
:notitle:
== Skins, Skin hints and Skinlets
Skins, Skin hints and Skinlets allow the user to define how specific
controls looke like. Controls are drawn on the screen by the
skinlet, and therefore it will read information from both the control
itself as well as read the skin hints from the skin:
.Skinlets query the control and the skin
image::/doc/tutorials/images/skins-1.png[Styling controls]
For instance, a button skinlet will read the margins from the skin and
the text to render from the button.
=== Skins
Skins are a way to define a look and feel for a whole set of UI
controls, e.g. a night time vs. day time skin, skins for different
brands or an Android Material skin. They contain all kinds of properties
(i.e. skin hints) like colors, margins, fonts and more.
[source]
....
class MySkin : public QskSkin
{
public:
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
{
// here define the skin with skin hints
}
};
....
The example below shows different implementations for a push button: One
has a traditional desktop skin, the other is a flat button with a skin
often found in mobile devices.
.desktop style button
image::/doc/tutorials/images/skinlets-button-1.png[desktop style button]
.flat button
image::/doc/tutorials/images/skinlets-button-2.png[flat button]
=== Skin hints
Each instance of a button will have unique properties like its text or
icon file name, but all buttons will have common properties like the
(default) background color and font size. These common properties are
called skin hints, and are defined in a skin. Skin hints are either
colors, e.g. the background color of a button, metrics (e.g. padding) or
flags (e.g. text alignment).
Skin hints being part of a skin means that each skin can have different
skin hints:
All buttons in a day time-like skin would have a light background color
and dark text color, while a night time skin would have a dark
background color and light text color by default.
Extending the `MySkin` example from above, here is an example of some
skin hints for a push button, setting the padding to 10 pixels, the
background color to magenta and the text color to black:
[source]
....
class MySkin : public QskSkin
{
public:
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
{
setGradient( QskPushButton::Panel, Qt::magenta );
setMargins( QskPushButton::Panel | QskAspect::Padding, 10 );
setColor( QskPushButton::Text, Qt::black );
}
};
....
.A button styled with skin hints
image::/doc/tutorials/images/skin-hints.png[Button with skin hints]
When writing a new skin, a developer needs to know which hints to set
for which control. This usually depends on the control itself; however,
since usually controls are broken down into the three primitives box,
text and graphic, the methods for rendering each of them will take the
following skin hints into account:
[cols=",",options="header",]
|=======================================================================
|Primitive |Skin hint from QskAspect
|Text |`Alignment` +
`Color` +
`TextColor` +
`StyleColor` +
`LinkColor` +
`Style` +
`FontRole`
|Graphic |`Alignment` +
`GraphicRole`
|Box | `Margin` +
`Metric` \| `Border` +
`Color` \| `Border` +
`Color` +
`Metric` \| `Shape`
|=======================================================================
Some special cases exist where elements other than the primitives above
are used.
==== States and animations
Skin hints can also depend on the state a control is in: Buttons for
instance can be in a `Pressed` or `Hovered` state. For such cases, skin
hints cannot only be set on a subcontrol, but also be made dependent on
a specific state. In the example below we define the background color of
the button to be magenta in the default state and cyan in the `Hovered`
state.
When dealing with states, QSkinny allows for animations between those (and other entities
like skins). The example below adds a different color for the `Hovered`
state and an animation when transitioning between the background colors.
The duration is set to be one second (1000 milliseconds in the
`setAnimation()` call below). Now when a user will hover over the
button, there will be a smooth animation from magenta to cyan
interpolating between the colors. Without the `setAnimation()` call, the
button would just switch to magenta when hovered right away.
[source]
....
class MySkin : public QskSkin
{
public:
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
{
setGradient( QskPushButton::Panel, Qt::magenta );
setMargins( QskPushButton::Panel | QskAspect::Padding, 10 );
setColor( QskPushButton::Text, Qt::black );
setGradient( QskPushButton::Panel | QskPushButton::Hovered, Qt::cyan );
setAnimation( QskPushButton::Panel | QskAspect::Color, 1000 );
}
};
....
.button in normal state
image::/doc/tutorials/images/skin-hints-states-1.png[button in normal state]
.button in hovered state
image::/doc/tutorials/images/skin-hints-states-2.png[button in hovered state]
==== Local skin hints
It is possible to set local skin hints on specific controls to override
skin-wide settings:
[source]
....
auto* label1 = new QskTextLabel( "control 1" );
label1->setMargins( 20 );
label1->setBackgroundColor( Qt::blue );
....
In general it is recommended to set the skin hints in the skin rather
than on the control locally, in order to separate the style from the
implementation, and to allow switching between skins. How to write
controls that are themable is explained in the section about
link:Writing-own-controls.html[writing own controls].
Taking animations and local skin hints into account, the architecture
diagram now looks like this:
.Skinlets can also read from local skinlets and animators
image::/doc/tutorials/images/skins-2.png[Animators and local skin hints]
=== Skinlets
A skinlet is in charge of drawing a control on the screen, similar to a
Delegate in QML. It will read all the hints it needs from either the
control itself or the skin, then it will draw the subcontrols that
represent the control: In the sample case of a button, the skinlet will
first draw the background panel, potentially consisting of a rectangle
with a fill color. Then it will draw the text of the button, and last it
will draw an icon, in case the button has one set.
Each skin can have a different skinlet to draw a control. Often the
skinlet is the same across different skins and the skins only differ in
skin hints, e.g. buttons having different fonts. However, it is also
possible to have completely different skinlets per skin. This ensures a
separation of application code instantiating the controls itself from
the visual representation of the controls.
QSkinny already contains implementations of many common controls like
text labels, buttons and so on. However, some custom controls might
need to be written from scratch, including the skinlet; for an
explanation on how to do this, see the example of
link:Writing-own-controls.html[writing own controls].
For a closer look at how the skinlet draws the controls in the scene
graph, see link:scene-graph.html[scene graph representations of controls].
Of course each app has different controls and therefore there are also
different skinlets, so a more complete version of the architecture
diagram looks like this:
.There is one skinlet for each atomic control
image::/doc/tutorials/images/skins-3.png[Animators and local skin hints]
=== Skin factories and switching between skins
Skins are usually not created by the user directly, but by a skin
factory. Such a factory keeps track of the skins registered in the
system, and handles creating a new skin when the user switches them
during application lifetime.
When having two skins called `MySkin` and `OtherSkin` in an app, the
corresponding skin factory might look like this:
[source]
....
class MySkinFactory : public QskSkinFactory
{
Q_OBJECT
public:
QStringList skinNames() const override
{
return { "MySkin", "OtherSkin" };
}
QskSkin* createSkin( const QString& skinName ) override
{
if ( skinName == "MySkin" )
return new MySkin;
if ( skinName == "OtherSkin" )
return new OtherSkin;
return nullptr;
}
};
....
That skin factory has to be registered during app start; it is also a
good idea to set a default skin right away:
[source]
....
int main( int argc, char* argv[] )
{
auto* skinFactory = new MySkinFactory;
qskSkinManager->registerFactory( "MySkinFactory", skinFactory );
QGuiApplication app( argc, argv );
qskSetup->setSkin( "MySkin" );
...
QskWindow window;
window.show();
return app.exec();
}
....
Now we can define the `OtherSkin` and define different skin hints for
e.g. push buttons. Here we define the background color and padding to be
different; also we configure buttons to have a blue border:
[source]
....
class OtherSkin : public QskSkin
{
public:
OtherSkin( QObject* parent = nullptr ) : QskSkin( parent )
{
setGradient( QskPushButton::Panel, Qt::cyan );
setMargins( QskPushButton::Panel | QskAspect::Padding, 15 );
setBoxBorderColors( QskPushButton::Panel, Qt::blue );
setBoxBorderMetrics( QskPushButton::Panel, 1 );
}
};
....
Switching between skins will change the look of `QskPushButton`
instances:
.button in `MySkin` (as above)
image::/doc/tutorials/images/skin-hints-states-1.png[button in normal state]
.button in `OtherSkin`
image::/doc/tutorials/images/skin-factory.png[Styling controls]