From b89621a3d40717be307090b442555c3a43004d9d Mon Sep 17 00:00:00 2001 From: Peter Hartmann Date: Wed, 20 Oct 2021 07:50:25 +0200 Subject: [PATCH] Iot dashboard arc rendering (#134) * add QskArcNode and QskArcRenderer * IOT example: Use QskArcNode instead of own arc node * move some functionality to the arc renderer * add QskArcMetrics * add methods to QskSkinlet * remove circular bar graph node We can now use updateArcNode() and don't need our own method. * support linear gradients in the arc renderer * clean up * incorporate Uwe's changes * add overloads for updateArcNode() when the angles are set dynamically The angles don't always come from the style, so we need overloads in QskSkinlet to set them dynamically. --- examples/iotdashboard/CircularProgressBar.cpp | 2 + .../CircularProgressBarSkinlet.cpp | 184 +----------------- .../iotdashboard/CircularProgressBarSkinlet.h | 3 - examples/iotdashboard/Skin.cpp | 15 +- examples/iotdashboard/Skin.h | 4 +- src/common/QskArcMetrics.cpp | 123 ++++++++++++ src/common/QskArcMetrics.h | 135 +++++++++++++ src/controls/QskSkinHintTableEditor.cpp | 25 +++ src/controls/QskSkinHintTableEditor.h | 13 ++ src/controls/QskSkinlet.cpp | 91 +++++++++ src/controls/QskSkinlet.h | 25 +++ src/controls/QskSkinnable.cpp | 19 ++ src/controls/QskSkinnable.h | 5 + src/nodes/QskArcNode.cpp | 43 ++++ src/nodes/QskArcNode.h | 30 +++ src/nodes/QskArcRenderer.cpp | 51 +++++ src/nodes/QskArcRenderer.h | 24 +++ src/src.pro | 6 + 18 files changed, 612 insertions(+), 186 deletions(-) create mode 100644 src/common/QskArcMetrics.cpp create mode 100644 src/common/QskArcMetrics.h create mode 100644 src/nodes/QskArcNode.cpp create mode 100644 src/nodes/QskArcNode.h create mode 100644 src/nodes/QskArcRenderer.cpp create mode 100644 src/nodes/QskArcRenderer.h diff --git a/examples/iotdashboard/CircularProgressBar.cpp b/examples/iotdashboard/CircularProgressBar.cpp index 3a839286..91578197 100644 --- a/examples/iotdashboard/CircularProgressBar.cpp +++ b/examples/iotdashboard/CircularProgressBar.cpp @@ -6,6 +6,7 @@ #include "CircularProgressBar.h" #include +#include #include QSK_SUBCONTROL( CircularProgressBar, Groove ) @@ -179,6 +180,7 @@ void CircularProgressBar::setValueInternal( qreal value ) if ( !qskFuzzyCompare( value, m_data->value ) ) { m_data->value = value; + Q_EMIT valueChanged( value ); update(); diff --git a/examples/iotdashboard/CircularProgressBarSkinlet.cpp b/examples/iotdashboard/CircularProgressBarSkinlet.cpp index 311a2ac8..643a9ad1 100644 --- a/examples/iotdashboard/CircularProgressBarSkinlet.cpp +++ b/examples/iotdashboard/CircularProgressBarSkinlet.cpp @@ -11,132 +11,6 @@ #include #include -namespace -{ - class ArcNode : public QskPaintedNode - { - public: - void setGradient( const QskGradient& gradient ) - { - m_gradient = gradient; - } - - void setGradientType( QGradient::Type type ) - { - m_gradientType = type; - } - - void setWidth( double width ) - { - m_width = width; - } - - void setValue( double value ) - { - m_value = value; - } - - void setOrigin( double origin ) - { - m_origin = origin; - } - - void setMaximum( double maximum ) - { - m_maximum = maximum; - } - - void setIndeterminate( bool isIndeterminate ) - { - m_isIndeterminate = isIndeterminate; - } - - void setPosition( double position ) - { - m_position = position; - } - - void paint( QPainter* painter, const QSizeF& size ) override - { - int startAngle; - int spanAngle; - - if( m_isIndeterminate ) - { - static const QEasingCurve curve( QEasingCurve::Linear ); - - startAngle = -1 * m_position * 360; - // the other option is to just set a fixed value for the - // span angle (or do some advanced stuff with easing curves) - spanAngle = qAbs( 0.5 - m_position ) * 360; - } - else - { - startAngle = 90 + -1 * ( m_origin / m_maximum ) * 360; - spanAngle = -1 * ( m_value / m_maximum ) * 360; - } - - painter->setRenderHint( QPainter::Antialiasing, true ); - - const QRectF r( 0.5 * m_width, 0.5 * m_width, - size.width() - m_width, size.height() - m_width ); - - QGradientStops stops; - - for( const QskGradientStop& stop : m_gradient.stops() ) - { - QGradientStop s( stop.position(), stop.color() ); - stops.append( s ); - } - - if( m_gradientType == QGradient::RadialGradient ) - { - QRadialGradient radialGradient( r.center(), qMin( r.width(), r.height() ) ); - radialGradient.setStops( stops ); - - painter->setPen( QPen( radialGradient, m_width, Qt::SolidLine, Qt::FlatCap ) ); - painter->drawArc( r, startAngle * 16, spanAngle * 16 ); - } - else - { - QConicalGradient conicalGradient( r.center(), startAngle ); - conicalGradient.setStops( stops ); - - painter->setPen( QPen( conicalGradient, m_width, Qt::SolidLine, Qt::FlatCap ) ); - painter->drawArc( r, startAngle * 16, spanAngle * 16 ); - } - } - - uint hash() const override - { - uint h = qHash( m_gradientType ); - h = qHash( m_width, h ); - h = qHash( m_value, h ); - h = qHash( m_origin, h ); - h = qHash( m_maximum, h ); - h = qHash( m_isIndeterminate, h ); - h = qHash( m_position, h ); - - for( const QskGradientStop& stop : m_gradient.stops() ) - { - h = stop.hash( h ); - } - - return h; - } - - private: - QskGradient m_gradient; - QGradient::Type m_gradientType; - double m_width; - double m_value; - double m_origin; - double m_maximum; - bool m_isIndeterminate; - double m_position; - }; -} - CircularProgressBarSkinlet::CircularProgressBarSkinlet( QskSkin* skin ) : QskSkinlet( skin ) { @@ -156,63 +30,23 @@ QRectF CircularProgressBarSkinlet::subControlRect( QSGNode* CircularProgressBarSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const { - const auto bar = static_cast< const CircularProgressBar* >( skinnable ); - switch( nodeRole ) { - case GrooveRole: // fall through + case GrooveRole: + { + return updateArcNode( skinnable, node, CircularProgressBar::Groove ); + } case BarRole: { - return updateBarNode( bar, nodeRole, node ); + const qreal startAngle = 90 * 16; + const auto bar = static_cast< const CircularProgressBar* >( skinnable ); + const qreal spanAngle = bar->valueAsRatio() * -5760; + return updateArcNode( skinnable, node, startAngle, spanAngle, + CircularProgressBar::Bar ); } } return Inherited::updateSubNode( skinnable, nodeRole, node ); } -QSGNode* CircularProgressBarSkinlet::updateBarNode( - const CircularProgressBar* bar, quint8 nodeRole, QSGNode* node ) const -{ - auto arcNode = static_cast< ArcNode* >( node ); - - if( arcNode == nullptr ) - { - arcNode = new ArcNode(); - } - - const auto subControl = ( nodeRole == GrooveRole ) ? CircularProgressBar::Groove - : CircularProgressBar::Bar; - - const QskGradient gradient = bar->gradientHint( subControl ); - - const QGradient::Type type = ( nodeRole == GrooveRole ) ? - QGradient::RadialGradient : QGradient::ConicalGradient; - - const double width = bar->metric( subControl | QskAspect::Size ); - const double value = ( nodeRole == GrooveRole ) ? bar->maximum() : bar->value(); - - arcNode->setGradient( gradient ); - arcNode->setGradientType( type ); - arcNode->setWidth( width ); - arcNode->setOrigin( bar->origin() ); - arcNode->setMaximum( bar->maximum() ); - arcNode->setIndeterminate( bar->isIndeterminate() ); - - if( bar->isIndeterminate() ) - { - const double position = bar->metric( CircularProgressBar::Bar | QskAspect::Position ); - arcNode->setPosition( position ); - } - else - { - arcNode->setValue( value ); - } - - QQuickWindow* window = bar->window(); - const QRect rect = bar->contentsRect().toRect(); - arcNode->update( window, QskTextureRenderer::AutoDetect, rect ); - - return arcNode; -} - #include "moc_CircularProgressBarSkinlet.cpp" diff --git a/examples/iotdashboard/CircularProgressBarSkinlet.h b/examples/iotdashboard/CircularProgressBarSkinlet.h index f7789fe8..be867d05 100644 --- a/examples/iotdashboard/CircularProgressBarSkinlet.h +++ b/examples/iotdashboard/CircularProgressBarSkinlet.h @@ -33,7 +33,4 @@ class CircularProgressBarSkinlet : public QskSkinlet protected: QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) const override; - - private: - QSGNode* updateBarNode( const CircularProgressBar*, quint8 nodeRole, QSGNode* ) const; }; diff --git a/examples/iotdashboard/Skin.cpp b/examples/iotdashboard/Skin.cpp index a02ff359..5fda4df0 100644 --- a/examples/iotdashboard/Skin.cpp +++ b/examples/iotdashboard/Skin.cpp @@ -21,6 +21,7 @@ #include "UsageBox.h" #include "UsageDiagram.h" +#include #include #include #include @@ -108,14 +109,16 @@ void Skin::initHints( const Palette& palette ) ed.setColor( TopBarItem::Item4 | QskAspect::TextColor, "#6776ff" ); // conical gradients are counterclockwise, so specify the 2nd color first: - ed.setGradient( TopBarItem::Item1, { Qt::Horizontal, "#FF3122", "#FF5C00" } ); - ed.setGradient( TopBarItem::Item2, { Qt::Horizontal, "#6100FF", "#6776FF" } ); - ed.setGradient( TopBarItem::Item3, { Qt::Horizontal, "#FF3122", "#FFCE50" } ); - ed.setGradient( TopBarItem::Item4, { Qt::Horizontal, "#6100FF", "#6776FF" } ); + ed.setGradient( TopBarItem::Item1, { QskGradient::Horizontal, "#FF3122", "#FF5C00" } ); + ed.setGradient( TopBarItem::Item2, { QskGradient::Horizontal, "#6100FF", "#6776FF" } ); + ed.setGradient( TopBarItem::Item3, { QskGradient::Horizontal, "#FF3122", "#FFCE50" } ); + ed.setGradient( TopBarItem::Item4, { QskGradient::Horizontal, "#6100FF", "#6776FF" } ); // the bar gradient is defined through the top bar items above - ed.setMetricHint( CircularProgressBar::Groove | QskAspect::Size, 8.53 ); - ed.setMetricHint( CircularProgressBar::Bar | QskAspect::Size, 8.53 ); + ed.setArcMetrics( CircularProgressBar::Groove, { 8.53, 90 * 16, -360 * 16 } ); + // the span angle will be set in the progress bar, we just give a dummy + // value here: + ed.setArcMetrics( CircularProgressBar::Bar, { 8.53, 90 * 16, -180 * 16 } ); ed.setFontRole( TimeTitleLabel::Text, Skin::TitleFont ); diff --git a/examples/iotdashboard/Skin.h b/examples/iotdashboard/Skin.h index 733d9818..88d80aa0 100644 --- a/examples/iotdashboard/Skin.h +++ b/examples/iotdashboard/Skin.h @@ -60,7 +60,7 @@ class DaytimeSkin : public Skin : Skin( Skin::Palette( {"#6D7BFB"}, {"#fbfbfb"}, {"#ffffff"}, "#ffffff", {"#f7f7f7"}, {"#f4f4f4"}, Qt::black, Qt::black, - { Qt::Horizontal, { { 0.0, 0xc4c4c4 }, { 0.5, 0xf8f8f8 }, { 1.0, 0xc4c4c4 } } } ) + { QskGradient::Vertical, { { 0.0, 0xc4c4c4 }, { 0.5, 0xf8f8f8 }, { 1.0, 0xc4c4c4 } } } ) , parent ) { } @@ -73,7 +73,7 @@ class NighttimeSkin : public Skin : Skin( Skin::Palette( {"#2937A7"}, {"#040404"}, {"#000000"}, "#000000", {"#0a0a0a"}, {"#0c0c0c"}, Qt::white, Qt::white, - { Qt::Horizontal, { { 0.0, 0x666666 }, { 0.5, 0x222222 }, { 1.0, 0x333333 } } } ) + { QskGradient::Vertical, { { 0.0, 0x666666 }, { 0.5, 0x222222 }, { 1.0, 0x333333 } } } ) , parent ) { } diff --git a/src/common/QskArcMetrics.cpp b/src/common/QskArcMetrics.cpp new file mode 100644 index 00000000..7e68b553 --- /dev/null +++ b/src/common/QskArcMetrics.cpp @@ -0,0 +1,123 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2021 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskArcMetrics.h" + +#include +#include + +static void qskRegisterArcMetrics() +{ + qRegisterMetaType< QskArcMetrics >(); +} + +Q_CONSTRUCTOR_FUNCTION( qskRegisterArcMetrics ) + +// copied from QskMargins.cpp, we should unify this somehwere: +static inline qreal qskInterpolated( qreal from, qreal to, qreal ratio ) +{ + return from + ( to - from ) * ratio; +} + +// copied from QskBoxBorderMetrics.cpp, we should unify this somewhere: +static inline qreal qskAbsoluted( qreal length, qreal percentage ) +{ + // 100% means -> 0.5 of length + percentage = qBound( 0.0, percentage, 100.0 ); + return percentage / 100.0 * 0.5 * length; +} + +void QskArcMetrics::setWidth( qreal width ) noexcept +{ + m_width = width; +} + +void QskArcMetrics::setStartAngle( int startAngle ) noexcept +{ + m_startAngle = startAngle; +} + +void QskArcMetrics::setSpanAngle( int spanAngle ) noexcept +{ + m_spanAngle = spanAngle; +} + +void QskArcMetrics::setSizeMode( Qt::SizeMode sizeMode ) noexcept +{ + m_sizeMode = sizeMode; +} + +QskArcMetrics QskArcMetrics::interpolated( + const QskArcMetrics& to, qreal ratio ) const noexcept +{ + if ( ( *this == to ) || ( m_sizeMode != to.m_sizeMode ) ) + return to; + + const qreal width = qskInterpolated( m_width, to.m_width, ratio ); + return QskArcMetrics( width, m_startAngle, m_spanAngle, m_sizeMode ); +} + +QVariant QskArcMetrics::interpolate( + const QskArcMetrics& from, const QskArcMetrics& to, + qreal progress ) +{ + return QVariant::fromValue( from.interpolated( to, progress ) ); +} + +QskArcMetrics QskArcMetrics::toAbsolute( const QSizeF& size ) const noexcept +{ + if ( m_sizeMode != Qt::RelativeSize ) + return *this; + + QskArcMetrics absoluted = *this; + + auto& w = absoluted.m_width; + + if ( size.isEmpty() ) + { + w = 0; + } + else + { + // for now we just use the width: + w = qskAbsoluted( size.width(), w ); + } + + absoluted.m_sizeMode = Qt::AbsoluteSize; + + return absoluted; +} + +uint QskArcMetrics::hash( uint seed ) const noexcept +{ + uint hash = qHash( m_width, seed ); + hash = qHash( m_startAngle, hash ); + hash = qHash( m_spanAngle, hash ); + const int mode = m_sizeMode; + return qHashBits( &mode, sizeof( mode ), hash ); +} + +#ifndef QT_NO_DEBUG_STREAM + +#include + +QDebug operator<<( QDebug debug, const QskArcMetrics& metrics ) +{ + QDebugStateSaver saver( debug ); + debug.nospace(); + + debug << "Arc" << '('; + debug << "width:" << metrics.width(); + debug << ", start angle:" << metrics.startAngle(); + debug << ", span angle:" << metrics.spanAngle(); + debug << ", size mode:" << metrics.sizeMode(); + debug << ')'; + + return debug; +} + +#endif + +#include "moc_QskArcMetrics.cpp" diff --git a/src/common/QskArcMetrics.h b/src/common/QskArcMetrics.h new file mode 100644 index 00000000..19a29287 --- /dev/null +++ b/src/common/QskArcMetrics.h @@ -0,0 +1,135 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2021 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_ARC_METRICS_H +#define QSK_ARC_METRICS_H + +#include "QskFunctions.h" + +#include + +class QVariant; + +class QSK_EXPORT QskArcMetrics +{ + Q_GADGET + + Q_PROPERTY( qreal width READ width WRITE setWidth ) + Q_PROPERTY( int startAngle READ startAngle WRITE setStartAngle ) + Q_PROPERTY( int spanAngle READ spanAngle WRITE setSpanAngle ) + Q_PROPERTY( Qt::SizeMode sizeMode READ sizeMode WRITE setSizeMode ) + + public: + constexpr QskArcMetrics() noexcept; + + constexpr QskArcMetrics( qreal width, int startAngle, int spanAngle, + Qt::SizeMode = Qt::AbsoluteSize ) noexcept; + + bool operator==( const QskArcMetrics& ) const noexcept; + bool operator!=( const QskArcMetrics& ) const noexcept; + + constexpr bool isNull() const noexcept; + + void setWidth( qreal width ) noexcept; + constexpr qreal width() const noexcept; + + void setStartAngle( int startAngle ) noexcept; + constexpr int startAngle() const noexcept; + + void setSpanAngle( int spanAngle ) noexcept; + constexpr int spanAngle() const noexcept; + + void setSizeMode( Qt::SizeMode ) noexcept; + constexpr Qt::SizeMode sizeMode() const noexcept; + + QskArcMetrics interpolated( const QskArcMetrics&, + qreal value ) const noexcept; + + QskArcMetrics toAbsolute( const QSizeF& ) const noexcept; + + uint hash( uint seed = 0 ) const noexcept; + + static QVariant interpolate( const QskArcMetrics&, + const QskArcMetrics&, qreal progress ); + + private: + qreal m_width; + int m_startAngle; + int m_spanAngle; + Qt::SizeMode m_sizeMode; +}; + +inline constexpr QskArcMetrics::QskArcMetrics() noexcept + : m_width( 0 ) + , m_startAngle( 0 ) + , m_spanAngle( 0 ) + , m_sizeMode( Qt::AbsoluteSize ) +{ +} + +inline constexpr QskArcMetrics::QskArcMetrics( qreal width, + int startAngle, int spanAngle, + Qt::SizeMode sizeMode ) noexcept + : m_width( width ) + , m_startAngle( startAngle ) + , m_spanAngle( spanAngle ) + , m_sizeMode( sizeMode ) +{ +} + +inline bool QskArcMetrics::operator==( + const QskArcMetrics& other ) const noexcept +{ + return ( qskFuzzyCompare( m_width, other.m_width ) + && m_startAngle == other.m_startAngle + && m_spanAngle == other.m_spanAngle + && m_sizeMode == other.m_sizeMode ); +} + +inline bool QskArcMetrics::operator!=( + const QskArcMetrics& other ) const noexcept +{ + return !( *this == other ); +} + +inline constexpr bool QskArcMetrics::isNull() const noexcept +{ + return ( qFuzzyIsNull( m_width ) + && m_startAngle == 0 + && m_spanAngle == 0 + && m_sizeMode == Qt::AbsoluteSize ); +} + +inline constexpr qreal QskArcMetrics::width() const noexcept +{ + return m_width; +} + +inline constexpr int QskArcMetrics::startAngle() const noexcept +{ + return m_startAngle; +} + +inline constexpr int QskArcMetrics::spanAngle() const noexcept +{ + return m_spanAngle; +} + +inline constexpr Qt::SizeMode QskArcMetrics::sizeMode() const noexcept +{ + return m_sizeMode; +} + +#ifndef QT_NO_DEBUG_STREAM + +class QDebug; +QSK_EXPORT QDebug operator<<( QDebug, const QskArcMetrics& ); + +#endif + +Q_DECLARE_TYPEINFO( QskArcMetrics, Q_MOVABLE_TYPE ); +Q_DECLARE_METATYPE( QskArcMetrics ) + +#endif diff --git a/src/controls/QskSkinHintTableEditor.cpp b/src/controls/QskSkinHintTableEditor.cpp index 414efac7..5f9e475a 100644 --- a/src/controls/QskSkinHintTableEditor.cpp +++ b/src/controls/QskSkinHintTableEditor.cpp @@ -6,6 +6,7 @@ #include "QskSkinHintTableEditor.h" #include "QskSkinHintTable.h" +#include "QskArcMetrics.h" #include "QskMargins.h" #include "QskBoxShapeMetrics.h" #include "QskBoxBorderMetrics.h" @@ -467,3 +468,27 @@ QskBoxBorderColors QskSkinHintTableEditor::boxBorderColors( QskAspect aspect ) c { return colorHint< QskBoxBorderColors >( aspectBorder( aspect ) ); } + +void QskSkinHintTableEditor::setArcMetrics( QskAspect aspect, qreal width, + int startAngle, int spanAngle, Qt::SizeMode sizeMode ) +{ + setMetricHint( aspectShape( aspect ), + QskArcMetrics( width, startAngle, spanAngle, sizeMode ) ); +} + +void QskSkinHintTableEditor::setArcMetrics( QskAspect aspect, + const QskArcMetrics& arcMetrics, QskStateCombination combination ) +{ + setMetricHint( aspectShape( aspect ), arcMetrics, combination ); +} + +void QskSkinHintTableEditor::removeArcMetrics( QskAspect aspect, + QskStateCombination combination ) +{ + return removeMetricHint( aspectShape( aspect ), combination ); +} + +QskArcMetrics QskSkinHintTableEditor::arcMetrics( QskAspect aspect ) const +{ + return metricHint< QskArcMetrics >( aspectShape( aspect ) ); +} diff --git a/src/controls/QskSkinHintTableEditor.h b/src/controls/QskSkinHintTableEditor.h index 8ba16c3d..8b991321 100644 --- a/src/controls/QskSkinHintTableEditor.h +++ b/src/controls/QskSkinHintTableEditor.h @@ -14,6 +14,7 @@ #include #include +class QskArcMetrics; class QskMargins; class QskGradient; class QskBoxShapeMetrics; @@ -220,6 +221,18 @@ class QSK_EXPORT QskSkinHintTableEditor void removeBoxBorderColors( QskAspect, QskStateCombination = QskStateCombination() ); QskBoxBorderColors boxBorderColors( QskAspect ) const; + // arcMetrics + + void setArcMetrics( QskAspect, qreal, int, int, + Qt::SizeMode = Qt::AbsoluteSize ); + + void setArcMetrics( QskAspect, + const QskArcMetrics&, QskStateCombination = QskStateCombination() ); + + void removeArcMetrics( QskAspect, QskStateCombination = QskStateCombination() ); + + QskArcMetrics arcMetrics( QskAspect ) const; + private: QskSkinHintTable* m_table = nullptr; }; diff --git a/src/controls/QskSkinlet.cpp b/src/controls/QskSkinlet.cpp index ea7a82b9..2a1e1471 100644 --- a/src/controls/QskSkinlet.cpp +++ b/src/controls/QskSkinlet.cpp @@ -5,6 +5,7 @@ #include "QskSkinlet.h" +#include "QskArcNode.h" #include "QskAspect.h" #include "QskBoxBorderColors.h" #include "QskBoxBorderMetrics.h" @@ -86,6 +87,12 @@ static inline bool qskIsBoxVisible( const QskBoxBorderMetrics& borderMetrics, return !borderMetrics.isNull() && borderColors.isVisible(); } +static inline bool qskIsArcVisible( const QskArcMetrics& arcMetrics, + const QskGradient& gradient ) +{ + return !arcMetrics.isNull() && gradient.isVisible(); +} + static inline QskTextColors qskTextColors( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) { @@ -313,6 +320,90 @@ QSGNode* QskSkinlet::updateBoxNode( const QskSkinnable* skinnable, return boxNode; } +QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable, + QSGNode* node, QskAspect::Subcontrol subControl ) const +{ + const auto rect = qskSubControlRect( this, skinnable, subControl ); + return updateArcNode( skinnable, node, rect, subControl ); +} + +QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable, + QSGNode* node, const QRectF& rect, QskAspect::Subcontrol subControl ) +{ + const auto fillGradient = skinnable->gradientHint( subControl ); + return updateArcNode( skinnable, node, rect, fillGradient, subControl ); +} + +QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable, + QSGNode* node, const QRectF& rect, const QskGradient& fillGradient, + QskAspect::Subcontrol subControl ) +{ + auto arcMetrics = skinnable->arcMetricsHint( subControl ); + return updateArcNode( skinnable, node ,rect, fillGradient, arcMetrics, + subControl ); +} + +QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable, + QSGNode* node, const QRectF& rect, const QskGradient& fillGradient, + const QskArcMetrics& arcMetrics, QskAspect::Subcontrol subControl ) +{ + const auto control = skinnable->owningControl(); + if ( control == nullptr ) + return nullptr; + + const auto margins = skinnable->marginHint( subControl ); + + const auto arcRect = rect.marginsRemoved( margins ); + + if ( arcRect.isEmpty() ) + return nullptr; + + auto absoluteArcMetrics = arcMetrics.toAbsolute( arcRect.size() ); + + if ( !qskIsArcVisible( arcMetrics, fillGradient ) ) + return nullptr; + + auto arcNode = static_cast< QskArcNode* >( node ); + + if ( arcNode == nullptr ) + arcNode = new QskArcNode(); + + arcNode->setArcData( rect, absoluteArcMetrics, fillGradient, + control->window() ); + + return arcNode; +} + +QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable, + QSGNode* node, int startAngle, int spanAngle, + QskAspect::Subcontrol subControl ) const +{ + const auto rect = qskSubControlRect( this, skinnable, subControl ); + return updateArcNode( skinnable, node, rect, startAngle, spanAngle, + subControl ); +} + +QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable, + QSGNode* node, const QRectF& rect, int startAngle, int spanAngle, + QskAspect::Subcontrol subControl ) +{ + const auto fillGradient = skinnable->gradientHint( subControl ); + return updateArcNode( skinnable, node, rect, fillGradient, startAngle, + spanAngle, subControl ); +} + +QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable, + QSGNode* node, const QRectF& rect, const QskGradient& fillGradient, + int startAngle, int spanAngle, QskAspect::Subcontrol subControl ) +{ + auto arcMetrics = skinnable->arcMetricsHint( subControl ); + arcMetrics.setStartAngle( startAngle ); + arcMetrics.setSpanAngle( spanAngle ); + + return updateArcNode( skinnable, node ,rect, fillGradient, arcMetrics, + subControl ); +} + QSGNode* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable, QSGNode* node, QskAspect::Subcontrol subControl ) const { diff --git a/src/controls/QskSkinlet.h b/src/controls/QskSkinlet.h index 9855f099..625b9cab 100644 --- a/src/controls/QskSkinlet.h +++ b/src/controls/QskSkinlet.h @@ -13,6 +13,7 @@ #include +class QskArcMetrics; class QskSkin; class QskControl; class QskSkinnable; @@ -52,6 +53,23 @@ class QSK_EXPORT QskSkinlet static QSGNode* updateBoxNode( const QskSkinnable*, QSGNode*, const QRectF&, const QskGradient&, QskAspect::Subcontrol ); + static QSGNode* updateArcNode( const QskSkinnable*, QSGNode*, + const QRectF&, QskAspect::Subcontrol ); + + static QSGNode* updateArcNode( const QskSkinnable*, QSGNode*, + const QRectF&, const QskGradient&, QskAspect::Subcontrol ); + + static QSGNode* updateArcNode( const QskSkinnable*, QSGNode*, + const QRectF&, const QskGradient&, const QskArcMetrics&, + QskAspect::Subcontrol ); + + static QSGNode* updateArcNode( const QskSkinnable*, QSGNode*, + const QRectF&, int startAngle, int spanAngle, QskAspect::Subcontrol ); + + static QSGNode* updateArcNode( const QskSkinnable*, QSGNode*, + const QRectF&, const QskGradient&, int startAngle, int spanAngle, + QskAspect::Subcontrol ); + static QSGNode* updateTextNode( const QskSkinnable*, QSGNode*, const QRectF&, Qt::Alignment, const QString&, const QskTextOptions&, QskAspect::Subcontrol ); @@ -85,6 +103,13 @@ class QSK_EXPORT QskSkinlet QSGNode* updateBoxNode( const QskSkinnable*, QSGNode*, QskAspect::Subcontrol ) const; + QSGNode* updateArcNode( const QskSkinnable*, QSGNode*, + QskAspect::Subcontrol ) const; + + QSGNode* updateArcNode( const QskSkinnable*, QSGNode*, + int startAngle, int spanAngle, + QskAspect::Subcontrol ) const; + QSGNode* updateBoxClipNode( const QskSkinnable*, QSGNode*, QskAspect::Subcontrol ) const; diff --git a/src/controls/QskSkinnable.cpp b/src/controls/QskSkinnable.cpp index d264d2ef..26f0b03d 100644 --- a/src/controls/QskSkinnable.cpp +++ b/src/controls/QskSkinnable.cpp @@ -6,6 +6,7 @@ #include "QskSkinnable.h" #include "QskAnimationHint.h" +#include "QskArcMetrics.h" #include "QskAspect.h" #include "QskColorFilter.h" #include "QskControl.h" @@ -521,6 +522,24 @@ QskBoxBorderColors QskSkinnable::boxBorderColorsHint( this, aspect | QskAspect::Border, status ); } +bool QskSkinnable::setArcMetricsHint( + const QskAspect aspect, const QskArcMetrics& arc ) +{ + return qskSetMetric( this, aspect | QskAspect::Shape, arc ); +} + +bool QskSkinnable::resetArcMetricsHint( const QskAspect aspect ) +{ + return resetMetric( aspect | QskAspect::Shape ); +} + +QskArcMetrics QskSkinnable::arcMetricsHint( + const QskAspect aspect, QskSkinHintStatus* status ) const +{ + return qskMetric< QskArcMetrics >( + this, aspect | QskAspect::Shape, status ); +} + bool QskSkinnable::setSpacingHint( const QskAspect aspect, qreal spacing ) { return qskSetMetric( this, aspect | QskAspect::Spacing, spacing ); diff --git a/src/controls/QskSkinnable.h b/src/controls/QskSkinnable.h index 9a9ca847..07479797 100644 --- a/src/controls/QskSkinnable.h +++ b/src/controls/QskSkinnable.h @@ -21,6 +21,7 @@ class QDebug; class QSGNode; +class QskArcMetrics; class QskControl; class QskAnimationHint; class QskColorFilter; @@ -189,6 +190,10 @@ class QSK_EXPORT QskSkinnable bool resetBoxBorderColorsHint( QskAspect ); QskBoxBorderColors boxBorderColorsHint( QskAspect, QskSkinHintStatus* = nullptr ) const; + bool setArcMetricsHint( QskAspect, const QskArcMetrics& ); + bool resetArcMetricsHint( QskAspect ); + QskArcMetrics arcMetricsHint( QskAspect, QskSkinHintStatus* = nullptr ) const; + bool setSpacingHint( QskAspect, qreal ); bool resetSpacingHint( QskAspect ); qreal spacingHint( QskAspect, QskSkinHintStatus* = nullptr ) const; diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp new file mode 100644 index 00000000..edb0f92c --- /dev/null +++ b/src/nodes/QskArcNode.cpp @@ -0,0 +1,43 @@ +/********************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskArcNode.h" +#include "QskArcRenderer.h" + +QskArcNode::QskArcNode() +{ +} + +QskArcNode::~QskArcNode() +{ +} + +void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& metrics, + const QskGradient &gradient, QQuickWindow* window ) +{ + m_metrics = metrics; + m_gradient = gradient; + + update( window, QskTextureRenderer::AutoDetect, rect.toRect() ); +} + +void QskArcNode::paint( QPainter* painter, const QSizeF &size ) +{ + const qreal w = m_metrics.width(); + const QRectF rect( 0.5 * w, 0.5 * w, size.width() - w, size.height() - w ); + + QskArcRenderer renderer; + renderer.renderArc( rect, m_metrics, m_gradient, painter ); +} + +uint QskArcNode::hash() const +{ + uint h = m_metrics.hash(); + + for( const auto& stop : m_gradient.stops() ) + h = stop.hash( h ); + + return h; +} diff --git a/src/nodes/QskArcNode.h b/src/nodes/QskArcNode.h new file mode 100644 index 00000000..ce24d691 --- /dev/null +++ b/src/nodes/QskArcNode.h @@ -0,0 +1,30 @@ +/********************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_ARC_NODE_H +#define QSK_ARC_NODE_H + +#include "QskArcMetrics.h" +#include "QskGradient.h" +#include "QskPaintedNode.h" + +class QSK_EXPORT QskArcNode : public QskPaintedNode +{ + public: + QskArcNode(); + ~QskArcNode() override; + + void setArcData( const QRectF&, const QskArcMetrics&, const QskGradient&, + QQuickWindow* ); + + void paint( QPainter* painter, const QSizeF& size ) override; + uint hash() const override; + + private: + QskArcMetrics m_metrics; + QskGradient m_gradient; +}; + +#endif diff --git a/src/nodes/QskArcRenderer.cpp b/src/nodes/QskArcRenderer.cpp new file mode 100644 index 00000000..804e3630 --- /dev/null +++ b/src/nodes/QskArcRenderer.cpp @@ -0,0 +1,51 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskArcRenderer.h" +#include "QskArcMetrics.h" +#include "QskGradient.h" + +#include +#include + +void QskArcRenderer::renderArc(const QRectF& rect, + const QskArcMetrics& metrics, const QskGradient& gradient, + QPainter* painter ) +{ + painter->setRenderHint( QPainter::Antialiasing, true ); + + QGradientStops stops; + + for( const QskGradientStop& stop : gradient.stops() ) + { + QGradientStop s( stop.position(), stop.color() ); + stops.append( s ); + } + + /* + horizontal is interpreted as in direction of the arc, + while vertical means from the inner to the outer border + */ + + QBrush brush; + + if( gradient.orientation() == QskGradient::Vertical ) + { + QRadialGradient gradient( rect.center(), qMin( rect.width(), rect.height() ) ); + gradient.setStops( stops ); + + brush = gradient; + } + else + { + QConicalGradient gradient( rect.center(), metrics.startAngle() / 16.0 ); + gradient.setStops( stops ); + + brush = gradient; + } + + painter->setPen( QPen( brush, metrics.width(), Qt::SolidLine, Qt::FlatCap ) ); + painter->drawArc( rect, metrics.startAngle(), metrics.spanAngle() ); +} diff --git a/src/nodes/QskArcRenderer.h b/src/nodes/QskArcRenderer.h new file mode 100644 index 00000000..4fda474a --- /dev/null +++ b/src/nodes/QskArcRenderer.h @@ -0,0 +1,24 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_ARC_RENDERER_H +#define QSK_ARC_RENDERER_H + +#include "QskGlobal.h" + +class QskArcMetrics; +class QskGradient; + +class QPainter; +class QRectF; + +class QSK_EXPORT QskArcRenderer +{ + public: + void renderArc( const QRectF&, const QskArcMetrics&, + const QskGradient&, QPainter* ); +}; + +#endif diff --git a/src/src.pro b/src/src.pro index 00a55da6..84223fe2 100644 --- a/src/src.pro +++ b/src/src.pro @@ -12,6 +12,7 @@ DEPENDPATH *= $${QSK_SUBDIRS} # DEFINES += QSK_LAYOUT_COMPAT HEADERS += \ + common/QskArcMetrics.h \ common/QskAspect.h \ common/QskBoxBorderColors.h \ common/QskBoxBorderMetrics.h \ @@ -38,6 +39,7 @@ HEADERS += \ common/QskTextOptions.h SOURCES += \ + common/QskArcMetrics.cpp \ common/QskAspect.cpp \ common/QskBoxBorderColors.cpp \ common/QskBoxBorderMetrics.cpp \ @@ -85,6 +87,8 @@ SOURCES += \ graphic/QskStandardSymbol.cpp HEADERS += \ + nodes/QskArcNode.h \ + nodes/QskArcRenderer.h \ nodes/QskBoxNode.h \ nodes/QskBoxClipNode.h \ nodes/QskBoxRenderer.h \ @@ -103,6 +107,8 @@ HEADERS += \ nodes/QskVertex.h SOURCES += \ + nodes/QskArcNode.cpp \ + nodes/QskArcRenderer.cpp \ nodes/QskBoxNode.cpp \ nodes/QskBoxClipNode.cpp \ nodes/QskBoxRendererRect.cpp \