speedometers: Fix layout and add more public API

This commit is contained in:
Peter Hartmann 2018-04-05 11:23:52 +02:00
parent b7c54d6916
commit 8ddd039870
8 changed files with 238 additions and 115 deletions

View File

@ -58,5 +58,5 @@ void ButtonBar::addIndicator( const char* name )
QSizeF ButtonBar::contentsSizeHint() const
{
return QSizeF( -1, 20 );
return { -1, 20 };
}

View File

@ -1,7 +1,7 @@
#include "MainWindow.h"
#include "ButtonBar.h"
#include "SoundControl.h"
#include "SkinFactory.h"
#include "SoundControl.h"
#include "SpeedometerDisplay.h"
#include <QskGraphic.h>

View File

@ -1,24 +1,20 @@
#include "Speedometer.h"
#include <QskSkinnable.h>
#include <QskSkinlet.h>
#include <QskSkinnable.h>
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"

View File

@ -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

View File

@ -2,6 +2,7 @@
#include "Speedometer.h"
#include <QskEvent.h>
#include <QskLinearBox.h>
#include <QskTextLabel.h>
@ -10,35 +11,108 @@
#include <QtGlobal>
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() );
}

View File

@ -3,10 +3,26 @@
#include <QskControl.h>
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

View File

@ -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,21 +81,30 @@ QSGNode* SpeedometerSkinlet::updatePanelNode( const Speedometer* speedometer, QS
if( boxNode == nullptr )
{
QRectF panelRect = subControlRect( speedometer, Speedometer::Panel );
boxNode = new QskBoxNode;
}
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 )
@ -108,21 +112,16 @@ QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QS
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,7 +154,12 @@ QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QS
vertexData += 2;
QString text = QString::number( i * 10 );
QVector< QString > labels = speedometer->labels();
// only create a text node if there is a label for it:
if ( labels.count() > i )
{
const QString& text = labels.at( i );
float w = fontMetrics.width( text );
float h = fontMetrics.height();
@ -188,6 +192,7 @@ QSGNode* SpeedometerSkinlet::updateTicksNode( const Speedometer* speedometer, QS
}
// ### remove nodes in case they are superfluous
}
}
geometry->setLineWidth( 2 );
geometry->markVertexDataDirty();
@ -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,6 +215,8 @@ 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 );
@ -222,7 +224,6 @@ QSGNode* SpeedometerSkinlet::updateNeedleNode( const Speedometer* speedometer, Q
QskBoxBorderColors borderColors( Qt::red );
QskGradient gradient( Qt::red );
boxNode->setBoxData( centerNodeRect, shapeMetrics, borderMetrics, borderColors, gradient );
}
TicksNode* needleNode;
@ -265,9 +266,6 @@ QSGNode* SpeedometerSkinlet::updateNeedleNode( const Speedometer* speedometer, Q
needleNode->markDirty( QSGNode::DirtyGeometry );
if ( boxNode->childCount() == 0 )
{
boxNode->appendChildNode( needleNode );

View File

@ -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;
};