/****************************************************************************** * QSkinny - Copyright (C) 2016 Uwe Rathmann * This file may be used under the terms of the QSkinny License, Version 1.0 *****************************************************************************/ #include "QskSkinRenderer.h" #include "QskSkinnable.h" #include "QskControl.h" #include "QskRgbValue.h" #include "QskTextRenderer.h" #include "QskPlainTextRenderer.h" #include "QskBorderGeometry.h" #include "QskBoxOptions.h" #include "QskTextNode.h" #include "QskBoxNode.h" #include QSizeF QskSkinRenderer::textSize( const QskSkinnable* skinnable, const QString& text, const QskTextOptions& options, QskAspect::Subcontrol subControl ) { using namespace QskAspect; auto font = skinnable->effectiveFont( subControl ); if ( options.isRichText( text ) ) { QskTextRenderer renderer; renderer.setFont( font ); renderer.setOptions( options ); return renderer.implicitSizeHint( text ); } else { QskPlainTextRenderer renderer; renderer.setFont( font ); renderer.setOptions( options ); return renderer.implicitSizeHint( text ); } } QSizeF QskSkinRenderer::textSize( const QskSkinnable* skinnable, const QSizeF& boundingSize, const QString& text, const QskTextOptions& options, QskAspect::Subcontrol subControl ) { const auto font = skinnable->effectiveFont( subControl ); if ( options.isRichText( text ) ) { QskTextRenderer renderer; renderer.setFont( font ); renderer.setOptions( options ); return renderer.textRect( boundingSize, text ).size(); } else { QskPlainTextRenderer renderer; renderer.setFont( font ); renderer.setOptions( options ); return renderer.textRect( boundingSize, text ).size(); } } void QskSkinRenderer::updateTextAt( const QskSkinnable* skinnable, const QPointF& pos, const QString& text, const QskTextOptions& options, QskTextNode* textNode, QskAspect::Subcontrol subControl ) { const QRectF r( pos.x(), pos.y(), 0.0, 0.0 ); updateText( skinnable, r, Qt::AlignLeft, text, options, textNode, subControl ); } void QskSkinRenderer::updateText( const QskSkinnable* skinnable, const QRectF& bounds, Qt::Alignment alignment, const QString& text, const QskTextOptions& options, QskTextNode* textNode, QskAspect::Subcontrol subControl ) { using namespace QskAspect; QMatrix4x4 matrix; matrix.translate( bounds.left(), bounds.top() ); textNode->setMatrix( matrix ); QskSkinHintStatus status; auto textColor = skinnable->color( subControl, &status ); if ( !status.isValid() ) textColor = skinnable->color( subControl | QskAspect::TextColor ); auto font = skinnable->effectiveFont( subControl ); auto styleColor = skinnable->color( subControl | StyleColor ); auto textStyle = Qsk::Normal; if ( styleColor.alpha() == 0 ) { textStyle = skinnable->flagHint< Qsk::TextStyle >( subControl | Style, Qsk::Normal ); } const auto isRichText = options.isRichText( text ) || options.format() == QskTextOptions::StyledText; // doesn't work - we end up with a black rectangle TODO ... #if 0 // Optimization: only update the color if that is all that has changed if ( !isRichText && ( ( skinnable->dirtyAspects() & TypeMask ) == Color ) ) { QskPlainTextRenderer::updateNodeColor( parentNode, textRgb, fontOptions.textStyle, styleRgb ); return; } #endif switch ( options.fontSizeMode() ) { case QskTextOptions::FixedSize: break; case QskTextOptions::HorizontalFit: Q_UNIMPLEMENTED(); break; case QskTextOptions::VerticalFit: font.setPixelSize( bounds.height() * 0.5 ); break; case QskTextOptions::Fit: Q_UNIMPLEMENTED(); break; } QColor linkColor; if ( isRichText ) linkColor = skinnable->color( subControl | LinkColor ); if ( textNode->setTextData( text, bounds.size(), font, options, alignment, textStyle, textColor, styleColor, linkColor ) ) { if ( isRichText ) { QskTextRenderer renderer; renderer.setFont( font ); renderer.setOptions( options ); renderer.setAlignment( alignment ); renderer.updateNode( skinnable->owningControl(), bounds.size(), text, textNode, textColor, textStyle, styleColor, linkColor ); } else { QskPlainTextRenderer renderer; renderer.setFont( font ); renderer.setOptions( options ); renderer.setAlignment( alignment ); renderer.updateNode( skinnable->owningControl(), bounds.size(), text, textNode, textColor, textStyle, styleColor ); } } } static inline QMarginsF qskRotatedMargins( QMarginsF margins, int count ) { count = count % 4; if ( count < 0 ) count += 4; // counter clockwise switch( count ) { case 1: { return QMarginsF( margins.top(), margins.right(), margins.bottom(), margins.left() ); } case 2: { return QMarginsF( margins.right(), margins.bottom(), margins.left(), margins.top() ); } case 3: { return QMarginsF( margins.bottom(), margins.left(), margins.top(), margins.right() ); } default: return margins; } } static inline QskAspect::Edge qskRotateEdge( QskAspect::Edge edge, int count ) { edge = ( count > 0 ) ? edge << ( count % 4 ) : edge >> ( std::abs( count ) % 4 ); if ( edge < QskAspect::LeftEdge ) edge = edge << 4; if ( edge > QskAspect::BottomEdge ) edge = edge >> 4; return edge; } QMarginsF QskSkinRenderer::margins( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int rotation ) { const QMarginsF m = skinnable->marginsHint( subControl | QskAspect::Margin ); return qskRotatedMargins( m, rotation ); } static inline qreal qskRadius( const QskSkinnable* skinnable, const QRectF& rect, QskAspect::Aspect aspect ) { using namespace QskAspect; qreal radius = skinnable->metric( aspect ); if ( radius <= 0.0 ) return 0.0; const auto mode = skinnable->flagHint< Qt::SizeMode >( aspect | SizeMode, Qt::AbsoluteSize ); if ( mode == Qt::RelativeSize ) { // radius is a percentage if ( radius > 100.0 ) radius = 100.0; radius /= 100.0; if ( aspect.boxPrimitive() & RadiusX ) radius *= 0.5 * rect.width(); else radius *= 0.5 * rect.height(); } return radius; } QskBoxOptions QskSkinRenderer::boxOptions( const QskSkinnable* skinnable, const QRectF& rect, QskAspect::Subcontrol subControl, int rotation ) { using namespace QskAspect; QskBoxOptions options; options.borders = qskRotatedMargins( skinnable->borderMetrics( subControl ), rotation ); options.shadows = qskRotatedMargins( skinnable->marginsHint( subControl | Shadow ), rotation ); const auto leftEdge = qskRotateEdge( LeftEdge, rotation ); const auto topEdge = qskRotateEdge( TopEdge, rotation ); const auto rightEdge = qskRotateEdge( RightEdge, rotation ); const auto bottomEdge = qskRotateEdge( BottomEdge, rotation ); // Edge/Corner are the same bits const auto topLeft = static_cast( leftEdge ); const auto topRight = static_cast( topEdge ); const auto bottomRight = static_cast( rightEdge ); const auto bottomLeft = static_cast( bottomEdge ); // corner radii options.radius.topLeftX = qskRadius( skinnable, rect, subControl | RadiusX | topLeft ); options.radius.topLeftY = qskRadius( skinnable, rect, subControl | RadiusY | topLeft ); options.radius.topRightX = qskRadius( skinnable, rect, subControl | RadiusX | topRight ); options.radius.topRightY = qskRadius( skinnable, rect, subControl | RadiusY | topRight ); options.radius.bottomRightX = qskRadius( skinnable, rect, subControl | RadiusX | bottomRight ); options.radius.bottomRightY = qskRadius( skinnable, rect, subControl | RadiusY | bottomRight ); options.radius.bottomLeftX = qskRadius( skinnable, rect, subControl | RadiusX | bottomLeft ); options.radius.bottomLeftY = qskRadius( skinnable, rect, subControl | RadiusY | bottomLeft ); // border colors options.color.borderLeft = skinnable->color( subControl | Border | leftEdge ).rgba(); options.color.borderTop = skinnable->color( subControl | Border | topEdge ).rgba(); options.color.borderRight = skinnable->color( subControl | Border | rightEdge ).rgba(); options.color.borderBottom = skinnable->color( subControl | Border | bottomEdge ).rgba(); // background colors options.color.fillTopLeft = skinnable->color( subControl | leftEdge ).rgba(); options.color.fillTopRight = skinnable->color( subControl | topEdge ).rgba(); options.color.fillBottomRight = skinnable->color( subControl | rightEdge ).rgba(); options.color.fillBottomLeft = skinnable->color( subControl | bottomEdge ).rgba(); return options; } void QskSkinRenderer::updateBox( const QskSkinnable* skinnable, QskBoxNode* node, const QRectF& rect, QskAspect::Subcontrol subControl, int rotation ) { const auto options = QskSkinRenderer::boxOptions( skinnable, rect, subControl, rotation ); // The shadow is inside the margins - in case of having // no margins then outside the bounding rectangle. // Is this really how we want it ??? QRectF boxRect = rect.marginsRemoved( margins( skinnable, subControl, rotation ) ); boxRect = boxRect.marginsAdded( options.shadows ); node->setBoxData( boxRect, options ); }