For entirely new visual components that cannot be achieved via subclassing or composition, you must implement both a control class (derived from QskControl) and a corresponding QskSkinlet.
1. Define the Control Class
Use QSK_SUBCONTROLS to define the logical parts of your control that need styling.
class CustomShape : public QskControl
{
Q_OBJECT
public:
QSK_SUBCONTROLS( Panel, InnerShape )
CustomShape( QQuickItem* parent = nullptr ) : QskControl( parent )
{
}
};
2. Implement the Skinlet
A skinlet handles the actual rendering. It requires three main components:
- Node Roles: Define an enum of roles that correspond to your subcontrols. These are passed to
setNodeRoles() in the skinlet constructor. The constructor must be Q_INVOKABLE. - Subcontrol Rectangles: Override
subControlRect() to define the bounding box for each subcontrol. You can use contentsRect or apply custom margins. - Drawing Logic: Override
updateSubNode() to perform the actual rendering for each node role. This is where you manipulate QSGNode objects (like QskBoxNode).
class CustomShapeSkinlet : public QskSkinlet
{
Q_GADGET
public:
enum NodeRole
{
PanelRole, InnerShapeRole
};
Q_INVOKABLE CustomShapeSkinlet( QskSkin* skin = nullptr ) : QskSkinlet( skin )
{
setNodeRoles( { PanelRole, InnerShapeRole } );
}
QRectF subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const override
{
const auto* customShape = static_cast< const CustomShape* >( skinnable );
if ( subControl == CustomShape::Panel )
{
return contentsRect;
}
else if ( subControl == CustomShape::InnerShape )
{
const auto margins = customShape->marginsHint( CustomShape::InnerShape );
return contentsRect.marginsRemoved( margins );
}
return QskSkinlet::subControlRect( skinnable, contentsRect, subControl );
}
protected:
QSGNode* updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const override
{
const auto* customShape = static_cast< const CustomShape* >( skinnable );
switch ( nodeRole )
{
case PanelRole:
{
auto panelNode = static_cast< QskBoxNode* >( node );
const auto panelRect = subControlRect( customShape, customShape->contentsRect(), CustomShape::Panel );
const qreal radius = panelRect.width() / 2;
panelNode->setBoxData( panelRect, shapeMetrics, borderMetrics, borderColors, gradient );
return panelNode;
}
case InnerShapeRole:
{
auto innerNode = static_cast< QskBoxNode* >( node );
const auto innerRect = subControlRect( customShape, customShape->contentsRect(), CustomShape::InnerShape );
const qreal radius = innerRect.width() / 2;
innerNode->setBoxData( innerRect, shapeMetrics, borderMetrics, borderColors, gradient );
return innerNode;
}
}
return QskSkinlet::updateSubNode( skinnable, nodeRole, node );
}
};
3. Connect the Control and Skinlet in a Skin
Use declareSkinlet<ControlClass, SkinletClass>() within your QskSkin to link them, then apply styles using the control's subcontrol identifiers.
class MySkin : public QskSkin
{
public:
MySkin( QObject* parent = nullptr ) : QskSkin( parent )
{
declareSkinlet< CustomShape, CustomShapeSkinlet >();
setGradient( CustomShape::Panel, Qt::blue );
setMargins( CustomShape::InnerShape, 20 );
setGradient( CustomShape::InnerShape, Qt::magenta );
}
};