qskinny/src/controls/QskSkinlet.cpp

550 lines
15 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskSkinlet.h"
#include "QskAspect.h"
#include "QskSetup.h"
#include "QskSkin.h"
#include "QskControl.h"
2017-10-20 13:09:30 +02:00
#include "QskBoxShapeMetrics.h"
#include "QskBoxBorderMetrics.h"
#include "QskBoxBorderColors.h"
2017-07-21 18:21:34 +02:00
#include "QskGradient.h"
#include "QskBoxNode.h"
#include "QskBoxClipNode.h"
2017-07-21 18:21:34 +02:00
#include "QskTextNode.h"
2017-10-20 20:26:39 +02:00
#include "QskTextColors.h"
#include "QskTextOptions.h"
2017-07-21 18:21:34 +02:00
#include "QskGraphicNode.h"
#include "QskGraphicTextureFactory.h"
#include "QskFunctions.h"
#include <QSGSimpleRectNode>
#include <unordered_map>
static const int qskBackgroundRole = 254;
static const int qskDebugRole = 253;
2017-12-07 17:12:52 +01:00
static inline QSGNode::Flags qskNodeFlags( quint8 nodeRole )
2017-07-21 18:21:34 +02:00
{
return static_cast< QSGNode::Flags >( ( nodeRole + 1 ) << 8 );
}
2017-12-07 17:12:52 +01:00
static inline quint8 qskRole( const QSGNode* node )
2017-07-21 18:21:34 +02:00
{
2017-12-07 17:12:52 +01:00
return static_cast< quint8 >( ( ( node->flags() & 0x0ffff ) >> 8 ) - 1 );
2017-07-21 18:21:34 +02:00
}
2017-12-07 17:12:52 +01:00
static inline void qskSetRole( quint8 nodeRole, QSGNode* node )
2017-07-21 18:21:34 +02:00
{
const QSGNode::Flags flags = qskNodeFlags( nodeRole );
node->setFlags( node->flags() | flags );
}
static inline QSGNode* qskFindNodeByFlag( QSGNode* parent, int nodeRole )
{
auto node = parent->firstChild();
while ( node )
{
if ( qskRole( node ) == nodeRole)
return node;
node = node->nextSibling();
}
return nullptr;
}
2017-10-23 07:46:46 +02:00
static inline QSGNode* qskUpdateGraphicNode(
const QskSkinnable* skinnable, QSGNode* node,
const QskGraphic& graphic, const QskColorFilter& colorFilter,
const QRect& rect )
2017-07-21 18:21:34 +02:00
{
if ( rect.isEmpty() )
return nullptr;
auto mode = QskGraphicTextureFactory::OpenGL;
2017-07-21 18:21:34 +02:00
const QskControl* control = skinnable->owningControl();
if ( control && control->testControlFlag( QskControl::PreferRasterForTextures ) )
mode = QskGraphicTextureFactory::Raster;
2017-07-21 18:21:34 +02:00
auto graphicNode = static_cast< QskGraphicNode* >( node );
if ( graphicNode == nullptr )
graphicNode = new QskGraphicNode();
graphicNode->setGraphic( graphic, colorFilter, mode, rect );
return graphicNode;
2017-07-21 18:21:34 +02:00
}
2017-10-20 13:09:30 +02:00
static inline bool qskIsBoxVisible( const QskBoxBorderMetrics& borderMetrics,
const QskBoxBorderColors& borderColors, const QskGradient& gradient )
{
if ( gradient.isVisible() )
return true;
return !borderMetrics.isNull() && borderColors.isVisible();
}
2017-10-20 20:26:39 +02:00
static inline QskTextColors qskTextColors(
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl )
{
using namespace QskAspect;
/*
Would be more efficient to have QskTextColors hints instead of
storing the colors as seperated hints. TODO ...
*/
QskSkinHintStatus status;
2017-10-23 07:46:46 +02:00
2017-10-20 20:26:39 +02:00
QskTextColors c;
c.textColor = skinnable->color( subControl, &status );
#if 1
if ( !status.isValid() )
c.textColor = skinnable->color( subControl | QskAspect::TextColor );
2017-10-23 07:46:46 +02:00
#endif
2017-10-20 20:26:39 +02:00
c.styleColor = skinnable->color( subControl | StyleColor );
c.linkColor = skinnable->color( subControl | LinkColor );
return c;
}
2017-07-21 18:21:34 +02:00
class QskSkinlet::PrivateData
{
public:
PrivateData( QskSkin* skin ):
skin( skin ),
ownedBySkinnable( false )
{
}
QskSkin* skin;
QVector< quint8 > nodeRoles;
bool ownedBySkinnable : 1;
};
QskSkinlet::QskSkinlet( QskSkin* skin ):
m_data( new PrivateData( skin ) )
{
}
QskSkinlet::~QskSkinlet()
{
}
QskSkin* QskSkinlet::skin() const
{
return m_data->skin;
}
void QskSkinlet::setOwnedBySkinnable( bool on )
{
m_data->ownedBySkinnable = on;
}
bool QskSkinlet::isOwnedBySkinnable() const
{
return m_data->ownedBySkinnable;
}
void QskSkinlet::setNodeRoles( const QVector< quint8 >& nodeRoles )
{
m_data->nodeRoles = nodeRoles;
}
void QskSkinlet::appendNodeRoles( const QVector< quint8 >& nodeRoles )
{
m_data->nodeRoles += nodeRoles;
}
const QVector< quint8 >& QskSkinlet::nodeRoles() const
{
return m_data->nodeRoles;
}
void QskSkinlet::updateNode( QskSkinnable* skinnable, QSGNode* parentNode ) const
{
QSGNode* oldNode;
QSGNode* newNode;
if ( const auto control = skinnable->controlCast() )
{
// background
oldNode = qskFindNodeByFlag( parentNode, qskBackgroundRole );
newNode = nullptr;
if ( control->autoFillBackground() )
newNode = updateBackgroundNode( control, oldNode );
insertRemoveNodes( parentNode, oldNode, newNode, qskBackgroundRole );
// debug
oldNode = qskFindNodeByFlag( parentNode, qskDebugRole );
newNode = nullptr;
if ( control->testControlFlag( QskControl::DebugForceBackground ) )
newNode = updateDebugNode( control, oldNode );
insertRemoveNodes( parentNode, oldNode, newNode, qskDebugRole );
}
for ( int i = 0; i < m_data->nodeRoles.size(); i++ )
{
2017-12-07 17:12:52 +01:00
const auto nodeRole = m_data->nodeRoles[i];
2017-07-21 18:21:34 +02:00
Q_ASSERT( nodeRole <= 245 ); // reserving 10 roles
oldNode = qskFindNodeByFlag( parentNode, nodeRole );
newNode = updateSubNode( skinnable, nodeRole, oldNode );
insertRemoveNodes( parentNode, oldNode, newNode, nodeRole );
}
}
QSGNode* QskSkinlet::updateBackgroundNode(
const QskControl* control, QSGNode* node ) const
{
const QRectF rect = control->rect();
2017-07-21 18:21:34 +02:00
if ( rect.isEmpty() )
return nullptr;
const QskGradient gradient = control->background();
2017-07-21 18:21:34 +02:00
if ( !gradient.isValid() )
return nullptr;
auto boxNode = static_cast< QskBoxNode* >( node );
if ( boxNode == nullptr )
boxNode = new QskBoxNode();
2017-07-21 18:21:34 +02:00
2017-10-20 13:09:30 +02:00
boxNode->setBoxData( rect, gradient );
return boxNode;
2017-07-21 18:21:34 +02:00
}
QSGNode* QskSkinlet::updateDebugNode(
const QskControl* control, QSGNode* node ) const
{
if ( control->size().isEmpty() )
{
return nullptr;
}
QSGSimpleRectNode* rectNode = static_cast< QSGSimpleRectNode* >( node );
if ( rectNode == nullptr )
{
rectNode = new QSGSimpleRectNode();
QColor color;
if ( control->inherits( "QskFocusIndicator" ) )
{
color = QColor( Qt::gray );
color.setAlpha( 60 );
}
else
{
static int idx = 0;
idx = ( idx + 3 ) % 14;
// maybe using something random based on web colors ???
color = QColor( Qt::GlobalColor( 4 + idx ) );
color.setAlpha( 200 );
}
rectNode->setColor( color );
}
const QRectF r = control->rect();
2017-07-21 18:21:34 +02:00
if ( rectNode->rect() != r )
rectNode->setRect( r );
return rectNode;
}
void QskSkinlet::insertRemoveNodes( QSGNode* parentNode,
2017-12-07 17:12:52 +01:00
QSGNode* oldNode, QSGNode* newNode, quint8 nodeRole ) const
2017-07-21 18:21:34 +02:00
{
if ( newNode && newNode->parent() != parentNode )
{
qskSetRole( nodeRole, newNode );
switch( nodeRole )
{
case qskBackgroundRole:
{
parentNode->prependChildNode( newNode );
break;
}
case qskDebugRole:
{
QSGNode* firstNode = parentNode->firstChild();
if ( firstNode && ( qskRole( firstNode ) == qskBackgroundRole ) )
parentNode->insertChildNodeAfter( newNode, firstNode );
else
parentNode->prependChildNode( newNode );
break;
}
default:
{
insertNodeSorted( newNode, parentNode );
}
}
}
if ( oldNode && oldNode != newNode )
{
parentNode->removeChildNode( oldNode );
if ( oldNode->flags() & QSGNode::OwnedByParent )
delete oldNode;
}
}
void QskSkinlet::insertNodeSorted( QSGNode* node, QSGNode* parentNode ) const
{
QSGNode* sibling = nullptr;
if ( parentNode->childCount() > 0 )
{
const int nodePos = m_data->nodeRoles.indexOf( qskRole( node ) );
// in most cases we are appending, so let's start at the end
for ( QSGNode* childNode = parentNode->lastChild();
childNode != nullptr; childNode = childNode->previousSibling() )
{
2017-12-07 17:12:52 +01:00
const auto childNodeRole = qskRole( childNode );
2017-07-21 18:21:34 +02:00
if ( childNodeRole == qskBackgroundRole )
{
sibling = childNode;
}
else
{
/*
Imperformant implementation, but as the number of roles is
usually < 5 we don't introduce some sort of support for faster lookups
*/
const int index = m_data->nodeRoles.indexOf( qskRole( childNode ) );
if ( index >= 0 && index < nodePos )
sibling = childNode;
}
if ( sibling != nullptr )
break;
}
}
if ( sibling )
parentNode->insertChildNodeAfter( node, sibling );
else
parentNode->prependChildNode( node );
}
void QskSkinlet::setNodeRole( QSGNode* node, quint8 nodeRole )
{
qskSetRole( nodeRole, node );
}
quint8 QskSkinlet::nodeRole( const QSGNode* node )
{
return node ? qskRole( node ) : 0;
}
QSGNode* QskSkinlet::findNodeByRole( QSGNode* parent, quint8 nodeRole )
{
return qskFindNodeByFlag( parent, nodeRole );
}
QSGNode* QskSkinlet::updateBoxNode( const QskSkinnable* skinnable,
QSGNode* node, QskAspect::Subcontrol subControl ) const
2017-07-21 18:21:34 +02:00
{
const QRectF rect = subControlRect( skinnable, subControl );
return updateBoxNode( skinnable, node, rect, subControl );
2017-07-21 18:21:34 +02:00
}
QSGNode* QskSkinlet::updateBoxNode( const QskSkinnable* skinnable,
2017-10-23 07:46:46 +02:00
QSGNode* node, const QRectF& rect, QskAspect::Subcontrol subControl )
2017-07-21 18:21:34 +02:00
{
using namespace QskAspect;
2017-10-20 13:09:30 +02:00
const QMarginsF margins = skinnable->marginsHint( subControl | Margin );
const QRectF boxRect = rect.marginsRemoved( margins );
if ( boxRect.isEmpty() )
2017-07-21 18:21:34 +02:00
return nullptr;
2017-10-20 13:09:30 +02:00
auto borderMetrics = skinnable->boxBorderMetricsHint( subControl );
borderMetrics = borderMetrics.toAbsolute( boxRect.size() );
const auto borderColors = skinnable->boxBorderColorsHint( subControl );
const auto fillGradient = skinnable->gradientHint( subControl );
if ( !qskIsBoxVisible( borderMetrics, borderColors, fillGradient ) )
2017-07-21 18:21:34 +02:00
return nullptr;
2017-10-20 13:09:30 +02:00
auto shape = skinnable->boxShapeHint( subControl );
shape = shape.toAbsolute( boxRect.size() );
2017-07-21 18:21:34 +02:00
auto boxNode = static_cast< QskBoxNode* >( node );
if ( boxNode == nullptr )
boxNode = new QskBoxNode();
2017-10-20 13:09:30 +02:00
boxNode->setBoxData( boxRect, shape, borderMetrics, borderColors, fillGradient );
2017-07-21 18:21:34 +02:00
return boxNode;
}
QSGNode* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable,
QSGNode* node, QskAspect::Subcontrol subControl ) const
{
const QRectF rect = subControlRect( skinnable, subControl );
return updateBoxClipNode( skinnable, node, rect, subControl );
}
QSGNode* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable,
2017-10-23 07:46:46 +02:00
QSGNode* node, const QRectF& rect, QskAspect::Subcontrol subControl )
{
using namespace QskAspect;
auto clipNode = static_cast< QskBoxClipNode* >( node );
if ( clipNode == nullptr )
clipNode = new QskBoxClipNode();
2017-10-20 13:09:30 +02:00
const QMarginsF margins = skinnable->marginsHint( subControl | Margin );
const QRectF clipRect = rect.marginsRemoved( margins );
if ( clipRect.isEmpty() )
{
clipNode->setIsRectangular( true );
clipNode->setClipRect( clipRect );
}
else
{
auto borderMetrics = skinnable->boxBorderMetricsHint( subControl );
borderMetrics = borderMetrics.toAbsolute( clipRect.size() );
auto shape = skinnable->boxShapeHint( subControl );
shape = shape.toAbsolute( clipRect.size() );
2017-10-20 13:09:30 +02:00
clipNode->setBox( clipRect, shape, borderMetrics );
}
return clipNode;
}
2017-07-21 18:21:34 +02:00
QSGNode* QskSkinlet::updateTextNode(
const QskSkinnable* skinnable, QSGNode* node,
const QRectF& rect, Qt::Alignment alignment,
const QString& text, const QskTextOptions& textOptions,
2017-10-23 07:46:46 +02:00
QskAspect::Subcontrol subControl )
2017-07-21 18:21:34 +02:00
{
if ( text.isEmpty() || rect.isEmpty() )
return nullptr;
auto textNode = static_cast< QskTextNode* >( node );
if ( textNode == nullptr )
textNode = new QskTextNode();
2017-10-20 20:26:39 +02:00
auto colors = qskTextColors( skinnable, subControl );
auto textStyle = Qsk::Normal;
if ( colors.styleColor.alpha() == 0 )
{
textStyle = skinnable->flagHint< Qsk::TextStyle >(
subControl | QskAspect::Style, Qsk::Normal );
}
auto font = skinnable->effectiveFont( subControl );
switch ( textOptions.fontSizeMode() )
{
case QskTextOptions::FixedSize:
break;
case QskTextOptions::HorizontalFit:
Q_UNIMPLEMENTED();
break;
case QskTextOptions::VerticalFit:
2017-12-07 17:12:52 +01:00
font.setPixelSize( static_cast<int>( rect.height() * 0.5 ) );
2017-10-20 20:26:39 +02:00
break;
case QskTextOptions::Fit:
Q_UNIMPLEMENTED();
break;
}
textNode->setTextData( skinnable->owningControl(),
text, rect, font, textOptions, colors, alignment, textStyle );
2017-07-21 18:21:34 +02:00
return textNode;
}
QSGNode* QskSkinlet::updateTextNode(
const QskSkinnable* skinnable, QSGNode* node,
const QString& text, const QskTextOptions& textOptions,
QskAspect::Subcontrol subControl ) const
{
const QRectF rect = subControlRect( skinnable, subControl );
const auto alignment = skinnable->flagHint< Qt::Alignment >(
QskAspect::Alignment | subControl, Qt::AlignLeft );
return updateTextNode( skinnable, node, rect, alignment,
text, textOptions, subControl );
}
QSGNode* QskSkinlet::updateGraphicNode(
const QskSkinnable* skinnable, QSGNode* node,
const QskGraphic& graphic, QskAspect::Subcontrol subcontrol ) const
2017-07-21 18:21:34 +02:00
{
const QRectF rect = subControlRect( skinnable, subcontrol );
2017-07-21 18:21:34 +02:00
const Qt::Alignment alignment = skinnable->flagHint< Qt::Alignment >(
subcontrol | QskAspect::Alignment, Qt::AlignCenter );
2017-10-23 07:46:46 +02:00
2017-07-21 18:21:34 +02:00
const auto colorFilter = skinnable->effectiveGraphicFilter( subcontrol );
2017-10-23 07:46:46 +02:00
2017-07-21 18:21:34 +02:00
return updateGraphicNode( skinnable, node,
graphic, colorFilter, rect, alignment );
}
QSGNode* QskSkinlet::updateGraphicNode(
const QskSkinnable* skinnable, QSGNode* node,
const QskGraphic& graphic, const QskColorFilter& colorFilter,
2017-10-23 07:46:46 +02:00
const QRectF& rect, Qt::Alignment alignment )
2017-07-21 18:21:34 +02:00
{
if ( graphic.isNull() )
return nullptr;
const QSizeF scaledSize = graphic.defaultSize().scaled(
rect.size(), Qt::KeepAspectRatio );
const QRect r = qskAlignedRect( qskInnerRect( rect ),
int( scaledSize.width() ), int( scaledSize.height() ), alignment );
return qskUpdateGraphicNode( skinnable, node, graphic, colorFilter, r );
2017-07-21 18:21:34 +02:00
}
QSGNode* QskSkinlet::updateGraphicNode(
const QskSkinnable* skinnable, QSGNode* node,
const QskGraphic& graphic, const QskColorFilter& colorFilter,
2017-10-23 07:46:46 +02:00
const QRectF& rect )
{
if ( graphic.isNull() )
return nullptr;
return qskUpdateGraphicNode( skinnable, node,
graphic, colorFilter, rect.toAlignedRect() );
2017-10-23 07:46:46 +02:00
}
2017-07-21 18:21:34 +02:00
#include "moc_QskSkinlet.cpp"