diff --git a/examples/automotive/DefaultSkin.cpp b/examples/automotive/DefaultSkin.cpp index 4dc76035..817a80ec 100644 --- a/examples/automotive/DefaultSkin.cpp +++ b/examples/automotive/DefaultSkin.cpp @@ -4,6 +4,9 @@ #include "SoundControl.h" #include "ButtonBar.h" +#include "Speedometer.h" +#include "SpeedometerSkinlet.h" + #include #include #include @@ -80,6 +83,7 @@ DefaultSkin::DefaultSkin( const QString& name, QObject* parent ): m_scheme( Daylight ) { setObjectName( "DefaultSkin" ); + declareSkinlet< Speedometer, SpeedometerSkinlet >(); initHints(); } diff --git a/examples/automotive/MainWindow.cpp b/examples/automotive/MainWindow.cpp index e076c28b..3675f75b 100644 --- a/examples/automotive/MainWindow.cpp +++ b/examples/automotive/MainWindow.cpp @@ -2,6 +2,7 @@ #include "ButtonBar.h" #include "SoundControl.h" #include "SkinFactory.h" +#include "SpeedometerDisplay.h" #include #include @@ -14,7 +15,7 @@ MainWindow::MainWindow() { - const QImage image( ":/images/background.jpg" ); + const QImage image( QStringLiteral( ":/images/background.jpg" ) ); auto backgroundImage = new QskGraphicLabel( contentItem() ); backgroundImage->setGraphic( QskGraphic::fromImage( image ) ); @@ -56,7 +57,8 @@ QQuickItem* MainWindow::headerBar() const QQuickItem* MainWindow::mainContent() const { - return new SoundControl(); + return new SpeedometerDisplay(); + //return new SoundControl(); ### } QQuickItem* MainWindow::footerBar() const diff --git a/examples/automotive/OtherSkin.cpp b/examples/automotive/OtherSkin.cpp index 05aed233..c0a34fbe 100644 --- a/examples/automotive/OtherSkin.cpp +++ b/examples/automotive/OtherSkin.cpp @@ -4,6 +4,9 @@ #include "SoundControl.h" #include "ButtonBar.h" +#include "Speedometer.h" +#include "SpeedometerSkinlet.h" + #include #include #include @@ -57,6 +60,7 @@ OtherSkin::OtherSkin( const QString& name, QObject* parent ): m_palette( new Palette ) { setObjectName( "OtherSkin" ); + declareSkinlet< Speedometer, SpeedometerSkinlet >(); initHints(); initGraphicFilters(); } diff --git a/examples/automotive/Speedometer.cpp b/examples/automotive/Speedometer.cpp new file mode 100644 index 00000000..e7fac012 --- /dev/null +++ b/examples/automotive/Speedometer.cpp @@ -0,0 +1,21 @@ +#include "Speedometer.h" + +#include +#include + +QSK_SUBCONTROL( Speedometer, Panel ) +QSK_SUBCONTROL( Speedometer, Ticks ) +QSK_SUBCONTROL( Speedometer, Numbers ) +QSK_SUBCONTROL( Speedometer, Needle ) + +Speedometer::Speedometer( QQuickItem* parent ) : + QskControl( parent ) +{ +} + +QSizeF Speedometer::contentsSizeHint() const +{ + return QSizeF( 300, 300 ); +} + +#include "moc_Speedometer.cpp" diff --git a/examples/automotive/Speedometer.h b/examples/automotive/Speedometer.h new file mode 100644 index 00000000..d8ac9022 --- /dev/null +++ b/examples/automotive/Speedometer.h @@ -0,0 +1,18 @@ +#ifndef SPEEDOMETER_H +#define SPEEDOMETER_H + +#include + +class Speedometer : public QskControl +{ + Q_OBJECT + +public: + QSK_SUBCONTROLS( Panel, Ticks, Numbers, Needle ) + + Speedometer( QQuickItem* parent = nullptr ); + + virtual QSizeF contentsSizeHint() const override; +}; + +#endif // SPEEDOMETER_H diff --git a/examples/automotive/SpeedometerDisplay.cpp b/examples/automotive/SpeedometerDisplay.cpp new file mode 100644 index 00000000..3fb07fff --- /dev/null +++ b/examples/automotive/SpeedometerDisplay.cpp @@ -0,0 +1,23 @@ +#include "SpeedometerDisplay.h" + +#include "Speedometer.h" + +#include +#include + +SpeedometerDisplay::SpeedometerDisplay( QQuickItem *parent ) : + QskControl( parent ) +{ + QskLinearBox* box = new QskLinearBox( Qt::Horizontal, this ); + box->setAutoAddChildren( true ); + box->setAutoLayoutChildren( true ); + box->setMargins( QMarginsF( 40, 20, 40, 20 ) ); + box->setAlignment( 0, Qt::AlignHCenter ); + + QskTextLabel* label = new QskTextLabel( QStringLiteral( "text" ), box ); + label->setFixedSize( QSizeF( 300, 300 ) ); + label->setAlignment( Qt::AlignHCenter | Qt::AlignCenter ); + + Speedometer* speedometer = new Speedometer( box ); + speedometer->setFixedSize( QSizeF( 400, 400 ) ); +} diff --git a/examples/automotive/SpeedometerDisplay.h b/examples/automotive/SpeedometerDisplay.h new file mode 100644 index 00000000..6c071cb7 --- /dev/null +++ b/examples/automotive/SpeedometerDisplay.h @@ -0,0 +1,12 @@ +#ifndef SPEEDOMETERDISPLAY_H +#define SPEEDOMETERDISPLAY_H + +#include + +class SpeedometerDisplay : public QskControl +{ +public: + SpeedometerDisplay( QQuickItem* parent = nullptr ); +}; + +#endif // SPEEDOMETERDISPLAY_H diff --git a/examples/automotive/SpeedometerSkinlet.cpp b/examples/automotive/SpeedometerSkinlet.cpp new file mode 100644 index 00000000..c9436212 --- /dev/null +++ b/examples/automotive/SpeedometerSkinlet.cpp @@ -0,0 +1,279 @@ +#include "SpeedometerSkinlet.h" +#include "Speedometer.h" + +#include +#include +#include +#include +#include +#include +#include // ### remove + +#include +#include +#include + +namespace +{ + + class TicksNode : public QSGGeometryNode + { + public: + TicksNode( const QColor& color ): + m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) + { + m_geometry.setDrawingMode( GL_LINES ); + m_geometry.setVertexDataPattern( QSGGeometry::StaticPattern ); + + m_material.setColor( color ); + + setGeometry( &m_geometry ); + setMaterial( &m_material ); + } + + private: + QSGFlatColorMaterial m_material; + QSGGeometry m_geometry; + }; +} + +SpeedometerSkinlet::SpeedometerSkinlet( QskSkin* skin ) : + QskSkinlet( skin ) +{ + setNodeRoles( { PanelRole, TicksRole, NumbersRole, NeedleRole } ); +} + +SpeedometerSkinlet::~SpeedometerSkinlet() +{ +} + +QRectF SpeedometerSkinlet::subControlRect( const QskSkinnable* skinnable, + QskAspect::Subcontrol ) const +{ + const auto speedometer = static_cast< const Speedometer* >( skinnable ); + + // ### differentiate for subcontrols + return speedometer->contentsRect(); +} + +QSGNode* SpeedometerSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, + QSGNode* node ) const +{ + const Speedometer* 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 NeedleRole: + return updateNeedleNode( speedometer, node ); + + default: + return nullptr; + } +} + +QSGNode* SpeedometerSkinlet::updatePanelNode( const Speedometer* speedometer, QSGNode* node ) const +{ + auto boxNode = static_cast< QskBoxNode* >( node ); + + 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 ); + } + + return boxNode; +} + +QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QSGNode* node ) const +{ + auto ticksNode = static_cast< TicksNode* >( node ); + + 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; + + auto geometry = ticksNode->geometry(); + geometry->allocate( tickCount * 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 ); + + QPointF center = QPointF( panelRect.x() + panelRect.width() / 2, + 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 ) ); + + float angle = startAngle; + + // Create a series of tickmarks from minimum to maximum + for( int i = 0; i < tickCount; ++i, angle += stepStride ) + { + qreal cosine = qCos( qDegreesToRadians( angle ) ); + qreal sine = qSin( qDegreesToRadians( angle ) ); + + float xStart = center.x() + radius * cosine; + float yStart = center.y() + radius * sine; + + // ### skin hint for each of highlighted / normal marks + qreal length = ( i % highlightedMarksStep == 0 ) ? 15 : 15; + float xEnd = center.x() + ( radius - length ) * cosine; + float yEnd = center.y() + ( radius - length ) * sine; + + vertexData[0].set( xStart, yStart ); + vertexData[1].set( xEnd, yEnd ); + + vertexData += 2; + + QString text = QString::number( i * 10 ); + + 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 ) + { + 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 + } + + geometry->setLineWidth( 2 ); + geometry->markVertexDataDirty(); + + ticksNode->markDirty( QSGNode::DirtyGeometry ); + + 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 ); + const QRectF panelRect = subControlRect( speedometer, Speedometer::Panel ).marginsRemoved( margins ); + auto radius = 15; // ### skin hint + QPointF center = QPointF( panelRect.x() + panelRect.width() / 2, + panelRect.y() + panelRect.height() / 2 ); + + auto boxNode = static_cast< QskBoxNode* >( node ); + + 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 ); + } + + TicksNode* needleNode; + + if ( boxNode->childCount() == 0 ) + { + needleNode = new TicksNode( Qt::red ); + } + else + { + needleNode = static_cast< TicksNode* >( boxNode->childAtIndex( 0 ) ); + } + + auto panelRadius = static_cast< float >( panelRect.width() / 2 ); + + auto needleWidth = 2; // ### do differently somehow + QRectF needleRect( center.x() - needleWidth , center.y() - needleWidth , + panelRadius - ( needleWidth + 10 ), 2 * needleWidth ); + float xStart = center.x() - needleWidth ; + float yStart = center.y(); + + float angle = 315; // ### API + qreal cosine = qCos( qDegreesToRadians( angle ) ); + qreal sine = qSin( qDegreesToRadians( angle ) ); + + float needleRadius = panelRadius - 10; // 10 == margins ### skinhint + float xEnd = center.x() + needleRadius * cosine; + float yEnd = center.y() + needleRadius * sine; + + auto geometry = needleNode->geometry(); + geometry->allocate( 2 ); + + auto vertexData = geometry->vertexDataAsPoint2D(); + memset( vertexData, 0, static_cast< size_t >( geometry->vertexCount() ) ); + + vertexData[0].set( xStart, yStart ); + vertexData[1].set( xEnd, yEnd ); + + geometry->setLineWidth( 2 * needleWidth ); + geometry->markVertexDataDirty(); + + needleNode->markDirty( QSGNode::DirtyGeometry ); + + + + + if ( boxNode->childCount() == 0 ) + { + boxNode->appendChildNode( needleNode ); + } + + return boxNode; +} + +#include "moc_SpeedometerSkinlet.cpp" diff --git a/examples/automotive/SpeedometerSkinlet.h b/examples/automotive/SpeedometerSkinlet.h new file mode 100644 index 00000000..06bb013f --- /dev/null +++ b/examples/automotive/SpeedometerSkinlet.h @@ -0,0 +1,38 @@ +#ifndef SPEEDOMETERSKINLET_H +#define SPEEDOMETERSKINLET_H + +#include + +class Speedometer; + +class SpeedometerSkinlet : public QskSkinlet +{ + Q_GADGET + +public: + + enum NodeRole + { + PanelRole, + TicksRole, + NumbersRole, + NeedleRole + }; + + Q_INVOKABLE SpeedometerSkinlet( QskSkin* skin = nullptr ); + virtual ~SpeedometerSkinlet() override; + + virtual QRectF subControlRect( const QskSkinnable* skinnable, + QskAspect::Subcontrol ) const override; + +protected: + virtual QSGNode* updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const override; + +private: + QSGNode* updatePanelNode( const Speedometer*, QSGNode* ) const; + QSGNode* updateTicksNode( const Speedometer*, QSGNode* ) const; + QSGNode* updateNumbersNode( const Speedometer*, QSGNode* ) const; + QSGNode* updateNeedleNode( const Speedometer*, QSGNode* ) const; +}; + +#endif // SPEEDOMETERSKINLET_H diff --git a/examples/automotive/automotive.pro b/examples/automotive/automotive.pro index cab1fdc9..bad63ff9 100644 --- a/examples/automotive/automotive.pro +++ b/examples/automotive/automotive.pro @@ -18,7 +18,10 @@ HEADERS += \ SkinFactory.h \ DefaultSkin.h \ OtherSkin.h \ - MainWindow.h + MainWindow.h \ + Speedometer.h \ + SpeedometerSkinlet.h \ + SpeedometerDisplay.h SOURCES += \ ButtonBar.cpp \ @@ -27,7 +30,10 @@ SOURCES += \ DefaultSkin.cpp \ OtherSkin.cpp \ MainWindow.cpp \ - main.cpp + main.cpp \ + Speedometer.cpp \ + SpeedometerSkinlet.cpp \ + SpeedometerDisplay.cpp QRCFILES += \ images.qrc diff --git a/examples/automotive/main.cpp b/examples/automotive/main.cpp index 7e95fafb..566cff41 100644 --- a/examples/automotive/main.cpp +++ b/examples/automotive/main.cpp @@ -16,7 +16,8 @@ int main( int argc, char** argv ) auto skinFactory = new SkinFactory(); qskSkinManager->setPluginPaths( QStringList() ); // no plugins - qskSkinManager->registerFactory( "SampleSkinFactory", skinFactory ); + qskSkinManager->registerFactory( QStringLiteral( "SampleSkinFactory" ), + skinFactory ); QGuiApplication app( argc, argv ); @@ -33,7 +34,7 @@ int main( int argc, char** argv ) // CTRL-S allow to rotate through the registered skins and CTRL-T // changes the colors, when the DefaultSkin is active. - qskSetup->setSkin( "DefaultSkin" ); + qskSetup->setSkin( QStringLiteral( "DefaultSkin" ) ); cout << "CTRL-S to change the skin." << endl; cout << "CTRL-T to change the color scheme, when the \"Default\" skin is active." << endl; diff --git a/examples/sliders/SliderSkinlet.h b/examples/sliders/SliderSkinlet.h index 39e1ab52..521f632d 100644 --- a/examples/sliders/SliderSkinlet.h +++ b/examples/sliders/SliderSkinlet.h @@ -23,7 +23,7 @@ public: }; SliderSkinlet(); - virtual ~SliderSkinlet(); + virtual ~SliderSkinlet() override; virtual QRectF subControlRect( const QskSkinnable*, QskAspect::Subcontrol ) const override;