diff --git a/examples/automotive/ButtonBar.cpp b/examples/automotive/ButtonBar.cpp index abc2a823..73a899ed 100644 --- a/examples/automotive/ButtonBar.cpp +++ b/examples/automotive/ButtonBar.cpp @@ -58,5 +58,5 @@ void ButtonBar::addIndicator( const char* name ) QSizeF ButtonBar::contentsSizeHint() const { - return QSizeF( -1, 20 ); + return { -1, 20 }; } diff --git a/examples/automotive/MainWindow.cpp b/examples/automotive/MainWindow.cpp index 5b4970f5..0c108812 100644 --- a/examples/automotive/MainWindow.cpp +++ b/examples/automotive/MainWindow.cpp @@ -1,7 +1,7 @@ #include "MainWindow.h" #include "ButtonBar.h" -#include "SoundControl.h" #include "SkinFactory.h" +#include "SoundControl.h" #include "SpeedometerDisplay.h" #include diff --git a/examples/automotive/Speedometer.cpp b/examples/automotive/Speedometer.cpp index fef8968e..b3f439f6 100644 --- a/examples/automotive/Speedometer.cpp +++ b/examples/automotive/Speedometer.cpp @@ -1,24 +1,20 @@ #include "Speedometer.h" -#include #include +#include QSK_SUBCONTROL( Speedometer, Panel ) -QSK_SUBCONTROL( Speedometer, Ticks ) -QSK_SUBCONTROL( Speedometer, Numbers ) +QSK_SUBCONTROL( Speedometer, Labels ) QSK_SUBCONTROL( Speedometer, Needle ) Speedometer::Speedometer( QQuickItem* parent ) : QskControl( parent ), - m_value( 0.0 ) + m_value( 0.0 ), + m_startAngle( -215 ), + m_endAngle( 35 ) { } -QSizeF Speedometer::contentsSizeHint() const -{ - return { 300, 300 }; -} - float Speedometer::value() const { return m_value; @@ -30,4 +26,34 @@ void Speedometer::setValue( float value ) update(); } +float Speedometer::startAngle() const +{ + return m_startAngle; +} + +void Speedometer::setStartAngle( float startAngle ) +{ + m_startAngle = startAngle; +} + +float Speedometer::endAngle() const +{ + return m_endAngle; +} + +void Speedometer::setEndAngle( float endAngle ) +{ + m_endAngle = endAngle; +} + +QVector< QString > Speedometer::labels() const +{ + return m_labels; +} + +void Speedometer::setLabels( const QVector< QString >& labels ) +{ + m_labels = labels; +} + #include "moc_Speedometer.cpp" diff --git a/examples/automotive/Speedometer.h b/examples/automotive/Speedometer.h index edaa957b..70b083df 100644 --- a/examples/automotive/Speedometer.h +++ b/examples/automotive/Speedometer.h @@ -8,17 +8,28 @@ class Speedometer : public QskControl Q_OBJECT public: - QSK_SUBCONTROLS( Panel, Ticks, Numbers, Needle ) + QSK_SUBCONTROLS( Panel, Labels, Needle ) Speedometer( QQuickItem* parent = nullptr ); - virtual QSizeF contentsSizeHint() const override; - float value() const; void setValue( float value ); // angle; should be within a set range + float startAngle() const; + void setStartAngle( float startAngle ); + + float endAngle() const; + void setEndAngle( float endAngle ); + + QVector< QString > labels() const; + void setLabels( const QVector< QString >& labels ); + private: float m_value; + float m_startAngle; + float m_endAngle; + float m_labelsStep; + QVector< QString > m_labels; }; #endif // SPEEDOMETER_H diff --git a/examples/automotive/SpeedometerDisplay.cpp b/examples/automotive/SpeedometerDisplay.cpp index 2d6799f0..86cfd28b 100644 --- a/examples/automotive/SpeedometerDisplay.cpp +++ b/examples/automotive/SpeedometerDisplay.cpp @@ -2,6 +2,7 @@ #include "Speedometer.h" +#include #include #include @@ -10,35 +11,108 @@ #include SpeedometerDisplay::SpeedometerDisplay( QQuickItem *parent ) : - QskControl( parent ) + QskControl( parent ), + m_box( new QskLinearBox( Qt::Horizontal, this ) ), + m_revCounter( new Speedometer( m_box ) ), + m_revCounterText( new QskTextLabel( QStringLiteral( "x 1000 min^-1" ), m_revCounter ) ), + m_speedometer( new Speedometer( m_box ) ), + m_speedometerText( new QskTextLabel( QStringLiteral( "km/h" ), m_speedometer ) ), + m_fuelGauge( new Speedometer( m_box ) ), + m_fuelGaugeText( new QskTextLabel( QStringLiteral( "fuel" ), m_fuelGauge ) ) { - qsrand( QTime::currentTime().msec() ); + qsrand( static_cast< uint >( QTime::currentTime().msec() ) ); - auto box = new QskLinearBox( Qt::Horizontal, this ); - box->setAutoAddChildren( true ); - box->setAutoLayoutChildren( true ); - box->setMargins( QMarginsF( 40, 20, 40, 20 ) ); - box->setAlignment( 0, Qt::AlignHCenter ); + setPolishOnResize( true ); - auto revCounter = new Speedometer( box ); - revCounter->setFixedSize( QSizeF( 300, 300 ) ); -// revCounter->setSizePolicy( QskSizePolicy::Maximum, QskSizePolicy::Maximum ); - revCounter->setValue( 270 ); + m_box->setAutoAddChildren( true ); + m_box->setAutoLayoutChildren( true ); + m_box->setSpacing( 20 ); - auto speedometer = new Speedometer( box ); - speedometer->setFixedSize( QSizeF( 300, 300 ) ); -// speedometer->setSizePolicy( QskSizePolicy::Maximum, QskSizePolicy::Maximum ); - speedometer->setValue( 270 ); + m_revCounter->setObjectName( QStringLiteral( "RevCounter" ) ); + int startAngle = 145, endAngle = 305, value = 200, numberLabels = 8; + m_revCounter->setStartAngle( startAngle ); + m_revCounter->setEndAngle( endAngle ); + m_revCounter->setValue( value ); + QVector< QString > revCounterLabels; + + for ( int i = 0; i < numberLabels; ++i ) + { + revCounterLabels.append( QString::number( i ) ); + } + + m_revCounter->setLabels( revCounterLabels ); + + m_speedometer->setObjectName( QStringLiteral( "Speedometer" ) ); + value = 280; + numberLabels = 23; + startAngle = -215; + endAngle = 35; + m_speedometer->setStartAngle( startAngle ); + m_speedometer->setEndAngle( endAngle ); + m_speedometer->setValue( value ); + QVector< QString > speedometerLabels; + speedometerLabels.reserve( numberLabels ); + + for ( int i = 0; i < numberLabels; ++i ) + { + speedometerLabels.append( QString::number( i * 10 ) ); + } + + m_speedometer->setLabels( speedometerLabels ); auto timer = new QTimer( this ); - connect( timer, &QTimer::timeout, this, [ speedometer ]() { - int newValue = speedometer->value() + qrand() % 3 - 0.8; - speedometer->setValue( newValue ); + + connect( timer, &QTimer::timeout, this, [ this ]() + { + auto speedometerValue = m_speedometer->value() + qrand() % 3 - 0.95; + m_speedometer->setValue( speedometerValue ); + + auto fuelGaugeValue = 0.99997 * m_fuelGauge->value(); + m_fuelGauge->setValue( fuelGaugeValue ); }); + timer->setInterval( 16 ); timer->start(); - auto fuelGauge = new Speedometer( box ); - fuelGauge->setFixedSize( QSizeF( 200, 200 ) ); - fuelGauge->setValue( 270 ); + + m_fuelGauge->setObjectName( QStringLiteral( "Fuel Gauge" ) ); + m_fuelGauge->setStartAngle( 195 ); + m_fuelGauge->setEndAngle( 345 ); + m_fuelGauge->setValue( 330 ); + + QVector< QString > fuelGaugeLabels; + fuelGaugeLabels.append( { "0", "", "1/2", "", "1/1" } ); + + m_fuelGauge->setLabels( fuelGaugeLabels ); + + + m_revCounterText->setMargins( 50 ); + m_speedometerText->setMargins( 50 ); + m_fuelGaugeText->setMargins( 50 ); +} + +void SpeedometerDisplay::updateLayout() +{ + auto radius = qMin( 0.33 * size().width(), size().height() ); + auto x = ( width() - radius * 2.7 - 2 * m_box->spacing() ) / 2; + auto y = ( height() - radius ) / 2; + m_box->setPosition( { x, y } ); + + m_revCounter->setFixedSize( radius, radius ); + QSizeF hint = m_revCounterText->sizeHint(); + x = ( radius - hint.width() ) / 2; + y = ( ( radius - hint.height() ) / 2 ) + m_revCounterText->margins().top(); + m_revCounterText->setGeometry( x, y, hint.width(), hint.height() ); + + m_speedometer->setFixedSize( radius, radius ); + hint = m_speedometerText->sizeHint(); + x = ( radius - hint.width() ) / 2; + y = ( ( radius - hint.height() ) / 2 ) + m_speedometerText->margins().top(); + m_speedometerText->setGeometry( x, y, hint.width(), hint.height() ); + + m_fuelGauge->setFixedSize( 0.7 * radius, 0.7 * radius ); + hint = m_fuelGaugeText->sizeHint(); + x = ( 0.7 * radius - hint.width() ) / 2; + y = ( ( 0.7 * radius - hint.height() ) / 2 ) + m_fuelGaugeText->margins().top(); + m_fuelGaugeText->setGeometry( x, y, hint.width(), hint.height() ); } diff --git a/examples/automotive/SpeedometerDisplay.h b/examples/automotive/SpeedometerDisplay.h index 6c071cb7..de7abae9 100644 --- a/examples/automotive/SpeedometerDisplay.h +++ b/examples/automotive/SpeedometerDisplay.h @@ -3,10 +3,26 @@ #include +class QskLinearBox; +class QskTextLabel; +class Speedometer; + class SpeedometerDisplay : public QskControl { public: SpeedometerDisplay( QQuickItem* parent = nullptr ); + +protected: + void updateLayout() override; + +private: + QskLinearBox* m_box; + Speedometer* m_revCounter; + QskTextLabel* m_revCounterText; + Speedometer* m_speedometer; + QskTextLabel* m_speedometerText; + Speedometer* m_fuelGauge; + QskTextLabel* m_fuelGaugeText; }; #endif // SPEEDOMETERDISPLAY_H diff --git a/examples/automotive/SpeedometerSkinlet.cpp b/examples/automotive/SpeedometerSkinlet.cpp index b5267969..8d4fee4b 100644 --- a/examples/automotive/SpeedometerSkinlet.cpp +++ b/examples/automotive/SpeedometerSkinlet.cpp @@ -35,20 +35,18 @@ namespace QSGFlatColorMaterial m_material; QSGGeometry m_geometry; }; -} +} // namespace SpeedometerSkinlet::SpeedometerSkinlet( QskSkin* skin ) : QskSkinlet( skin ) { - setNodeRoles( { PanelRole, TicksRole, NumbersRole, NeedleRole } ); + setNodeRoles( { PanelRole, LabelsRole, NeedleRole } ); } -SpeedometerSkinlet::~SpeedometerSkinlet() -{ -} +SpeedometerSkinlet::~SpeedometerSkinlet() = default; QRectF SpeedometerSkinlet::subControlRect( const QskSkinnable* skinnable, - QskAspect::Subcontrol ) const + QskAspect::Subcontrol /*unused*/ ) const { const auto speedometer = static_cast< const Speedometer* >( skinnable ); @@ -59,18 +57,15 @@ QRectF SpeedometerSkinlet::subControlRect( const QskSkinnable* skinnable, QSGNode* SpeedometerSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const { - const Speedometer* speedometer = static_cast< const Speedometer* >( skinnable ); + const auto speedometer = static_cast< const Speedometer* >( skinnable ); switch( nodeRole ) { case PanelRole: return updatePanelNode( speedometer, node ); - case TicksRole: - return updateTicksNode( speedometer, node ); - - case NumbersRole: - return updateNumbersNode( speedometer, node ); + case LabelsRole: + return updateLabelsNode( speedometer, node ); case NeedleRole: return updateNeedleNode( speedometer, node ); @@ -86,43 +81,47 @@ QSGNode* SpeedometerSkinlet::updatePanelNode( const Speedometer* speedometer, QS if( boxNode == nullptr ) { - QRectF panelRect = subControlRect( speedometer, Speedometer::Panel ); boxNode = new QskBoxNode; - qreal radius = panelRect.width() / 2; - QskBoxShapeMetrics shapeMetrics( radius, radius, radius, radius ); - QskBoxBorderMetrics borderMetrics( 2 ); - QskBoxBorderColors borderColors( Qt::white ); - QskGradient gradient( Qt::black ); - boxNode->setBoxData( panelRect, shapeMetrics, borderMetrics, borderColors, gradient ); } + QRectF panelRect = subControlRect( speedometer, Speedometer::Panel ); + qreal radius = panelRect.width() / 2; + QskBoxShapeMetrics shapeMetrics( radius, radius, radius, radius ); + QskBoxBorderMetrics borderMetrics( 2 ); + QskBoxBorderColors borderColors( Qt::white ); + QskGradient gradient( Qt::black ); + boxNode->setBoxData( panelRect, shapeMetrics, borderMetrics, borderColors, gradient ); + return boxNode; } -QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QSGNode* node ) const +QSGNode* SpeedometerSkinlet::updateLabelsNode( const Speedometer* speedometer, QSGNode* node ) const { + const int labelsCount = speedometer->labels().count(); + + // ### actually, we could draw labels with only one entry + if ( labelsCount <= 1 ) + { + return nullptr; + } + auto ticksNode = static_cast< TicksNode* >( node ); - if( ticksNode == nullptr ) + if ( ticksNode == nullptr ) { ticksNode = new TicksNode( Qt::white ); } - // ### add API for this: - // ### make qfloat etc.? - float startAngle = -215; - float endAngle = 35; // ### angle is still wrong somehow - const int tickCount = 18; - int highlightedMarksStep = 3; + const float startAngle = speedometer->startAngle(); + const float endAngle = speedometer->endAngle(); + const auto step = ( endAngle - startAngle ) / ( labelsCount - 1 ); auto geometry = ticksNode->geometry(); - geometry->allocate( tickCount * 2 ); + geometry->allocate( labelsCount * 2 ); auto vertexData = geometry->vertexDataAsPoint2D(); memset( vertexData, 0, static_cast< size_t >( geometry->vertexCount() ) ); - auto stepStride = ( endAngle - startAngle ) / ( tickCount - 1 ); - QMarginsF panelMargins = speedometer->marginsHint( Speedometer::Panel | QskAspect::Margin ); const QRectF panelRect = subControlRect( speedometer, Speedometer::Panel ).marginsRemoved( panelMargins ); @@ -131,13 +130,13 @@ QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QS panelRect.y() + panelRect.height() / 2 ); auto radius = static_cast< float >( panelRect.width() / 2 ); - const QMarginsF numbersMargins = speedometer->marginsHint( Speedometer::Numbers | QskAspect::Margin ); - QFontMetrics fontMetrics( speedometer->effectiveFont( Speedometer::Numbers ) ); + const QMarginsF numbersMargins = speedometer->marginsHint( Speedometer::Labels | QskAspect::Margin ); + QFontMetrics fontMetrics( speedometer->effectiveFont( Speedometer::Labels ) ); float angle = startAngle; // Create a series of tickmarks from minimum to maximum - for( int i = 0; i < tickCount; ++i, angle += stepStride ) + for( int i = 0; i < labelsCount; ++i, angle += step ) { qreal cosine = qCos( qDegreesToRadians( angle ) ); qreal sine = qSin( qDegreesToRadians( angle ) ); @@ -146,7 +145,7 @@ QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QS float yStart = center.y() + radius * sine; // ### skin hint for each of highlighted / normal marks - qreal length = ( i % highlightedMarksStep == 0 ) ? 15 : 15; + qreal length = 15; float xEnd = center.x() + ( radius - length ) * cosine; float yEnd = center.y() + ( radius - length ) * sine; @@ -155,38 +154,44 @@ QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QS vertexData += 2; - QString text = QString::number( i * 10 ); + QVector< QString > labels = speedometer->labels(); - float w = fontMetrics.width( text ); - float h = fontMetrics.height(); - float adjustX = ( -0.5 * cosine - 0.5 ) * w; - float adjustY = ( -0.5 * sine - 0.5 ) * h; - - float numbersX = xEnd + ( -1 * numbersMargins.left() * cosine ) + adjustX; - float numbersY = yEnd + ( -1 * numbersMargins.top() * sine ) + adjustY; - - QRectF numbersRect( numbersX, numbersY, w, h ); - - QskTextNode* numbersNode; - - if ( ticksNode->childCount() > i ) + // only create a text node if there is a label for it: + if ( labels.count() > i ) { - numbersNode = static_cast< QskTextNode* >( ticksNode->childAtIndex( i ) ); - } - else - { - numbersNode = new QskTextNode(); - } + const QString& text = labels.at( i ); - numbersNode->setTextData( speedometer, text, numbersRect, QFont(), - QskTextOptions(), QskTextColors( Qt::white ), - Qt::AlignCenter | Qt::AlignHCenter, Qsk::Normal ); + float w = fontMetrics.width( text ); + float h = fontMetrics.height(); + float adjustX = ( -0.5 * cosine - 0.5 ) * w; + float adjustY = ( -0.5 * sine - 0.5 ) * h; - if ( ticksNode->childCount() <= i ) - { - ticksNode->appendChildNode( numbersNode ); + float numbersX = xEnd + ( -1 * numbersMargins.left() * cosine ) + adjustX; + float numbersY = yEnd + ( -1 * numbersMargins.top() * sine ) + adjustY; + + QRectF numbersRect( numbersX, numbersY, w, h ); + + QskTextNode* numbersNode; + + if ( ticksNode->childCount() > i ) + { + numbersNode = static_cast< QskTextNode* >( ticksNode->childAtIndex( i ) ); + } + else + { + numbersNode = new QskTextNode(); + } + + numbersNode->setTextData( speedometer, text, numbersRect, QFont(), + QskTextOptions(), QskTextColors( Qt::white ), + Qt::AlignCenter | Qt::AlignHCenter, Qsk::Normal ); + + if ( ticksNode->childCount() <= i ) + { + ticksNode->appendChildNode( numbersNode ); + } + // ### remove nodes in case they are superfluous } - // ### remove nodes in case they are superfluous } geometry->setLineWidth( 2 ); @@ -197,11 +202,6 @@ QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QS return ticksNode; } -QSGNode* SpeedometerSkinlet::updateNumbersNode( const Speedometer* speedometer, QSGNode* node ) const -{ - return nullptr; -} - QSGNode* SpeedometerSkinlet::updateNeedleNode( const Speedometer* speedometer, QSGNode* node ) const { QMarginsF margins = speedometer->marginsHint( Speedometer::Panel | QskAspect::Margin ); @@ -215,15 +215,16 @@ QSGNode* SpeedometerSkinlet::updateNeedleNode( const Speedometer* speedometer, Q if( boxNode == nullptr ) { boxNode = new QskBoxNode; - QRectF centerNodeRect( center.x() - radius, center.y() - radius, - 2 * radius, 2 * radius ); - QskBoxShapeMetrics shapeMetrics( radius, radius, radius, radius ); - QskBoxBorderMetrics borderMetrics( 2 ); - QskBoxBorderColors borderColors( Qt::red ); - QskGradient gradient( Qt::red ); - boxNode->setBoxData( centerNodeRect, shapeMetrics, borderMetrics, borderColors, gradient ); } + QRectF centerNodeRect( center.x() - radius, center.y() - radius, + 2 * radius, 2 * radius ); + QskBoxShapeMetrics shapeMetrics( radius, radius, radius, radius ); + QskBoxBorderMetrics borderMetrics( 2 ); + QskBoxBorderColors borderColors( Qt::red ); + QskGradient gradient( Qt::red ); + boxNode->setBoxData( centerNodeRect, shapeMetrics, borderMetrics, borderColors, gradient ); + TicksNode* needleNode; if ( boxNode->childCount() == 0 ) @@ -265,9 +266,6 @@ QSGNode* SpeedometerSkinlet::updateNeedleNode( const Speedometer* speedometer, Q needleNode->markDirty( QSGNode::DirtyGeometry ); - - - if ( boxNode->childCount() == 0 ) { boxNode->appendChildNode( needleNode ); diff --git a/examples/automotive/SpeedometerSkinlet.h b/examples/automotive/SpeedometerSkinlet.h index 06bb013f..87e8655a 100644 --- a/examples/automotive/SpeedometerSkinlet.h +++ b/examples/automotive/SpeedometerSkinlet.h @@ -14,8 +14,7 @@ public: enum NodeRole { PanelRole, - TicksRole, - NumbersRole, + LabelsRole, NeedleRole }; @@ -30,8 +29,7 @@ protected: private: QSGNode* updatePanelNode( const Speedometer*, QSGNode* ) const; - QSGNode* updateTicksNode( const Speedometer*, QSGNode* ) const; - QSGNode* updateNumbersNode( const Speedometer*, QSGNode* ) const; + QSGNode* updateLabelsNode( const Speedometer*, QSGNode* ) const; QSGNode* updateNeedleNode( const Speedometer*, QSGNode* ) const; };